0. 개요

이전 포스트에서 투사체를 생성했다.

이제 투사체를 발사하는 어빌리티를 구현한다.

 


- 게임플레이 어빌리티 클래스의 주석 설명이다.


UGameplayAbility는 활성화되거나 트리거될 수 있는 사용자 정의 게임플레이 로직을 정의합니다.

AbilitySystem에서 GameplayAbilities를 위해 제공하는 주요 기능은 다음과 같습니다:

1. 사용 가능 여부(CanUse) 기능
   - 쿨다운 (Cooldowns)
   - 비용 관리 (마나, 스태미너 등)
   - 기타 조건

2. 복제(Replication) 지원
   - 능력 활성화를 위한 클라이언트/서버 통신
   - 능력 활성화에 대한 클라이언트 예측(Client Prediction)

3. 인스턴싱(Instancing) 지원
   - 능력은 다음과 같은 형태로 동작할 수 있음:
   - 비인스턴스화 (네이티브 전용)
   - 소유자당 인스턴스화 (Instanced per owner)
   - 실행당 인스턴스화 (기본값) (Instanced per execution)

4. 기본적이고 확장 가능한 지원
   - 입력 바인딩(Input Binding)
   - 액터에게 '사용 가능한' 능력을 부여

**GameplayAbility_Montage**를 참조하세요:
- 이 비인스턴스화된 능력의 예로, 특정 몽타주를 재생하면서 몽타주 재생 중 대상에게 GameplayEffect를 적용합니다.
- 몽타주가 끝나면 GameplayEffect를 제거합니다.

**복제(Replication) 지원에 대한 참고사항**:
- 비인스턴스화된 능력은 복제 지원이 제한적입니다.
- 상태를 가질 수 없으므로(당연히) 복제된 속성을 사용할 수 없음
- Ability 클래스에서 RPC(Remote Procedure Call)를 사용할 수 없음

상태(State) 또는 이벤트 복제를 지원하려면 능력이 인스턴스화되어야 합니다.

이는 InstancingPolicy 속성을 통해 설정할 수 있습니다.


1. 투사체 발사 게임플레이 어빌리티

어빌리티가 인스턴스화 되지 않으면 많은 제약을 가진다.

인스턴스화 된 어빌리티는 내부에 정보를 저장할 수 없다.

 

(1) C++ 클래스 생성

이 클래스는 AuraGameplayAbility를 상속받으며 AuraProjectileSpell의 이름을 가진다.

UCLASS()
class AURA_API UAuraProjectileSpell : public UAuraGameplayAbility
{
	GENERATED_BODY()

protected:

	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
	
};

 

(2) ActivateEffect 함수 오버라이드

게임플레이 어빌리티 클래스에서 정의된 ActivateEffect 함수를 오버라이드하여 사용할 것이다.

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

void UAuraProjectileSpell::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	UKismetSystemLibrary::PrintString(this, FString("Ability Activate(C++)"), true, true, FLinearColor::Yellow, 3.f);
}

이 어빌리티가 활성화되면 스트링을 출력하도록 한다.

 

 

(3) 투사체 게임플레이 어빌리티 블루프린트 생성

GA_Firebolt를 생성한다.

AuraCharacter의 디테일 창에 어빌리티에 FireBolt 게임플레이 어빌리티를 추가한다.

 

 

(4) 투사체 복제(Replication)

class AURA_API UAuraProjectileSpell : public UAuraGameplayAbility
{
	GENERATED_BODY()

protected:

	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<AAuraProjectile> ProjectileClass
};

어빌리티 자체가 투사체 클래스를 저장한다.

이 투사체는 클라이언트 로컬에서 생성될 뿐 아니라 서버로 복제되어서 다른 클라이언트에게도 표시되어야 한다.

따라서 AuraProjectile 클래스의 생성자를 수정한다.

AAuraProjectile::AAuraProjectile()
{
	PrimaryActorTick.bCanEverTick = false;
	bReplicates = true;

 

 

(5) 투사체 발사

이제 투사체 발사 어빌리티 클래스에서 투사체 액터를 생성하는 코드를 작성한다.

void UAuraProjectileSpell::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	// 서버 상인지 확인
	const bool bIsServer = HasAuthority(&ActivationInfo);

	if (bIsServer == false)
		return;

	FTransform SpawnTransform;
	GetWorld()->SpawnActorDeferred<AAuraProjectile>(
		ProjectileClass,
		SpawnTransform,
		GetOwningActorFromActorInfo(),
		Cast<APawn>(GetOwningActorFromActorInfo()),
		ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
}

SpawnActorDeferred 함수는 SpawnActor와 다르게 BeginPlay 같은 초기화 함수가 없으며, 생성된 이후에 속성을 수정할 수 있다.

또한 충돌 메서드를 직접 지정할 수 있다.

 

 

2. 투사체 소환 위치 조정 - ICombatInterface

(1) 전투 인터페이스

이제 생성되는 투사체가 무기의 끝에서 생성되도록 할 것이다.

위의 투사체 발사 어빌리티가 특정 캐릭터에 종속되지 않기를 원하기 때문에 전투 인터페이스를 사용한다.

전투를 하는 모든 캐릭터(플레이어, 몬스터)는 해당 인터페이스를 상속받는다.

class AURA_API ICombatInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	virtual int32 GetPlayerLevel();
	virtual FVector GetCombatSocketLocation();
};
#include "Interaction/CombatInterface.h"

// Add default functionality here for any ICombatInterface functions that are not pure virtual.

int32 ICombatInterface::GetPlayerLevel()
{
    return 0;
}

FVector ICombatInterface::GetCombatSocketLocation()
{
    return FVector();
}

이제 인터페이스를 상속받는 AuraCharacterBase에서 Getter 함수를 오버라이드할 것이다.

 

 

(2) Getter 함수 오버라이드 - AuraCharacterBase

protected:
	virtual void BeginPlay() override;
	virtual void InitAbilityActorInfo();

	UPROPERTY(EditAnywhere, Category="Combat")
	TObjectPtr<USkeletalMeshComponent> Weapon;

	// 주문 발동 체크용 소켓
	UPROPERTY(EditAnywhere, Category = "Combat")
	FName WeaponTipSocketName;

	virtual FVector GetCombatSocketLocation() override;
FVector AAuraCharacterBase::GetCombatSocketLocation()
{
	return Weapon->GetSocketLocation(WeaponTipSocketName);
}

 

 

(3) 소켓 위치 가져와서 투사체의 시작 위치로 조정하기 - AuraProjectileSpell

void UAuraProjectileSpell::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	// 서버 상인지 확인
	const bool bIsServer = HasAuthority(&ActivationInfo);

	if (bIsServer == false)
		return;

	ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAvatarActorFromActorInfo());
	
	if (CombatInterface)
	{
		const FVector SocketLocation = CombatInterface->GetCombatSocketLocation();

		FTransform SpawnTransform;
		SpawnTransform.SetLocation(SocketLocation);

		AAuraProjectile* Projectile = GetWorld()->SpawnActorDeferred<AAuraProjectile>(
			ProjectileClass,
			SpawnTransform,
			GetOwningActorFromActorInfo(),
			Cast<APawn>(GetOwningActorFromActorInfo()),
			ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

		Projectile->FinishSpawning(SpawnTransform);
	}
}

무기 끝을 가져오는 함수를 구현했던 인터페이스를 가져와 무기 끝의 소켓 위치를 구한다.

 

 

3. 블루프린트 설정

(1) 무기에 소켓 이름 지정

이제 에디터에서 무기 스켈레탈 메시를 열고 무기 끝의 소켓을 찾는다.

해당 소켓의 이름을 해당 무기를 들고있는 캐릭터의 블루프린트 디테일 창에서 변경한다.

 

(2) 게임플레이 어빌리티 - Projectile Class 지정

파이어볼트 블루프린트에서 투사체 클래스를 지정한다.

 

이제 몬스터를 클릭하면 캐릭터 전방 방향으로 파이어볼트를 발사한다.

이 투사체는 무기 끝에서 발사되지만, 몬스터를 향하지 않는다. 이는 추후에 수정할 예정이다.

또한 Gameplay Ability Spec을 이용하여 Spawn(Deferred)된 액터에 데미지를 부여할 것이다.