ACPlayer1STCharacter is the main playable character class. It inherits ACCharacter and implements five interfaces: ICPlayerControllerBoundable, ICActorBoundable, ICDraggerWithCursor, ICInputReceiver, ICCommonPlayer.
This class is the most complex base class in the module. It handles: camera management (spring arm with adaptive target offset), perspective switching (first-person / top-view), fight mode with turn-in-place animations, a complete input system via ICInputReceiver slot pattern, mobile support (gyroscope, touch gestures), and actor lifecycle (possession, dynamic event binding).
The constructor removes CAutomationComponent, CNavigationInvokerComponent, and CFloatingMovementComponent from the inherited ACCharacter setup, then calls UCInitializerComponent::complete_CSTR_CPLAYER_FIRST_PERSON_ONLY to wire player-specific components.
Components
Name
Description
springArmComponent
Camera boom with collision test, adaptive Z target offset, slope-based auto-pitch, and optional mobile gyroscope pitch
cameraComponent
Player camera attached to the spring arm
actionMappingComponent
Enhanced Input action mapping via UCActionMappingComponent
turnByTurnComponent
Turn-by-turn combat coordination, notified on fight reason changes
fowComponent
Fog of War component
minimapComponent
Minimap scene capture
compassComponent
Compass direction indicator
Constructor — Spring Arm Lambdas
The constructor passes three lambdas to complete_CSTR_CPLAYER_FIRST_PERSON_ONLY for spring arm behavior:
Lambda
Description
Spring Arm Init
Enables bDoCollisionTest and bUsePawnControlRotation
Spring Arm Tick
Adaptive Z target offset using a quadratic formula based on camera pitch. When bInheritPitch is false (single-stick mobile or auto-pitch mode), computes pitch from slope angle + speed, interpolated with FInterpTo. On mobile, modulates pitch using device gyroscope gravity.Z
Spring Arm Reset
Sets probe size, relative location from mesh bounds, rotation (-89° top view or -30° first person), arm length, socket offset, and target offset. Enables component tick
// Spring Arm Tick — adaptive Z target offset formula:
// val = a + pow(x + c * d, P) * -(a / pow(c, P))
// where a = defaultTargetOffset.Z, x = camera pitch, c = 40, d = 0.333, P = 2
// Result is clamped and smoothed: TargetOffset.Z = (current + val) * 0.5
// Spring Arm Tick — auto-pitch (mobile gyroscope):
// defaultArmPitch = Lerp(50, -25, Clamp(gravity.Z, 0, 1))
// newPitch = FInterpTo(currentPitch, slope - defaultArmPitch, deltaTime, interpSpeed)
// interpSpeed = 5 if moving fast, 0.5 if slow
// Spring Arm Reset — view-dependent configuration:
// Top view: rotation(-89, 0, 0), armLength = (max - min) * 0.5, socketOffset = (0,0,0)
// First person: rotation(-30, 0, 0), armLength = meshBound.Z * 2.3, socketOffset = (0,80,0)
Properties
Name
Type
Description
springArmRotationStart
FRotator
Cached rotation at RMB press start, used to detect if camera was rotated during right-click
cameraIsTopView
bool
Tracks current view mode
defaultMinArmLenght
float
Minimum arm length in first-person mode (default 100)
defaultMaxArmLenght
float
Maximum arm length in first-person mode (default 3000)
defaultMinArmLenghtTopView
float
Minimum arm length in top-view mode (default 2000)
defaultMaxArmLenghtTopView
float
Maximum arm length in top-view mode (default 4000)
stopTargetEase
bool
When true, disables the adaptive Z target offset in spring arm tick
defaultTargetOffset
FVector
Cached initial target offset, set during spring arm reset
fightReasonSet
TSet<int>
Set of adversary indices currently causing fight mode. Any system can add/remove fight reasons
cachedDestination
FVector
Last click-to-move destination in top-view mode
currentArmLenght
float
Cached arm length at pinch start for ratio-based mobile zoom
rangeAttack
bool
True while a range attack is in progress (Ctrl+LMB held)
socketOffsetWhenTopView
FVector
Spring arm socket offset in top view (0, 0, 0)
socketOffsetWhen1stTopView
FVector
Spring arm socket offset in first person (0, 80, 0)
Lifecycle
Method
Description
BeginPlay
Calls initSpeedFactor(). On mobile, automatically switches to top view via changeView()
PossessedBy
Shows mouse cursor, sets input mode (hide cursor during capture depends on view), displays skill widget, binds dynamic events, broadcasts perspective type
Binds to ActorUtility::onActorEvent (via ICActorBoundable) and ACPlayerController::onPlayerControllerEvent (via ICPlayerControllerBoundable)
unboundDynamic
Unbinds from both event sources
Fight Mode
Fight mode is driven by fightReasonSet: any system can add or remove fight reasons by adversary index. Fight mode is active when the set is non-empty AND the player is not in top view.
Override. In fight mode: turn-in-place logic with montage animations when stationary, delayed bUseControllerDesiredRotation when moving. Suppresses rotation while blocking
makeMeAttack
Dispatches melee or range attack. In top view melee, rotates actor to face target before attacking
// innerUpdateFighMode — rotation configuration:
// FIGHTING:
// CDRYX_FIGHTING = true
// bUseControllerRotationYaw = false
// bOrientRotationToMovement = false
// bUseControllerDesiredRotation = false
// RotationRate = (0, 270, 0)
//
// NOT FIGHTING:
// CDRYX_FIGHTING = false
// bUseControllerRotationYaw = false
// bOrientRotationToMovement = true
// bUseControllerDesiredRotation = false
// RotationRate = (0, 540, 0)
//
// Always notifies turnByTurnComponent->fightReasonUpdated()
// AddControllerYawInput — fight mode turn-in-place:
// If blocking + stationary: suppress rotation entirely
// If stationary (speed near 0) and yawDiff > 66°: play turnLeft/turnRight montage, delay 0.45s
// If moving (speed > 0) and yawDiff > 25°: set bUseControllerDesiredRotation = true, delay 0.22s
// If yawDiff near 0: set bUseControllerDesiredRotation = false
// Montages stop as soon as speed > 0 (via CANIMBUILDER stopAsSoonAs)
ACPlayer1STCharacter implements ICInputReceiver, an interface that exposes 15 FCInputStruct<T> fields. Each field has 5 TFunction callbacks (onTriggered, onStarted, onGoing, onCanceled, onCompleted) initialized to no-op lambdas.
The flow works as follows:
UCActionMappingComponent::createDefaultInputAction() creates a UInputMappingContext and maps each ECInputActionType to a UInputAction with physical key bindings.
UCActionMappingComponent::setup() binds each action to UEnhancedInputComponent via the AMC_BIND_ACTION macro, which casts the owner to ICInputReceiver and calls the matching FCInputStruct callback.
ACPlayer1STCharacter::initActionEvent() overrides the default no-op lambdas with actual game logic.
This is a slot-based pattern: the component routes Enhanced Input events, the interface defines the slots, and the character fills the slots with behavior.
Input Action Table
Event Field
Type<T>
Physical Keys
Behavior in ACPlayer1STCharacter
moveEvent
FVector2D
ZQSD, Arrows, Gamepad L
Desktop/fighting: forward + strafe relative to camera yaw. Mobile non-fighting: 1-stick logic (forward + auto-yaw from stick deflection)
rotateEvent
FVector2D
Mouse XY, Gamepad R
Adds controller yaw and pitch input
jumpEvent
bool
SpaceBar
proceedStartJump on start, proceedEndJump on cancel/complete
blockEvent
bool
Tab
proceedStartBlock on start, proceedEndBlock on cancel/complete
interactEvent
bool
F
Executes interaction on interactionComponent->actorToInteractWith
Top view: click-to-move (speed scaled by distance in capsule heights, SimpleMoveToLocation on short click) or attack if target is attackable. First person: melee attack. Disables look input while held
ctrlLMBEvent
bool
Ctrl + Left Mouse Button
Range attack: proceedStartRangeAttack on start, proceedEndRangeAttack on release
RMBEvent
bool
Right Mouse Button
Re-enables look input (camera rotation). On release without rotation: releaseAndDestroy
pinchEvent
float
Gesture_Pinch (mobile)
Zoom: arm length = cached length / pinch ratio, clamped to view-dependent min/max
scrollEvent
float
Mouse Wheel
Zoom: arm length += value * 10% step, clamped to view-dependent min/max
alternativeScrollEvent
float
Ctrl + Mouse Wheel
FOV change: FieldOfView += value * 10, clamped 1-180 degrees
changeViewEvent
bool
V
Calls changeView()
displayResourceEvent
bool
I
Calls ACPlayerController::spawnResourceWidget()
displayMapEvent
bool
M
Calls CoordinatorUtility::showOrHideMap()
// Click-to-move in top view (LMBEvent.onTriggered):
// 1. Ray cast from mouse position
// 2. If hit actor is attackable (collisionTestIfAttack): makeMeAttack(false, actor)
// 3. Else: compute speedFactor = Clamp(distance / capsuleHeight, 0, 5)
// If speedFactor > 0.33: AddMovementInput toward destination
// 4. On release (< 0.5s): SimpleMoveToLocation(cachedDestination)
// Mobile 1-stick movement (moveEvent, non-fighting):
// Forward: AddMovementInput(forward * max(value.X, 0))
// Steering: AddControllerYawInput based on stick deflection
// value.X >= 0: yaw = value.Y
// value.X < 0, value.Y > 0: yaw = 1 - value.X
// value.X < 0, value.Y <= 0: yaw = -1 + value.X
Interface Implementations
Interface
Method
Description
ICActorBoundable
onActorEvent
When an adversary is deactivated (isActivated returns false), removes it from fightReasonSet
ICPlayerControllerBoundable
onFightReasonChange
Resolves the actor’s advIdx via UCInitializerComponent::actorId, then calls updateFightReason(advIdx, ON/OFF)