티스토리 뷰
🟩 학습 목표
- UMG 드래그 앤 드롭 시스템의 리턴 값(True/False)에 따른 엔진 내부 마우스 이벤트 처리 메커니즘을 파악한다.
- 상이한 인벤토리 컴포넌트 인스턴스 간 데이터 이관을 위한 TransferItemTo 파이프라인 및 멀티 캐스팅 UI 동기화를 구현한다.
- USaveGame 시스템을 아키텍처에 통합하여 레벨 전이 시 데이터 영속성(Persistence)을 보장하는 저장/로드 프레임워크를 수립한다.
🟧 1. UMG Drag & Drop 이벤트의 Return Value (True vs False)의 함정
🟦 마우스 드롭 이벤트 취소 시 액터 유실 버그 분석
UI 인벤토리 슬롯 간에 아이템 스왑(Swap) 조건이 맞지 않아 백엔드 C++ 로직이 false를 반환했을 때, 이를 위젯 블루프린트 On Drop 함수의 Return Value에 그대로 매핑하여 전달할 경우, 스왑이 조용히 취소되는 것이 아니라 아이템이 월드 바닥에 강제로 드롭(Drop)되어 버리는 심각한 비조작성 버그가 발생하였다.
🟦 UMG 마우스 이벤트 버블링 및 캔슬 아키텍처 원인
언리얼 엔진 UMG 프레임워크의 구조적 특성 때문이다. On Drop 오버라이드 함수가 False를 반환하면, 엔진 컴파일러는 "해당 UI 위젯이 마우스 드롭 이벤트를 소비(Handle)하지 않고 통과시켰다"라고 판단한다.
결과적으로 마우스가 UI 가방 영역이 아닌 '허공(월드 뷰포트)'에 놓였다고 간주하여 On Drag Cancelled 이벤트를 강제로 트리거한다. 당시 프로젝트 구조는 드래그 캔슬 시 화면 밖에 아이템을 버리는 로직(Server_DropItem)과 묶여 있었기에 연쇄 버그로 이어졌다.
🟦 방어적 UI 리턴 설계 규칙
슬롯 UI 위젯은 내부 백엔드 로직의 성공/실패 여부와 무관하게, 마우스 단에서 '드롭'이라는 인터랙션 행위 자체를 정상적으로 접수했다면 무조건 Return Value를 True로 고정하여 이벤트를 소모시켜야 한다. 데이터 테이블 검증 실패나 스왑 불가 등의 예외 처리는 백엔드 데이터(배열)를 건드리지 않는 방식으로 엔진 내부에서 조용히 리턴(Early Return) 처리해야 뷰포트 화면의 예기치 못한 오작동을 원천 봉쇄할 수 있다.
🟧 2. 인벤토리 컴포넌트 간의 데이터 이동 (Move vs Transfer)
🟦 단일 컴포넌트 제어와 다중 컴포넌트 통신의 차이
가방 내부의 슬롯 이동용으로 설계된 MoveItem 함수는 동일한 메모리 배열 내에서 인덱스 교환만 처리하므로 타겟 인스턴스를 고려할 필요가 없다. 하지만 플레이어 가방(Inventory)과 외부 창고(Stash) 시스템처럼 독립된 두 개의 인벤토리 컴포넌트 간에 물품을 교환할 때는 단순 내부 인덱스 스왑 구조를 사용할 수 없다.
🟦 TransferItemTo 매커니즘 구현
상이한 컴포넌트 간 안전한 데이터 이관을 위해 TransferItemTo(TargetInventory, FromIndex, ToIndex) 함수를 신규 설계하였다.
// NCInventoryBaseComponent.cpp 내부 이관 코어 로직
bool UNCInventoryBaseComponent::TransferItemTo(UNCInventoryBaseComponent* TargetInventory, int32 FromIndex, int32 ToIndex)
{
if (!TargetInventory || !Items.IsValidIndex(FromIndex)) return false;
// 1. 출발지 데이터를 목적지 임시 버퍼로 얕은 복사
FInventorySlot ItemToTransfer = Items[FromIndex];
// 2. 목적지 컴포넌트의 가드 조건(빈칸, 동일 아이템 병합, 타 아이템 스왑)에 맞게 수량 분배 함수 호출
if (TargetInventory->ReceiveItemFrom(ItemToTransfer, ToIndex))
{
// 3. 목적지 적재가 완수되면 출발지 메모리 데이터를 안전하게 제거
ClearSlot(FromIndex);
// 4. 독립된 두 UI 위젯 컴포넌트를 동시에 갱신하기 위해 양측 확성기(Delegate) 호출
this->OnInventoryUpdated.Broadcast();
TargetInventory->OnInventoryUpdated.Broadcast();
return true;
}
return false;
}
두 인벤토리의 소유 주체가 다를 수 있으므로 데이터 변경 즉시 원본 인스턴스(this)와 목적지 인스턴스(TargetInventory)의 OnInventoryUpdated 멀티캐스트 델리게이트를 양측 모두 브로드캐스트하여 클라이언트 화면의 동기화 무결성을 유지하였다.
🟧 3. USaveGame 시스템을 활용한 데이터 영속성(Persistence) 유지
🟦 레벨 전환(Level Transition) 시 임시 메모리 휘발 문제
로비 씬에서 창고와 가방 데이터를 정교하게 정돈하더라도, 실제 인게임 레벨로 진입(OpenLevel)하는 순간 기존 월드와 컴포넌트의 임시 RAM 메모리 데이터는 모두 가비지 컬렉팅되어 파괴된다. 캐릭터의 성장 데이터 및 가방 스탯을 영구 보존하기 위해 하드디스크에 바이너리로 기록되는 구조가 필수적이다.
🟦 USaveGame 기반 영속성 프레임워크 설계
엔진 내장 파일 저장 아키텍처인 USaveGame 라이브러리를 상속받는 전용 세이브 객체 NCSaveGame C++ 클래스를 구축하였다.
// NCSaveGame.h 구조체 배열 직렬화 정의
UCLASS()
class UNCSaveGame : public USaveGame
{
GENERATED_BODY()
public:
// 인벤토리 배열 구조체를 통째로 시리얼라이즈 변수로 등록
UPROPERTY()
TArray<FInventorySlot> SavedInventoryItems;
};
🟦 PlayerState 중심의 직렬화/역직렬화(Serialization) 세이브 가이드
레벨 전이 과정에서도 소멸되지 않고 데이터 허브 역할을 수행하는 고유 PlayerState 클래스를 직렬화 제어 주체로 설정하였다.
- Save 시퀀스: 로비 씬 종료 및 레벨 이동 트리거가 당겨지는 시점에 UGameplayStatics::SaveGameToSlot을 호출하여 인벤토리 컴포넌트의 구조체 배열(TArray<FInventorySlot>) 데이터를 로컬 디스크의 바이너리 슬롯 파일로 영구 직렬화 보존한다.
- Load 시퀀스: 새로운 인게임 레벨이 생성되고 PlayerState의 BeginPlay() 라이프사이클 타이밍이 도래하면 UGameplayStatics::LoadGameFromSlot을 실행한다. 세이브 파일 존재 여부에 따라 조건 분기를 처리한다.
- 세이브 파일 존재 시: 세이브 데이터 내부의 배열을 역직렬화하여 인벤토리 컴포넌트에 안전하게 덮어씌운다(SetItemsArray).
- 최초 접속(파일 누락) 시: 공백 상태의 초기화 가방 함수(InitializeInventory)를 분기 구동하여 런타임 가드 락을 예방한다.
🟧 핵심 요약
- UMG On Drop 함수가 False를 리턴하면 마우스 이벤트를 소모하지 않은 것으로 간주하여 On Drag Cancelled 연쇄 버그(아이템 무단 버림 등)를 유발하므로 무조건 True를 반환해 차단해야 한다.
- 가방-창고 간 상이한 컴포넌트 통신을 제어하기 위해 얕은 복사 후 가중치를 분배하는 TransferItemTo 인터페이스를 구현하고 양측 델리게이트를 개별 호출하여 UI를 갱신한다.
- 레벨 전이 시 발생하는 메모리 휘발 한계는 USaveGame을 상속받은 직렬화 데이터 클래스를 통해 하드디스크 바이너리 포맷으로 아카이빙하여 극복한다.
- 데이터 세이브/로드의 핵심 생명주기는 소멸성이 짙은 액터가 아닌, 네트워크 무결성을 지닌 PlayerState 클래스의 BeginPlay 시점 등에서 동기화 분기를 처리해야 안전하다.
'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글
| OnConstruction SSOT 수립, 무기 스왑 인터락 및 복제 안전장치 설계 (0) | 2026.06.10 |
|---|---|
| 파밍 상자 확률 시스템 구현 및 PlayerState 기반 인벤토리 아키텍처 리팩토링 (0) | 2026.06.09 |
| 데이터 주도형(Data-Driven) 인벤토리 리팩터링 및 에디터 작업 프로세스 최적화 (0) | 2026.06.05 |
| 멀티플레이 인벤토리 - 드래그 앤 드롭 구현 및 데이터·액터 분리 구조의 이해 (0) | 2026.06.02 |
| 캐릭터 상호작용 시스템 아키텍처 구현 및 포스트 프로세스 최적화 (0) | 2026.06.01 |

