Character Framework¶
Summary: The character framework defines the hierarchy of player and enemy characters, their component ownership rules, and integration with the Gameplay Ability System. Key design decisions include GAS ownership on PlayerState, persistent components on PlayerController, and interface-driven cross-system communication.
Table of Contents¶
- Design Philosophy
- Actor Hierarchy
- Component Ownership
- GAS Integration
- Movement & Attribute Binding
- Replication Strategy
- Source Reference
- Related Systems
- Recent Changes
Design Philosophy¶
The Persistence Problem¶
In a multiplayer ARPG, characters can die and respawn. This creates a fundamental question: what survives death?
+------------------------------------------------------------------+
| THE PERSISTENCE SPECTRUM |
+------------------------------------------------------------------+
| |
| EPHEMERAL PERSISTENT IDENTITY |
| (Dies with pawn) (Survives death) (Session) |
| |
| - Combat state - Inventory - Abilities |
| - Animations - Equipment - Attributes |
| - Poise - Glyphs - Quests |
| - Crafting mats - Progress |
| - Active |
| effects |
| Lives on: Lives on: Lives on: |
| Character (Pawn) PlayerController PlayerState |
| |
+------------------------------------------------------------------+
Active gameplay effects are not ephemeral. They live on the ASC, which lives on the PlayerState, so they survive the pawn swap along with abilities and attributes. Reading them as pawn-scoped is exactly the misunderstanding that let a damage-over-time effect keep ticking on a freshly respawned player after the effect had already killed the previous pawn.
Respawn therefore has to reset them explicitly: UEternalAbilitySystemComponent::ResetForRespawn()
clears State.Dead and strips finite-duration effects. Infinite effects — gear modifiers, kit
passives, auras — are deliberately left in place: those belong to the character, not to the life.
Why GAS on PlayerState?¶
The Ability System Component (ASC) lives on PlayerState for four critical reasons:
| Reason | Explanation |
|---|---|
| Persistence | Abilities and attributes survive character death/respawn — and so do active effects and loose tags, which is why respawn must explicitly reset them |
| Replication | PlayerState automatically replicates to all clients |
| Authority | Single source of truth for all ability-related data |
| Network Ownership | Proper RPC routing for multiplayer |
Actor Hierarchy¶
Overview Diagram¶
+------------------------------------------------------------------+
| CHARACTER FRAMEWORK |
+------------------------------------------------------------------+
AEternalPlayerState (Identity Layer)
+-- UAbilitySystemComponent <-- GAS lives here
+-- UEternalAttributeSet <-- All attributes
+-- UPlayerQuestComponent <-- Quest tracking
|
| Implements: IAbilitySystemInterface
| IQuestParticipant
| IPlayerInventory
|
+---------------------------------------------------------------+
AEternalPlayer (PlayerController - Persistence Layer)
+-- UInventoryComponent <-- Items survive death
+-- UEquipmentComponent <-- Gear persists
+-- UPlayerGlyphComponent <-- Stone plates
+-- UCraftingContainerComponent <-- Crafting storage
+-- UCraftingResourceComponent <-- Crafting materials
+-- UMapExplorationComponent <-- Map discovery
|
+---------------------------------------------------------------+
AEternalCharacter (Pawn - Physical Layer)
+-- UEternalCharacterMovementComponent <-- ALS movement + GAS speed scaling
+-- UCombatComponent <-- Combat state
+-- UMontageManagerComponent <-- Animation montages
+-- UPoiseSystemComponent <-- Stagger/break
+-- UResourceRecoveryComponent <-- Regeneration
+-- UCombatEffectsManager <-- VFX/Audio
+-- UMotionWarpingComponent <-- Movement warping
+-- USpringArmComponent <-- Camera boom
+-- UCameraComponent <-- Top-down camera
|
| Implements: IAbilitySystemInterface (proxies to PlayerState)
| ICombatInterface
| IMontageManagerInterface
| ITargetable
|
+---------------------------------------------------------------+
AEternalPlayerCharacter (Player-Specific Pawn)
+-- Extends AEternalCharacter
+-- UDungeonModifierComponent <-- Area modifier GAS effects
+-- Provides GetPlayerCombatComponent()
Character Class Responsibilities¶
| Class | Role | Key Interfaces |
|---|---|---|
AEternalPlayerState |
GAS authority, identity | IAbilitySystemInterface |
AEternalPlayer |
Persistent components, input | (none) |
AEternalCharacter |
Physical representation | ICombatInterface, ITargetable |
AEternalPlayerCharacter |
Player-specific logic | (extends base) |
Component Ownership¶
Ownership Decision Matrix¶
+------------------------------------------------------------------+
| WHERE SHOULD THIS COMPONENT LIVE? |
+------------------------------------------------------------------+
| |
| Ask yourself: |
| |
| 1. Does it reset when the character dies? |
| YES --> Character (Pawn) |
| |
| 2. Does it persist across death but is player-specific? |
| YES --> PlayerController |
| |
| 3. Is it part of the ability/attribute system? |
| YES --> PlayerState |
| |
| 4. Does all clients need to see/query it? |
| YES --> Consider PlayerState (replicates automatically) |
| |
+------------------------------------------------------------------+
Character (Pawn) Components¶
| Component | Purpose | Why Here |
|---|---|---|
UEternalCharacterMovementComponent |
ALS movement + GAS speed scaling | Drives locomotion of the pawn |
UCombatComponent |
Combat state, hit tracking | Resets on death |
UMontageManagerComponent |
Animation playback | Tied to skeleton |
UPoiseSystemComponent |
Stagger/break states | Poise resets on death |
UResourceRecoveryComponent |
Health/resource regen | Only while alive |
UCombatEffectsManager |
VFX/audio | Tied to pawn mesh |
UMotionWarpingComponent |
Movement warping | Relative to skeleton |
UDungeonModifierComponent |
Area modifier GAS effects | Applied/removed per dungeon |
PlayerController Components¶
| Component | Purpose | Why Here |
|---|---|---|
UInventoryComponent |
Player inventory | Items survive death |
UEquipmentComponent |
Equipped items | Respawn with gear |
UPlayerGlyphComponent |
Stone plates | Progression data |
UCraftingContainerComponent |
Crafting storage | Materials persist |
UMapExplorationComponent |
Map discovery | Account-level data |
PlayerState Components¶
| Component | Purpose | Why Here |
|---|---|---|
UAbilitySystemComponent |
GAS authority | Replication, persistence |
UEternalAttributeSet |
All attributes | Tied to ASC |
UPlayerQuestComponent |
Quest tracking | Account progression |
GAS Integration¶
Ability System Architecture¶
+------------------------------------------------------------------+
| GAS OWNERSHIP MODEL |
+------------------------------------------------------------------+
| |
| AEternalPlayerState |
| +-- OWNER of AbilitySystemComponent |
| | |
| +-- Replication Mode: Mixed |
| | (Full to owner, minimal to others) |
| | |
| +-- UEternalAttributeSet |
| +-- Hard Stats: Ferocity, Grace, Insight, Clarity... |
| +-- Soft Stats: Resolve, Presence |
| +-- Vitals: Health, Stamina, Resonance |
| +-- Combat: MovementSpeed |
| +-- Poise: Poise, MaxPoise, PoiseRecoveryRate |
| +-- Armor: Armor, ArmorPenetration |
| +-- Resistances: Fire, Corruption, Electric |
| +-- Critical Strike: Chance, Damage |
| +-- Area Modifiers: DamageDealt, DamageTaken |
| |
| AEternalCharacter |
| +-- AVATAR (physical representation) |
| +-- Caches ASC reference from PlayerState |
| +-- Implements IAbilitySystemInterface |
| +-- Returns cached ASC reference |
| |
+------------------------------------------------------------------+
GAS Initialization Flow¶
Character Spawned & Possessed
|
v
+---------------------------+
| PossessedBy(Controller) |
| - Called on server |
+---------------------------+
|
v
+---------------------------+
| OnRep_PlayerState() |
| - Called when PS arrives |
+---------------------------+
|
v
+---------------------------+
| InitAbilityActorInfo() |
| |
| 1. Get PlayerState |
| 2. Get ASC from PS |
| 3. Call InitAbilityActor |
| Info(Owner, Avatar) |
| - Owner = PlayerState |
| - Avatar = Character |
| 4. Cache ASC reference |
| 5. Cache AttributeSet |
+---------------------------+
|
v
+---------------------------+
| Grant Startup Abilities |
| (Server Only) |
| |
| For each ability in |
| StartupAbilities: |
| ASC->GiveAbility() |
+---------------------------+
|
v
+---------------------------+
| Initialize Attributes |
| (Server Only) |
| |
| Apply GameplayEffects: |
| - DefaultPrimaryAttributes|
| - DefaultSecondaryAttribs |
| - DefaultVitalAttributes |
+---------------------------+
Movement & Attribute Binding¶
Why a Custom Movement Component¶
The MovementSpeed attribute on UEternalAttributeSet is the single source of truth for how
fast a character actually moves. The problem: GAS attributes live on the ASC, but locomotion
speed is decided by the movement component (GetMaxSpeed()). Rather than have every gameplay
effect (block slow, haste buffs, chill debuffs) poke movement settings directly, the framework
routes all speed influence through one attribute and lets the movement component read it.
UEternalCharacterMovementComponent subclasses the ALS movement component and applies a single
SpeedMultiplier in its GetMaxSpeed() override. The base pawn installs it as the default
movement component class via the constructor's ObjectInitializer, so every character (player
and enemy) gets GAS-driven movement for free.
The Attribute -> Multiplier Pattern¶
+------------------------------------------------------------------+
| GAS EFFECT -> ACTUAL LOCOMOTION SPEED |
+------------------------------------------------------------------+
| |
| Any GameplayEffect modifies MovementSpeed |
| (block slow / buff / debuff) |
| | |
| v |
| UEternalAttributeSet.MovementSpeed changes |
| | |
| v |
| ASC attribute-change delegate fires |
| (bound in AEternalCharacter::BindMovementSpeedAttribute) |
| | |
| v |
| OnMovementSpeedChanged -> SetSpeedMultiplier(NewValue / 100) |
| (attribute is a percentage: 100 = 100% = 1.0x) |
| | |
| v |
| UEternalCharacterMovementComponent::GetMaxSpeed() |
| = Super::GetMaxSpeed() * SpeedMultiplier |
| |
+------------------------------------------------------------------+
This is the canonical way gameplay effects influence movement: modify the MovementSpeed
attribute, never the movement component directly. The binding is set up at
InitAbilityActorInfo time on the base AEternalCharacter, so both players and enemies
(AEternalEnemy) inherit it with no per-effect or per-class code. The attribute value is treated
as a percentage (100 = full speed), converted to a 1.0-based multiplier.
Replication Strategy¶
Actor Replication Overview¶
+------------------------------------------------------------------+
| REPLICATION SUMMARY |
+------------------------------------------------------------------+
| |
| Actor Replicates? Frequency Notes |
| ----------------------------------------------------------------|
| AEternalPlayer No N/A Controllers |
| don't replicate |
| |
| AEternalPlayerState Yes (auto) 100 Hz High freq for |
| GAS updates |
| |
| AEternalCharacter Yes Default Position, |
| animation, |
| combat state |
| |
+------------------------------------------------------------------+
Component Replication¶
| Component | Location | Replication Mode |
|---|---|---|
UAbilitySystemComponent |
PlayerState | Mixed (full to owner, minimal to others) |
UCombatComponent |
Character | Partial (state to clients) |
UInventoryComponent |
Controller | Server authoritative |
UEquipmentComponent |
Controller | Yes (equipment visible to all) |
ASC Replication Mode¶
The ASC uses Mixed replication mode: - Full replication to owning client (all GE, abilities, attributes) - Minimal replication to other clients (just what they need to see)
Source Reference¶
| Topic | File | Line |
|---|---|---|
| Base Character Definition | Source/ProjectEternal/Public/Character/EternalCharacter.h |
1-120 |
| Base Character Implementation | Source/ProjectEternal/Private/Character/EternalCharacter.cpp |
1-300 |
| Movement Component (speed scaling) | Source/ProjectEternal/Public/Character/EternalCharacterMovementComponent.h |
- |
| MovementSpeed Binding | Source/ProjectEternal/Private/Character/EternalCharacter.cpp -> BindMovementSpeedAttribute() |
- |
| Player Character | Source/ProjectEternal/Public/Character/EternalPlayerCharacter.h |
1-40 |
| PlayerController Definition | Source/ProjectEternal/Public/Character/EternalPlayer.h |
1-100 |
| PlayerController Implementation | Source/ProjectEternal/Private/Character/EternalPlayer.cpp |
1-200 |
| PlayerState Definition | Source/ProjectEternal/Public/Character/EternalPlayerState.h |
1-80 |
| PlayerState Implementation | Source/ProjectEternal/Private/Character/EternalPlayerState.cpp |
1-100 |
| Attribute Set | Source/ProjectEternal/Public/AbilitySystem/EternalAttributeSet.h |
1-150 |
| GAS Init Helper | Source/ProjectEternal/Public/Utils/EternalAbilitySystemLibrary.h |
1-50 |
Related Systems¶
- Core Architecture - Naming conventions and structure
- Interfaces - ICombatInterface, ITargetable, etc.
- GAS Overview - Ability system details
- Combat Overview - Combat component details
- Equipment System - Equipment system
Recent Changes¶
| Date | Change | Impact |
|---|---|---|
| 2026-08-06 | Persistence spectrum corrected: active gameplay effects moved out of EPHEMERAL onto the PlayerState identity layer | They live on the ASC and survive the pawn swap; treating them as pawn-scoped let a killing damage-over-time effect resume ticking on the respawned pawn. Respawn now strips finite-duration effects explicitly while infinite ones (gear, kit passives, auras) persist |
| 2026-03-12 | MovementSpeed attribute wired to ALS movement | UEternalCharacterMovementComponent scales GetMaxSpeed() from the GAS MovementSpeed attribute; any effect modifying that attribute now changes actual locomotion speed for players and enemies |
| 2026-02 | DungeonModifierComponent on player pawn | Area modifiers applied via GAS on dungeon entry |
| 2026-02 | Updated attribute categories | Critical Strike, Area Modifiers, trimmed resistances |
| - | Initial documentation | - |