Character Class
게임 내 등장하는 여러 캐릭터들은 전사, 궁수, 마법사 같은 직업(Class)으로 분류될 예정임.
- 해당 캐릭터들에 대한 정의를 어떻게 하고, 능력치/스킬 등은 어떻게 다르게 할 지에 대한 정의를 내려야 함.

캐릭터의 클래스를 ECharacterClass라는 이름의 Enum을 생성하여 그 곳에 정의하고,
클래스마다 다른 기본 능력치를 CurveTable을 이용하여 지정함.
만들어둔 각 클래스의 정보를 UCharacterClassInfo라는 이름의 DataAsset을 정의하여 그 곳에 통합하는 식으로 구성함.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "CharacterClassInfo.generated.h"
class UGameplayEffect;
UENUM(BlueprintType)
enum class ECharacterClass : uint8
{
Elementalist,
Warrior,
Ranger
};
USTRUCT(BlueprintType)
struct FCharacterClassDefaultInfo
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, Category = "Class Defaults")
TSubclassOf<UGameplayEffect> PrimaryAttributes;
};
UCLASS()
class AURA_API UCharacterClassInfo : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Character Class Defaults")
TMap<ECharacterClass, FCharacterClassDefaultInfo> CharacterClassInformation;
UPROPERTY(EditDefaultsOnly, Category = "Common Class Defaults")
TSubclassOf<UGameplayEffect> SecondaryAttributes;
UPROPERTY(EditDefaultsOnly, Category = "Common Class Defaults")
TSubclassOf<UGameplayEffect> VitalAttributes;
FCharacterClassDefaultInfo GetClassDefaultInfo(ECharacterClass CharacterClass);
};
// UCharacterClassInfo.cpp
#include "AbilitySystem/Data/CharacterClassInfo.h"
FCharacterClassDefaultInfo UCharacterClassInfo::GetClassDefaultInfo(ECharacterClass CharacterClass)
{
return CharacterClassInformation.FindChecked(CharacterClass);
}
Attribute의 초기화는 GameplayEffect를 사용하는 방식으로 했으므로, 각 캐릭터 클래스에 따른 Attribute 초기화도 마찬가지로 진행함.
생성한 UCharacterClassInfo DataAsset에 별도의 GameplayEffect를 설정하고,
해당 GameplayEffect들이 CurveTable을 이용하여 레벨마다 적절한 값을 갖도록 함.
- CurveTable은 CSV나 JSON 포맷을 이용하여 Import/Export 하는 것이 가능함.
UCharacterClassInfo는 각 캐릭터들이 지니는 것도 가능은 하겠지만, 정보를 초기화 하는데 한 번만 사용할 뿐더러, 다른 직업에 대한 정보도 모두 지니고 있기 때문에 메모리 차원에서 비효율적임.
이러한 내용은 게임의 규칙과 직접적으로 연관되어 있으므로, GameplayMode에 배치하는 것이 좋은 방법임.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "AuraGameModeBase.generated.h"
class UCharacterClassInfo;
UCLASS()
class AURA_API AAuraGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Character Class Info")
TObjectPtr<UCharacterClassInfo> CharacterClassInfo;
};
그리고 GameMode에 존재하는 UCharacterClassInfo에 쉽게 접근할 수 있도록, 블루프린트 함수 라이브러리에 함수를 작성하여 사용할 수 있음.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "UI/WidgetController/OverlayWidgetController.h"
#include "Data/CharacterClassInfo.h"
#include "AuraAbilitySystemLibrary.generated.h"
class UAbilitySystemComponent;
UCLASS()
class AURA_API UAuraAbilitySystemLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "AuraAbilitySystemLibrary|WidgetController")
static UOverlayWidgetController* GetOverlayWidgetController(const UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "AuraAbilitySystemLibrary|WidgetController")
static UAttributeMenuWidgetController* GetAttributeMenuWidgetController(const UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "AuraAbilitySystemLibrary|CharacterClassInfo")
static void InitializeDefaultAttributes(const UObject* WorldContextObject, ECharacterClass CharacterClass, float Level, UAbilitySystemComponent* AbilitySystemComponent);
};
// AuraAbilitySystemLibrary.cpp
void UAuraAbilitySystemLibrary::InitializeDefaultAttributes(const UObject* WorldContextObject, ECharacterClass CharacterClass, float Level, UAbilitySystemComponent* AbilitySystemComponent)
{
AAuraGameModeBase* AuraGameMode = Cast<AAuraGameModeBase>(UGameplayStatics::GetGameMode(WorldContextObject));
if (AuraGameMode == nullptr) return;
AActor* AvatarActor = AbilitySystemComponent->GetAvatarActor();
UCharacterClassInfo* CharacterClassInfo = AuraGameMode->CharacterClassInfo;
FCharacterClassDefaultInfo ClassDefaultInfo = CharacterClassInfo->GetClassDefaultInfo(CharacterClass);
FGameplayEffectContextHandle PrimaryAttributeContextHandle = AbilitySystemComponent->MakeEffectContext();
PrimaryAttributeContextHandle.AddSourceObject(AvatarActor);
FGameplayEffectSpecHandle PrimaryAttributeSpecHandle = AbilitySystemComponent->MakeOutgoingSpec(ClassDefaultInfo.PrimaryAttributes, Level, AbilitySystemComponent->MakeEffectContext());
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*PrimaryAttributeSpecHandle.Data.Get());
FGameplayEffectContextHandle SecondaryAttributeContextHandle = AbilitySystemComponent->MakeEffectContext();
SecondaryAttributeContextHandle.AddSourceObject(AvatarActor);
FGameplayEffectSpecHandle SecondaryAttributeSpecHandle = AbilitySystemComponent->MakeOutgoingSpec(CharacterClassInfo->SecondaryAttributes, Level, AbilitySystemComponent->MakeEffectContext());
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SecondaryAttributeSpecHandle.Data.Get());
FGameplayEffectContextHandle VitalAttributeContextHandle = AbilitySystemComponent->MakeEffectContext();
VitalAttributeContextHandle.AddSourceObject(AvatarActor);
FGameplayEffectSpecHandle VitalAttributeSpecHandle = AbilitySystemComponent->MakeOutgoingSpec(CharacterClassInfo->VitalAttributes, Level, AbilitySystemComponent->MakeEffectContext());
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*VitalAttributeSpecHandle.Data.Get());
}