Monster
URR은 다양한 몬스터가 존재한다.
스테이지가 시작하면 미리 설정한 데이터에 의해 몬스터의 종류, 수, 간격 등의 정보가 초기화된다.
해당 웨이브에 스폰된 몬스터가 모두 죽거나 목적지에 도착해 플레이어에게 피해를 입혀 월드에 존재하지 않으면 다음 웨이브가 실행된다.
모든 웨이브의 몬스터를 처치하면 해당 스테이지를 클리어한다.
Monster 생성
설정된 정보를 통해 GA를 실행하여 몬스터를 생성한다.
몬스터를 스폰하면 타이머를 설정해 몬스터끼리의 간격을 설정한다.
void UURRGA_SpawnMonster::StartSpawnMonster()
{
FActorSpawnParameters params;
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
AURRCharacterMonster* Monster = Spawner->GetWorld()->SpawnActorDeferred<AURRCharacterMonster>(MonsterClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn);
if (Monster)
{
Monster->InitMonster(MonsterID);
Monster->FinishSpawning(Spawner->GetActorTransform());
Monster->StartMove();
Spawner->AddSpawnedMonster(Monster);
MonsterNum--;
Spawner->GetWorldTimerManager().SetTimer(MonsterSpawnTimer, this, &UURRGA_SpawnMonster::CheckSpawnMonster, MonsterSpawnTerm, false);
}
}
void UURRGA_SpawnMonster::CheckSpawnMonster()
{
MonsterSpawnTimer.Invalidate();
if (MonsterNum > 0)
{
StartSpawnMonster();
return;
}
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, false, false);
}
Monster Stat
몬스터들은 종류에 따라 다른 스탯을 갖는다.
체력, 이동 속도 등이 있다.
이러한 스탯은 종류마다 고유한 ID를 통해 GE를 실행하여 초기화한다.
GE에서 커브테이블을 통해 스탯을 불러온다.
void AURRCharacterMonster::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (ASC)
{
ASC->InitAbilityActorInfo(this, this);
FGameplayEffectContextHandle EffectContextHandle = ASC->MakeEffectContext();
EffectContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(InitStatEffect, MonsterID + 1, EffectContextHandle);
if (EffectSpecHandle.IsValid())
{
ASC->BP_ApplyGameplayEffectSpecToSelf(EffectSpecHandle);
}
}
...
}
Monster 공격 판정
몬스터는 공격을 받으면 Health값이 줄어든다.
이를 표시할 위젯이 필요하다.
따라서, 몬스터는 WidgetComponent를 갖고 해당 Widget에서는 Owner의 ASC에서 AttributeSet의 값이 변경되면 델리게이트를 받아 위젯을 업데이트한다.
void UURRHpBarWidget::SetAbilitySystemComponent(AActor* InOwner)
{
Super::SetAbilitySystemComponent(InOwner);
if (ASC)
{
ASC->GetGameplayAttributeValueChangeDelegate(UURRMonsterAttributeSet::GetHealthAttribute()).AddUObject(this, &UURRHpBarWidget::OnHealthChanged);
ASC->GetGameplayAttributeValueChangeDelegate(UURRMonsterAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &UURRHpBarWidget::OnMaxHealthChanged);
const UURRMonsterAttributeSet* CurrentAttributeSet = ASC->GetSet<UURRMonsterAttributeSet>();
if (CurrentAttributeSet)
{
CurrentHealth = CurrentAttributeSet->GetHealth();
CurrentMaxHealth = CurrentAttributeSet->GetMaxHealth();
UpdateHpBar();
}
}
}
Monster 이동
몬스터는 정해진 경로를 따라 성문(목적지)까지 이동해야 한다.
GAS를 사용하지 않으면 BehaviorTree를 이용하여 목적지까지 걸어가게 할 수 있다.
하지만, 유닛의 공격에 의해 뒤로 밀릴 수 있다.
맵이 사각형이기 때문에 뒤로 밀릴 때 모서리에 꺾일 수 있다.
BT로 이를 구현하기 쉽지 않다.
따라서, GA를 통해 정해진 경로를 따라가며 자신의 위치를 변수로 관리할 수 있게 하면 문제없이 기능을 구현할 수 있다.
우선, 뒤로 밀리는 것을 제외하고 몬스터가 정해진 경로를 따라가게 만들어 보자.
몬스터를 스폰하는 액터인 MonsterSpawner에 SplineComponent를 추가하고 해당 Spline을 따라가게 만들 수 있다.
PathSpline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::World);
SplineComponent에는 GetTransformAtDistanceAlongSpline이라는 함수가 있다.
해당 함수는 첫 번째 매개변수를 통해 Spline의 위치를 얻을 수 있다.
0은 시작점이고 길이에 따라 Distance값이 늘어난다고 생각하면 된다.
따라서, Distance값을 몬스터가 유지하고 있다면 자신의 위치를 계속 체크할 수 있으며 개체마다 가지고 있는 이동 속도에 따라 위치를 조정할 수 있다.
즉, AttributeSet에서 이를 관리하여 위치를 업데이트하며 유닛 공격에 의한 넉백까지 구현할 수 있다.
몬스터는 스폰되면 목적지로 향하는 GA를 실행시키고 해당 GA에서는 목적지까지 Spline을 따라가는 AT를 실행한다.
해당 AT에서는 Tick을 통해 몬스터의 AttributeSet에 있는 Distance를 업데이트하며 SplineComponent를 통해 위치를 받아와 위치를 업데이트한다.
void UURRAT_MoveWithSpline::TickTask(float DeltaTime)
{
if (bIsFinished)
{
return;
}
Super::TickTask(DeltaTime);
AURRCharacterMonster* MyCharacter = Cast<AURRCharacterMonster>(GetAvatarActor());
if (MyCharacter)
{
if (MyCharacter->GetActorLocation() == GatePos)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnTargetLocationReached.Broadcast();
EndTask();
}
}
UCharacterMovementComponent* CharMoveComp = Cast<UCharacterMovementComponent>(MyCharacter->GetMovementComponent());
if (CharMoveComp)
{
CharMoveComp->SetMovementMode(MOVE_Custom, 0);
}
UAbilitySystemComponent* ASC = MyCharacter->GetAbilitySystemComponent();
if (ASC)
{
const UURRMonsterAttributeSet* URRMonsterAttributeSet = ASC->GetSet<UURRMonsterAttributeSet>();
if (URRMonsterAttributeSet)
{
float Speed = URRMonsterAttributeSet->GetSpeed();
float Distance = URRMonsterAttributeSet->GetDistance();
Distance += DeltaTime * Speed;
AURRMonsterSpawner* Spanwer = MyCharacter->GetSpawner();
FTransform newTransform = Spanwer->GetPathTransform(Distance);
ASC->ApplyModToAttribute(URRMonsterAttributeSet->GetDistanceAttribute(), EGameplayModOp::Override, Distance);
MyCharacter->SetActorTransform(newTransform);
}
}
}
else
{
bIsFinished = true;
EndTask();
}
}