Skip to content

04_Player_Character_Systems

github-actions[bot] edited this page Oct 19, 2026 · 3 revisions

04. Player & Character Systems

Sonheim의 플레이어 시스템은 PlayerState에 보유 데이터와 최종 스탯을 두고, Pawn에 실제 이동·전투·애니메이션 상태를 적용합니다. 장비를 바꾸면 Inventory의 선택 상태가 스탯 보너스, 사용 가능한 스킬, 무기 Mesh·HUD로 전달되고, 플레이어의 입력 가능 범위는 EPlayerState와 FActionRestrictions로 제어합니다.

게임 화면 — 장비 전환

04_weapon_skill_switch.mp4

곡괭이와 Shotgun을 전환하면서 장착 무기 Mesh·HUD 표시·공격 방식이 함께 바뀌는 동작을 확인할 수 있습니다.

핵심 구현

기능 상태를 관리하는 곳 실제 적용·결과
플레이어 보유 기록 ASonheimPlayerState의 Inventory·PalInventory·MonsterDex·Quest 컴포넌트 장비·보유 Pal·도감·퀘스트 기록
기본/최종 스탯 ASonheimPlayerState + UStatBonusComponent ASonheimPlayer::StatChanged로 Health·공격/방어·Movement에 반영
장비와 무기 선택 UInventoryComponent UStatBonusComponent·USonheimSkillComponent·무기 Mesh/HUD 갱신
행동 제한 ASonheimPlayer::CurrentPlayerState + FActionRestrictions 이동·회전·행동 입력 허용 여부
상태 조건 UConditionComponent Dead / Invincible / Hidden 비트 조합·시간 만료
월드 캐릭터 ASonheimPlayer : AAreaObject 카메라·무기·스프린트·락온·글라이더·Pal 관련 행동

PlayerState/Pawn을 나눈 이유와 전체 시스템 소유권은 02. Gameplay Architecture에서 설명합니다. 이 문서는 플레이어 기능 간 실제 데이터·이벤트 연결을 중심으로 다룹니다.

목차


플레이어를 구성하는 객체

PlayerState — 플레이어별 기록·스탯

현재 ASonheimPlayerState 생성자는 다음 컴포넌트를 생성합니다.

// SonheimPlayerState.cpp — 실제 생성 필드
m_InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("Inventory"));
m_StatBonusComponent = CreateDefaultSubobject<UStatBonusComponent>(TEXT("StatBonus"));
m_PalInventoryComponent = CreateDefaultSubobject<UPalInventoryComponent>(TEXT("PalInventory"));
m_MonsterDexComponent = CreateDefaultSubobject<UMonsterDexComponent>(TEXT("MonsterDex"));
m_QuestComponent = CreateDefaultSubobject<UQuestComponent>(TEXT("Quest"));
컴포넌트 데이터·역할
UInventoryComponent 인벤토리 슬롯, 장비 목록, 현재 무기 슬롯
UStatBonusComponent 장비 출처별 스탯 Modifier·무기 슬롯 보너스
UPalInventoryComponent 보유 Pal 목록·선택 슬롯
UMonsterDexComponent 발견·처치·포획·보상 수령 기록
UQuestComponent 퀘스트 단계·목표 진행·완료 기록

컴포넌트마다 복제·저장 방식을 별도로 정했습니다. 도감·퀘스트는 소유 클라이언트에 기록을 복제하고, 서버에서는 SaveGame 처리도 수행합니다. PlayerState에 배치한 것만으로 디스크에 저장되지는 않습니다.

Pawn — 캐릭터의 현재 행동과 외형

ASonheimPlayer는 AAreaObject를 상속하며 기본 캐릭터 상태와 플레이어 전용 기능을 결합합니다.

ASonheimPlayer : AAreaObject
 ├─ AAreaObject
 │   ├─ UHealthComponent / UStaminaComponent
 │   ├─ UConditionComponent / ULevelComponent
 │   ├─ USonheimSkillComponent
 │   └─ UMoveUtilComponent / URotateUtilComponent
 └─ ASonheimPlayer
     ├─ CharacterMovement / Animation / Camera
     ├─ WeaponMesh / PalSphereMesh / GliderMesh
     ├─ UInteractionComponent
     ├─ UPalCaptureComponent
     └─ UPalPartnerSkillComponent

HP 조작은 AAreaObject::DecreaseHP처럼 캐릭터 공통 API에서 시작하고, 실제 값은 UHealthComponent가 관리합니다. Pal 보유 목록은 PlayerState에, PalSphere 투척·Partner 스킬처럼 Pawn과 함께 움직이는 실행 기능은 플레이어 컴포넌트에 둡니다.


장비 변경이 스탯·스킬·외형에 반영되는 흐름

하나의 무기 슬롯 전환이 세 경로로 이어집니다.

flowchart TB
    INPUT["무기 전환 입력"] --> INV["UInventoryComponent<br/>SwitchWeaponSlot"]
    INV --> ACTIVE["CurrentWeaponSlot 변경"]
    ACTIVE --> STATS["UStatBonusComponent<br/>SetCurrentWeaponSlot"]
    STATS --> MOD["활성 슬롯 Modifier 적용"]
    MOD --> PLAYERSTATE["ASonheimPlayerState<br/>ModifiedStat · OnPlayerStatsChanged"]
    PLAYERSTATE --> PAWN["ASonheimPlayer::StatChanged"]
    ACTIVE --> NOTIFY["NotifyWeaponSlot<br/>OnWeaponChanged"]
    NOTIFY --> WEAPON["ASonheimPlayer<br/>SelectedWeapon · Mesh / Anim"]
    NOTIFY --> SKILL["USonheimSkillComponent<br/>ReplaceGrant"]
    WEAPON --> HUD["무기 타입·크로스헤어"]
Loading

1. 현재 슬롯에 해당하는 보너스만 적용

UInventoryComponent::SwitchWeaponSlot은 서버에서 현재 무기 슬롯을 바꾸고 UStatBonusComponent::SetCurrentWeaponSlot을 호출합니다.

SetCurrentWeaponSlot은 이전 무기의 Modifier를 제거하고 새로운 활성 무기의 Modifier를 추가합니다. 장착돼 있지만 선택하지 않은 다른 무기 슬롯의 보너스는 WeaponSlotBonuses에 보관했다가 선택될 때 적용합니다.

2. 선택한 무기의 스킬 사용 권한 교체

UInventoryComponent::NotifyWeaponSlot은 현재 ItemID의 FItemData::EquipmentData.SkillID를 조회하고 USonheimSkillComponent::ReplaceGrant를 호출합니다.

// InventoryComponent.cpp — 선택 무기 스킬 변경부 발췌
TArray<int32> Skills;
if (SkillId > 0)
{
    Skills.Add(SkillId);
}
Comp->ReplaceGrant(ActiveWeaponGrantId, Skills);

ActiveWeaponGrantId로 무기 출처의 스킬 집합을 교체하므로, 기본 보유 스킬이나 다른 Grant에서 제공한 스킬과 구분할 수 있습니다. Grant 참조 카운트·스킬 객체의 수명은 05. Combat, Skill & Animation에서 설명합니다.

3. 다른 플레이어도 무기 변경 결과를 확인

OnWeaponChanged는 ASonheimPlayer::UpdateSelectedWeapon으로 전달됩니다. 서버에서 SelectedWeaponSlot·CurrentWeaponItemID를 변경하고, OnRep_SelectedWeapon이 FItemData의 무기 Mesh와 Animation 설정을 적용합니다.

OnWeaponChanged(Slot, ItemID)
    ↓ UpdateSelectedWeapon (Server)
SelectedWeaponSlot / CurrentWeaponItemID
    ↓ OnRep_SelectedWeapon
UpdateWeaponMesh 또는 ClearWeaponMesh
    ↓
WeaponMesh + AnimInstance 정보
    ↓
로컬 CurrentWeaponType / 크로스헤어 반영

무기 슬롯 변경은 Inventory가 처리하고, 스탯·스킬 권한·외형 반영은 각 담당 컴포넌트와 Pawn이 수행합니다.


기본 스탯과 Source별 Modifier

PlayerState에서 최종 스탯 계산

ASonheimPlayerState는 서버에서 BaseStat을 초기화합니다. UStatBonusComponent는 각 스탯별 Modifier를 관리하며, PlayerState::UpdateStat / UpdateStats가 최종 값을 계산합니다.

BaseStat
   + UStatBonusComponent::StatBonuses
   ↓ GetModifiedStatValue
ModifiedStat
   ├─ OnPlayerStatsChanged → Pawn::StatChanged
   └─ ReplicatedStats → OnRep_ReplicatedStats → 클라이언트 UI

UStatBonusComponent::GetModifiedStatValue는 Additive를 기본값에 더한 다음, Multiplicative 보너스를 곱하고, Override가 있으면 그 값을 최종 값으로 사용합니다.

// StatBonusComponent.h — Modifier 필드 발췌
struct FStatModifier
{
    EAreaObjectStatType StatType;
    float Value;
    EStatModifierType ModifierType;
    int SourceID;
};

장비가 등록한 Modifier는 SourceID(아이템 ID)로 추적하므로 RemoveAllBonusesFromSource(ItemID)로 특정 아이템에서 적용한 보너스만 해제할 수 있습니다.

최종 값을 Pawn의 컴포넌트에 반영

ASonheimPlayer::BindDelegates는 서버에서 PlayerState의 OnPlayerStatsChanged를 StatChanged에 연결합니다.

최종 스탯 ASonheimPlayer::StatChanged 적용
HP UHealthComponent::SetMaxHP
Attack · Defense m_Attack · m_Defence
RunSpeed CharacterMovement::MaxWalkSpeed
JumpHeight CharacterMovement::JumpZVelocity

위 표는 현재 StatChanged에서 실제로 처리하는 스탯입니다. 작업 속도·스태미나·최대 무게 등의 최종 값은 PlayerState에 계산·보관되지만, 이 함수의 switch에 모두 포함된 것은 아닙니다.

최종 스탯은 PlayerState에서 계산하고, 캐릭터의 HP·공격/방어·Movement 설정은 Pawn에서 적용합니다.


Action State로 입력 범위 제어

ASonheimPlayer는 EPlayerState와 FActionRestrictions를 사용해 동작별 허용 여부를 조회합니다.

// SonheimPlayer.h — 주요 필드
struct FActionRestrictions
{
    bool bCanLook = true;
    bool bCanMove = true;
    bool bCanRotate = true;
    bool bCanOnlyRotate = false;
    bool bCanAction = true;
};
상태 이동 행동 실제 처리 의도
NORMAL 허용 허용 일반 플레이
ONLY_ROTATE 제한 제한 이동 입력을 위치 이동 대신 대상 방향 회전으로 사용
ACTION 허용 제한 새 행동 시작 제한
CANACTION 제한 허용 연계 행동·회피 등을 위한 상태
DIE 제한 제한 사망
GLIDING 제한 제한 일반 이동 입력 대신 글라이더 로직

ACTION의 bCanMove=false는 현재 코드에서 주석 처리돼 있습니다. 따라서 이 상태가 이동까지 차단한다고 설명하지 않습니다.

상태와 실제 입력 검사

InitializeStateRestrictions가 상태별 설정을 채우고, CanPerformAction(CurrentPlayerState, "Move")처럼 필요한 제약을 조회합니다.

// SonheimPlayer.cpp — Move 내부
if (CanPerformAction(CurrentPlayerState, "Move"))
{
    AddMovementInput(ForwardDirection, MovementVector.Y);
    AddMovementInput(RightDirection, MovementVector.X);
}
if (CanPerformAction(CurrentPlayerState, "OnlyRotate"))
{
    LookAtLocation(targetLocation, EPMRotationMode::Speed, 1000.f);
}

SetPlayerState는 현재 상태를 변경하고 bOrientRotationToMovement를 갱신합니다. 이동 가능 여부는 Move의 입력 검사에서 적용하며, 상태 전환만으로 CharacterMovement::DisableMovement를 호출하는 방식은 아닙니다.

Montage와 행동 가능 시점

USetPlayerStateNotify는 Montage의 타이밍에 맞춰 ACTION / CANACTION / NORMAL 등으로 상태를 전환합니다.

공격 Montage
  → ACTION : 새 행동 제한
  → CANACTION : 연계 행동 허용
  → NORMAL : 일반 동작 복귀

이 흐름은 해당 Notify를 배치한 공격의 입력 가능 구간을 애니메이션 타임라인에서 조절하기 위한 것입니다. 실제 스킬 실행과 타격 Notify는 05. Combat, Skill & Animation에서 설명합니다.


Condition Bitmask와 시간 관리

UConditionComponent는 조합 가능한 캐릭터 상태를 uint32 ConditionFlags로 관리합니다.

// SonheimGameType.h
enum class EConditionBitsType : uint32
{
    None       = 0,
    Dead       = 1 << 0,
    Invincible = 1 << 1,
    Hidden     = 1 << 2,
};

AddCondition은 서버에서 ConditionFlags에 비트를 추가하고, RemoveCondition은 해당 비트를 제거합니다. AAreaObject::CanAttack·TakeDamage 등은 HasCondition을 이용해 사망·무적·숨김 상태를 확인합니다.

같은 종류의 Condition을 다시 적용할 때

UConditionComponent::AddCondition(Type, Duration)에서 같은 비트가 이미 켜져 있고 Duration이 양수라면 기존 Timer를 지우고 새 만료 Timer를 설정합니다. Duration이 끝나면 RemoveConditionInternal이 비트를 제거합니다.

역할 현재 구현
중복 Condition 표시 비트 하나로 존재 여부 표현
Duration TMap<EConditionBitsType, FTimerHandle>
복제 ConditionFlags 프로퍼티 복제
클라이언트 반영 OnRep_ConditionFlags에서 추가·제거된 비트 계산

같은 Condition을 재적용하면 기존 Timer를 교체하므로 종류별 만료 시간은 하나만 유지합니다.


이동·무기 상태와 복제

위치·이동은 CharacterMovement를 사용하고, 프로젝트 고유의 현재 행동 표현은 ASonheimPlayer의 복제 변수와 OnRep로 처리합니다.

복제 변수 화면·이동에 반영되는 내용
bIsSprinting OnRep_IsSprinting → ApplySprinting으로 이동 속도 적용
bIsLockOn OnRep_IsLockOn으로 회전 정책·Animation 상태 적용
bIsGliding OnRep_IsGliding → ApplyGliderState로 글라이더 Mesh·Movement 설정
SelectedWeaponSlot · CurrentWeaponItemID OnRep_SelectedWeapon으로 무기 Mesh·Animation 갱신
bWeaponVisible OnRep_WeaponVisible로 무기 표시 전환
CurrentWeaponType COND_OwnerOnly 복제, 로컬 크로스헤어 갱신

Sprint_Pressed / Released는 로컬에서도 ApplySprinting을 먼저 호출해 입력 피드백을 주고, Server_SetSprinting 이후 복제값을 반영합니다. 서버 Sprint_Triggered는 스태미나를 소모하고 부족하면 Sprint를 종료합니다.

글라이더는 Server_SetGliding에서 허용 여부를 검사한 뒤 상태를 복제합니다. ApplyGliderState는 글라이더 Mesh·Animation·중력·공중 제어를 변경하고, UpdateGliding은 활성 상태의 이동 속도를 갱신합니다.

복제 조건과 예측·보정은 12. Multiplayer Synchronization에서 다룹니다.


PlayerState 연결 시점

Pawn과 PlayerState의 초기화 시점이 항상 같지는 않습니다. 특히 원격 클라이언트의 PlayerState가 늦게 준비되는 경우를 위해 서버 Possess 경로와 클라이언트 복제 경로에서 연결을 수행합니다.

flowchart TB
    SERVER["서버 PossessedBy"] --> BIND["S_PlayerState 확보<br/>InitializeByPlayerState"]
    SERVER --> DELEGATE["BindDelegates<br/>OnWeaponChanged · OnPlayerStatsChanged"]
    CLIENT["클라이언트 OnRep_PlayerState"] --> SET["S_PlayerState 확보"]
    SET --> INIT["InitializeByPlayerState"]
    INIT --> PAL["PalCaptureComponent / PalPartnerSkillComponent"]
    SET --> HUD["로컬 InitializeHUD"]
Loading

ASonheimPlayer::InitializeByPlayerState는 다음 두 컴포넌트에 PlayerState를 다시 연결합니다.

PalCaptureComponent->InitializeWithPlayerState(S_PlayerState);
PalPartnerSkillComponent->InitializeWithPlayerState(S_PlayerState);

서버 Possess와 클라이언트 OnRep_PlayerState 양쪽에서 Pal 컴포넌트를 연결합니다. 포획 판정과 보유 목록 변경은 08. Pal Capture & Partner Lifecycle에서 다룹니다.


설계 선택과 비용

선택 얻은 효과 관리할 부분
PlayerState / Pawn 상태 분리 플레이어별 보유 기록과 현재 캐릭터 실행 상태 분리 Possess·복제 시 PlayerState 재연결
장비 이벤트 기반 연동 무기 하나를 바꿔도 스탯·스킬·Mesh가 각 담당 코드에서 갱신 이벤트 연결·초기 동기화 시점
SourceID Modifier 장비 출처별 보너스 제거 아이템 ID·무기 슬롯 구분과 Modifier 갱신
Action State + Restrictions 입력별 bool 분기 대신 상태별 허용 정책 조회 상태 전환과 입력 검사 위치 관리
ConditionFlags + Timer 조합 가능한 상태와 시간 만료 관리 같은 Condition에 출처별 독립 Duration은 없음
상태 복제 + OnRep 클라이언트 무기·이동·외형 갱신 로컬 입력 피드백과 서버 결과 정합성

장비·스탯·조건 상태는 담당 컴포넌트에 유지하고, Pawn은 현재 행동과 외형에 필요한 값을 반영합니다.

관련 코드 및 문서

Clone this wiki locally