0. 개요

주술사가 공격하는 것 이외에 데몬을 소환하는 어빌리티를 사용하게 한다.

 

1. AuraSummonAbility - 소환 게임플레이 어빌리티

데미지를 입히는 어빌리티가 아니기 때문에, AuraGameplayAbility를 상속받는 C++ 클래스를 생성한다.

 

(1) C++ 클래스 생성

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystem/Abilities/AuraGameplayAbility.h"
#include "AuraSummonAbility.generated.h"

/**
 * 
 */
UCLASS()
class AURA_API UAuraSummonAbility : public UAuraGameplayAbility
{
	GENERATED_BODY()
	
public:
	UFUNCTION(BlueprintCallable)
	TArray<FVector> GetSpawnLocations();

	UPROPERTY(EditDefaultsOnly, category="Summoning")
	int32 NumMinions = 5;

	UPROPERTY(EditDefaultsOnly, category = "Summoning")
	TArray<TSubclassOf<APawn>> MinionClasses;

	UPROPERTY(EditDefaultsOnly, category = "Summoning")
	float MinSpawnDistance = 50.f;

	UPROPERTY(EditDefaultsOnly, category = "Summoning")
	float MaxSpawnDistance = 250.f;

	// 소환 스폰 각도
	UPROPERTY(EditDefaultsOnly, category = "Summoning")
	float SpawnSpread = 90.f;
};

NumMinions는 소환되는 미니언의 수이다.

MinionClasses는 확장성을 위해 어떤 폰이든 등록할 수 있다.

Min/Max SpawnDistance는 최소/최대 소환 거리이다.

SpawnSpread는 소환사 전방 기준으로 퍼지기 원하는 각도이다.

 

#include "AbilitySystem/Abilities/AuraSummonAbility.h"
#include "Kismet/KismetSystemLibrary.h"

TArray<FVector> UAuraSummonAbility::GetSpawnLocations()
{
	const FVector Forward = GetAvatarActorFromActorInfo()->GetActorForwardVector();
	const FVector Location = GetAvatarActorFromActorInfo()->GetActorLocation();

	// Z축 기준 좌우 45도의 방향벡터
	const FVector LeftSpread = Forward.RotateAngleAxis(-SpawnSpread / 2, FVector::UpVector);
	UKismetSystemLibrary::DrawDebugArrow(GetAvatarActorFromActorInfo(), Location, Location + LeftSpread * MaxSpawnDistance, 4.f, FLinearColor::Green, 3.f);

	const FVector RightSpread = Forward.RotateAngleAxis(SpawnSpread / 2, FVector::UpVector);
	UKismetSystemLibrary::DrawDebugArrow(GetAvatarActorFromActorInfo(), Location, Location + RightSpread * MaxSpawnDistance, 4.f, FLinearColor::Blue, 3.f);

	DrawDebugSphere(GetWorld(), Location + LeftSpread * MinSpawnDistance, 15.f, 12, FColor::Red, false, 3.f);
	DrawDebugSphere(GetWorld(), Location + LeftSpread * MaxSpawnDistance, 15.f, 12, FColor::Red, false, 3.f);

	DrawDebugSphere(GetWorld(), Location + RightSpread * MinSpawnDistance, 15.f, 12, FColor::Red, false, 3.f);
	DrawDebugSphere(GetWorld(), Location + RightSpread * MaxSpawnDistance, 15.f, 12, FColor::Red, false, 3.f);

	return TArray<FVector>();
}

테스트를 위해 소환사 Z축 기준 전방 0도에서 시작해 좌우 45도 각도에 디버그 화살표가 나타나도록 설정한다.

또한 최소 거리와 최대 거리를 확인하기 위해 해당 지점에 디버그 구체를 그린다.

Z축 기준으로 시계방향이 +이다.

 

 

(2) 게임플레이 어빌리티 블루프린트 생성

비헤이비어 트리에서 작동하기 위해서 어빌리티 태그를 Attack으로 지정한다.

 

 

(3) DA_CharacterClassInfo - Elementalist 클래스에 어빌리티 부여

이제 Elementalist 클래스에 초기 어빌리티를 부여한다.

 

 

 

(4) 소환 어빌리티

TArray<FVector> UAuraSummonAbility::GetSpawnLocations()
{
	const FVector Forward = GetAvatarActorFromActorInfo()->GetActorForwardVector();
	const FVector Location = GetAvatarActorFromActorInfo()->GetActorLocation();
	
	// 일정한 거리마다 소환수 배치
	const float DeltaSpread = SpawnSpread / NumMinions;

	// Z축 기준 좌우 45도의 방향벡터
	const FVector LeftSpread = Forward.RotateAngleAxis(-SpawnSpread / 2, FVector::UpVector);
	TArray<FVector> SpawnLocations;
	for (int32 i = 0; i < NumMinions; i++)
	{
		// 나눈 각도 씩 회전
		const FVector Direction = LeftSpread.RotateAngleAxis(DeltaSpread * i, FVector::UpVector);
		const FVector ChosenSpawnLocation = Location + Direction * FMath::FRandRange(MinSpawnDistance, MaxSpawnDistance);
		
		SpawnLocations.Add(ChosenSpawnLocation);
		
		DrawDebugSphere(GetWorld(), ChosenSpawnLocation, 18.f, 12, FColor::Cyan, false, 3.f);

		UKismetSystemLibrary::DrawDebugArrow(GetAvatarActorFromActorInfo(), Location, Location + Direction * MaxSpawnDistance, 4.f, FLinearColor::Green, 3.f);
		DrawDebugSphere(GetWorld(), Location + Direction * MinSpawnDistance, 15.f, 12, FColor::Red, false, 3.f);
		DrawDebugSphere(GetWorld(), Location + Direction * MaxSpawnDistance, 15.f, 12, FColor::Red, false, 3.f);
	}

	return SpawnLocations;
}

이제 90도의 각도를 하수인 수로 나누어 DeltaSpread를 구하고, 해당 각도마다 한 마리씩 소환한다.

이 때, 각 하수인은 MinSpawnDistance와 MaxSpawnDistance 사이에 위치하게 된다.

 

 

2. 랜덤한 위치 선택하기

(1) GA_SummonAbility 블루프린트

Spawn Location에 의해 생성된 배열을 사용하여 해당 배열을 섞은 후 하나씩 선택하여 디버그 구체를 그리도록 한다.

 

 

(2) 라인 트레이스를 이용하여 바닥에 소환하도록 지정하기

TArray<FVector> UAuraSummonAbility::GetSpawnLocations()
{
	const FVector Forward = GetAvatarActorFromActorInfo()->GetActorForwardVector();
	const FVector Location = GetAvatarActorFromActorInfo()->GetActorLocation();
	
	// 일정한 거리마다 소환수 배치
	const float DeltaSpread = SpawnSpread / NumMinions;

	// Z축 기준 좌우 45도의 방향벡터
	const FVector LeftSpread = Forward.RotateAngleAxis(-SpawnSpread / 2, FVector::UpVector);
	TArray<FVector> SpawnLocations;
	for (int32 i = 0; i < NumMinions; i++)
	{
		// 나눈 각도 씩 회전
		const FVector Direction = LeftSpread.RotateAngleAxis(DeltaSpread * i, FVector::UpVector);
		FVector ChosenSpawnLocation = Location + Direction * FMath::FRandRange(MinSpawnDistance, MaxSpawnDistance);
		
		FHitResult Hit;
		GetWorld()->LineTraceSingleByChannel(Hit, ChosenSpawnLocation + FVector(0.f, 0.f, 400.f), ChosenSpawnLocation - FVector(0.f, 0.f, 400.f), ECC_Visibility);

		if (Hit.bBlockingHit)
		{
			ChosenSpawnLocation = Hit.ImpactPoint;
		}
		SpawnLocations.Add(ChosenSpawnLocation);
	}

	return SpawnLocations;
}

라인 트레이스를 이용하여 소환 지점에서 위 아래로 400 유닛에 해당하는 지점까지의 라인 트레이스를 실행한다.

해당 라인 트레이스가 Visiblity 채널과 콜리전이 발생하면 해당 지점의 Location을 가져와 저장한다.

 

 

3. 소환 이펙트 생성하기

NS_GroundSummon 나이아가라 시스템을 사용하여 벡터의 위치에 소환 파티클을 가져온다.

 

 

4. 소환수 소환하기

(1) GA_SummonAbility에 소환수 지정

 

 

(2) 소환수 클래스 랜덤 선택

추가된 소환수 클래스들 중에서 랜덤으로 하나를 선택하는 함수이다.

	UFUNCTION(BlueprintPure, category = "Summoning")
	TSubclassOf<APawn> GetRandomMinionClass();
TSubclassOf<APawn> UAuraSummonAbility::GetRandomMinionClass()
{
	// 랜덤 선택
	int32 Selection = FMath::RandRange(0, MinionClasses.Num() - 1);

	return MinionClasses[Selection];
}

 

 

(3) 각 위치에 랜덤 소환수 소환하기

이제 랜덤 액터를 소환한다.

AI를 부여하기 위해 컨트롤러도 붙여줘야 한다.

 

 

5. 소환 몽타주

(1) 소환 몽타주 생성

애님 노티파이를 이용하여 이벤트를 호출한다.

노티파이가 호출되는 시간을 조정할 수 있다.

 

GA_SummonAbility에서 해당 이벤트를 받으면 적을 소환한다.

 

(2) 소환수가 바깥쪽을 바라보게 만들기

트랜스폼 핀을 분할하여 소환수와 소환사의 벡터 차이로 바깥을 바라보는 벡터를 만들어 Rotation에 대입한다.

 

 

6. 마무리 작업

(1) AuraGameplayTags에서 소환 Native 게임플레이 태그 추가

	// 어빌리티
	FGameplayTag Abilities_Attack;
	FGameplayTag Abilities_Summon;
	// 어빌리티
	GameplayTags.Abilities_Attack = UGameplayTagsManager::Get().AddNativeGameplayTag(
		FName("Abilities.Attack"),
		FString("Attack Ability")
	);

	GameplayTags.Abilities_Summon = UGameplayTagsManager::Get().AddNativeGameplayTag(
		FName("Abilities.Summon"),
		FString("Summon Ability")
	);

 

 

(2) AuraCharacterBase에서 최대 소환수 개수 Getter 함수 선언

캐릭터 별로 가질 수 있는 최대 소환수 개수를 정할 것이다.

 

(2-1) CombatInterface

	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
	int32 GetMinionCount();
};

 

(2-2) AuraCharacterBase

	/** 전투 인터페이스 **/
	virtual UAnimMontage* GetHitReactMontage_Implementation() override;
	virtual void Die() override;
	virtual FVector GetCombatSocketLocation_Implementation(const FGameplayTag& MontageTag) override;
	virtual bool IsDead_Implementation() const override;
	virtual AActor* GetAvatar_Implementation() override;
	virtual TArray<FTaggedMontage> GetAttackMontages_Implementation() override;
	virtual UNiagaraSystem* GetBloodEffect_Implementation() override;
	virtual FTaggedMontage GetTaggedMontageByTag_Implementation(const FGameplayTag& MontageTag) override;
	virtual int32 GetMinionCount_Implementation() override;
	/**여기까지 전투 인터페이스**/
int32 AAuraCharacterBase::GetMinionCount_Implementation()
{
	return MinionCount;
}

 

 

(3) GA_SummonAbility 에서 어빌리티 태그 수정

 

(4) 소환 몽타주의 애님 노티파이에서 이벤트 태그 수정

 

이제 테스트하면 소환사가 소환 어빌리티를 사용하지 못하는 것을 볼 수 있다.

이는 비헤이비어 트리의 BTT_Attack 태스크가 Attack Tag인 Abilities.Attack 태그를 확인하고 있기 때문이다.

이를 위해 소환사를 위한 별개의 비헤이비어 트리를 생성한다.