본문 바로가기

게임프로그래밍/실습2

[실습2] 53. 네트워크 직렬화

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. 커스텀 게임플레이 컨텍스트 사용하기

 

이제 커스텀 컨텍스트의 구조체는 완성하였다. 다만 이것을 실제로 사용하려면 언리얼 엔진에서 기존의 컨텍스트를 우리가 만든 컨텍스트로 사용하도록 설정해야 한다. 이것은 다음에 이어서 하도록 하겠다.