UE - 라이라 Cosmetic 분석-2
UnrealEngine, Lyra
개요
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Replicated list of applied character parts
USTRUCT(BlueprintType)
struct FLyraCharacterPartList : public FFastArraySerializer
{
GENERATED_BODY()
FLyraCharacterPartList()
: OwnerComponent(nullptr)
{
}
public:
//~FFastArraySerializer contract
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
//~End of FFastArraySerializer contract
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
{
return FFastArraySerializer::FastArrayDeltaSerialize<FLyraAppliedCharacterPartEntry, FLyraCharacterPartList>(Entries, DeltaParms, *this);
}
FLyraCharacterPartHandle AddEntry(FLyraCharacterPart NewPart);
void RemoveEntry(FLyraCharacterPartHandle Handle);
void ClearAllEntries(bool bBroadcastChangeDelegate);
FGameplayTagContainer CollectCombinedTags() const;
void SetOwnerComponent(ULyraPawnComponent_CharacterParts* InOwnerComponent)
{
OwnerComponent = InOwnerComponent;
}
private:
friend ULyraPawnComponent_CharacterParts;
bool SpawnActorForEntry(FLyraAppliedCharacterPartEntry& Entry);
bool DestroyActorForEntry(FLyraAppliedCharacterPartEntry& Entry);
private:
// Replicated list of equipment entries
UPROPERTY()
TArray<FLyraAppliedCharacterPartEntry> Entries;
// The component that contains this list
UPROPERTY(NotReplicated)
TObjectPtr<ULyraPawnComponent_CharacterParts> OwnerComponent;
// Upcounter for handles
int32 PartHandleCounter = 0;
};
FFastArraySerializer
: 네트워크 동기화가 필요한 최적화된 방식으로 복제할 때 사용.
서버-클라이언트 간 플레이어 인벤토리, 스코어보드, 액티브 버프 목록 등 자주 변하는 데이터 구조에서 사용한다.
배열 전체를 복제하는 대신 변경된 부분만 동기화하는 방식을 제공하는 클래스라고 한다?
데이터 동기화 함수들
PreReplicatedRemove
PostReplicatedAdd
PostReplicatedChange
위 세 함수들은 데이터 동기화를 위해 필요한 함수들이다.
PreReplicatedRemove
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void FLyraCharacterPartList::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
{
bool bDestroyedAnyActors = false;
for (int32 Index : RemovedIndices)
{
FLyraAppliedCharacterPartEntry& Entry = Entries[Index];
bDestroyedAnyActors |= DestroyActorForEntry(Entry);
}
if (bDestroyedAnyActors && ensure(OwnerComponent))
{
OwnerComponent->BroadcastChanged();
}
}
복제되기 전, 삭제할 요소들을 제거하는 함수다. DestrotyActorForEntry
함수로
1
2
3
4
5
6
7
8
9
10
11
12
13
bool FLyraCharacterPartList::DestroyActorForEntry(FLyraAppliedCharacterPartEntry& Entry)
{
bool bDestroyedAnyActors = false;
if (Entry.SpawnedComponent != nullptr)
{
Entry.SpawnedComponent->DestroyComponent();
Entry.SpawnedComponent = nullptr;
bDestroyedAnyActors = true;
}
return bDestroyedAnyActors;
}
Entry
의 SpawnedComponent
가 존재하면 삭제하고 true
를 반환한다.
이제 하나라도 삭제된 것이 있고, 컴포넌트의 주인이 남아있다면 주인에게 알림을 보내준다.
PostReplicatedAdd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void FLyraCharacterPartList::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
{
bool bCreatedAnyActors = false;
for (int32 Index : AddedIndices)
{
FLyraAppliedCharacterPartEntry& Entry = Entries[Index];
bCreatedAnyActors |= SpawnActorForEntry(Entry);
}
if (bCreatedAnyActors && ensure(OwnerComponent))
{
OwnerComponent->BroadcastChanged();
}
}
이건 새로 추가할 때 사용하는 함수
PostReplicatedChange
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void FLyraCharacterPartList::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
{
bool bChangedAnyActors = false;
// We don't support dealing with propagating changes, just destroy and recreate
for (int32 Index : ChangedIndices)
{
FLyraAppliedCharacterPartEntry& Entry = Entries[Index];
bChangedAnyActors |= DestroyActorForEntry(Entry);
bChangedAnyActors |= SpawnActorForEntry(Entry);
}
if (bChangedAnyActors && ensure(OwnerComponent))
{
OwnerComponent->BroadcastChanged();
}
}
이 함수는 변경된 파츠들을 삭제하고 재생성하는 함수,
위 세 함수는 서버에서 배열이 변경될 때 클라이언트에서 자동으로 실행되는 함수다.
배열에 요소를 추가하는 함수
AddEntry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FLyraCharacterPartHandle FLyraCharacterPartList::AddEntry(FLyraCharacterPart NewPart)
{
FLyraCharacterPartHandle Result;
Result.PartHandle = PartHandleCounter++;
if (ensure(OwnerComponent && OwnerComponent->GetOwner() && OwnerComponent->GetOwner()->HasAuthority()))
{
FLyraAppliedCharacterPartEntry& NewEntry = Entries.AddDefaulted_GetRef();
NewEntry.Part = NewPart;
NewEntry.PartHandle = Result.PartHandle;
if (SpawnActorForEntry(NewEntry))
{
OwnerComponent->BroadcastChanged();
}
MarkItemDirty(NewEntry);
}
return Result;
}
캐릭터 파츠를 매개변수로 받아 핸들에 카운터 증가.
서버면 새로운 엔트리를 생성하고, 엔트리에 파츠랑 핸들 인덱스를 설정 후 SpawnActorForEntry
, MakeItemDirty
호출
마지막으로 핸들을 반환해준다.
1
2
3
4
5
6
7
ElementType& AddDefaulted_GetRef() UE_LIFETIMEBOUND
{
const SizeType Index = AddUninitialized();
ElementType* Ptr = GetData() + Index;
DefaultConstructItems<ElementType>((void*)Ptr, 1);
return *Ptr;
}
Entry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
USTRUCT()
struct FLyraAppliedCharacterPartEntry : public FFastArraySerializerItem
{
GENERATED_BODY()
FLyraAppliedCharacterPartEntry()
{}
FString GetDebugString() const;
private:
friend FLyraCharacterPartList;
friend ULyraPawnComponent_CharacterParts;
private:
// The character part being represented
UPROPERTY()
FLyraCharacterPart Part;
// Handle index we returned to the user (server only)
UPROPERTY(NotReplicated)
int32 PartHandle = INDEX_NONE;
// The spawned actor instance (client only)
UPROPERTY(NotReplicated)
TObjectPtr<UChildActorComponent> SpawnedComponent = nullptr;
};
보유한 변수로 보아 단일 캐릭터 파츠를 관리하는 구조체로 예상
이것도 FFastArraySerializerItem
을 상속받았기에 자동으로 복제되는 구조체다.
마지막 변수는 리플랙션으로 동기화를 막아줬는데, 이건 클라이언트에서 생성된 액터의 인스턴스를 저장하는 용도로 보인다.
SpawnActorForEntry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
bool FLyraCharacterPartList::SpawnActorForEntry(FLyraAppliedCharacterPartEntry& Entry)
{
bool bCreatedAnyActors = false;
if (ensure(OwnerComponent) && !OwnerComponent->IsNetMode(NM_DedicatedServer))
{
if (Entry.Part.PartClass != nullptr)
{
UWorld* World = OwnerComponent->GetWorld();
if (USceneComponent* ComponentToAttachTo = OwnerComponent->GetSceneComponentToAttachTo())
{
const FTransform SpawnTransform = ComponentToAttachTo->GetSocketTransform(Entry.Part.SocketName);
UChildActorComponent* PartComponent = NewObject<UChildActorComponent>(OwnerComponent->GetOwner());
PartComponent->SetupAttachment(ComponentToAttachTo, Entry.Part.SocketName);
PartComponent->SetChildActorClass(Entry.Part.PartClass);
PartComponent->RegisterComponent();
if (AActor* SpawnedActor = PartComponent->GetChildActor())
{
switch (Entry.Part.CollisionMode)
{
case ECharacterCustomizationCollisionMode::UseCollisionFromCharacterPart:
// Do nothing
break;
case ECharacterCustomizationCollisionMode::NoCollision:
SpawnedActor->SetActorEnableCollision(false);
break; }
// Set up a direct tick dependency to work around the child actor component not providing one
if (USceneComponent* SpawnedRootComponent = SpawnedActor->GetRootComponent())
{
SpawnedRootComponent->AddTickPrerequisiteComponent(ComponentToAttachTo);
}
}
Entry.SpawnedComponent = PartComponent;
bCreatedAnyActors = true;
}
}
}
return bCreatedAnyActors;
}
서버 전용으로 보이며, 주인의 Scene
을 가져온다.
UChildActorComponent*
변수를 생성한다.
이걸 소켓에 붙이고, SetChildActorClass
, RegisterComponent
두 함수 실행
제대로 생성되었는지 확인하고, 생성했으면 충돌 모드에 따라 파츠의 충돌 설정
파츠의 루트에 AddTickPrerequisiteComponent
를 해준다?
Entry
의 SpawnedComponent
를 여기서 생성한 PartComponent
로 설정해준다.
MakeItemDrity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** This must be called if you add or change an item in the array */
void MarkItemDirty(FFastArraySerializerItem & Item)
{
if (Item.ReplicationID == INDEX_NONE)
{
Item.ReplicationID = ++IDCounter;
if (IDCounter == INDEX_NONE)
{
IDCounter++;
}
}
Item.ReplicationKey++;
MarkArrayDirty();
}
만약 아이템이 추가되거나 변경될 때 반드시 호출되는 함수
매개변수로 받은 Item
의 ReplicationID
가 -1 이면 0으로 세팅하고, IDCounter
가 -1이면 한 번 더 해준다.
그냥 -1 이상으로 세팅하는, 인덱스 부여 함수.
여기서 호출하는 MarkArrayDirty
까지 포함해서 클라이언트 동기화를 하는 부분으로 보인다.
Owner 결정 함수
1
2
3
4
void SetOwnerComponent(ULyraPawnComponent_CharacterParts* InOwnerComponent)
{
OwnerComponent = InOwnerComponent;
}
Owner
는 ULyraPawnComponent_CharacterParts*
클래스, 그 자식만 사용 가능.
결론
FLyraCharacterPartList
는 캐릭터 파츠를 관리하는 구조체Entry
라는 구조체로 단일 캐릭터 파츠를 관리하고, 이Entry
를 여러 개 가지게 된다.
1
2
UPROPERTY()
TArray<FLyraAppliedCharacterPartEntry> Entries;
AddEntry
를 통해 Entry
를 추가 가능하고,
1
FLyraAppliedCharacterPartEntry& NewEntry = Entries.AddDefaulted_GetRef();
이 코드로 새로운 요소를 추가 후 데이터 세팅
완료된 후 MakeItemDirty
로 동기화를 해준다.