PART 1 - 강의 4/4

Delta Serialization

NetSerialize, NetDeltaSerialize, Fast TArray Replication 구현

01

커스텀 NetSerialize

구조체의 네트워크 직렬화 최적화

C++ USTRUCT() struct FMyNetSerializableStruct { GENERATED_BODY() UPROPERTY() float Value1; UPROPERTY() int32 Value2; // 커스텀 직렬화 구현 bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) { // 압축된 형태로 직렬화 (float를 uint8로) uint8 ByteValue1 = FMath::Clamp( FMath::RoundToInt(Value1 * 255.f), 0, 255); Ar << ByteValue1; Ar << Value2; if (Ar.IsLoading()) { Value1 = ByteValue1 / 255.f; } bOutSuccess = true; return true; } }; // TStructOpsTypeTraits 설정 template<> struct TStructOpsTypeTraits<FMyNetSerializableStruct> : public TStructOpsTypeTraitsBase2<FMyNetSerializableStruct> { enum { WithNetSerializer = true }; };
대역폭 절약

float(4바이트)를 uint8(1바이트)로 압축하면 75% 대역폭 절약. 정밀도가 덜 중요한 값(0~1 범위)에 유용합니다.

02

Fast TArray Replication (FTR)

배열의 효율적인 델타 복제

Step 1: 아이템 엔트리 정의

C++ USTRUCT() struct FInventoryItem : public FFastArraySerializerItem { GENERATED_BODY() UPROPERTY() int32 ItemID; UPROPERTY() int32 Quantity; // 클라이언트 콜백 void PreReplicatedRemove( const struct FInventoryArray& ArraySerializer); void PostReplicatedAdd( const struct FInventoryArray& ArraySerializer); void PostReplicatedChange( const struct FInventoryArray& ArraySerializer); };

Step 2: 배열 래퍼 정의

C++ USTRUCT() struct FInventoryArray : public FFastArraySerializer { GENERATED_BODY() UPROPERTY() TArray<FInventoryItem> Items; bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms) { return FFastArraySerializer::FastArrayDeltaSerialize< FInventoryItem, FInventoryArray>(Items, DeltaParms, *this); } }; template<> struct TStructOpsTypeTraits<FInventoryArray> : public TStructOpsTypeTraitsBase2<FInventoryArray> { enum { WithNetDeltaSerializer = true }; };

Step 3: Actor에서 사용

C++ UCLASS() class AMyCharacter : public ACharacter { GENERATED_BODY() UPROPERTY(Replicated) FInventoryArray Inventory; void AddItem(int32 ItemID, int32 Quantity) { FInventoryItem NewItem; NewItem.ItemID = ItemID; NewItem.Quantity = Quantity; Inventory.MarkItemDirty( Inventory.Items.Add_GetRef(NewItem)); } void RemoveItem(int32 Index) { if (Inventory.Items.IsValidIndex(Index)) { Inventory.Items.RemoveAt(Index); Inventory.MarkArrayDirty(); } } };
FTR 콜백 타이밍
  • PreReplicatedRemove - 아이템 제거 직전 호출
  • PostReplicatedAdd - 새 아이템 추가 후 호출
  • PostReplicatedChange - 기존 아이템 변경 후 호출
03

일반 TArray vs Fast TArray

언제 어떤 것을 사용할지

기능 일반 TArray Fast TArray
전송 방식 전체 배열 재전송 변경분만 전송
콜백 없음 Add/Remove/Change
구현 복잡도 단순 복잡
적합한 케이스 작은 배열, 드문 변경 인벤토리, 버프 목록
Delta Serialization 주의

Struct의 기본 Delta Serialization은 원자성(Atomicity)이 보장되지 않습니다. 패킷 손실 시 불완전한 상태가 될 수 있으므로, 중요한 데이터는 NetSerialize로 원자적 전송을 구현하세요.

04

NetDeltaSerialize 고급 패턴

조건부 직렬화와 버전 관리

조건부 프로퍼티 직렬화

C++ bool FMyStruct::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) { // 비트마스크로 어떤 필드가 변경되었는지 표시 uint8 Flags = 0; if (Ar.IsSaving()) { if (bHealthChanged) Flags |= 0x01; if (bPositionChanged) Flags |= 0x02; if (bRotationChanged) Flags |= 0x04; } Ar << Flags; if (Flags & 0x01) Ar << Health; if (Flags & 0x02) Ar << Position; if (Flags & 0x04) Ar << Rotation; bOutSuccess = true; return true; }
버전 호환성 주의

커스텀 NetSerialize를 변경할 때는 클라이언트와 서버가 동일한 직렬화 형식을 사용해야 합니다. 핫픽스로 형식이 변경되면 버전 불일치로 크래시가 발생할 수 있습니다. 버전 번호를 추가하여 하위 호환성을 유지하세요.

FTR과 Push Model 결합

C++ void AMyActor::GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); FDoRepLifetimeParams Params; Params.bIsPushBased = true; DOREPLIFETIME_WITH_PARAMS_FAST( AMyActor, Inventory, Params); } void AMyActor::AddItem(int32 ItemID, int32 Qty) { FInventoryItem Item; Item.ItemID = ItemID; Item.Quantity = Qty; Inventory.Items.Add(Item); Inventory.MarkItemDirty(Inventory.Items.Last()); // Push Model과 FTR 동시 사용 시 둘 다 마킹! MARK_PROPERTY_DIRTY_FROM_NAME( AMyActor, Inventory, this); }
SUMMARY

핵심 요약

  • NetSerialize - 구조체의 커스텀 직렬화, 대역폭 최적화에 유용
  • TStructOpsTypeTraits - WithNetSerializer, WithNetDeltaSerializer 플래그 설정
  • Fast TArray Replication - 변경된 요소만 복제, 콜백 지원
  • MarkItemDirty/MarkArrayDirty - FTR에서 변경 알림 필수
  • 콜백 - PreReplicatedRemove, PostReplicatedAdd, PostReplicatedChange
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 커스텀 NetSerialize 구현

FVector를 16비트 정밀도로 압축하는 FCompressedPosition 구조체를 만들고, NetSerialize를 구현하여 일반 FVector 복제 대비 대역폭 절감량을 측정해보세요.

실습 2: Fast TArray로 인벤토리 구현

FFastArraySerializerItem을 상속한 FInventorySlot 구조체와 FFastArraySerializer를 상속한 FInventoryArray를 만들고, PostReplicatedAdd에서 UI 위젯을 업데이트하는 시스템을 구현해보세요.

심화 과제

Push Model과 Fast TArray Replication을 결합한 버프/디버프 시스템을 구현하세요. MarkItemDirty와 MARK_PROPERTY_DIRTY_FROM_NAME을 동시에 사용하고, 네트워크 프로파일러(stat net)로 대역폭 사용량을 비교해보세요.