UE5/Devlog

[UE5] Last Bot Standing Devlog - 18

sulfurman 2026. 6. 26. 16:34

목차

     

     

    반갑습니다.

    정말 오랜만의 글인데, 지금 확인해보니 한 달이 넘도록 글을 쓰지 않았네요 하하

    변명아닌 변명을 하자면, 가족행사가 많이 겹치는 6월 + 종강시즌으로 잦은 술약속 + 월드컵 정도 있겠군요

    적어보니 그냥 논것 뿐이 없지만, 딱히 개발을 멈춘 적은 없었습니다.

    코드만 쳐다보다 비주얼 스튜디오를 꺼버리는 나날도 많았지만, 최대한 진행하려고 발버둥을 쳐 본 금월입니다.

     

    매치메이킹 구현이 어느정도 마무리 되고, 다음 할일로 인벤토리 구현을 선택했을때가
    5월 중순이었던거로 기억합니다.

    별거 없어 보여서 6월 안쪽으로 나름대로 데드라인을 잡아봤지만,
    서버의 존재를 또다시 과소평가하고 말았습니다.

    클라 - 서버 - DB 간의 영속성 데이터 처리, 통신 방식(타이밍, 위치), 데이터 구조 등등

    인벤토리라는 새로운 바운더리에는 새로운 아키텍처가 필요했습니다.

    그래서 오늘은 인벤토리에 대한 내용을 적어볼까 합니다.


    1. 개요

    플레이어 인벤토리는 3개 영역으로 구성되고, 모든 데이터는 AWS DynamoDB에 영속 저장된다.

    figma 인벤토리 스케치(왼쪽)와, 클라이언트 내에서 구현중인 UI(오른쪽)

    • Gear(장비창) — 로봇에 장착된 부품. 슬롯별(머리/몸통/팔/다리/무기) 1개씩
    • Loadout(전장 반입) — 매치에 들고 갈 그리드(3×5 = 15칸)
    • Stash(창고) — 보관용 그리드(4×10 = 40칸)

    클라이언트는 낙관적 업데이트(Optimistic Update)로 즉시 UI를 갱신하고, 서버 응답이 실패하면 백업 상태로 롤백한다.


    2. 데이터 구조

    인벤토리에 사용하는 모든 데이터 구조를 담았다.

    아이템이 가지는 다양한 속성들에서 공통된 부분과 차별되는 점들을 최대한 분류하기 위해 고안되었다.

    간단하게 예시를 들면

     

    M4 소총 - 내구도형 아이템, 무기 타입, 수리 가능

    수리 키트 - 내구도형 아이템, 수리 키트 타입, 수리 불가능

    소총 탄약 - 개수형 아이템, 탄약 타입, 수리 불가능

     

    이 세가지 아이템에 대한 현실적인 특징을 데이터 측면에서의 스키마로 옮겨냈다.

    1. 열거형(Enum)

    아이템들을 분류하기 위한 다양한 열거형들로, ItemEnumType에 정의됨.

    Enum 설명
    EInventoryZone None(0), Gear(1), LoadOut(2), Stash(3)  영역 식별. 백엔드 ZoneID와 1:1 매핑.
    EEquipmentSlot Head, Torso, RightArm, LeftArm, RightLeg, LeftLeg, PrimaryWeapon, SecondaryWeapon 장비창 세부 슬롯. Gear 영역에서 SlotIndex가 이
    값으로 캐스팅됨.
    EItemCategory Stackable, Durability  아이템 분류. 인스턴스 클래스 결정
    EItemTier Common ~ Legendary 희귀도
    EItemActionType  None, Use, Equip, Unequip, Drop, Split  컨텍스트 메뉴 액션

     

    2. 아이템 인스턴스 (런타임 객체)

    UItemInstanceBase (추상) 를 상속한 두 종류. 인게임에서 아이템을 분류하는 기준 중 가장 상위에 있는 기준.

    클래스 고유 필드 용도
    UItemInstance_Stackable Count  탄약, 리페어킷 등 "개수형" 아이템. Count
    > 1이면 Split 가능.
    UItemInstance_Durability  MaxDurability , CurrentMaxDurability ,
    CurrentDurability , bIsBroken
    장비/무기 등 "내구도형" 아이템. 수리 시 최
    대치 패널티 적용.

     

    공통 필드(ItemInstanceBase 클래스) -  InstanceID (백엔드 고유 ID), ItemID (정의 테이블 키)

     

    3. 아이템 정의 (DataTable)

    FItemDefinitionRow — Row 이름 = ItemID 문자열. 정적 속성(인게임에 영향이 없는 속성)과 기본 속성을 가짐

    필드 설명
    ItemID / ItemName / ItemDescription / ItemIcon 아이템 식별 및 표시 정보 - 아이템 툴팁에 명시되는 정보
    ItemTypeTag 게임플레이 태그 (예: Item.Type.Weapon.Rifle )
    ItemTier  희귀도 (EItemTier)
    ItemCategory Stacable / Durabilty - 아이템 인스턴스 생성할 때, 분기 기준
    bIsBound / bIsRepairable 귀속 여부 / 수리 가능 여부
    BaseAttributes 자잘한 속성 배열 (MaxDurability, Armor, Shield, MaxSpeed, Damage, FireRate ...)

     

    4. 아이템 타입 태그

    Weapon : Pistol / Rifle / SMG / Shotgun / Sniper
    Ammo : PistolAmmo / RifleAmmo / ShotgunShell / SniperAmmo
    Gear : RobotHead / RobotBody / RobotArm / RobotLeg
    RepairKit : Part / Full


    3. 아키텍처

    1. UItemRegistrySubsystem (중앙 허브)

    GameInstanceSubsystem

    아이템 정의 캐싱 + 인벤토리 상태 보관 + 낙관적 업데이트 로직 + 로봇 스탯 계산

    • 상태 보관: StashItems , LoadoutItems (배열), EquippedGearItems (TMap<EEquipmentSlot, Item>)
    • 네트워크 위임: RequestFetchInventory , RequestMoveItem , RequestSplitItem → UInventoryManager 호출
    • 낙관적 업데이트: ExecuteOptimisticMove , ExecuteOptimisticSplit , BackupInventoryState / RollbackInventoryState
    • 스탯: CalculateRobotStats , IsRobotAssemblyComplete , 프리뷰 스탯
    • 델리게이트: OnInventoryDataUpdated , OnInventoryMoveCompleted , OnInventorySplitCompleted , OnGearStatsUpdated

    2. 통신 계층

    모듈 역할
    UInventoryManager HTTP 요청/응답. FetchInventory (GET), MoveItem (POST), SplitItem (POST).
    응답 파싱 후 델리게이트 브로드캐스트
    UAPIData 엔드포인트 Data Asset
    Resources 맵(태그→경로)
    InvokeURL/Stage/리소스 조합으로 최종 URL 생성
    FDSInventoryItem /
    FDSInventoryResponse
    서버 JSON ↔ UStruct 변환 구조체. 필드는 PascalCase으로

     

    3. 백엔드 (AWS)

    • DynamoDB 테이블: PlayerInventory (PK: playerId , 속성: items[] )
    • Lambda: FetchInventory(GET), SplitItem(POST), MoveItem(POST)
    • 저장 아이템 필드는 PascalCase
      ( InstanceID, ItemID, Count, ZoneID, SlotIndex, CurrentMaxDurability, CurrentDurability )
      요청 바디 파라미터는 camelCase

    4. UI 위젯 구성

    Widget_InventoryScreen (CommonActivatableWidget, 최상위 컨트롤러)
        ├── Widget_Gear (장비창 - 로봇 부위별 슬롯)
        ├── Widget_Loadout (3×5 그리드)
        └── Widget_Stash (10×10 그리드)
                └── Widget_ItemSlot → Widget_Item (드래그/드롭, 우클릭)
                        ├── Widget_ItemContextMenu (Use/Equip/Drop/Split ...)
                        ├── Widget_ItemTooltip (정의·속성 표시)
                        └── Widget_ItemSplitScreen (수량 분할 팝업)

    5. 주요 동작 흐름

    1. 인벤토리 조회 (FetchInventory)

    InventoryScreen.NativeConstruct → RequestFetchInventory(playerId)
     → InventoryManager.FetchInventory (GET)
     → Lambda: DynamoDB 조회 (테스트 환경 - 기본 아이템 생성·저장)
     → OnInventoryFetched → HandleInventoryFetched
         · ItemID로 DataTable 조회 (정의 없으면 스킵)
         · ItemCategory에 따라 Stackable/Durability 인스턴스 생성
         · ZoneID로 Stash/Loadout/EquippedGear에 분배
     → OnInventoryDataUpdated → 각 그리드 RefreshXxx() ex)RefreshLoutdout()

     

    2. 아이템 이동 (Move)

    드래그&드롭 → HandleItemMoveRequest
         · (Gear 대상이면) ItemTypeTag ↔ 슬롯 매칭 검사
         · BackupInventoryState() → ExecuteOptimisticMove() (즉시 UI 반영)
         · RequestMoveItem (서버)
         → 성공: 유지 / 실패: RollbackInventoryState()

     

    3. 아이템 수량 분할 (Split)

    우클릭 → Split → ShowItemSplitScreen (전체화면 팝업)
         · 슬라이더로 분할 수량 조정 → Confirm
         → OnItemSplitRequested → HandleItemSplitRequest
             · BackupInventoryState() → ExecuteOptimisticSplit() (임시 InstanceID로 새 스택 생성)
             · RequestSplitItem (서버)
         → 성공: 임시 ID를 서버 newInstanceId로 교체
           실패: RollbackInventoryState()

    6. ZoneID / 슬롯 규칙

    Zone  ZoneID SlotIndex 의미 최대 슬롯 저장 컨테이너
    Gear 1 EEquipmentSlot (0=Head ... 7=SecondaryWeapon)  슬롯 8종 EquippedGearItems (TMap)
    LoadOut  2 그리드 인덱스 (0~14) 15 (3×5) LoadoutItems (배열)
    Stash  3 그리드 인덱스 (0~39)  40 (4 x 10) StashItems (배열)

    7. 로봇 조립 & 스탯

     로봇은 6개의 장비 (Head, Torso, RightArm, LeftArm, RightLeg, LeftLeg)와 주무기가 모두 장착 + 미파손 상태 가 되어야
    게임에 참가할 수 있다. -> bIsRobotAssembled = true

     

    로봇의 각 부품들은 아래 표의 스탯을 가진다.

    스탯 산출
    체력(Health)  무기 제외 장비들의 내구도 합산 (Original/CurrentMax/Current)
    실드(Shield) Torso 부위의 Item.Attribure.Shield
    이동속도(MaxSpeed)  양쪽 다리( MaxSpeed ) 평균 — 두 다리 모두 있을 때만
    표시 체력 DisplayMaxHealth = CurrentMaxHealth × 0.9 -> 인게임에서, 최대체력의 10퍼 미만일 시 사망처리

     

    장착 미리보기: CalculatePreviewGearStats 로 실제 적용 전 스탯 변화를 시뮬레이션해 UI에 표시.

    8. 백엔드 데이터 스키마

    DynamoDB PlayerInventory 의 items[] 한 항목 예시. DB에는 당연히 인스턴스 데이터가 들어간다.

    {
     "InstanceID": "uuid...",
     "ItemID": 3001,
     "Count": 30, // 개수형(Stackable)
     "ZoneID": 2, // 2 = LoadOut
     "SlotIndex": 0,
     "CurrentMaxDurability": 100.0, // 내구도형만 사용
     "CurrentDurability": 100.0
    }

     


    마치며

    장비 장착 및 탈착

     

    아이템 분할

     

    사실 인벤토리는 아직 할게 많아서 계속 구현을 진행할 예정입니다.

    아이템 정리 버튼, 일괄 이동, 아이템 컨텍스트 메뉴 구성 추가, 자잘한 버그등이 있어서 차근차근 해나갈 것 같습니다.

    2026년이 절반도 채 남지 않았습니다.
    날도 덥고 정신이 가장 해이해질 무렵이라 이걸 좀 경계하고 있지만 쉽지 않네요...

    더위 조심하시고 다음 글에서 뵙겠습니다!

    'UE5 > Devlog' 카테고리의 다른 글

    [UE5] Last Bot Standing Devlog - 17  (0) 2026.04.13
    [UE5] Last Bot Standing Devlog - 16  (0) 2026.03.30
    [UE5] Last Bot Standing Devlog - 15  (0) 2026.02.15
    [UE5] Last Bot Standing Devlog - 14  (0) 2026.02.01
    [UE5] Last Bot Standing Devlog - 13  (0) 2026.01.18