티스토리 뷰
🟩 학습 목표
- 에디터 디테일 패널의 변수 리셋 버그를 해결하기 위해 OnConstruction 내부에서 단일 진실 공급원(SSOT) 구조를 확립한다.
- 기존 무기 액터가 소켓에 잔상으로 남는 고아 액터(Orphan Actor) 문제를 인터락(Interlock) 설계를 통해 해결한다.
- 네트워크 지연 환경(Ping)에서 상태 불일치로 인한 패킷 증발을 막기 위해 클라이언트 단 가드 조건문을 최적화한다.
🟧 1. OnConstruction 구조 개선: 단일 진실 공급원(Single Source of Truth)
🟦 디테일 패널 데이터 롤백 현상과 원인
레벨에 배치된 액터 인스턴스의 변수 값을 에디터 디테일 패널에서 수정해도, 엔터를 누르는 순간 원래 값으로 리셋되는 현상이 발생하였다. 이는 에디터 타임 빌드 함수인 OnConstruction(생성 스크립트) 내부에서 특정 데이터를 기준으로 값을 강제로 덮어쓰고(Overwrite) 있을 때 발생하는 전형적인 데이터 동기화 오류다.
🟦 수동 입력 허용 및 SSOT 확립
기존 클래스가 ItemRowHandle 구조체에 과도하게 종속되어 에디터 상에서의 수동 입력을 거부하던 정적 구조를 개편하였다. 사용자가 직접 입력한 고유 문자열 코드인 ItemID를 단일 진실 공급원(Single Source of Truth)으로 설정하였다.
ItemID 가공 데이터가 유효할 때 데이터 테이블 경로에서 행 데이터를 검색하고, 매핑된 ItemTypeTag와 스태틱 메쉬를 동적으로 로드하는 방식으로 변경하였다. 이로써 기획 데이터와 컴포넌트 간의 결합도를 낮추고 에디터 조작 편의성을 극대화하였다.
void ANCItemActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
if (!ItemID.IsNone())
{
UDataTable* LoadedItemDataTable = LoadObject<UDataTable>(nullptr, TEXT("/Game/NakwonClone/Blueprints/Item/ItemData/DT_ItemTypeData.DT_ItemTypeData"));
if (LoadedItemDataTable)
{
FItemData* FoundData = LoadedItemDataTable->FindRow<FItemData>(ItemID, TEXT("ActorConstruction"));
if (FoundData)
{
ItemTypeTag = FoundData->ItemTypeTag;
if (ItemMesh && FoundData->ItemMesh)
{
ItemMesh->SetStaticMesh(FoundData->ItemMesh);
}
}
}
}
}
🟧 2. 무기 스왑(Swap) 인터락 설계 및 고아 액터(Orphan Actor) 방지
🟦 고아 액터(Orphan Actor) 버그의 메커니즘
기존 무기가 장착된 상태에서 명시적인 해제 과정 없이 곧바로 새 무기를 장착할 때 발생하는 비주얼 크래시 현상이다. 컴뱃 컴포넌트 내부의 액터 포인터 변수 주소는 새 무기의 메모리 주소로 정상 치환되지만, 기존 무기 액터는 캐릭터 손 소켓에 붙은 채 메모리에 둥둥 떠 있는 고아 객체(Orphan Actor)가 된다.
이 상태에서 맨손 전환(UnArm)을 호출하면 현재 포인터가 가리키는 최신 액터만 참조하여 소멸시키기 때문에, 이전에 소외된 옛날 무기가 캐릭터 손에서 절대 사라지지 않는 치명적인 비주얼 버그를 유발한다.
🟦 인터락(Interlock) 상호 배제(Mutual Exclusion) 구현
무기를 교체하기 직전, 현재 손에 무언가 파지 되어 있다면(CurrentEquippedSlotIndex != -1) 반드시 기존 무기를 청소하는 UnEquipWeapon을 선행 프로세스로 강제 실행하도록 설계하였다. 이를 통해 아키텍처적 상호 배제(Mutual Exclusion)를 달성하여 무기 잔상 문제를 해결하였다.
기획적으로도 1, 2번 단축키는 장착 및 무기 간 스왑만 전담하도록 제한하고, H키에만 맨손 전환(ForceUnArm) 명령을 독립 바인딩하여 유저 조작의 직관성을 높였다.
// 퀵슬롯 장비 사용 처리 분기문 내부
if (CurrentEquippedSlotIndex == QuickSlotIndex) return true; // 같은 무기면 입력 무시
if (CurrentEquippedSlotIndex != -1)
{
NCCombatComp->UnEquipWeapon(); // 다른 무기 소지 중일 시 선행 파괴 후 스왑 인터락 가동
}
NCCombatComp->EquipWeapon(WeapontoEquip);
CurrentEquippedSlotIndex = QuickSlotIndex;
🟧 3. 네트워크 레이턴시를 고려한 복제(Replication) 안전장치
🟦 네트워크 지연(Ping)과 클라이언트 예측 불일치
멀티플레이어 환경에서 클라이언트 유저가 단축키 연타 등으로 서버에 RPC 명령 패킷을 과도하게 쏟아낼 때, 클라이언트가 화면을 보고 예측(Prediction)하는 상태 값과 서버 Authority가 가진 실제 상태 값이 순간적으로 불일치하는 현상이 발생할 수 있다.
🟦 클라이언트 가드 조건문 제거를 통한 신뢰성 확보
기존 UnEquipWeapon 함수 최상단 앞단에 배치되어 있던 if (!bIsEquipped) return;과 같은 로컬 가드 조건문이 지연 시간(Ping) 환경에서 치명적인 문제를 야기하였다.
네트워크 레이턴시로 인해 클라이언트가 본인의 장착 상태를 이미 해제된 것으로 일시 오해했을 때, 서버로 해제 요청 패킷 자체를 보내지 않고 로컬 단에서 가드문 확정 리턴을 해버리는 버그였다. 결국 서버 측 무기는 해제되지 않아 동기화가 완전히 깨지게 된다.
이를 해결하기 위해 앞단의 클라이언트 가드 조건문을 과감히 제거하였다. 유효성 및 안전 검사는 어차피 권한을 가진 서버 단 내부 로직(Internal_UnEquipWeapon)에서 철저하게 수행하므로, 앞단 레이어를 개방하여 네트워크 지연 상황에서도 예외 없이 해제 명령 패킷이 서버에 도달하도록 동기화 안정을 보장하였다.
🟧 핵심 요약
- 기획 데이터 롤백 현상을 방지하기 위해 ItemID 문자열을 최우선 기준으로 삼고 OnConstruction에서 에셋 명세를 실시간 로드하는 SSOT 아키텍처를 확립하였다.
- 무기 스왑 시 포스트 액터 레퍼런스가 유실되어 소켓에 잔상이 남는 고아 객체 버그를 막고자 무기 교체 전 기존 무기 해제를 강제하는 인터락 시퀀스를 구현하였다.
- 네트워크 지연 및 입력 오버랩 상황에서 RPC 패킷이 무단 유실되는 현상을 방지하기 위해 클라이언트 로컬 가드문을 소거하고 검증 권한을 서버 내부 함수로 일임하였다.
'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글
| 인벤토리 시스템 고도화 : 네이티브 태그 도입 및 파밍 동시 접근 제어 (0) | 2026.06.12 |
|---|---|
| 파밍 상자 확률 시스템 구현 및 PlayerState 기반 인벤토리 아키텍처 리팩토링 (0) | 2026.06.09 |
| UMG Drag & Drop 이벤트 처리의 함정과 인벤토리 세이브/로드 아키텍처 (0) | 2026.06.08 |
| 데이터 주도형(Data-Driven) 인벤토리 리팩터링 및 에디터 작업 프로세스 최적화 (0) | 2026.06.05 |
| 멀티플레이 인벤토리 - 드래그 앤 드롭 구현 및 데이터·액터 분리 구조의 이해 (0) | 2026.06.02 |

