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)

Input System — ICInputReceiver Slot Pattern

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:

  1. UCActionMappingComponent::createDefaultInputAction() creates a UInputMappingContext and maps each ECInputActionType to a UInputAction with physical key bindings.
  2. 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.
  3. 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
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)