1. 게임플레이 이펙트 컨텍스트(Gameplay Effect Context)

(1) 정의

게임플레이 이펙트 컨텍스트는 게임플레이 이펙트가 발생한 맥락에 대한 정보를 가진다.

 

(2) 구성 요소

- 발생 주체(Instigator) :이펙트를 발생한 주체(ASC를 가지고 있는 클래스)

- EffectCauser : 이펙트를 일으킨 객체(데미지를 주는 것, 투사체 등)

- 대상(Target)

- 발생 위치(Origin) : 기본적으로 0

- 어빌리티 레벨 : 기본적으로 1

- HitResult 구조체 : 설정하지 않으면 nullptr

 

(3) 게임플레이 이펙트 컨텍스트 핸들(Gameplay Effect Context Handle)

게임플레이 이펙트 컨텍스트의 래퍼.

컨텍스트를 안전하게 복사하고 전달한다.

레퍼런스 카운팅을 통해 메모리 관리가 자동으로 이루어진다.

 

(4) 활용 - AuraProjectileSpell 게임플레이 어빌리티 클래스

게임플레이 이펙트 컨텍스트 핸들에 기본적으로 존재하는 멤버 변수들에 정보를 담아 전송할 수도 있다.

// TODO : 투사체에 게임플레이 이펙트 스펙 붙이기 - 데미지 부여
const UAbilitySystemComponent* SourceASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetAvatarActorFromActorInfo());
FGameplayEffectContextHandle EffectContextHandle = SourceASC->MakeEffectContext();
EffectContextHandle.SetAbility(this);
EffectContextHandle.AddSourceObject(Projectile);
FHitResult HitResult;
EffectContextHandle.AddHitResult(HitResult);

위와 같이 정보를 설정하여 다른 클래스에서 다른 클래스로 쉽게 접근할 수 있다.

 

 

2. 커스텀 게임플레이 이펙트 컨텍스트 - AuraAbilityTypes

게임 내에 블록과 크리티컬 적중 시스템을 추가하였으므로, 이펙트 컨텍스트를 통해 이 효과들이 발동했는지에 대한 불리언을 넣으려고 한다.

또한 이런 정보들을 담아 서버로 전송하기 위해 직렬화(Serialize)도 오버라이드 한다.

#pragma once

#include "GameplayEffectTypes.h"
#include "AuraAbilityTypes.generated.h"

USTRUCT(BlueprintType)
struct FAuraGameplayEffectContext : public FGameplayEffectContext
{
	GENERATED_BODY()

public:
	// 구조체를 리플렉션 시스템에 사용하기 위해 반드시 오버라이드
	virtual UScriptStruct* GetScriptStruct() const override { return FGameplayEffectContext::StaticStruct(); }

	// 직렬화를 커스텀하기 위해 반드시 오버라이드
	virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;

	bool IsCriticalHit() const { return bIsCriticalHit; }
	bool IsBlockedHit() const { return bIsBlockedHit; }

	void IsCriticalHit(bool bInIsCriticalHit) { bIsCriticalHit = bInIsCriticalHit; }
	void IsBlockedHit(bool bInIsBlockedHit) { bIsBlockedHit = bInIsBlockedHit; }

protected:
	UPROPERTY()
	bool bIsBlockedHit = false;

	UPROPERTY()
	bool bIsCriticalHit = false;
};

 

 

(1) 직렬화(Serialize)란?

서버와 통신하기 위해 데이터를 이진수 또는 String으로 눌러 담는 것을 뜻한다.

 

(1-1) '<<(Left-Shift)' 연산자 오버로딩

FArchive 구조체에서는 '<<' 연산자가 오버로딩되어 있다.

이 연산자를 이용해 데이터를 직렬화(데이터 저장)하거나 역직렬화(데이터 가져오기) 할 수 있다.

 

(1-2) 비트 연산 - '<<' ; Shift Left

모든 비트를 오른쪽의 수 만큼 왼쪽으로 민다.

 

 

(1-3) 비트 연산 - '|=' ; Bitwise OR Equal

Bitwise OR는 두 값을 비교해 둘 중 하나라도 1이면 1을 표기한다.

직렬화는 비트를 오른쪽 끝에서부터 1로 채우고 한 자리씩 옮김으로써 각 이진수 한 개에 한 개의 정보를 저장할 수 있게 한다.

즉 0, 1로 저장되는 불리언들을 모아서 직렬화를 통해 비트로 전환하여 저장할 수 있게 된다.

 

 

(1-4) 비트 연산 - '&' ; Bitwise AND

저장 중이 아닐 때 데이터를 불러오기 위해 사용한다.

Bitwise AND는 두 값을 비교해 둘 다 1일 때만 1로 표기한다.

즉 오른쪽 끝부터 1비트씩을 이동시킨 후 & 연산자를 이용하여 값이 0인지 1인지 체크할 수 있게 된다.

 

 

(2) NetSerialize 함수 오버라이드

새로 추가한 두 개의 불리언 멤버 변수를 직렬화하기 위해 직렬화 함수를 오버라이드한다.

기본적인 내용은 부모의 것을 가져오고 값만 변경 및 추가한다.

#include "AuraAbilityTypes.h"

bool FAuraGameplayEffectContext::NetSerialize(FArchive& Ar, 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 (bIsBlockedHit)
		{
			RepBits |= 1 << 7;
		}
		if (bIsCriticalHit)
		{
			RepBits |= 1 << 8;
		}
	}

	// 9개의 비트 직렬화
	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))
	{
		Ar << bIsBlockedHit;
	}
	if (RepBits & (1 << 8))
	{
		Ar << bIsCriticalHit;
	}

	if (Ar.IsLoading())
	{
		AddInstigator(Instigator.Get(), EffectCauser.Get());
	}

	bOutSuccess = true;
	return true;
}