AI/Unity

AntiGravity를 이용한 SRPG만들기 11일차 (아이템 장비 버그 수정)

blacknabis 2026. 1. 26. 23:26

[개발 일지] 11일차: 아이템 복제 버그 섬멸 & 소유권 시스템 확립 (feat. Antigravity)

*본 개발 일지는 Google의 차세대 AI 에이전트 **'Antigravity'*와 함께 작성되었습니다.

🏗️ 11일차 개발 목표: "데이터 무결성 확보와 소유권 기반 인벤토리 구축"

검 하나로 온 파티원이 돌려쓰는 '의좋은 형제' 버그는 이제 안녕입니다. 오늘은 ScriptableObject의 한계를 극복하고, 모든 아이템에 고유한 주민등록번호(GUID)를 부여하여 **'단일 진실 원칙(Single Source of Truth)'**을 세우는 데 집중했습니다.


1. ⚔️ "검 하나를 두 유닛이?" 복제 버그의 원인 분석

기존 시스템은 ItemData라는 에셋을 직접 참조했습니다. 이는 마치 서점에 있는 '책 샘플'을 모두가 자기 책이라고 우기는 것과 같았죠.

  • 문제점: 누가 아이템을 가지고 있는지 추적 불가, 인벤토리-유닛 간 동기화 누락.
  • 리스크: 에디터에서 변경한 데이터가 실제 에셋 파일에 덮어씌워지는 '데이터 오염' 발생.

2. 🆔 ItemInstance: 아이템에 '자아'를 부여하다

이제 모든 아이템은 생성되는 순간 고유한 instanceID를 가집니다.

  • 소유권(Ownership) 로직: ownerID 필드를 통해 해당 아이템이 인벤토리에 있는지, 혹은 특정 유닛이 장착 중인지 명확히 판별합니다.
  • 상태 논리 검증:
  • 아이템의 상태 $S$는 다음 조건 중 하나만 만족해야 합니다.
  • $$S_{item} \in \{ Inventory, \text{Equipped}(UnitID) \}$$

3. 🛡️ 런타임 데이터 보호: [NonSerialized]의 마법

UnitData가 ScriptableObject일 때 발생하는 가장 큰 고충인 '플레이 모드에서의 데이터 영구 변경'을 해결했습니다. [NonSerialized] 속성을 활용해 런타임 중에만 존재하는 동적 데이터를 원본 에셋으로부터 격리했습니다.

  • 효과: 게임을 껐다 켜도 원본 유닛 데이터는 깨끗하게 유지되며, 오직 세이브 데이터를 통해서만 장비 상태가 복원됩니다.

4. 🛠️ 트랜잭션 기반 장착 로직 & 디버그 툴

장착 프로세스를 **'인벤토리 제거 → 유닛 장착'**의 트랜잭션 구조로 개편하여 데이터가 중간에 붕 뜨는 현상을 방지했습니다.

  • Validate & Fix: 로드 시점에 인벤토리와 파티 장비를 전수 조사하여, 혹시라도 남아있을지 모를 중복 참조를 자동 소거하는 자가 복구 로직을 추가했습니다.
  • Debug Analyzer: 에디터 메뉴 상단에 Analyze Equipment 도구를 추가하여, 클릭 한 번으로 프로젝트 내의 모든 소유권 불일치를 추적합니다.

📊 11일차 개발 통계

항목 성과 및 개선점
아이템 중복 착용 0건 (완전 차단)
데이터 무결성 GUID 기반 1:1 매칭 시스템 확립
에셋 안전성 Runtime Data 격리를 통한 원본 SO 오염 방지
디버그 효율 전용 분석 툴을 통한 장비 상태 가시화

📝 마치며

Antigravity의 조언: "버그를 고치는 것보다 중요한 것은 버그가 발생할 수 없는 '구조'를 만드는 것입니다. 오늘 구축한 소유권 시스템은 향후 구현할 장비 강화나 경매장, 복잡한 인벤토리 시스템의 가장 단단한 주춧돌이 될 것입니다."



작업전 플랜 설계 파일 내용

장비 시스템 개편 계획
목표 설명
현재 장비 시스템은 

ItemData
 스크립터블 오브젝트를 직접 참조하고 있어, 하나의 "철검"을 구매하면 모든 캐릭터가 동시에 착용할 수 있는 문제가 있습니다. (물리적 실체가 공유됨) 이를 해결하기 위해 

ItemData
를 감싸는 ItemInstance 클래스를 도입하여, 각 아이템이 고유한 소유권을 가지도록 개편합니다.

사용자 검토 필요
IMPORTANT

데이터 마이그레이션: 기존 세이브 파일은 아이템 이름(문자열)만 저장하고 있습니다. 새로운 시스템은 아이템별 고유 ID(GUID)를 사용합니다. 따라서 구형 세이브 파일을 로드할 때, 저장된 이름 기반으로 새로운 인스턴스를 생성하고 ID를 부여하는 "자동 변환(Migration)" 로직이 포함됩니다. (데이터 손실 없음)

제안된 변경 사항
데이터 구조: 새로운 ItemInstance 클래스
SRPG.Data.ItemInstance 클래스를 신규 생성합니다.

필드:
string instanceID: 고유 식별자 (GUID).
ItemData data: 원본 데이터 참조 (스탯, 이름 등).
string ownerID: 이 아이템을 장착 중인 유닛의 ID (인벤토리에 있으면 null).
bool isLocked: 장금 기능 (추후 확장을 위해).
메서드:
Equip(string unitID): 소유자 설정.
Unequip(): 소유자 해제.
매니저: 

GameManager
인벤토리 관리 로직을 개편합니다.

List<ItemData> inventory -> List<ItemInstance> inventory로 변경.

AddItem
 업데이트:
아이템 획득 시 new ItemInstance(itemData) 생성 후 리스트 추가.

RemoveItem
 업데이트:
특정 인스턴스를 삭제하도록 변경.
GetFreeEquipment(EquipmentSlot slot) 추가:
해당 슬롯에 맞고 ownerID가 없는(미장착) 인스턴스 목록 반환.
캐릭터: 

Unit
장비 슬롯 타입을 변경합니다.

ItemData equippedWeapon -> ItemInstance equippedWeapon.
ItemData equippedArmor -> ItemInstance equippedArmor.

Setup
, GetTotalStrength 등에서 equippedWeapon.data.bonusAttack 형태로 접근하도록 수정.
EquipItem(ItemInstance item) 추가:
아이템이 다른 사람 소유인지 확인.
내 장비 슬롯에 할당하고, 아이템의 ownerID를 내 ID로 설정.
UnequipItem(EquipmentSlot slot) 추가.
저장 시스템: 

SaveData
인스턴스 상태를 저장하도록 변경합니다.

신규 클래스 ItemSaveData:
instanceID, dataName, ownerID 저장.

SaveData
 업데이트:
List<ItemSaveData> inventoryItems 사용.

UnitSaveData
 업데이트:
string equippedWeaponID, equippedArmorID 저장 (이름 대신 ID 참조).
마이그레이션 로직:
구버전 세이브(이름만 있음) 로드 시, 즉석에서 인스턴스를 생성하여 호환성 유지.
UI: 

ItemListUI
 & 

UnitInfoUI

ItemListUI
:
ItemInstance 리스트를 표시하도록 변경.
현재 유닛이 착용 가능하지만, 다른 유닛이 사용 중인 경우 "사용 중" 표시 또는 비활성화 처리.

UnitInfoUI
:
스탯 계산 로직을 ItemInstance에 맞춰 수정.
검증 계획
자동화 테스트
별도의 자동화 테스트 코드는 작성하지 않음. 수동 검증 진행.
수동 검증
새 게임 테스트:
새 게임 시작 (기본 파티 생성).
기본 지급 아이템들이 각각 고유 인스턴스로 생성되었는지 확인.
유닛 A의 무기를 유닛 B가 바로 뺏어 낄 수 없는지 (해제 후 장착해야 함) 확인.
인벤토리 제한 테스트:
인벤토리에 "철검" 1개 추가.
유닛 A가 장착.
유닛 B가 장비 창을 열었을 때, 해당 철검이 목록에 없거나 "착용 중"으로 뜨는지 확인.
저장/로드 테스트:
파티원이 장비를 착용한 상태에서 저장.
게임 재시작 후 로드.
장비가 증발하거나 복사되지 않고, 정확히 소유권이 유지되는지 확인.

반응형