1. New Serialize
네트워크 직렬화란 데이터를 네트워크상에서 주고 받을 수 있게 객체나 데이터 구조를 바이트로 변환하는 것을 말한다.
게임플레이 컨텍스트 구조체에는 네트워크 시리얼라이즈 함수가 있고 이곳에서 네트워크 직렬화를 진행하여 데이터를 주고 받는 것이 가능해진다.
이 함수를 오버라이드 하여 사용하도록 하자. 오버라이드 하기 전 원래 구조를 살펴볼 것이다.
bool FGameplayEffectContext::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
uint8 RepBits = 0;
if (Ar.IsSaving())
{
if (bReplicateInstigator && Instigator.IsValid())
{
RepBits |= 1 << 0;
}
if (bReplicateEffectCauser && EffectCauser.IsValid() )
{
RepBits |= 1 << 1;
}
if (AbilityCDO.IsValid())
{
RepBits |= 1 << 2;
}
if (bReplicateSourceObject && SourceObject.IsValid())
{
RepBits |= 1 << 3;
}
if (Actors.Num() > 0)
{
RepBits |= 1 << 4;
}
if (HitResult.IsValid())
{
RepBits |= 1 << 5;
}
if (bHasWorldOrigin)
{
RepBits |= 1 << 6;
}
}
Ar.SerializeBits(&RepBits, 7);
if (RepBits & (1 << 0))
{
Ar << Instigator;
}
if (RepBits & (1 << 1))
{
Ar << EffectCauser;
}
if (RepBits & (1 << 2))
{
Ar << AbilityCDO;
}
if (RepBits & (1 << 3))
{
Ar << SourceObject;
}
if (RepBits & (1 << 4))
{
SafeNetSerializeTArray_Default<31>(Ar, Actors);
}
if (RepBits & (1 << 5))
{
if (Ar.IsLoading())
{
if (!HitResult.IsValid())
{
HitResult = TSharedPtr<FHitResult>(new FHitResult());
}
}
HitResult->NetSerialize(Ar, Map, bOutSuccess);
}
if (RepBits & (1 << 6))
{
Ar << WorldOrigin;
bHasWorldOrigin = true;
}
else
{
bHasWorldOrigin = false;
}
if (Ar.IsLoading())
{
AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent
}
bOutSuccess = true;
return true;
}
2. 직렬화 함수 커스텀 하기
이제 저기에 있는 기본 기능에 더해 우리가 원하는 정보를 직렬화/역직렬화 해서 사용할 것이다.
bool FAuraGameplayEffectContext::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
uint32 RepBits = 0;
if (Ar.IsSaving())
{
if (bReplicateInstigator && Instigator.IsValid())
{
RepBits |= 1 << 0;
}
if (bReplicateEffectCauser && EffectCauser.IsValid() )
{
RepBits |= 1 << 1;
}
if (AbilityCDO.IsValid())
{
RepBits |= 1 << 2;
}
if (bReplicateSourceObject && SourceObject.IsValid())
{
RepBits |= 1 << 3;
}
if (Actors.Num() > 0)
{
RepBits |= 1 << 4;
}
if (HitResult.IsValid())
{
RepBits |= 1 << 5;
}
if (bHasWorldOrigin)
{
RepBits |= 1 << 6;
}
if (bIsBlockHit)
{
RepBits |= 1 << 7;
}
if (bIsCrticalHit)
{
RepBits |= 1 << 8;
}
}
Ar.SerializeBits(&RepBits, 9);
if (RepBits & (1 << 0))
{
Ar << Instigator;
}
if (RepBits & (1 << 1))
{
Ar << EffectCauser;
}
if (RepBits & (1 << 2))
{
Ar << AbilityCDO;
}
if (RepBits & (1 << 3))
{
Ar << SourceObject;
}
if (RepBits & (1 << 4))
{
SafeNetSerializeTArray_Default<31>(Ar, Actors);
}
if (RepBits & (1 << 5))
{
if (Ar.IsLoading())
{
if (!HitResult.IsValid())
{
HitResult = TSharedPtr<FHitResult>(new FHitResult());
}
}
HitResult->NetSerialize(Ar, Map, bOutSuccess);
}
if (RepBits & (1 << 6))
{
Ar << WorldOrigin;
bHasWorldOrigin = true;
}
else
{
bHasWorldOrigin = false;
}
if (RepBits & (1 << 7))
{
bIsBlockHit = true;
}
if (RepBits & (1 << 8))
{
bIsCrticalHit = true;
}
if (Ar.IsLoading())
{
AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent
}
bOutSuccess = true;
return true;
}
크리티컬과 블록을 저장하는 구간을 추가하였다. 더 많은 비트가 필요하기 때문에 8비트가 아닌 32비트를 사용하였다.
2. Struct Ops Type Traits
이제 데이터를 주고 받기 위해 해야하는 작업이 하나 더 남았다. Struct Ops Type Traits 구조체는 언리얼 리플렉션 시스템과 결합하여 특정 구조체가 어떻게 직렬화 등과 관련된 작업을 할지 정하는 구조체이다.
이 구조체를 정의하기에 앞서 한가지 함수를 오버라이드 할 것이다.
virtual FGameplayEffectContext* Duplicate() const
{
FGameplayEffectContext* NewContext = new FGameplayEffectContext();
*NewContext = *this;
if (GetHitResult())
{
// Does a deep copy of the hit result
NewContext->AddHitResult(*GetHitResult(), true);
}
return NewContext;
}
해당함수는 게임플레이 컨텍스트를 복제하는 기능을 구현하는 함수이다. 이것은 우리가 만들고 있는 커스텀 게임플레이 컨텍스트 구조체에 정의하면 된다.
이제 Struct Ops Type Traits 구조체를 구현하자.
template<>
struct TStructOpsTypeTraits<FAuraGameplayEffectContext> : public TStructOpsTypeTraitsBase2<FAuraGameplayEffectContext>
{
enum
{
WithNetSerializer = true,
WithCopy = true
};
};
커스텀 구조체의 복사와 직렬화를 가능하다고 정의하였다.
전체 구조체의 구조는 아래와 같다.
USTRUCT(BlueprintType)
struct FAuraGameplayEffectContext : public FGameplayEffectContext
{
GENERATED_BODY()
public:
virtual UScriptStruct* GetScriptStruct() const override
{
return StaticStruct();
}
virtual FAuraGameplayEffectContext* Duplicate() const
{
FAuraGameplayEffectContext* NewContext = new FAuraGameplayEffectContext();
*NewContext = *this;
if (GetHitResult())
{
// Does a deep copy of the hit result
NewContext->AddHitResult(*GetHitResult(), true);
}
return NewContext;
}
virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;
bool IsBlockedHit() const { return bIsBlockHit; }
bool IsCriticalHit() const { return bIsCrticalHit; }
void SetIsBlockedHit(bool bInIsBlockedHit ) { bIsBlockHit = bInIsBlockedHit; }
void SetIsCriticalHit(bool bInIsCriticalHit) { bIsCrticalHit = bInIsCriticalHit; }
protected:
UPROPERTY()
bool bIsBlockHit = false;
UPROPERTY()
bool bIsCrticalHit = false;
};
template<>
struct TStructOpsTypeTraits<FAuraGameplayEffectContext> : public TStructOpsTypeTraitsBase2<FAuraGameplayEffectContext>
{
enum
{
WithNetSerializer = true,
WithCopy = true
};
};
3. 커스텀 게임플레이 컨텍스트 사용하기
이제 커스텀 컨텍스트의 구조체는 완성하였다. 다만 이것을 실제로 사용하려면 언리얼 엔진에서 기존의 컨텍스트를 우리가 만든 컨텍스트로 사용하도록 설정해야 한다. 이것은 다음에 이어서 하도록 하겠다.
'게임프로그래밍 > 실습2' 카테고리의 다른 글
[실습2] 55. 블랙보드와 행동트리 (0) | 2024.12.28 |
---|---|
[실습2] 54. 커스텀 게임플레이 이펙트 컨텍스트 사용하기 (0) | 2024.12.07 |
[실습2] 52. 커스텀 게임플레이 이펙트 컨텍스트 만들기 (0) | 2024.12.06 |
[실습2] 51. UGameplayEffectExecutionCalculation 클래스 (0) | 2024.12.06 |
[실습2] 50. 에너미가 죽을때 디졸브 적용하기 (0) | 2024.12.06 |