Skip to content

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

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


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 -