본문 바로가기

게임프로그래밍/실습2

[실습2] 21. 이펙트 액터 기능 구현하기

1. 이펙트 액터 리팩터링

 

지난 번에 이펙트 액터 클래스 자체는 만들었지만 따로 특별한 설정은 하지 않았다. 이제 게임플레이 이펙트를 통해 플레이어의 어트리뷰트 속성에 영향을 주게 만들어보자.

 

class PRACTICE2_API AAuraEffectActor : public AActor
{
    GENERATED_BODY()
    
public: 
    AAuraEffectActor();

protected:
    virtual void BeginPlay() override;

    UFUNCTION()
    virtual void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& HitResult);

    UFUNCTION()
    virtual void EndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

private:
    UPROPERTY(VisibleAnywhere)
    TObjectPtr<USphereComponent> Sphere;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<UStaticMeshComponent> Mesh;

};

 

현재 이펙트 액터의 구성은 위와 같다. 사실 굳이 오버랩 이벤트가 C++에서 구현될 필요가 없다고 한다. 성능상 이점이 있는 것도 아니고 이 오버랩 이벤트를 다른 C++클래스에서 호출하는 것도 아니기 때문에 정말 특별한 이유나 극한의 최적화를 요구하는 것이 아니면 블루프린트에서 구현해도 된다고 한다.

 

그렇기에 메시랑 스피어 컴포넌트는 물론 오버랩, 엔드 오버랩 이벤트 전부 블루프린트에서 구현할 것이고 C++ 클래스에서는 전부 삭제해 주자.

 

루트 컴포넌트만 새로 구성할 것이다.

AAuraEffectActor::AAuraEffectActor()
{
    PrimaryActorTick.bCanEverTick = false;
    
    SetRootComponent(CreateDefaultSubobject<USceneComponent>("SceneRoot"));
}

 

물론 이렇게 한다고 모든 기능을 블루프린트에서 구현하려는 것이 아니다. 게임플레이 이펙트는 C++로 구현하고 이를 블루프린트와 연결할 것이다.


2. 게임플레이 이펙트 효과 주기

 

class PRACTICE2_API AAuraEffectActor : public AActor
{
    GENERATED_BODY()
    
public: 
    AAuraEffectActor();

protected:
    virtual void BeginPlay() override;

    UFUNCTION(BlueprintCallable)
    void ApplyEffectToTarget(AActor* target, TSubclassOf<UGameplayEffect> GameplayEffectClass);

    UPROPERTY(EditAnywhere, Category="Applied Effects")
    TSubclassOf<UGameplayEffect> InstanceGameplayEffectClass;
};

 

게임플레이 이펙트를 담을 변수와 이를 적용할 함수를 만들자.

 

void AAuraEffectActor::ApplyEffectToTarget(AActor* Target, TSubclassOf<UGameplayEffect> GameplayEffectClass)
{
    IAbilitySystemInterface* ASCInterface = Cast<IAbilitySystemInterface>(Target);;
    if (ASCInterface)
    {
       UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target);

       TargetASC->ApplyGameplayEffectSpecToSelf()
    }
}

 

함수를 만들고 있다. 

TargetASC->ApplyGameplayEffectSpecToSelf()

 

이곳에 들어갈 매개변수를 설정해야 한다.

 

이펙트 컨텍스트 핸들을 만들자. 핸들은 이펙트 컨텍스트를 래핑한 구조체로 이펙트 컨텍스트를 좀 더 다루기 쉽게 해주는 핸들이다.

 

이펙트 컨텍스트는 효과가 발생한 맥락, 즉 누가 시전했는지 효과가 어디서 발동된 위치가 어딘지 등이 저장되는 구조체이다.

FGameplayEffectContextHandle EffectContextHandle = TargetASC->MakeEffectContext();

 

아래와 같이 소스 오브젝트를 선택한다던지 다양한 작업을 할 수 있다.

EffectContextHandle.AddSourceObject(this);

 

 

다음으로 만들것은 이펙트 스펙 핸들이다. 이펙트 스펙 핸들은 게임플레이 이펙트와 이펙트 컨텍스트를 바탕으로 만들어 진다.

FGameplayEffectSpecHandle EffectSpecHandle = TargetASC->MakeOutgoingSpec(GameplayEffectClass, 1.f, EffectContextHandle);

 

이펙트 스펙 핸들은 이펙트 스펙을 다루는 핸들로 이펙트 스펙에서는 이펙트의 실제 효과, 가령 데미지, 지속 시간 등을 가지고 있는 구조체이다.

 

이제 이를 함수의 매개변수로 넣어주면 된다.

 

TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());

 

함수를 자세히 살펴보면 이 안에 들어가는 것은 참조이지 포인터가 아니다. 그렇기 때문에 역참조하여 실제 데이터를 가져와야 한다.

 

전체 코드를 보면 다음과 같다.

void AAuraEffectActor::ApplyEffectToTarget(AActor* Target, TSubclassOf<UGameplayEffect> GameplayEffectClass)
{
    IAbilitySystemInterface* ASCInterface = Cast<IAbilitySystemInterface>(Target);;
    if (ASCInterface)
    {
       UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target);

       FGameplayEffectContextHandle EffectContextHandle = TargetASC->MakeEffectContext();
       EffectContextHandle.AddSourceObject(this);
       
       FGameplayEffectSpecHandle EffectSpecHandle = TargetASC->MakeOutgoingSpec(GameplayEffectClass, 1.f, EffectContextHandle);
       TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
    }
}

 

이제 코드를 보면 GameplayEffectClass 가 필요한 것을 알 수 있다. 에디터로 가서 이것을 만들어보자.