Skip to content

Crow Inspect System

Module: CrowInspectSystem | Engine: Unreal Engine 5.6 | © 2026 SuspectCrow

1. Overview

The Crow Inspect System provides a turn-and-zoom object inspection mechanic for Unreal Engine 5. It lets players pick up a world actor, rotate it freely in 3D, zoom in or out, and exit inspection — all driven by a data asset with zero per-item C++ code.

Design Goals

GoalHow the System Achieves It
No per-item codeAll input configuration lives in USCInspectConfig; inspectable actors only need a component and an interface
Two-sidedPlayer logic and object logic are cleanly separated into two components that communicate via a narrow API
Rotation without gimbal lockYaw is applied in world space; pitch is applied in local space
Flexible zoomTwo independent zoom modes — component translation or SceneCaptureComponent2D FOV
Extensible inputAny number of extra action tags can be routed through FSCInspectInputBinding and surfaced as Blueprint delegates

2. Architecture

The system has two sides: a player side that lives on the PlayerController and an object side that lives on each inspectable actor.

┌───────────────────────────────────────────┐
│            APlayerController              │
│                                           │
│  ┌─────────────────────────────────────┐  │
│  │    USCPlayerInspectComponent        │  │
│  │                                     │  │
│  │  StartInspect(TargetActor)          │  │
│  │    │                                │  │
│  │    ├── ISCInspectableInterface ─────┼──┼──► GetInspectableObjectComponent()
│  │    │                                │  │
│  │    └── ActiveInspectComponent ──────┼──┼──► USCInspectableObjectComponent
│  │                                     │  │         │
│  │  HandleEquipmentInput()             │  │         ├─ ApplyRotation()
│  │    └── OnInspectActionExecuted ─────┼──┼──►      ├─ ApplyZoom()
│  └─────────────────────────────────────┘  │         └─ ResetTransform()
│                                           │
│  USCInspectConfig (data asset)            │
│    ├─ InspectMappingContext               │
│    ├─ LookAction / ZoomAction / ...       │
│    └─ InputBindings[]                     │
└───────────────────────────────────────────┘

                           ▲
                           │  implements
              ┌────────────────────────────┐
              │  Inspectable World Actor   │
              │  ISCInspectableInterface   │
              │  USCInspectableObjectComponent │
              │    ├─ TargetComponent      │
              │    ├─ CaptureComponent     │
              │    └─ Delegates            │
              └────────────────────────────┘

Inspection Session Lifecycle

StartInspect(Actor)
     │
     ├─ Resolve USCInspectableObjectComponent via ISCInspectableInterface
     ├─ BeginInspectMode()   → enables scene capture (if configured)
     ├─ BindInputActions()   → adds InspectMappingContext, binds delegates
     └─ OnInspectStarted.Broadcast(Actor)

          [Player rotates / zooms / triggers custom actions]

EndInspect()
     ├─ ResetTransform()     → restores initial transform, zeros zoom
     ├─ EndInspectMode()     → disables scene capture
     ├─ ActiveInspectComponent = nullptr
     ├─ OnInspectEnded.Broadcast()
     └─ UnbindInputActions() → removes mapping context, clears all bindings

3. Quick Start

Step 1 — Add USCPlayerInspectComponent to Your PlayerController

Important: This component must be owned by an APlayerController, not a Pawn or Character, because it casts GetOwner() to APlayerController to reach the input component.

// MyPlayerController.h
#include "Components/SCPlayerInspectComponent.h"
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TObjectPtr<USCPlayerInspectComponent> InspectComponent;
// MyPlayerController.cpp — constructor
InspectComponent = CreateDefaultSubobject<USCPlayerInspectComponent>(TEXT("InspectComponent"));

Step 2 — Create a USCInspectConfig Data Asset

In the Content Browser: Right-click → Miscellaneous → Data Asset → USCInspectConfig

PropertyExample Value
InspectMappingContextIMC_Inspect
LookActionIA_Inspect_Look (2D Axis)
ZoomActionIA_Inspect_Zoom (1D Axis / Mouse Wheel)
ResetActionIA_Inspect_Reset (Digital)
ExitActionIA_Inspect_Exit (Digital)

Assign this asset in the component’s Inspect Config property via the Details panel on your PlayerController Blueprint.

Step 3 — Set Up an Inspectable Actor

Add USCInspectableObjectComponent to your prop Blueprint and implement ISCInspectableInterface:

// MyInspectableProp.h
#include "Interfaces/SCInspectableInterface.h"
#include "Components/SCInspectableObjectComponent.h"
UCLASS()
class AMyInspectableProp : public AActor, public ISCInspectableInterface
{
    GENERATED_BODY()
    UPROPERTY(VisibleAnywhere)
    TObjectPtr<USCInspectableObjectComponent> InspectableComponent;
    virtual USCInspectableObjectComponent* GetInspectableObjectComponent_Implementation() const override
    {
        return InspectableComponent;
    }
};

This is also straightforward to do entirely in Blueprint: add the component, implement the interface, and return the component reference from GetInspectableObjectComponent.

Step 4 — Trigger Inspection from Gameplay Code

// Example: called from an interaction system when the player presses "Use"
void AMyPlayerController::OnInteract(AActor* HitActor)
{
    InspectComponent->StartInspect(HitActor);
}

Blueprint: Call the Start Inspect node on the Inspect Component and pass the actor the player is looking at.

4. Core Classes

4.1 USCInspectConfig

File: DataAssets/SCInspectConfig.h | Base: UPrimaryDataAsset

Read-only data asset that configures the entire inspection session. Assign one instance to every USCPlayerInspectComponent.

PropertyTypeDescription
InspectMappingContextUInputMappingContext*Added to the local player subsystem when inspection begins; removed on end.
LookActionUInputAction*2D Axis action. Drives ApplyRotation(FVector2D) — X maps to yaw, Y to pitch.
ZoomActionUInputAction*1D Axis action. Drives ApplyZoom(float). Bind to mouse wheel or a stick axis.
ResetActionUInputAction*Digital action. Calls ResetTransform() on the active object component.
ExitActionUInputAction*Digital action. Calls EndInspect() and clears all input bindings.
InputBindingsTArray<FSCInspectInputBinding>Optional extra actions routed as FGameplayTag signals via OnInspectActionExecuted.
FSCInspectInputBinding
struct FSCInspectInputBinding
{
    UInputAction* InputAction;            // The UE5 Input Action asset
    bool          bRouteAllTriggerEvents; // true → Started, Triggered, Completed, Canceled
    ETriggerEvent TriggerEvent;           // Used only when bRouteAllTriggerEvents is false
    FGameplayTag  ActionTag;              // Tag broadcast via OnInspectActionExecuted
};

4.2 USCPlayerInspectComponent

File: Components/SCPlayerInspectComponent.h | Base: UActorComponent

The player-side controller for a single inspection session. Holds a reference to the currently active USCInspectableObjectComponent and manages all Enhanced Input bindings.

MethodBlueprintDescription
StartInspect(TargetActor)✅ CallableResolves the object component via ISCInspectableInterface, starts the session, and binds input.
EndInspect()✅ CallableResets the object transform, ends the session, and unbinds all input.
ExecuteLook(FVector2D)✅ CallableDirectly drives rotation. Useful for non-input triggers (e.g., touch drag in a UI widget).
ExecuteZoom(float)✅ CallableDirectly drives zoom.
ExecuteReset()✅ CallableDirectly triggers a transform reset without ending the session.
Delegates
DelegateSignatureFired When
OnInspectStarted(AActor* InspectedActor)StartInspect succeeds and the session is active.
OnInspectEnded()EndInspect is called and the session is torn down.
OnInspectActionExecuted(FGameplayTag, FInputActionInstance, FInputActionValue)A custom InputBindings entry fires.

4.3 USCInspectableObjectComponent

File: Components/SCInspectableObjectComponent.h | Base: UActorComponent

The object-side component. It controls which scene component gets rotated and zoomed, and it fires delegates so Blueprints can react to state changes.

Configuration Properties
PropertyDefaultDescription
TargetComponentTagNAME_NoneIf set, BeginPlay searches all USceneComponents on the owner for one with this tag and uses it as the rotate/zoom target. Falls back to the root component.
RotationSpeed2.0Degrees-per-unit multiplier applied to the input axis before rotation.
ZoomSpeed10.0Units-per-unit multiplier applied to the zoom axis.
ZoomLimits(-50, 50)Min and max values for CurrentZoomLevel. Both the component-translation and FOV zoom modes clamp to this range.
bUseSceneCapturefalseActivates the CaptureComponent on BeginInspectMode and deactivates it on EndInspectMode.
bUseSceneCaptureForZoomfalseWhen true, zoom adjusts CaptureComponent->FOVAngle. When false, zoom translates the target component.
CaptureComponentnullptrReference to a USceneCaptureComponent2D that must be set up (in Blueprint or C++) before play. Exposed as BlueprintReadWrite.
Public API
MethodDescription
BeginInspectMode()Enables the capture component and restricts its render list to the owning actor.
EndInspectMode()Disables the capture component and removes the actor from the show-only list.
ApplyRotation(FVector2D)Applies yaw (X, world up) and pitch (Y, local right) to the target component.
ApplyZoom(float)Adjusts zoom via FOV or component translation depending on bUseSceneCaptureForZoom.
ResetTransform()Restores the target component to its BeginPlay relative transform and zeroes CurrentZoomLevel.
ReportActionTag(FGameplayTag)Manually fires OnActionExecuted. Use from Blueprints to surface custom events back to listeners.
SetTargetComponent(USceneComponent*)Overrides the target component at runtime and re-caches InitialTransform from its current transform.

4.4 ISCInspectableInterface

File: Interfaces/SCInspectableInterface.h

A minimal interface implemented by any actor that can be inspected. Its sole purpose is to expose the actor’s USCInspectableObjectComponent without requiring a direct cast.

USCInspectableObjectComponent* GetInspectableObjectComponent() const;

Implement this in C++ or Blueprint. The simplest implementation just returns the component reference:

// Blueprint implementation (override GetInspectableObjectComponent)
// → Return value: InspectableComponent (variable reference)
// C++ implementation
USCInspectableObjectComponent* AMyProp::GetInspectableObjectComponent_Implementation() const
{
    return InspectableComponent;
}

5. Delegates Reference

Player Side — USCPlayerInspectComponent

OnInspectStarted       (AActor* InspectedActor)
  └── Fired once per session start. Use to: open inspect UI, lock camera, hide HUD.

OnInspectEnded         ()
  └── Fired once per session end. Use to: close inspect UI, restore camera, show HUD.

OnInspectActionExecuted (FGameplayTag ActionTag, FInputActionInstance, FInputActionValue)
  └── Fired for each FSCInspectInputBinding entry in USCInspectConfig.
      Use to: trigger examine animations, play audio cues, update inspection notes UI.

Object Side — USCInspectableObjectComponent

OnRotationChanged      (FRotator NewRotation)
  └── Fired every frame while the player is moving the mouse/stick.
      Use to: update a rotation readout in a debug UI.

OnZoomChanged          (float CurrentZoomLevel)
  └── Fired in component-translation zoom mode only (not when bUseSceneCaptureForZoom).
      Use to: show a zoom percentage indicator.

OnTransformReset       ()
  └── Fired after ResetTransform. Use to: snap UI back to its initial state.

OnActionExecuted       (FGameplayTag ActionTag)
  └── Fired by ReportActionTag. Use to: react to custom actions from the player side.

6. Input System Integration

How Input Flows Through the System

[Player input event]
        │
        ▼
  Enhanced Input  (InspectMappingContext is active)
        │
        ├─ LookAction   → Input_Look()   → ExecuteLook()   → ActiveInspectComponent->ApplyRotation()
        ├─ ZoomAction   → Input_Zoom()   → ExecuteZoom()   → ActiveInspectComponent->ApplyZoom()
        ├─ ResetAction  → Input_Reset()  → ExecuteReset()  → ActiveInspectComponent->ResetTransform()
        ├─ ExitAction   → Input_Exit()   → EndInspect()
        │
        └─ Custom InputBindings
                │
                ▼
         HandleInspectInput(FInputActionInstance)
                │   looks up InputRouteMap by UInputAction*
                ▼
         OnInspectActionExecuted.Broadcast(ActionTag, Instance, Value)

Configuring Extra Input Bindings

InspectConfig → InputBindings:
  [0]  InputAction:             IA_Inspect_Flip
       bRouteAllTriggerEvents:  false
       TriggerEvent:            Triggered
       ActionTag:               Inspect.Action.Flip

  [1]  InputAction:             IA_Inspect_Highlight
       bRouteAllTriggerEvents:  true   ← routes Started + Triggered + Completed + Canceled
       ActionTag:               Inspect.Action.Highlight

Bind to OnInspectActionExecuted in Blueprint to react to these tags:

OnInspectActionExecuted(ActionTag, ...)
  ├─ If ActionTag == Inspect.Action.Flip   → play flip animation on prop
  └─ If ActionTag == Inspect.Action.Highlight → enable emissive material

7. Zoom Modes

The component supports two distinct zoom behaviors, selected per-object via the bUseSceneCaptureForZoom toggle.

Mode A — Component Translation (Default)

bUseSceneCaptureForZoom = false

The target component’s relative location is offset along its forward vector relative to the InitialTransform origin:

NewLocation = InitialTransform.Location + (TargetComponent.ForwardVector × CurrentZoomLevel)

CurrentZoomLevel starts at 0 and is clamped to [ZoomLimits.X, ZoomLimits.Y] on every zoom input. OnZoomChanged fires after each update.

Mode B — Scene Capture FOV

bUseSceneCaptureForZoom = true

The zoom level is written directly to CaptureComponent->FOVAngle instead of moving the mesh:

CaptureComponent->FOVAngle = Clamp(CurrentZoomLevel + (ZoomAxis × ZoomSpeed), ZoomLimits.X, ZoomLimits.Y)

OnZoomChanged is not fired in this mode (zoom is visual-only at the capture level). Use this when inspection is rendered into a Render Target displayed in a UI widget.

Note: Configure ZoomLimits carefully in FOV mode. Typical FOV values range from 10° (tight zoom) to 90° (wide). Setting ZoomLimits = (10, 90) with ZoomSpeed = 1 gives one degree per scroll tick.

8. Scene Capture Integration

When bUseSceneCapture = true, the system manages a USceneCaptureComponent2D to render only the inspected object into a Render Target.

Required Setup

  1. Add a USceneCaptureComponent2D to the inspectable actor in Blueprint.
  2. Assign it to the CaptureComponent variable on USCInspectableObjectComponent.
  3. Point the capture component at a UTextureRenderTarget2D asset.
  4. Display the render target in a UMG widget (e.g., an Image with a Material that samples the render target).

What the System Does Automatically

EventSystem Action
BeginInspectMode()Enables tick, sets bCaptureEveryFrame = true, switches to PRM_UseShowOnlyList, adds the owner to ShowOnlyActors.
EndInspectMode()Disables tick, sets bCaptureEveryFrame = false, removes the owner from ShowOnlyActors.

Using PRM_UseShowOnlyList ensures the render target shows only the inspected actor, not the rest of the scene — no post-process or lighting bleed from the environment.

9. Common Patterns & Recipes

Pattern 1 — Open/Close an Inspect UI on Session Events

// In a UMG widget or HUD Blueprint:

BeginPlay
  └── GetPlayerController → InspectComponent → Bind OnInspectStarted → OnInspectEnded

OnInspectStarted(InspectedActor)
  └── Set InspectPanel Visibility = Visible
      Set ItemNameText = InspectedActor.GetName()

OnInspectEnded
  └── Set InspectPanel Visibility = Collapsed

Pattern 2 — Drive Inspection from a Touch Drag (Mobile)

ExecuteLook and ExecuteZoom are fully callable from Blueprint without going through Enhanced Input, so a touch widget can drive inspection directly:

// UMG Widget — OnMouseMove or Touch input
DeltaPosition = CurrentTouchPosition - LastTouchPosition
PlayerController → InspectComponent → ExecuteLook(DeltaPosition × Sensitivity)

Pattern 3 — Per-Object TargetComponentTag

For an actor whose root component should not rotate (e.g., a physics-simulated base), tag the display mesh and point the component at it:

// In the inspectable actor Blueprint:
// Add tag "InspectMesh" to the StaticMeshComponent (not the root)

// In USCInspectableObjectComponent Details:
TargetComponentTag = "InspectMesh"

At BeginPlay, the system finds and caches that mesh. All rotation and zoom applies to it, leaving the root component untouched.

Pattern 4 — Override the Target Component at Runtime

Useful for actors that swap meshes (e.g., showing a damaged vs. pristine version):

// When the player repairs the item:
USCInspectableObjectComponent* InspectComp =
    Cast<USCInspectableObjectComponent>(
        Actor->GetComponentByClass(USCInspectableObjectComponent::StaticClass()));
if (InspectComp)
{
    InspectComp->SetTargetComponent(RepairedMeshComponent);
    // InitialTransform is re-cached from RepairedMeshComponent's current transform
}

Pattern 5 — Custom Action: Flip the Object

Add an FSCInspectInputBinding in the config asset:

InputAction:  IA_Inspect_Flip
ActionTag:    Inspect.Action.Flip
bRouteAll:    false
TriggerEvent: Triggered

In the player-side Blueprint (bound to OnInspectActionExecuted):

OnInspectActionExecuted(ActionTag, ...)
  ├─ ActionTag == Inspect.Action.Flip?
  └─ YES → Call ExecuteLook(FVector2D(0, 180))   ← instant 180° pitch flip

Or, if you want the actor to animate its own flip, call ReportActionTag from within the object Blueprint after receiving OnInspectActionExecuted to feed OnActionExecuted on the object side.

Pattern 6 — Safe Call Without Interface Check

StartInspect does the interface check internally, so you can call it directly on any actor from an interaction trace:

void AMyPlayerController::OnPrimaryInteract()
{
    AActor* HitActor = GetActorUnderCrosshair(); // your trace logic
    // Safe — does nothing if HitActor doesn't implement ISCInspectableInterface
    InspectComponent->StartInspect(HitActor);
}

Pattern 7 — End Inspection from the Object’s Own Blueprint

A prop might want to trigger EndInspect itself (e.g., after a timed reveal animation finishes). Since the object side has no direct reference to the player component, route it through the OnActionExecuted delegate:

// In the prop Blueprint actor:
OnActionExecuted(ActionTag)
  ├─ ActionTag == Inspect.Action.RevealComplete?
  └─ YES → GetPlayerController → InspectComponent → EndInspect()