2 minute read


8. RPG Attribute


8.8. Player Level & Combat Interface


Player Level


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// AuraPlayerState.h
public:
  virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

private:
  	UFUNCTION()
	void OnRep_Level(int32 OldLevel);
	
	
	UPROPERTY(VisibleAnywhere, ReplicatedUsing=OnRep_Level)
	int32 Level = 1;


// AuraPlayerState.cpp
void AAuraPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(AAuraPlayerState, Level);
}
  • Level 변수를 Replicate하기 위해 GetLifetimeReplicatedProps 함수 재정의

  • Level이 변경될 때마다 OnRep_Level함수 호출(notify)

    • OldLevel 변수를 통해 이전 값과 현재 값 비교

    • UFUNCTION() 선언이 있어야 notify 역할 수행 가능

  • ReplicatedUsing: Replicated된 변수가 서버에서 변경되면 클라이언트에서 바인딩 된 함수가 자동으로 실행

    • eg) 서버에서 복제된 Level 변경 시 클라이언트에서 OldRep_Level 함수가 실행됨
  • DOREPLIFETIME(클래스명, 복제할 변수): 복제된 항목을 표시하는 가장 기본적인 매크로


Combat Interface


전투를 전용으로 다루는 인터페이스를 만들어 플레이어 캐릭터와 적 캐릭터를 관리할 수 있다. AAuraCharacterBase 클래스의 부모 클래스로 CombatInterface 클래스를 추가한다.

AuraCharacter의 Level은 AuraPlayerState 클래스에 지역 변수로 있으므로 캐릭터 클래스의 구현부에서 getter로 받아 Level을 리턴한다.


1
2
3
4
5
6
7
8
9
10
11
// AuraPlayerState.h
FORCEINLINE int32 GetPlayerLevel() const { return Level; };

// AuraCharacter.cpp
int32 AAuraCharacter::GetPlayerLevel()
{
	const AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
	check(AuraPlayerState);
	
	return AuraPlayerState->GetPlayerLevel();
}
  • 인라인 함수에 FORCEINLINE 매크로를 붙여 최적화할 수 있다.

    • GetPlayerLevel 함수를 호출할 때마다 컴파일 이전 전처리기가 대체 (컴파일 속도 향상)


💡주의!

캐릭터 클래스의 부모 클래스로 인터페이스를 추가하면 기존에 사용하던 TObjectPtr<IEnemyInterface>에 오류가 발생한다.

TObjectPtr은 가리키는 오브젝트가 유효한지 검사하기 위해 하위 클래스의 모든 멤버 변수와 함수를 관리하는데, AuraEnemy가 상속한 ICombatInterface에 접근할 수 없기 때문!

이렇게 오류가 발생할 경우에는 TObjectPtr 대신 원시 포인터를 사용해 원하는 클래스에 바로 접근하자.

1
2
3
4
// AuraPlayerController.h
private:
	IEnemyInterface* LastActor; /** 원시포인터로 변경 */
	IEnemyInterface* ThisActor;


8.9. Modifier Magnitude Calculations


attributes를 ovrride나 add 대신 직접 커스텀하고 싶다면 MMC 클래스를 상속받아 코드로 작성할 수 있다.


1
2
3
4
5
// UMMC_MaxHealth.h
public:
	UMMC_MaxHealth();
		
	virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
  • FGameplayEffectSpec에 접근->Attribute 내부의 모든 정보에 액세스가 가능함!


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
// UAuraAttributeSet.h
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
		GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
		GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
		GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
		GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Vigor, Category = "Primary Attributes")
FGameplayAttributeData Vigor;
ATTRIBUTE_ACCESSORS(UAuraAttributeSet, Vigor);


// UMMC_MaxHealth.cpp
UMMC_MaxHealth::UMMC_MaxHealth()
{
	/** 캡쳐할 속성 지정 */
	VigorDef.AttributeToCapture = UAuraAttributeSet::GetVigorAttribute();
	
	/** AuraCharacter에게 Vigor 캡처 */
	VigorDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;
	VigorDef.bSnapshot = false;

	// 캡처할 속성은 RelevantAttributesToCapture에 추가해야 함(TArray 형식)
	RelevantAttributesToCapture.Add(VigorDef);
}


  • AuraAttributeSet 클래스에서 Vigor 속성에 매크로 작성

    • GAMEPLAYATTRIBUTE_PROPERTY_GETTER 매크로를 통해 별도의 함수 구현 없이 다른 클래스에서 getter 사용 가능

    • getter로 불러온 함수는 정적 함수이므로 별도의 포인터가 필요하지 않음

  • Vigor를 어디로부터 캡처할지 Source/Target 중 선택해야 함

  • bSnapshot: 속성을 캡처할 타이밍. 효과가 적용되는 시점에서 속성 캡처

    • Vigor는 GE가 생성되는 즉시 동일한 함수에서 GameplayEffectSpec이 적용되므로 관련x

    • cf) GESpec을 생성하고 공기 중으로 날아가는 불덩이에 설정할 경우, 별도의 타이밍 지정이 필요함

  • RelevantAttributesToCapture: 캡처할 속성을 TArray에 추가해야 함

    • 생성자에서 추가해 구현부에서 해당 배열에 접근해 원하는 속성 연산

Leave a comment