ACPlayer1STCharacter
Overview
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 |
| UnPossessed |
Removes skill widget, unbinds dynamic events |
| BeginDestroy |
Destroys the controller, logs destruction |
| Tick |
Delegates to Super (no additional logic) |
// PossessedBy sequence:
// 1. SetShowMouseCursor(true)
// 2. SetInputMode(GameAndUI, HideCursorDuringCapture = !isTopView())
// 3. UCSkillComponent::displaySkillWidget()
// 4. boundDynamic() — binds to ActorUtility::onActorEvent + PlayerController events
// 5. Broadcast perspectiveTypeChange (after boundDynamic)
// UnPossessed sequence:
// 1. UCSkillComponent::removeSkillWidget()
// 2. unboundDynamic()
// 3. Super::UnPossessed() — called last
Dynamic Event Binding
| Method |
Description |
| boundDynamic |
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.
| Method |
Description |
| isFighting |
Returns fightReasonSet.Num() > 0 && !isTopView(). BlueprintCallable |
| updateFightReason |
Adds or removes an advIdx from fightReasonSet, then calls innerUpdateFighMode(). BlueprintCallable |
| cleanFightReason |
Empties fightReasonSet, then calls innerUpdateFighMode(). BlueprintCallable |
| innerUpdateFighMode |
Sets animation variable CDRYX_FIGHTING, configures rotation settings (see below), notifies turnByTurnComponent |
| AddControllerYawInput |
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)
Perspective Switching
| Method |
Description |
| isTopView |
Returns !springArmComponent->bUsePawnControlRotation |
| changeView |
Toggles between first-person and top-view. Configures spring arm inheritance, input mode, and broadcasts perspectiveTypeChange |
// changeView() — switching to TOP VIEW:
// HideCursorDuringCapture = false
// bInheritPitch = false, bInheritYaw = false, bUsePawnControlRotation = false
// customReset()
// cleanFightReason() — exits fight mode
// changeView() — switching to FIRST PERSON:
// HideCursorDuringCapture = true
// bInheritPitch = !runningOnMobile(), bInheritYaw = true, bUsePawnControlRotation = true
// customReset()
// SetControlRotation(GetActorRotation())
// initSpeedFactor()
// Both directions broadcast:
// perspectiveTypeChange(TOP_VIEW or FIRT_VIEW)
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.
| 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 |
| touchEvent |
bool |
Touch[0] (mobile) |
1 finger: no-op. 2 fingers: handled by pinch. 3 fingers: changeView() |
| LMBEvent |
bool |
Left Mouse Button |
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) |
// onActorEvent — removes fight reason when adversary deactivates:
// If retrieveActor(advIdx)->isActivated() == false:
// updateFightReason(advIdx, false)
// onFightReasonChange — bridges ACPlayerController fight events:
// advIdx = actor->UCInitializerComponent->actorId
// updateFightReason(advIdx, fightReason == ON)