Unit
URR은 숫자별로 유닛이 달라진다.
예를 들어, 1은 권총을 든 군인이고 2는 라이플을 든 군인이다.
각 유닛은 공격력, 사거리, 공격 속도 등 다양한 스탯을 가지고 있으며 숫자가 높을수록 강해지도록 설계하였다.
Unit 생성
플레이어는 보유하고 있는 코인을 소모하여 생성할 수 있다.
GE를 통해 coin을 소모하며, GA를 통해 유닛을 생성한다.
GE에서 coin을 소모할 때, cost라는 메타 어트리뷰트를 사용해 확장성을 열어두었다.
또한, GA에서는 확률에 따라 스폰할 유닛의 단계와 위치를 정하고 해당 타일에 Spawn요청을 한다.
Unit 초기화
유닛이 생성되면 스탯을 초기화해야 한다.
유닛은 각 단계에 따라 스탯이 다르며 GE를 통해 초기화한다.
초기화하는 시점은 ASC가 최기화되어야 하기 때문에 PostInitializeComponents가 적절하다.
ASC가 초기화되었다면 FGameplayEffectContextHandle을 만든 뒤, FGameplayEffectHandle을 만들어 스탯을 초기화하는 GE를 실행한다.
FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext();
EffectContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(InitStatEffect, Rank + 1, EffectContextHandle);
if (EffectSpecHandle.IsValid())
{
ASC->BP_ApplyGameplayEffectSpecToSelf(EffectSpecHandle);
}
GE에서는 커브테이블을 통해 유닛의 스탯을 초기화한다.
유닛의 단계별로 스탯이 정해져 있고 한 번에 초기화할 수 있어 편리하다.
Unit 공격
유닛은 생성된 시점부터 사거리안에 적을 찾아 공격해야 한다.
공격은 유닛의 공격속도에 따라 다르게 실행돼야 하지만, 적을 찾고 적을 바라보게 하는 것은 매 프레임에 일어나야 한다.
따라서, 유닛에게 적을 찾는 어빌리티를 부여한 뒤, 해당 어빌리티에서 TagetData를 만드는 AT를 생성하여 Callback을 받도록 구현하였다.
유닛에 따라 몬스터의 선두를 공격할 수도 있고 최대 체력을 가진 몬스터를 공격할 수도 있다.
또한, 단일 몬스터만 공격할 수도 있고 광역으로 공격할 수도 있다.
따라서, Strategy 패턴을 적용시켜 TA클래스를 변수로 받아 AT를 생성하도록 하였다.
전달받은 TA 클래스를 생성하여 TargetData를 생성한 뒤 Callback한다.
Callback을 받은 이후에는 유닛이 TargetData에 있는 Actor를 향하도록 Tick함수에서 방향을 조절해 주며 실제로 공격을 하는 GA를 발동한다.
하지만, 해당 GA에 cooldown을 설정하여 태그를 통해 공격 속도에 따른 딜레이가 발생하도록 구현하였다.
매 Tick마다 공격하면 안 되기 때문이다.
실제로 공격을 하는 GA에서는 해당 타깃에게 GE를 통해 피해를 입히며, Montage를 실행하는 AT를 생성하여 공격 모션을 취하도록 하였다.
이때, 유닛이 옮겨지며 떨어지는 Montage와 공격 Montage가 겹치는 상황이 발생할 수 있다.
따라서, 공격 모션이 Cancel 되는 경우 EndAbility가 호출되지 않기 때문에 이에 처리를 Delegate를 통해 해주어야 한다.
SendEvent를 통해 GA를 실행한 이유는 TargetData를 전달하기 위해서이다.
//FindTarget AT
FGameplayEventData PayloadData;
PayloadData.TargetData = DataHandle;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(Unit, URRTAG_UNIT_ATTACK, PayloadData);
//Attack GA
UAbilityTask_PlayMontageAndWait* MontageAT = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayMontage"), Unit->GetAttackMontage(), 1.f);
MontageAT->OnCompleted.AddDynamic(this, &UURRGA_Attack::OnCompleteCallback);
MontageAT->OnInterrupted.AddDynamic(this, &UURRGA_Attack::OnInterruptedCallback);
MontageAT->OnCancelled.AddDynamic(this, &UURRGA_Attack::OnCancelledCallback);
MontageAT->ReadyForActivation();