
- 인벤토리는 별도의 ‘컴포넌트’로 구현하여 Player에만 부착
- ShopManager는 ItemDefinition을 통해 인벤토리에 아이템을 추가하며,
해당 과정에서 Inventory는 ItemInstance를 생성하여 관리한다
- 데이터 확인을 ItemDefinition 위주로 하며,
실제 적용할때는 ItemInstance의 상태 역시 확인하여 진행

- ItemDefinition (PrimaryDataAsset) 과 ItemInstance (UOjbect)를 통해 아이템을 표현
- Definition에 바뀌지 않을 데이터를 넣으며,
상점, 인벤토리 등에서 사용하며
Weapon, Moding 등의 Def를 넣어 자유롭게 아이템을 만들 수 있도록 설정
- Instance는 자체적으로 Stack 수를 가지는 ‘실체’이며
인벤토리는 이것을 관리한다
- WeaponItemInstance는 추가적으로 ModeItemInstance들을 관리하는 TArray 보관
또한, 자신이 플레이어의 어디에 ‘장착’ 되었는지를 가지는 Enum 소유
- ModingItemInstance는 자신이 부착된 WeaponItemInstance를 관리

- 이쪽은 ItemDef가 실제로 담을 Definition 중 하나인 WeaponDefinition
(역시 DataAsset)
- 이 쪽도 무기 타입별로 세부적인 Definition이 존재하며
이것은 ShootWeaponDefinition

- 문제의 상황은 다음과 같다
- Weapon이 Moding을 별도로 관리해야 하는 상황
(추가적인 스탯이나 게임적인 효과 적용을 위해)
- 그러나, 같은 데이터를 양쪽에서 관리하는 것은 SSOT (Single Source Of Truth) 방식에 위배
(잠재적으로 양측 데이터의 모순점이 발생 가능)
- ex) 무기에서는 모딩이 없는데, 인벤토리쪽 아이템은 모딩이 있다고 착각하는 버그 등
- 처음에 무기에서 해당 상황을 전부 관리할 것이라 생각하였기에
이 부분을 어떻게 적용할지 막막했다
(작업 순서가 Weapon 을 먼저 프로토타입 구현 → 아이템 과 인벤토리 구현)
- 곰곰히 생각을 해보니, 아예 반대로 적용하면 문제가 없다고 생각하게 되었다
(Inventory → Weapon Actor)
- 애초에 아이템 데이터를 관리하는 용도로 만들어진 것이 인벤토리 컴포넌트이기에,
Weapon이 오히려 Inventory 데이터를 복사하여 사용하는 것이 맞지 않나?
는 결론에 도달하였다
- 그렇기에 다음과 같이 작업을 진행하였다
- 무기 장착
- 현재 가진 ‘모딩’이 존재하는지를 확인하고
WeaponActor에 그 데이터를 같이 보내줌
- 모딩 장착
- Weapon 아이템의 특정 Moding 칸에 장착
- 다만 Weapon이 현재 ‘장착’ 중인 상황이라면
무기의 능력치를 Rebuild하라고 알려준다
bool AShootWeapon::EquipModing(EModingSlot ModingSlot, UModingInstance* ModeInstance)
{
if (ModeInstance == nullptr)
return false;
EquipModingMap.Add(ModingSlot, ModeInstance);
if (ModeInstance->GetModeEffectClass() != nullptr)
{
EquipModingEffectClassMap.Add(ModingSlot, ModeInstance->GetModeEffectClass());
}
else
{
EquipModingEffectClassMap.Remove(ModingSlot);
}
ApplyCurrentModing();
return true;
}
...
void AShootWeapon::ApplyCurrentModing()
{
FShootWeaponStats ShootStat = ShootWeaponStatBase;
for (const auto& Pair : EquipModingMap)
{
UModingInstance* ModingIns = Pair.Value;
if (ModingIns == nullptr)
continue;
UShootWeaponDefinition* ShootDef = Cast<UShootWeaponDefinition>(ModingIns->GetModeWeaponDef());
if (ShootDef == nullptr)
continue;
FShootWeaponStats& ModingStat = ShootDef->ShootWeaponStat;
EWeaponModifier ModifierType = ModingIns->GetModeApplyType();
ApplyStat(ModingStat, ModifierType, ShootStat);
}
ShootWeaponStat = ShootStat;
AmmoChangeUIBroadCast();
}
...
void AShootWeapon::ApplyStat(const FShootWeaponStats& ModingStat, EWeaponModifier ModifierType, FShootWeaponStats& OutStat)
{
switch (ModifierType)
{
case EWeaponModifier::EWM_Add:
{
OutStat.AttackPower += ModingStat.AttackPower;
OutStat.AttackRate += ModingStat.AttackRate;
OutStat.Magazine += ModingStat.Magazine;
OutStat.ReloadTime += ModingStat.ReloadTime;
OutStat.SpareAmmo += ModingStat.SpareAmmo;
}
break;
case EWeaponModifier::EWM_Percent:
{
OutStat.AttackPower *= (1.0f + ModingStat.AttackPower);
OutStat.AttackRate *= (1.0f + ModingStat.AttackRate);
OutStat.Magazine *= (1.0f + ModingStat.Magazine);
OutStat.ReloadTime *= (1.0f + ModingStat.ReloadTime);
OutStat.SpareAmmo *= (1.0f + ModingStat.SpareAmmo);
}
break;
case EWeaponModifier::EWM_Multiple:
{
OutStat.AttackPower *= ModingStat.AttackPower;
OutStat.AttackRate *= ModingStat.AttackRate;
OutStat.Magazine *= ModingStat.Magazine;
OutStat.ReloadTime *= ModingStat.ReloadTime;
OutStat.SpareAmmo *= ModingStat.SpareAmmo;
}
break;
}
}