[UE GAS] 캐릭터 어트리뷰트 설정
- 다른 GAS 액터의 상황을 확인할 수 있는 디버그 시스템 설정 방법의 학습
- 기본 캐릭터 어트리뷰트 설정 방법의 학습
- 공격 대상의 어트리뷰트 값을 변경하는 방법의 학습
- 특정 어트리뷰트 변경 시 사전, 사후 점검하는 방법의 학습
인프런 이득우님의 '언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템' 강의를 참고하였습니다.
😎 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 들으러 가기!
목차
캐릭터 어트리뷰트 설정 방향
캐릭터 어트리뷰트 설정 기획
- 캐릭터에 설정할 어트리뷰트 목록
- 기본 어트리뷰트
- 체력 Health
- 일반 공격 길이 AttackRange
- 일반 공격 변경 AttackRadius
- 일반 공격력 AttackRate
- 최대값 어트리뷰트
- 최대 체력 MaxHealth
- 최대 일반 공격 길이 MaxAttackRange
- 최대 일반 공격 반경 MaxAttackRadius
- 최대 공격력 MaxAttackRate
핵심 구성요소
ASC를 중심으로 어트리뷰트를 설정하게 되면 이 둘이 서로 연동하게 된다.
Attribute Set 바탕으로 Gamplay Ability(GA)와 Ability Task(AT)에 영향을 주는 형태로 작업
AttributeSet 활용한 GAS NPC의 설정
ABGASCharacterNonPlayer 생성
ABCharacterNonPlayer - ABGASCharacterNonPlayer 생성
ABGASCharacterNonPlayer.h
#pragma once
#include "CoreMinimal.h"
#include "Character/ABCharacterNonPlayer.h"
#include "AbilitySystemInterface.h"
#include "ABGASCharacterNonPlayer.generated.h"
UCLASS()
class ARENABATTLEGAS_API AABGASCharacterNonPlayer : public AABCharacterNonPlayer, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
AABGASCharacterNonPlayer();
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
virtual void PossessedBy(AController* NewController) override; // AIController가 이 NPCController를 장악할 때 그 타이밍에 ASC를 초기화
protected:
UPROPERTY(EditAnywhere, Category = GAS)
TObjectPtr<class UAbilitySystemComponent> ASC;
UPROPERTY()
TObjectPtr<class UABCharacterAttributeSet> AttributeSet;
};
ABGASCharacterNonPlayer.cpp
#include "Character/ABGASCharacterNonPlayer.h"
#include "AbilitySystemComponent.h"
#include "Attribute/ABCharacterAttributeSet.h"
AABGASCharacterNonPlayer::AABGASCharacterNonPlayer()
{
ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("AttributeSet"));
}
UAbilitySystemComponent* AABGASCharacterNonPlayer::GetAbilitySystemComponent() const
{
return ASC;
}
void AABGASCharacterNonPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
ASC->InitAbilityActorInfo(this, this);
}
어트리뷰트 세트 Attribute Set
Attribute Set
- 단일 어트리뷰트 데이터인 GameplayAttributeData의 묶음
- GameplayAttributeData는 하나의 값이 아닌 두 가지 값이 구성되어 있음
- BaseValue : 기본값. 영구히 적용되는 고정 스탯 값을 관리하는데 사용
- CurrentValue : 변동값. 버프(Buff) 등으로 임시적으로 변동된 값을 관리하는데 사용
- AttributeSet의 주요 함수
- PreAttributeChange : 어트리뷰트 변경 전에 호출
- virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
- 두번째 인자가 레퍼런스(&)로 들어온다.
- 바꾸기 전에 들어온 값이 마음에 안 드는 경우, 다시 설정할 수 있게 하는 함수다.
- PostAttributeChange : 어트리뷰트 변경 후에 호출
- PreAttributeChange와 달리 값을 변경할 수 없다.
- 로그를 남길 때 자주 사용하는 함수다.
- PreGameplayEffectExecute : 게임플레이 이펙트 적용 전에 호출
- PostGameplayEffectExecute : 게임플레이 이펙트 적용 후에호출
- PreAttributeChange : 어트리뷰트 변경 전에 호출
- AttributeSet 접근자 매크로
- 많이 수행되는 기능에 대해 매크로를 만들어 제공함
- ASC는 초기화될 때 같은 액터에 있는 UAttributeSet 타입 객체를 찾아서 등록함
- 사용자가 별도로 AttributeSet를 AttributeSystemComponent(ASC)에 수동으로 등록할 필요 없다.
- ASC는 초기화될 때 자기를 부모로 가지는 OwnerActor에 있는 AttributeSet 타입 객체를 찾아서 자동으로 등록한다.
아래 예제에서는 BaseValue만 사용했다.
※ AttributeSet.h에 설명된 ATTRIBUTE_ACCESSORS 사용법
- GAMEPLAYATTRIBUTE_PROPERTY_GETTER는 AttributeSet나 Attribute를 나타내는 클래스에 등록된 프로퍼티를 가져오는 매크로다.
- GAMEPLAYATTRIBUTE_VALUE_GETTER는 CurrentValue를 가져오는 매크로다.
- GAMEPLAYATTRIBUTE_VALUE_SETTER는 BaseValue 를 바꿔주는 매크로다.
- GAMEPLAYATTRIBUTE_VALUE_INITTER는 BaseValue와 CurrentValue를 같은 값으로 지정해주는 매크로다.
※ ASC가 UAttributeSet 타입 객체를 찾는 로직
- AbilitySystemComponent.h + AbilitySystemComponent_Abilities.cpp
- AbilitySystemComponent.h의 함수 virtual void IntializeComponent override;
- AbilitySystemComponent_Abilities.cpp에서 해당 함수를 재정의한다.
- TArray<UObject*> ChildObjects;
GetObjectsWithOuter(Owner, ChildObjects, false, RF_NoFlags, EInternalObjectFlags::Garbage); - Owner가 가지고 있는 자식 오브젝트(= ChildObjects)를 TArray로 가져온 후 자식 오브젝트 중에 형이 AttributeSet 얘가 있으면 SpawnedAttributes라는 어트리뷰트 목에 AddUnique로 추가한다.
- TArray<UObject*> ChildObjects;
AbilitySystemComponent.h
AbilitySystemComponent_Abilities.cpp
ABCharacterAttributeSet 생성
AttributeSet - ABCharacterAttributeSet 생성
ABCharacterAttributeSet.h
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "ABCharacterAttributeSet.generated.h"
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
UCLASS()
class ARENABATTLEGAS_API UABCharacterAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UABCharacterAttributeSet();
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRange);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRange);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRadius);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRadius);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, AttackRate);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxAttackRate);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, Health);
ATTRIBUTE_ACCESSORS(UABCharacterAttributeSet, MaxHealth);
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
protected:
UPROPERTY(BlueprintReadOnly, Category="Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData AttackRange;
UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxAttackRange;
UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData AttackRadius;
UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxAttackRadius;
UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData AttackRate;
UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxAttackRate;
UPROPERTY(BlueprintReadOnly, Category = "Health", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData Health;
UPROPERTY(BlueprintReadOnly, Category = "Attack", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxHealth;
};
ABCharacterAttributeSet.cpp
#include "Attribute/ABCharacterAttributeSet.h"
#include "ArenaBattleGAS.h"
UABCharacterAttributeSet::UABCharacterAttributeSet() :
AttackRange(100.0f),
AttackRadius(50.f),
AttackRate(30.0f),
MaxAttackRange(300.0f),
MaxAttackRadius(150.0f),
MaxAttackRate(100.0f),
MaxHealth(100.0f)
{
InitHealth(GetMaxHealth());
}
void UABCharacterAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
}
void UABCharacterAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
if (Attribute == GetHealthAttribute())
{
ABGAS_LOG(LogABGAS, Log, TEXT("Health : %f -> %f"), OldValue, NewValue);
}
}
ABGASPlayerState - ABCharacterAttributeSet 포인터 변수 추가
ABGASPlayerState.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "AbilitySystemInterface.h"
#include "ABGASPlayerState.generated.h"
UCLASS()
class ARENABATTLEGAS_API AABGASPlayerState : public APlayerState, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
AABGASPlayerState();
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
UPROPERTY(EditAnywhere, Category = GAS)
TObjectPtr<class UAbilitySystemComponent> ASC;
UPROPERTY()
TObjectPtr<class UABCharacterAttributeSet> AttributeSet;
};
변수 추가
- UPROPERTY()
TObjectPtr<class UABCharacterAttributeSet> AttributeSet;
ABGASPlayerState.cpp
#include "Player/ABGASPlayerState.h"
#include "AbilitySystemComponent.h"
#include "Attribute/ABCharacterAttributeSet.h"
AABGASPlayerState::AABGASPlayerState()
{
ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
//ASC->SetIsReplicated(true);
AttributeSet = CreateDefaultSubobject<UABCharacterAttributeSet>(TEXT("AttributeSet"));
}
UAbilitySystemComponent* AABGASPlayerState::GetAbilitySystemComponent() const
{
return ASC;
}
AABGASPlayerState::AABGASPlayerState()
- ASC가 AttributeSet를 직접 관리한다.
- AttributeSet을 수동으로 ASC에 추가한 적이 없다. 하지만 ASC는 초기화될 때 자기를 부모로 가지는 OwnerActor에 있는 AttributeSet 타입 객체를 찾아서 자동으로 등록한다.
ABTA_Trace
ABTA_Trace.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbilityTargetActor.h"
#include "ABTA_Trace.generated.h"
UCLASS()
class ARENABATTLEGAS_API AABTA_Trace : public AGameplayAbilityTargetActor
{
GENERATED_BODY()
public:
AABTA_Trace();
virtual void StartTargeting(UGameplayAbility* Ability) override;
virtual void ConfirmTargetingAndContinue() override;
void SetShowDebug(bool InShowDebug) { bShowDebug = InShowDebug; }
protected:
virtual FGameplayAbilityTargetDataHandle MakeTargetData() const;
bool bShowDebug = false;
};
변경사항 없음
ABTA_Trace.cpp
#include "GA/TA/ABTA_Trace.h"
#include "Abilities/GameplayAbility.h"
#include "GameFramework/Character.h"
#include "Components/CapsuleComponent.h"
#include "Physics/ABCollision.h"
#include "DrawDebugHelpers.h"
#include "AbilitySystemComponent.h"
#include "Attribute/ABCharacterAttributeSet.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "ArenaBattleGAS.h"
AABTA_Trace::AABTA_Trace()
{
}
void AABTA_Trace::StartTargeting(UGameplayAbility* Ability)
{
Super::StartTargeting(Ability);
SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
}
void AABTA_Trace::ConfirmTargetingAndContinue()
{
if (SourceActor)
{
FGameplayAbilityTargetDataHandle DataHandle = MakeTargetData();
TargetDataReadyDelegate.Broadcast(DataHandle);
}
}
FGameplayAbilityTargetDataHandle AABTA_Trace::MakeTargetData() const
{
ACharacter* Character = CastChecked<ACharacter>(SourceActor);
UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(SourceActor);
if (!ASC)
{
ABGAS_LOG(LogABGAS, Error, TEXT("ASC not found!"));
return FGameplayAbilityTargetDataHandle();
}
const UABCharacterAttributeSet* AttributeSet = ASC->GetSet<UABCharacterAttributeSet>();
if (!AttributeSet)
{
ABGAS_LOG(LogABGAS, Error, TEXT("ABCharacterAttributeSet not found!"));
return FGameplayAbilityTargetDataHandle();
}
FHitResult OutHitResult;
const float AttackRange = AttributeSet->GetAttackRange();
const float AttackRadius = AttributeSet->GetAttackRadius(); // GetAttackRadius()는 GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) 매크로 내에 구현되었다
FCollisionQueryParams Params(SCENE_QUERY_STAT(UABTA_Trace), false, Character);
const FVector Forward = Character->GetActorForwardVector();
const FVector Start = Character->GetActorLocation() + Forward * Character->GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + Forward * AttackRange;
bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
FGameplayAbilityTargetDataHandle DataHandle;
if (HitDetected)
{
FGameplayAbilityTargetData_SingleTargetHit* TargetData = new FGameplayAbilityTargetData_SingleTargetHit(OutHitResult);
DataHandle.Add(TargetData);
}
#if ENABLE_DRAW_DEBUG
if (bShowDebug)
{
FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
float CapsuleHalfHeight = AttackRange * 0.5f;
FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(Forward).ToQuat(), DrawColor, false, 5.0f);
}
#endif
return DataHandle;
}
FGameplayAbilityTargetDataHandle AABTA_Trace::MakeTargetData() const
ABGA_AttackHitCheck
ABGA_AttackHitCheck.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "ABGA_AttackHitCheck.generated.h"
UCLASS()
class ARENABATTLEGAS_API UABGA_AttackHitCheck : public UGameplayAbility
{
GENERATED_BODY()
public:
UABGA_AttackHitCheck();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
protected:
UFUNCTION()
void OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle);
};
ABGA_AttackHitCheck.cpp
#include "GA/ABGA_AttackHitCheck.h"
#include "ArenaBattleGAS.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GA/AT/ABAT_Trace.h"
#include "GA/TA/ABTA_Trace.h"
#include "Attribute/ABCharacterAttributeSet.h"
#include "ArenaBattleGAS.h"
UABGA_AttackHitCheck::UABGA_AttackHitCheck()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
void UABGA_AttackHitCheck::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
UABAT_Trace* AttackTraceTask = UABAT_Trace::CreateTask(this, AABTA_Trace::StaticClass());
AttackTraceTask->OnComplete.AddDynamic(this, &UABGA_AttackHitCheck::OnTraceResultCallback);
AttackTraceTask->ReadyForActivation();
}
void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
// TargetDataHandle는 배열로 들어오기 때문에 0번 인덱스(=첫번째 데이터)에 이것이 있는지 확인
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, 0))
{
FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, 0);
ABGAS_LOG(LogABGAS, Log, TEXT("Target %s Detected"), *(HitResult.GetActor()->GetName()));
UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor());
if (!SourceASC || !TargetASC)
{
ABGAS_LOG(LogABGAS, Error, TEXT("ASC not found!"));
return;
}
const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
UABCharacterAttributeSet* TargetAttribute = const_cast<UABCharacterAttributeSet*>(TargetASC->GetSet<UABCharacterAttributeSet>());
if (!SourceAttribute || !TargetAttribute)
{
ABGAS_LOG(LogABGAS, Error, TEXT("ASC not found!"));
return;
}
const float AttackDamage = SourceAttribute->GetAttackRate();
TargetAttribute->SetHealth(TargetAttribute->GetHealth() - AttackDamage);
}
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
void UABGA_AttackHitCheck::OnTraceResultCallback(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
- UAbilitySystemComponent* SourceASC = GetAbilitySystemComponentFromActorInfo_Checked();
- 소스에서 타겟으로 넘겨야 하므로 체크로 확인한 AbilitySystemComponent를 가져옴
- UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor());
- 물리 판정을 감지한 타겟 액터
- const UABCharacterAttributeSet* SourceAttribute = SourceASC->GetSet<UABCharacterAttributeSet>();
- UABCharacterAttributeSet* TargetAttribute = const_cast<UABCharacterAttributeSet*>(TargetASC->GetSet<UABCharacterAttributeSet>());
- 데미지 전달 로직
const float AttackDamage = SourceAttribute->GetAttackRate();
TargetAttribute->SetHealth(TargetAttribute->GetHealth() - AttackDamage);
DefaultGame.ini - NonPlayer도 debug abilitysystem에 보이도록 수정
DefaultGame.ini
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=5E31F5844E5898517BA834A3EF9332C9
[/Script/Engine.AssetManagerSettings]
-PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass=/Script/Engine.World,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
-PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass=/Script/Engine.PrimaryAssetLabel,bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="Map",AssetBaseClass="/Script/Engine.World",bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game/Maps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="PrimaryAssetLabel",AssetBaseClass="/Script/Engine.PrimaryAssetLabel",bHasBlueprintClasses=False,bIsEditorOnly=True,Directories=((Path="/Game")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
bOnlyCookProductionAssets=False
bShouldManagerDetermineTypeAndName=False
bShouldGuessTypeAndNameInEditor=True
bShouldAcquireMissingChunksOnLoad=False
bShouldWarnAboutInvalidAssets=True
MetaDataTagsForAssetRegistry=()
[/Script/GameplayAbilities.AbilitySystemGlobals]
bUseDebugTargetFromHud=True
코드 추가
- [/Script/GameplayAbilities.AbilitySystemGlobals]
bUseDebugTargetFromHud=True
실행화면
좌측상단의 Health가 줄어드는것을 확인할 수 있다.
정리
- GAS NPC 제작과 캐릭터에 어트리뷰트를 부여하는 방법의 이해
- ASC에서 AttributeSet을 초기화하는 과정의 학습
- NPC의 어트리뷰트와 태그 값을 확인할 수 있는 디버그 시스템의 추가 설정
- ASC와 매크로를 사용해 어트리뷰트 값을 가져오고, 변경하는 기능의 구현
- 변경되는 어트리뷰트 값의 검증 방법의 학습
'⭐ Unreal Engine > UE Game Ability System(GAS)' 카테고리의 다른 글
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI (0) | 2024.03.09 |
---|---|
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect (0) | 2024.03.08 |
[UE GAS] 공격 판정 시스템의 구현 (0) | 2024.03.06 |
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기 (0) | 2024.03.05 |
[UE GAS] 캐릭터 입력처리 (0) | 2024.03.05 |
댓글
이 글 공유하기
다른 글
-
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI
[UE GAS] 어트리뷰트와 UI 연동 Integration with Attribute and UI
2024.03.09 -
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
[UE GAS] 게임플레이 이펙트의 활용 Applying Gameplay Effect
2024.03.08 -
[UE GAS] 공격 판정 시스템의 구현
[UE GAS] 공격 판정 시스템의 구현
2024.03.06 -
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기
2024.03.05