RIT-B05 | RiteSeekers — Item과 Equipment가 Gameplay State로 이어지는 흐름
처음에는 Equipment를 비교적 단순하게 생각했습니다.
Text
아이템 선택
→ 장비 Slot에 넣기
→ Weapon Mesh 변경
→ 장착 완료화면만 보면 실제로 그렇게 보입니다.
그런데 Source를 다시 따라가 보니 하나의 Item이 실제 전투에 영향을 주기까지 여러 상태를 지나고 있었습니다.
Text
ItemData
→ ItemTemplate / Fragment
→ ItemInstance
→ Inventory
→ Equipment Slot
→ Active Equip
→ AbilitySet / Stat
→ ASC
→ Gameplay Runtime이 흐름을 보고 나서 Item을 가지고 있는 상태, Equipment Slot에 들어 있는 상태, 실제 전투에서 사용 중인 상태를 따로 보기 시작했습니다.
그리고 Weapon이 손에 보인다고 해서 ASC의 Ability와 Attribute까지 같이 바뀌었다고 바로 판단하지 않게 됐습니다.
Item의 정의와 실제 Runtime Item은 나뉘어 있었다
처음 Item 코드를 보면 이름, Icon, Stat, 장비 기능이 하나의 Item Object 안에 모두 들어 있을 것처럼 생각하기 쉽습니다.
RiteSeekers에서는 정적인 Item 정보와 실제 Runtime Item 상태가 나뉘어 있습니다.
Text
ItemData
→ ItemTemplate
→ ItemFragment
→ ItemInstance제가 이해한 역할은 다음과 같습니다.
Text
ItemData
→ TemplateID와 ItemTemplate 연결
ItemTemplate
→ 같은 종류의 Item이 공유하는 정의
ItemFragment
→ 필요한 특성과 설정을 기능별로 나눠 구성
ItemInstance
→ 실제 게임 안에 존재하는 Item 하나이 구분을 알고 나니 Item을 하나의 Object로만 보는 것보다 훨씬 이해하기 쉬웠습니다.
ItemData에서 ItemTemplate을 찾았다
현재 Source에서 ItemData는 TemplateID와 실제 ItemTemplate을 연결하는 Registry 역할을 합니다.
Text
TemplateID
→ ItemData
→ ItemTemplateRuntime의 ItemInstance는 자신이 어떤 종류의 Item인지 나타내는 TemplateID를 가지고 있습니다.
이 ID를 기준으로 정적인 Item 정보가 있는 Template까지 다시 찾아갈 수 있습니다.
Text
ItemInstance
→ TemplateID
→ ItemData
→ ItemTemplate
→ 이름 / Icon / Fragment처음에는 단순히 ID와 Asset을 매핑해둔 목록처럼 보였습니다.
Source를 따라가면서 Runtime에 존재하는 Item과 공통 Item Definition을 연결해주는 기준점에 가깝다고 이해하게 됐습니다.

📸 Screenshot 1. ItemData_RiteCombatGame_RS의 TemplateID → ItemTemplate 연결과 B_ItemTemplate_OneHandSword_001의 Equipable / Weapon Fragment 구성을 함께 확인한 설정.
ItemTemplate 안에서는 Fragment로 특성을 나눴다
ItemTemplate에는 이름, 설명, Icon처럼 같은 종류의 Item이 공유할 수 있는 정보가 들어갑니다.
그리고 필요한 특성은 Fragment 단위로 나눠서 구성할 수 있습니다.
현재 프로젝트에서 확인한 대표 계열은 다음과 같습니다.
Text
Equipable Fragment
Weapon Fragment
Armor Fragment
Utility Fragment처음에는 Fragment가 ActorComponent와 비슷한 개념처럼 느껴졌습니다.
하지만 ActorComponent가 Actor에 붙어서 Runtime의 기능이나 상태를 담당한다면, RiteSeekers의 Item Fragment는 ItemTemplate에서 필요한 아이템 특성과 설정을 기능별로 나눠 조합하는 쪽에 가깝다고 이해했습니다.
제가 Fragment 개념을 이해할 때는 이런 예시가 쉬웠습니다.
Text
Pistol ItemTemplate
+
Disposable 특성을 담당하는 Fragment일반 권총에 일회용이라는 특성을 추가한다고 생각하면, 매번 새로운 Item Class를 하나 더 파생하기보다 필요한 특성을 따로 나눠 Template에 조합하는 식입니다.
이건 Fragment 구조를 이해하기 위해 든 예시이고, RiteSeekers에 실제 일회용 권총이 구현되어 있다는 뜻은 아닙니다.
또 모든 Runtime Stat을 담당하는 하나의 Stat Fragment가 따로 있다고 보지도 않습니다.
현재 Source에서는 실제 Stat 상태가 ItemInstance의 StatContainer에 남고, Weapon / Armor / Utility 계열 Fragment가 ItemInstance가 만들어질 때 초기 Stat 구성에 관여하는 흐름을 확인했습니다.
같은 Template이라도 실제 Item 상태는 달라질 수 있었다
ItemTemplate과 ItemInstance를 나눠놓은 이유도 여기서 이해하기 쉬웠습니다.
같은 종류의 Sword라도 실제로 획득한 Item은 서로 다른 상태를 가질 수 있습니다.
Text
Iron Sword A
→ Common
→ Attack +3
Iron Sword B
→ Rare
→ Attack +8둘은 같은 Template을 사용할 수 있지만 Runtime에서는 다른 Item입니다.
Text
ItemTemplate
= 이 종류의 Item은 무엇인가?
ItemInstance
= 실제 획득한 이 Item은 현재 어떤 상태인가?현재 ItemInstance에서는 대표적으로 TemplateID, Rarity, StatContainer 같은 Runtime 정보를 확인할 수 있습니다.
이 구분을 하고 나니 이름이나 Icon처럼 공유하는 정보와, Item마다 달라질 수 있는 Rarity나 Stat을 따로 보는 이유가 더 잘 보였습니다.
ItemInstance가 있다고 바로 장비가 되는 것은 아니었다
ItemInstance가 만들어졌다고 바로 Equipment가 되는 것은 아닙니다.
먼저 Inventory에 들어갈 수 있습니다.
Text
ItemInstance
→ InventoryEntry
→ Inventory여기서도 ItemInstance와 InventoryEntry의 역할이 다릅니다.
Text
ItemInstance
→ 어떤 실제 Item인가
InventoryEntry
→ 그 Item이 어디에 있고 몇 개 있는가Inventory에서는 위치, 수량, Grid 사용 범위, Stack 같은 보유 상태도 같이 관리할 수 있습니다.
처음에는 Item이 Inventory에 들어가 있으면 장비 시스템과도 거의 같은 상태라고 생각하기 쉬웠는데, 실제로는 한 단계 더 나뉘어 있었습니다.
Inventory에 있는 것과 Equipment Slot에 있는 것도 달랐다
Inventory에 Item이 있다고 해서 장착된 것은 아닙니다.
Text
Inventory
→ 가지고 있는 Item
Equipment
→ 장비 Slot에 배치한 Item예를 들어 Sword와 Bow를 모두 Inventory에 가지고 있으면서:
Text
Primary Weapon
→ Sword
Secondary Weapon
→ Bow처럼 Equipment Slot에 나눠둘 수 있습니다.
그런데 여기서도 끝이 아니었습니다.
Equipment Slot에 Item이 들어 있는 것과 현재 전투에서 실제로 사용하는 Equipment도 다시 나뉘어 있었습니다.
EquipmentManager와 EquipManager는 역할이 달랐다
Source를 처음 볼 때는 EquipmentManager와 EquipManager 이름이 비슷해서 거의 같은 역할처럼 느껴졌습니다.
실제로 따라가 보니 다음처럼 나뉘어 있었습니다.
Text
EquipmentManager
→ 어느 Slot에 어떤 Item이 들어 있는지 관리
EquipManager
→ 현재 어떤 Equipment를 Gameplay에 반영할지 관리조금 더 쉽게 보면:
Text
EquipmentManager
= 장비창의 Slot 상태
EquipManager
= 현재 Active Equipment 상태에 가깝습니다.
EquipManager에서는 CurrentEquipState를 기준으로 현재 Equipment를 바꾸고, 그 결과가 Weapon Actor, AbilitySet, Stat, Animation 같은 실제 Gameplay 상태로 이어질 수 있습니다.
그래서 UI의 Primary Weapon Slot에 Sword가 들어갔다는 사실만으로 실제 전투 상태도 Sword로 바뀌었다고 보지는 않았습니다.
현재 Source에서도 URSEquipmentManagerComponent와 URSEquipManagerComponent는 별도 Component로 존재하고, Active Equipment 전환에는 ChangeEquipState 계열이 별도 Anchor로 확인됩니다.
Active Equipment가 바뀌면 Ability도 같이 들어오고 빠졌다
Equipment가 실제 Active 상태가 되면 Ability 쪽에도 변화가 생깁니다.
현재 Source에서는 다음 흐름을 확인했습니다.
Text
FRSEquipEntry::Equip
→ BaseAbilitySet
→ GiveToAbilitySystem
→ GrantedHandles 저장
→ ASC AbilitySpec장비를 해제하면 반대 흐름이 이어집니다.
Text
FRSEquipEntry::Unequip
→ BaseAbilitySetHandles
→ TakeFromAbilitySystem여기서 새 Ability가 들어오는 것만 보면 반쪽짜리 확인이었습니다.
Text
Equipment 교체
→ Previous Ability Take
→ Current Ability Give
Unequip
→ Current Ability Take까지 같이 봐야 장비 Ability의 Lifetime이 닫힙니다.
새 Weapon Ability가 들어왔더라도 이전 장비의 Ability가 ASC에 남아 있다면 Equipment 교체가 완전히 끝난 상태라고 보기 어렵습니다. 그래서 Equipment Ability의 Lifetime은
Give만 보지 않고, 이전 상태의Take까지 함께 확인했습니다.
여기서 실제 RiteSeekers Source를 같이 볼 수 있습니다.

🔡 SourceCode 1. FRSEquipEntry::Equip()에서 현재 Equipment의 BaseAbilitySet을 가져와 ASC에 Grant하고, 결과를 BaseAbilitySetHandles에 저장하면서 ItemInstance를 함께 전달하는 부분.
장비가 Active 상태가 될 때 어떤 AbilitySet을 사용할지 EquippableFragment에서 가져오고, GiveToAbilitySystem()을 통해 ASC에 적용합니다. 이때 Grant 결과를 BaseAbilitySetHandles에 남겨두기 때문에 이후 같은 Equipment가 추가했던 Ability를 다시 회수할 수 있습니다.

🔡 SourceCode 2. FRSEquipEntry::Unequip()에서 Equip 시 저장해둔 BaseAbilitySetHandles를 사용해 해당 Equipment가 ASC에 Grant했던 Ability 상태를 회수하는 부분.
바로 아래 설명:
그래서
ItemInstance가 SourceObject로 전달됐다는 사실과 Ability의 제거 시점은 별개의 문제였습니다. 실제 제거는 Unequip 시 저장해둔 Handles를TakeFromAbilitySystem()에 넘기는 흐름으로 닫힙니다.
SourceObject가 있다고 Ability가 자동으로 사라지는 것은 아니었다
AbilitySet을 ASC에 Grant할 때 ItemInstance가 SourceObject로 연결될 수 있습니다.
이 값은 해당 Ability가 어떤 Item에서 왔는지를 연결하는 데 사용할 수 있습니다.

🔡 Runtime Debug. GA_Skill_Buff_SwiftBlade의 AbilitySpec을 펼쳐 확인한 결과, SourceObject가 LyraPlayerState_1을 가리키는 상태.

🔡 Runtime Debug. Staff의 GA_Spell_Projectile_SoulBall AbilitySpec에서는 SourceObject가 현재 Equipment의 RSItemInstance를 가리키는 상태.
ASC의
ActivatableAbilities에는 서로 다른SourceObject를 가진 AbilitySpec이 함께 존재했습니다. 그래서 Ability 이름만 보고 출처를 단정하지 않고SourceObject도 같이 확인했습니다. Staff Equipment에서 Grant된 SoulBall의 경우RSItemInstance가 연결되어 있어,GiveToAbilitySystem()에서ItemInstance를 함께 전달하는 Source 흐름과도 이어지는 것을 확인할 수 있었습니다.
하지만:
Text
SourceObject = ItemInstance라고 해서 ItemInstance가 사라질 때 Ability까지 자동으로 제거되는 구조는 아닙니다.
실제 Ability 제거는 다음 Lifetime으로 이어집니다.
Text
Give
→ GrantedHandles 저장
Equipment 교체 / Unequip
→ 저장한 Handles로 TakeFromAbilitySystem그래서 Ability가 어디에서 왔는가와 Ability를 언제 제거하는가를 같은 의미로 보지 않았습니다.
실제 Give 호출에서도 ItemInstance가 함께 전달되는 반면, 제거에는 저장했던 BaseAbilitySetHandles가 사용됩니다.
Weapon이 바뀐 것과 ASC가 바뀐 것도 따로 확인했다
Equipment 변경은 화면에서 가장 먼저 보입니다. Weapon이 손에서 바뀌는 것은 바로 확인할 수 있지만, 화면의 변화만으로 Ability까지 함께 바뀌었다고 판단하지는 않았습니다. 그래서 Weapon의 Visual State와 Equipment가 선택한 Gameplay 구성을 따로 확인했습니다.
Staff

📸 Screenshot 2. Staff를 Active Equipment로 사용 중인 Runtime 화면.
Staff를 장착했을 때 BaseAbilitySet으로 AbilitySet_Staff가 선택됐고, GrantedGameplayAbilities에는 Staff에서 사용할 세 개의 Ability가 구성되어 있었습니다.

🔡 Runtime Debug 1. AbilitySet_Staff의 GrantedGameplayAbilities[0]에 GA_MeleeCombo_Staff_1이 설정된 상태.
※ 파란 테두리는 현재 확인 중인
AbilitySet / GrantedGameplayAbilities영역, 빨간 테두리는 해당 배열 원소에 설정된 실제 Ability입니다.

🔡 Runtime Debug 2. AbilitySet_Staff의 GrantedGameplayAbilities[1]에 GA_MeleeCombo_Staff_2가 설정된 상태.

🔡 Runtime Debug 3. AbilitySet_Staff의 GrantedGameplayAbilities[2]에 GA_Spell_Projectile_SoulBall이 설정된 상태.
TwoHandSword

📸 Screenshot 3. TwoHandSword를 Active Equipment로 사용 중인 Runtime 화면.
TwoHandSword로 Equipment를 바꾸면 선택되는
BaseAbilitySet도AbilitySet_TwoHandSword로 바뀌었고,GrantedGameplayAbilities역시 Staff와 다른 네 개의 Ability로 구성되어 있었습니다.

🔡 Runtime Debug 4. AbilitySet_TwoHandSword의 GrantedGameplayAbilities[0]에 GA_MeleeCombo_TwoHandSword_1가 설정된 상태.

🔡 Runtime Debug 5. AbilitySet_TwoHandSword의 GrantedGameplayAbilities[1]에 GA_MeleeCombo_TwoHandSword_2가 설정된 상태.

🔡 Runtime Debug 6. AbilitySet_TwoHandSword의 GrantedGameplayAbilities[2]에 GA_MeleeCombo_TwoHandSword_3이 설정된 상태.

🔡 Runtime Debug 7. AbilitySet_TwoHandSword의 GrantedGameplayAbilities[3]에 GA_Block_TwoHandSword가 설정된 상태.
이 비교로 Weapon의 외형만 달라진 것이 아니라, Equipment에 따라 선택되는
BaseAbilitySet과 그 안의 Ability 구성도 달라지는 것을 확인했습니다. 다만 여기까지는 AbilitySet의 구성과 선택 상태를 확인한 것이고, 실제 ASC에AbilitySpec이 등록됐는지는 별도로 확인해야 했습니다.
실제 ASC에서는 OneHandSword → Staff 전환을 확인했다
화면에서 Weapon이 바뀐 것과 실제 ASC의 Ability 상태가 바뀐 것은 따로 확인했습니다.
게임 시작 시 장착되어 있던 OneHandSword에서 Staff로 Equipment를 변경하면서, TakeFromAbilitySystem() 실행 전과 Staff의 GiveToAbilitySystem() 실행 후를 각각 비교했습니다.

📸 Runtime Debug 8. OneHandSword에서 Staff로 교체하는 과정에서 TakeFromAbilitySystem()이 실행되기 전의 ASC 상태. 이 시점에는 기존 Equipment에서 Grant된 AbilitySpec이 아직 유지되고 있습니다.
이 상태에서 BaseAbilitySetHandles.TakeFromAbilitySystem(ASC)가 실행되면 이전 Equipment가 Grant했던 Ability들이 회수됩니다.

📸 Runtime Debug 9. Staff의 GiveToAbilitySystem() 호출이 끝난 뒤 ASC를 다시 확인한 상태. GA_MeleeCombo_Staff_1, GA_MeleeCombo_Staff_2, GA_Spell_Projectile_SoulBall에 대응하는 AbilitySpec이 ActivatableAbilities에 등록되어 있습니다.
실제 Runtime에서는 이전 Equipment Ability를 회수한 뒤 현재 Equipment의 BaseAbilitySet을 다시 Grant하고 있었습니다. 그래서 Weapon이 화면에서 바뀌었다는 것만으로 끝내지 않고, ASC의 ActivatableAbilities까지 확인해 Staff AbilitySpec이 실제로 등록된 것을 확인했습니다.
Equipment Stat도 실제 적용 단계가 따로 있었다
Equipment는 Ability뿐 아니라 Character Stat에도 영향을 줄 수 있습니다.
ItemInstance에는 StatContainer가 있고, 현재 Source에서는 Equip 과정에서 이 값을 GameplayEffect의 SetByCaller로 전달해 Attribute에 적용하는 흐름을 확인했습니다.
Text
ItemInstance
→ StatContainer
→ Equip
→ Attribute Modifier GameplayEffect
→ SetByCaller
→ ASC Attribute따라서 StatContainer 안에 값이 존재한다는 것만으로 Character의 Gameplay Attribute까지 실제로 바뀌었다고 보기는 어렵습니다.
GameplayEffect가 적용되고 최종 Attribute까지 변경되어야 합니다.
현재 Source에서 Equipment Stat이 Runtime Attribute까지 이어지는 구조는 확인했지만, 정확한 GameplayEffect Mapping과 Attribute Before / After 수치까지는 별도 Runtime Evidence가 있을 때 더 강하게 말하는 편이 맞습니다.
UI에서 보이는 상태도 Runtime State와 따로 봤다
Inventory와 Equipment는 UI에서 보이는 변화가 큰 시스템입니다.
Icon이 바뀌고 Slot이 이동하면 기능 전체가 정상적으로 끝난 것처럼 느껴질 수 있습니다.
하지만 제가 따라간 방향은 다음에 더 가까웠습니다.
Text
Item / Equipment State
→ UI 표시Gameplay State는 정상적으로 바뀌었는데 UI만 늦게 갱신될 수도 있고, 반대로 화면에서는 Slot이 바뀌었지만 Active Equipment나 ASC State가 기대와 다를 수도 있습니다.
그래서 Equipment를 확인할 때는 UI만 보지 않고 그 뒤의 Runtime State를 같이 봤습니다.
Network에서는 화면과 실제 State의 차이가 더 분명했다
Network 환경에서는 Client에서 보이는 화면 변화와 실제 Gameplay State 변경을 더 조심해서 볼 필요가 있습니다.
Source에서 따라간 큰 흐름은 다음과 같습니다.
Text
Client Request
→ Server-side 처리
→ Inventory / Equipment State 변경
→ Replication
→ Client UI 갱신Inventory / Equipment Entry가 Replication 구조와 연결돼 있어도 Client 화면에서 Drag가 성공한 것처럼 보인다는 사실만으로 Server State까지 변경됐다고 단정할 수는 없습니다.
이 부분은 Network Architecture를 깊게 파고든 내용이라기보다, 화면에 보이는 상태와 실제 Gameplay State가 항상 같다고 생각하지 않게 된 이유에 가깝습니다.
문제가 생기면 Item 흐름의 어디까지 왔는지 봤다
Item / Equipment는 연결되는 시스템이 많아서 증상 하나만 보고 전체 Source를 동시에 열면 범위가 너무 넓어졌습니다.
그래서 상태가 어디까지 만들어졌는지를 먼저 봤습니다.
Text
Item을 찾지 못한다
→ ItemData / Template
Equipment Slot까지는 정상이다
→ Equipment State
Slot은 바뀌었는데 실제 무기가 바뀌지 않는다
→ Active Equip
Weapon은 바뀌었는데 Skill이 그대로다
→ AbilitySet / ASC
Stat이 예상과 다르다
→ StatContainer / GameplayEffect / Attribute이렇게 보면 Item 시스템 전체를 한 번에 뒤지기보다 실제로 상태가 달라지기 시작한 부분 근처로 범위를 줄일 수 있었습니다.
특히 Weapon은 바뀌었는데 Ability가 그대로인 상황은 B07에서 CurrentEquipState와 ASC의 AbilitySpec을 같이 비교하면서 더 자세히 확인했습니다.
Source에서 다시 찾은 위치
Item과 Equipment 전체 흐름을 다시 따라갈 때 주로 본 위치는 다음 정도였습니다.
Text
ItemData
→ TemplateID / TemplateClass
ItemTemplate / ItemFragment
→ Equipable / Weapon / BaseAbilitySet
ItemInstance
→ TemplateID / Rarity / StatContainer
EquipmentManager
→ EquipmentEntry / Slot State
EquipManager
→ CurrentEquipState / Active Equip
FRSEquipEntry::Equip
→ GiveToAbilitySystem
FRSEquipEntry::Unequip
→ TakeFromAbilitySystem실제 Item / Equipment Source Anchor도 이 흐름에 맞춰 나뉘어 있습니다.
Text
Source/RSGame/Data/RSItemData.*
Source/RSGame/Item/RSItemTemplate.*
Source/RSGame/Item/RSItemInstance.*
Source/RSGame/Item/Fragments/*
Source/RSGame/Item/Managers/RSInventoryManagerComponent.*
Source/RSGame/Item/Managers/RSEquipmentManagerComponent.*
Source/RSGame/Item/Managers/RSEquipManagerComponent.*현재 Source Map에서도 FRSEquipEntry::Equip, FRSEquipEntry::Unequip, URSEquipManagerComponent::ChangeEquipState 등이 별도 Anchor로 잡혀 있습니다.
Class 이름을 전부 외우는 것보다:
Text
Item Definition
→ Runtime Item
→ Inventory
→ Equipment Slot
→ Active Equip
→ Ability / Stat이 순서를 알고 있는 편이 필요한 Source를 다시 찾기 쉬웠습니다.
프로젝트를 진행하며 달라진 부분
처음에는 Equipment를:
Text
Item 장착
→ Mesh 변경정도로 생각했습니다.
지금은 그 사이의 상태를 조금 더 나눠서 봅니다.
Text
ItemTemplate
→ 어떤 종류의 Item인가
ItemInstance
→ 실제 획득한 Item의 상태
Inventory
→ 가지고 있는가
Equipment
→ 어느 Slot에 들어 있는가
EquipManager
→ 현재 실제로 사용 중인가
AbilitySet / Stat
→ 어떤 Gameplay 구성을 적용할 것인가
ASC
→ 실제 Runtime State가 바뀌었는가장비 하나를 바꾸는 기능 안에서도 Item Definition, 보유 상태, Slot 상태, Active 상태, Gameplay 상태가 서로 다르다는 점을 이해한 것이 가장 크게 달라진 부분이었습니다.
정리
RiteSeekers에서 Item / Equipment Source를 따라가면서 가장 크게 달라진 것은 Item을 하나의 상태로 보지 않게 된 점이었습니다.
전체 흐름은 다음처럼 이어집니다.
Text
ItemData
→ ItemTemplate / Fragment
→ ItemInstance
→ Inventory
→ Equipment Slot
→ Active Equip
→ AbilitySet / Stat
→ ASC
→ Gameplay Runtime처음에는 장비가 화면에 잘 보이고 교체되는지를 가장 먼저 확인했습니다.
지금은:
Text
어떤 Template의 Item인가
→ 실제 ItemInstance는 어떤 상태인가
→ Inventory에 있는가
→ Equipment Slot에 있는가
→ 현재 Active Equip인가
→ Ability / Stat이 실제 Runtime에 적용됐는가까지 같이 봅니다.
이 흐름을 이해하고 나니 Item과 Equipment가 단순한 Inventory 기능이라기보다 현재 Character의 Gameplay 구성을 바꾸는 한 축이라는 점도 더 분명하게 보였습니다.