1. 준비사항
1.1 uproject 파일 수정
- Edit > Plugins > Gameplay Ability System 플러그인 체크 후 .uproject 파일 수정
<hide/>
{
"FileVersion": 3,
"EngineAssociation": "5.1",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "TPSGAS",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine",
"GameplayAbilities"
]
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "GameplayAbilities",
"Enabled": true
}
]
}
1.2 Build.cs 파일 수정
<hide/>
// GAS.Build.cs
using UnrealBuildTool;
public class GAS : ModuleRules
{
public GAS(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.Add("GAS/Public");
PrivateIncludePaths.Add("GAS/Private");
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay",
// Input
"EnhancedInput",
// GameplayAbilitySystem
"GameplayAbilities", "GameplayTags", "GameplayTasks",
});
}
}
1.3 GASGlobal 헤더파일 수정
<hide/>
// GASGlobal.h
...
#define LIMIT (99999.f)
UENUM(BlueprintType)
enum class EGASAbilityInputID : uint8
{
None,
Confirm,
Cancel,
Idle,
Jump,
Move,
};
UENUM(BlueprintType)
enum class EGASAbilityID : uint8
{
None,
Idle,
Walk,
Jump,
Run,
Sprint,
};
2 GameAbilitySystem
2.1 AbilitySystem 컴포넌트 생성
- 새로운 C++ 클래스 > AbilitySystemComponent 클래스 > "GASAbilitySystemComponent" 클래스
AbilitySystem도 결국 캐릭터나 원하는 액터에 컴포넌트로 부착하는 방식.
2.2 AttributeSet
- 새로운 C++ 클래스 > AttributeSet 클래스 > "GASCharacterAttributeSet" 클래스
- 해당 액터에 부여될 속성들
해당 속성들이 멀티플레이 환경에서도 동작할 수 있도록 코드가 작성되어 있음.
또한 델리게이트를 활용해서 해당 속성이 변경되거나 소진되었을 경우를 확인 가능.
<hide/>
// UGASCharacterAttributeSet.h
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "GASCharacterAttributeSet.generated.h"
/*
* 이 매크로 함수 하나만 작성하면, 자동으로 해당 속성에대한 게터/세터가 generated.h 파일에 작성됨.
* 덕분에 런타임 중, 블루 프린트에서도 해당 속성을 접근할 수 있게 됨.
*/
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/* 속성의 변화를 알리기 위한 델리게이트들. 필요에 따라 추가. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAttributeChangeDelegate, float, CurrentAttribute);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnOutOfAttributeDlegate);
UCLASS()
class GAS_API UGASCharacterAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category = Attribute, ReplicatedUsing = OnRep_MaxHP)
FGameplayAttributeData MaxHP;
ATTRIBUTE_ACCESSORS(UGASCharacterAttributeSet, MaxHP);
UPROPERTY(BlueprintReadOnly, Category = Attribute, ReplicatedUsing = OnRep_CurrentHP)
FGameplayAttributeData CurrentHP;
ATTRIBUTE_ACCESSORS(UGASCharacterAttributeSet, CurrentHP);
UPROPERTY(BlueprintReadOnly, Category = Attribute, ReplicatedUsing = OnRep_MaxSP)
FGameplayAttributeData MaxSP;
ATTRIBUTE_ACCESSORS(UGASCharacterAttributeSet, MaxSP);
UPROPERTY(BlueprintReadOnly, Category = Attribute, ReplicatedUsing = OnRep_CurrentSP)
FGameplayAttributeData CurrentSP;
ATTRIBUTE_ACCESSORS(UGASCharacterAttributeSet, CurrentSP);
FOnAttributeChangeDelegate OnCurrentSPChanged;
FOnOutOfAttributeDlegate OnOutOfCurrentSP;
public:
UGASCharacterAttributeSet();
/* 이펙트가 적용된 후에 자동으로 호출되는 함수 */
void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
/* 속성이 변경된 후에 자동으로 호출되는 함수 */
void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
/* 이하는 레플리케이션을 위한 함수들 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION()
virtual void OnRep_MaxHP(const FGameplayAttributeData& OldMaxHP);
UFUNCTION()
virtual void OnRep_CurrentHP(const FGameplayAttributeData& OldCurrentHP);
UFUNCTION()
virtual void OnRep_MaxSP(const FGameplayAttributeData& OldMaxSP);
UFUNCTION()
virtual void OnRep_CurrentSP(const FGameplayAttributeData& OldCurrentSP);
};
<hide/>
// UGASCharacterAttributeSet.cpp
#include "GASCharacterAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "GASGlobal.h"
#include "Net/UnrealNetwork.h"
UGASCharacterAttributeSet::UGASCharacterAttributeSet()
{
}
void UGASCharacterAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
Super::PostAttributeChange(Attribute, OldValue, NewValue);
/* 당연하지만, 어트리뷰트의 세터 함수들을 PostAttributeChange()에서 호출하면 안됨. */
}
void UGASCharacterAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Attribute == GetMaxHPAttribute())
{
SetMaxHP(FMath::Clamp(GetMaxHP(), 0.f, LIMIT));
}
if (Attribute == GetCurrentHPAttribute())
{
SetCurrentHP(FMath::Clamp(GetCurrentHP(), 0.f, GetMaxHP()));
}
if (Attribute == GetMaxSPAttribute())
{
SetMaxSP(FMath::Clamp(GetMaxSP(), 0.f, LIMIT));
}
if (Attribute == GetCurrentSPAttribute())
{
SetCurrentSP(FMath::Clamp(GetCurrentSP(), 0.f, GetMaxSP())); /* 제한 코드 */
OnCurrentSPChanged.Broadcast(GetCurrentSP()); /* 변경시 알림 */
if (KINDA_SMALL_NUMBER < OldValue && NewValue < KINDA_SMALL_NUMBER) /* 소진시 알림 */
{
if (OnOutOfCurrentSP.IsBound())
{
OnOutOfCurrentSP.Broadcast();
}
}
}
}
void UGASCharacterAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UGASCharacterAttributeSet, MaxHP, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UGASCharacterAttributeSet, CurrentHP, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UGASCharacterAttributeSet, MaxSP, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UGASCharacterAttributeSet, CurrentSP, COND_None, REPNOTIFY_Always);
}
void UGASCharacterAttributeSet::OnRep_MaxHP(const FGameplayAttributeData& OldMaxHP)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGASCharacterAttributeSet, MaxHP, OldMaxHP);
}
void UGASCharacterAttributeSet::OnRep_CurrentHP(const FGameplayAttributeData& OldCurrentHP)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGASCharacterAttributeSet, MaxHP, OldCurrentHP);
}
void UGASCharacterAttributeSet::OnRep_MaxSP(const FGameplayAttributeData& OldMaxSP)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGASCharacterAttributeSet, MaxHP, OldMaxSP);
}
void UGASCharacterAttributeSet::OnRep_CurrentSP(const FGameplayAttributeData& OldCurrentSP)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGASCharacterAttributeSet, MaxHP, OldCurrentSP);
}
2.3 GameplayAbility 만들기
- 새로운 C++ 클래스 > GameplayAbility 클래스 > "GASGameplayAbility" 클래스
이번 실습 내의 모든 어빌리티들이 상속 받게될 클래스.
- AbilityInputID는 액션 매핑시에 사용하게 됨. Enhanced Input을 바인드할 때도 사용됨.
해당 ID를 가진 어빌리티가 인풋 액션이 들어오면 자동으로 활성화 되는 방식.
- EffectHandle은 어빌리티의 여러 함수들에 자주 사용되기 때문에 따로 캐시해둠.
<hide/>
// GASGameplayAbility.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "GASGlobal.h"
#include "GASGameplayAbility.generated.h"
UCLASS()
class GAS_API UGASGameplayAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = InputID)
EGASAbilityInputID AbilityInputID;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ID)
EGASAbilityID AbilityID;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Handles)
mutable FActiveGameplayEffectHandle EffectHandle;
public:
UGASGameplayAbility();
};
<hide/>
// GASGameplayAbility.cpp
#include "GASGameplayAbility.h"
UGASGameplayAbility::UGASGameplayAbility()
: AbilityInputID(EGASAbilityInputID::None)
, AbilityID(EGASAbilityID::None)
{
}
2.4 Character 클래스와 GameAbilitySystem
<hide/>
// GASCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "GameplayAbilities/Public/AbilitySystemInterface.h"
#include "GASCharacter.generated.h"
UCLASS()
class AGASCharacter : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
#pragma region DefaultCharacterVariables
...
#pragma endregion
#pragma region GAS
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = GAS, meta = (AllowPrivateAccess = "true"))
class UGASAbilitySystemComponent* AbilitySystemComponent;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GAS, meta = (AllowPrivateAccess = "true"))
TArray<TSubclassOf<class UGASGameplayAbility>> Abilities;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = GAS, meta = (AllowPrivateAccess = "true"))
class UGASCharacterAttributeSet* AttributeSet;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GAS, meta = (AllowPrivateAccess = "true"))
TSubclassOf<class UGameplayEffect> DefaultAttributeEffect;
#pragma endregion
public:
...
virtual void PossessedBy(AController* NewController) override;
public:
...
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
private:
...
void InitializeAttributes();
void GiveAbilities();
};
<hide/>
// GASCharacter.cpp
#include "GASCharacter.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/Controller.h"
#include "GASGlobal.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "Components/InputComponent.h"
#include "GASInputConfigData.h"
#include "AbilitySystemComponent.h"
#include "GASAbilitySystemComponent.h"
#include "GASGameplayAbility.h"
#include "GASCharacterAttributeSet.h"
#include "GameplayEffectTypes.h"
AGASCharacter::AGASCharacter()
{
...
#pragma region InitializeAbilitySystem
AbilitySystemComponent = CreateDefaultSubobject<UGASAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
/*
* EGameplayEffectReplicationMode::Minimal -> Only replicate minimal gameplay effect info.
* EGameplayEffectReplicationMode::Mixed -> Only replicate minimal gameplay effect info to simulated proxies but full info to owners and autonomous proxies.
* simulated proxies란, 네트워크 반대편에서 예측을 통해 시뮬레이션하는 액터를 말함.(정확하지 않음. 필자 생각.) ex. 총알 액터의 정보는 출발점 정보와 속도 정보 정도의 Minimal gameplayeffect만 보내면 알아서 행동을 시뮬레이션하는 것과 같은듯.
* autonomous(자발적인) proxies는 자발적으로 움직인다는 뜻이기에 플레이어 자신을 뜻함. 플레이어 자신에게는 모든 정보를 넘김.
* owners는 해당 액터를 가지고 있는 액터들(ex. controller, ...)을 뜻함. 이들에게는 모든 정보를 넘김.
* EGameplayEffectReplicationMode::Full -> Replicate full gameplay info to all.
*/
AttributeSet = CreateDefaultSubobject<UGASCharacterAttributeSet>(TEXT("AttributeSet"));
#pragma endregion
}
...
void AGASCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
AbilitySystemComponent->InitAbilityActorInfo(this, this);
InitializeAttributes();
GiveAbilities();
}
UAbilitySystemComponent* AGASCharacter::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void AGASCharacter::InitializeAttributes()
{
/* 이펙트를 적용해서 자신의 어트리뷰트들을 초기화하는 코드. */
if (AbilitySystemComponent && DefaultAttributeEffect)
{
FGameplayEffectContextHandle EffectContextHandle = AbilitySystemComponent->MakeEffectContext();
EffectContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle EffectSpecHandle = AbilitySystemComponent->MakeOutgoingSpec(DefaultAttributeEffect, 1, EffectContextHandle);
if (EffectSpecHandle.IsValid())
{
FActiveGameplayEffectHandle EffectHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
}
}
}
void AGASCharacter::GiveAbilities()
{
/* 기본 어빌리티들은 블루프린트 클래스에서 지정해줌. 해당 기본 어빌리티들이 캐릭터에게 주어지게끔 하는 코드. */
if (HasAuthority() && AbilitySystemComponent)
{
for (TSubclassOf<UGASGameplayAbility>& Ability : DefaultAbilities)
{
/*
* 각 어빌리티마다 AbilityInputID가 있음. 주의해야할 것은 AbilityInputID != AbilityID.
* AbilityLocalInputPressed(AbilityInputID) 함수가 호출되면, AbilityInputID 어빌리티의 InputPressed() 함수가 호출되게끔 함.
* AbilityLocalInputReleased() 함수도 마찬가지.
*/
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability, 1, static_cast<int32>(Ability.GetDefaultObject()->AbilityInputID), this));
}
}
}
...
2.5 GameplayEffect 블루프린트 클래스 만들기
- Content > GAS > Blueprints > 새폴더 "GameplayEffects" 생성
새 블루프린트 클래스 > GameplayEffect 부모 클래스 > "BPGE_GASCharacterDefaults"
컴파일 버튼 옆 세 개 동그라미 기호 클릭 > Save on Compile > On Success Only 체크 후 컴파일.
- BP_GASCharacter > Default Attribute Effect에 BPGE_GASCharacterDefaults 지정.
- 플레이 > 뷰포트 클릭 > 틸트키(`) > showdebug abilitysystem > 어트리뷰트 초기화된거 확인.
3. 어빌리티 만들기
3.1 Jump ability 구현
3.1-1 Jump 어빌리티 클래스 생성
- 새 C++ 클래스 > GASGameplayAbility 부모 클래스 > "GASGameplayAbility_Jump" 클래스 생성
- 당연히, GASGameplayAbility를 상속 받는 블루프린트 클래스로 작성해도 됨.
<hide/>
// GASGameplayAbility_Jump.h
#pragma once
#include "CoreMinimal.h"
#include "GASGameplayAbility.h"
#include "GASGameplayAbility_Jump.generated.h"
UCLASS()
class GAS_API UGASGameplayAbility_Jump : public UGASGameplayAbility
{
GENERATED_BODY()
public:
UGASGameplayAbility_Jump();
virtual void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
virtual void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
/* CommitCost()는 어빌리티가 활성되기 전에 비용 지불이 가능한지 확인하고 지불함. 반면에 ApplyCost()는 어빌리티가 활성된 후 지불이 가능한지 확인하고 지불함. */
virtual void ApplyCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const override;
};
<hide/>
// GASGameplayAbility_Jump.cpp
#include "GASGameplayAbility_Jump.h"
#include "Kismet/KismetSystemLibrary.h"
UGASGameplayAbility_Jump::UGASGameplayAbility_Jump()
{
AbilityID = EGASAbilityID::Jump;
AbilityInputID = EGASAbilityInputID::Jump;
// 이렇게 태그를 지정해두면, 이후에 해당 태그를 가진 어빌리티들을 비활성할 수도 있음. ex. 마나가 없으니 마나를 사용하는 스킬은 모두 꺼라.
AbilityTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("GASAbilities.UseSP")));
AbilityTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("GASAbilities.UseSP.Jump")));
}
void UGASGameplayAbility_Jump::InputPressed(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
Super::InputPressed(Handle, ActorInfo, ActivationInfo);
// 멀티플레이 환경에서 이 함수를 활용해서 디버깅 가능.
UKismetSystemLibrary::PrintString(GetWorld(), FString::Printf(TEXT("InputPressed: %s"), *GetName()));
}
void UGASGameplayAbility_Jump::InputReleased(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
Super::InputReleased(Handle, ActorInfo, ActivationInfo);
UKismetSystemLibrary::PrintString(GetWorld(), FString::Printf(TEXT("InputReleased: %s"), *GetName()));
}
void UGASGameplayAbility_Jump::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
UKismetSystemLibrary::PrintString(GetWorld(), FString::Printf(TEXT("ActivateAbility: %s"), *GetName()));
}
void UGASGameplayAbility_Jump::EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
UKismetSystemLibrary::PrintString(GetWorld(), FString::Printf(TEXT("EndAbility: %s"), *GetName()));
}
void UGASGameplayAbility_Jump::ApplyCost(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const
{
Super::ApplyCost(Handle, ActorInfo, ActivationInfo);
UKismetSystemLibrary::PrintString(GetWorld(), FString::Printf(TEXT("ApplyCost: %s"), *GetName()));
}
3.1-2 어빌리티와 입력의 바인딩 준비
- GASInputConfigData 클래스 수정.
- DA_GASInputConfigData 에셋 수정.
<hide/>
// GASInputConfigData.h
...
UCLASS()
class GAS_API UGASInputConfigData : public UDataAsset
{
GENERATED_BODY()
public:
const UInputAction* FindNativeInputActionByTag(const FGameplayTag& NewInputTag, bool bLogNotFound = true) const;
const UInputAction* FindAbilityInputActionByTag(const FGameplayTag& NewInputTag, bool bLogNotFound = true) const;
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputActions"))
TArray<FTagBindingInputAction> NativeInputActions;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputActions"))
TArray<FTagBindingInputAction> AbilityInputActions;
};
<hide/>
// GASInputConfigData.cpp
...
const UInputAction* UGASInputConfigData::FindAbilityInputActionByTag(const FGameplayTag& NewInputTag,
bool bLogNotFound) const
{
for (const FTagBindingInputAction& Action : AbilityInputActions)
{
if (Action.InputAction && Action.InputTag.MatchesTagExact(NewInputTag))
{
return Action.InputAction;
}
}
if (bLogNotFound)
{
UE_LOG(LogTemp, Error, TEXT("Can't find AbilityInputAction for InputTag [%s] on InputConfig [%s]."), *NewInputTag.ToString(), *GetNameSafe(this));
}
return nullptr;
}
3.1-3 GASCharacter 클래스 수정
- 컴파일 후 플레이 하면 확인 가능.
<hide/>
// TPSCharacter.h
...
class TPSGAS_API ATPSCharacter : public ACharacter, public IAbilitySystemInterface
{
...
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = AbilitySystem, meta = (AllowPrivateAccess = "true"))
float CurrentSP;
public:
...
private:
...
virtual void Jump() override;
};
<hide/>
// GASCharacter.cpp
...
void AGASCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
#pragma region BindNativeInputActions
...
#pragma endregion
#pragma region BindAbilityInputAction
/* 바인딩 설명은 GiveAbilities() 함수 참고 */
EnhancedInputComponent->BindAction(InputActions->FindAbilityInputActionByTag(FGameplayTag::RequestGameplayTag(FName("EnhancedInput.Jump"))),
ETriggerEvent::Triggered, AbilitySystemComponent, &UAbilitySystemComponent::AbilityLocalInputPressed, static_cast<int32>(EGASAbilityInputID::Jump));
EnhancedInputComponent->BindAction(InputActions->FindAbilityInputActionByTag(FGameplayTag::RequestGameplayTag(FName("EnhancedInput.Jump"))),
ETriggerEvent::Completed, AbilitySystemComponent, &UAbilitySystemComponent::AbilityLocalInputReleased, static_cast<int32>(EGASAbilityInputID::Jump));
#pragma endregion
}
}
...
3.1-4 이펙트 만들기
- 점프할 때마다 10 SP씩 소모되게끔 구현하고자 함.
어트리뷰트를 수정하려면 GameplayEffect의 도움을 받아야함.
- GAS > Blueprints > GameplayEffects
새 블루프린트 클래스 > GameplayEffect 부모 클래스 > "BPGE_Jump" 클래스
- 점프 1회마다 10 SP씩 소모되는 방식이라, Duration Policy는 Instant로 지정함.
- GAS > Blurprints > 새 폴더 "GameplayAbilities" 생성
새 블루프린트 클래스 > GASGameplayAbility_Jump 부모 클래스 > "BPGE_Jump" 클래스 생성
- BPGE_Jump 블루프린트 클래스 > 아래와 같이 설정
3.1-5 InputPressed
- Pressed를 falling edge에 1번만 작동하게끔 하고싶다.
IA_GASJump > Details를 아래와 같이 설정
3.1-6 이펙트 적용
- 점프를 할 때마다 이펙트를 적용해서 CurrentSP가 줄어들게끔 구현하고자 함.
아래 코드를 작성하고 컴파일.
- Content > GAS > 새 폴더 "GameplayAbilities"
새 블루프린트 클래스 > GASGameplayAbility_Jump 부모 클래스 > "BPGA_Jump" 생성.
아래와 같이 Details 설정.
- BP_GASCharacter > Default Abilities > 기존 C++ 클래스 대신 BPGA_Jump로 변경.
<hide/>
// GASGameplayAbility_Jump.cpp
#include "GASGameplayAbility_Jump.h"
#include "AbilitySystemComponent.h"
#include "Kismet/KismetSystemLibrary.h"
UGASGameplayAbility_Jump::UGASGameplayAbility_Jump()
{
AbilityID = EGASAbilityID::Jump;
AbilityInputID = EGASAbilityInputID::Jump;
AbilityTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("GASAbilities.UseSP.Jump")));
}
void UGASGameplayAbility_Jump::InputPressed(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
}
void UGASGameplayAbility_Jump::InputReleased(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
}
void UGASGameplayAbility_Jump::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (CommitAbility(Handle, ActorInfo, ActivationInfo))
{
}
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
}
void UGASGameplayAbility_Jump::EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
if (IsEndAbilityValid(Handle, ActorInfo))
{
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
if (ASC)
{
if (EffectHandle.IsValid())
{
if (ASC->RemoveActiveGameplayEffect(EffectHandle))
{
EffectHandle.Invalidate();
}
}
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
}
void UGASGameplayAbility_Jump::ApplyCost(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const
{
UGameplayEffect* GECost = GetCostGameplayEffect();
if (GECost)
{
EffectHandle = ApplyGameplayEffectToOwner(Handle, ActorInfo, ActivationInfo, GECost, GetAbilityLevel(Handle, ActorInfo));
}
}
3.1-7 문제점
- 체공 중에 스페이스바를 한 번 더 누르면 SP가 또 감소함.
체공 중에는 SP가 감소하지 않게끔 해야함. [여기서부터 다시]
<hide/>
// GASCharacter.cpp
...
void AGASCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
#pragma region BindNativeInputActions
// EnhancedInputComponent->BindAction(InputActions->FindNativeInputActionByTag(FGameplayTag::RequestGameplayTag(FName("EnhancedInput.Jump"))),
// ETriggerEvent::Triggered, this, &ACharacter::Jump);
// EnhancedInputComponent->BindAction(InputActions->FindNativeInputActionByTag(FGameplayTag::RequestGameplayTag(FName("EnhancedInput.Jump"))),
// ETriggerEvent::Completed, this, &ACharacter::StopJumping);
EnhancedInputComponent->BindAction(InputActions->FindNativeInputActionByTag(FGameplayTag::RequestGameplayTag(FName("EnhancedInput.Move"))),
ETriggerEvent::Triggered, this, &ThisClass::Move);
EnhancedInputComponent->BindAction(InputActions->FindNativeInputActionByTag(FGameplayTag::RequestGameplayTag(FName("EnhancedInput.Look"))),
ETriggerEvent::Triggered, this, &ThisClass::Look);
#pragma endregion
...
}
}
...
3.2 Idle 상태에서 SP 회복
3.2-1 패시브 스킬 어빌리티
- 패시브 스킬은 결국 무한히 적용되는 스킬.
Project Settings > GameplayTags > 새 태그 "GASAbilities.NoCost.RegenSP" 추가.
- 새 C++ 클래스 > GASGameplayAbility 부모 클래스 > "GASGameplayAbility_RegenSP" 클래스
아래와 같이 작성 후 컴파일.
<hide/>
// GASGameplayAbility_RegenSP.h
#pragma once
#include "CoreMinimal.h"
#include "GASGameplayAbility.h"
#include "GASGameplayAbility_RegenSP.generated.h"
UCLASS()
class GAS_API UGASGameplayAbility_RegenSP : public UGASGameplayAbility
{
GENERATED_BODY()
public:
UGASGameplayAbility_RegenSP();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
};
<hide/>
// GASGameplayAbility_RegenSP.cpp
#include "GASGameplayAbility_RegenSP.h"
#include "AbilitySystemComponent.h"
UGASGameplayAbility_RegenSP::UGASGameplayAbility_RegenSP()
{
AbilityID = EGASAbilityID::Idle;
AbilityInputID = EGASAbilityInputID::None;
AbilityTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("GASAbilities.NoCost.RegenSP")));
}
void UGASGameplayAbility_RegenSP::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (CommitAbility(Handle, ActorInfo, ActivationInfo))
{
}
else
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
}
}
void UGASGameplayAbility_RegenSP::EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
if (IsEndAbilityValid(Handle, ActorInfo))
{
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
if (ASC)
{
if (EffectHandle.IsValid())
{
if (ASC->RemoveActiveGameplayEffect(EffectHandle))
{
EffectHandle.Invalidate();
}
}
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
}
3.2-2 패시브 스킬 이펙트
- Content > GAS > Blueprints > GameplayEffect
새 블루프린트 클래스 > GameplayEffect 부모 클래스 > "BPGE_RegenSP" 생성
아래와 같이 Details 설정.
- Content > GAS > Blueprints > GameplayAbilities
새 블루프린트 클래스 >
- Content > GAS > Blueprints > GameplayAbilities
새 블루프린트 클래스 > GASGameplayAbility_RegenSP 부모 클래스 > "BPGA_RegenSP" 클래스
아래와 같이 Details 설정.
'Unreal > GAS 토이프로젝트' 카테고리의 다른 글
[GameplayAbilitySystem 찍먹 프로젝트 - 1] Test project 생성 및 Enhanced Input (0) | 2023.03.25 |
---|
댓글