0. 개요

향상된 입력 시스템의 변화에 따라 어빌리티 활성화 이후 InputPress 함수가 호출되도록 바뀌었다.

변경된 구조를 이용하고 기존의 블루프린트 로직을 C++로 이관한다.

 

 

1. 범위 표시기 - MagicCircle 수정

어빌리티 내에서 Show Magic Circle / Hide Magic Circle을 이용하여 BP_MagicCircle을 생성 / 제거하고 있다.

이 때, UI에 나타나는 문구는 게임플레이 큐를 이용하는데, 이 게임플레이 큐를 각 어빌리티 내에서 부여하고 있다.

해당 로직을 MagicCircle 내로 옮긴다.

 

 

(1) MagicCircle

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCircleInitialized, AActor*, AvatarActor);

UCLASS()
class AURA_API AMagicCircle : public AActor
{
	GENERATED_BODY()
	
public:	
	AMagicCircle();
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TObjectPtr<UDecalComponent> MagicCircleDecal;

	UPROPERTY(BlueprintAssignable)
	FOnCircleInitialized CircleInitialized;

	UPROPERTY(BlueprintAssignable)
	FOnCircleInitialized RemoveCircle;
	
protected:
	virtual void BeginPlay() override;
};

블루프린트 델리게이트를 추가하고 매개 변수로 액터를 넘겨준다.

 

 

(2) BP_MagicCircle

델리게이트에 각각 게임플레이 큐를 추가하는 이벤트와 태그를 제거하는 이벤트를 바인딩한다.

 

 

 

2. 아케인 파편 어빌리티 - C++

블루프린트 이벤트 그래프의 로직 대부분을 C++ 코드로 변경한다.

 

(1) 마우스 적중 결과 처리 함수

void UArcaneShards::ReceivedMouseHitResult(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
    if (!TargetDataHandle.IsValid(0))
       return;

    const FGameplayAbilityTargetData* TargetData = TargetDataHandle.Get(0);
    const FHitResult* HitResult = TargetData->GetHitResult();

    // 마우스 히트 정보를 멤버 변수로 만듬
    CurrentMouseLocation = HitResult->ImpactPoint;
    NumPoints = GetAbilityLevel();

    GroundPoints.Empty();

    // 어빌리티 업그레이드 확인
    CheckAbilityUpgrades(FAuraGameplayTags::Get().Abilities_Arcane_ArcaneShards);
    
    FTransform PointCollectionTransform;
    PointCollectionTransform.SetLocation(CurrentMouseLocation);

    PointCollection = GetWorld()->SpawnActorDeferred<APointCollection>(
       PointCollectionClass,
       PointCollectionTransform,
       GetAvatarActorFromActorInfo(),
       nullptr,
       ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

    PointCollection->FinishSpawning(PointCollectionTransform);

    GroundPoints = PointCollection->GetGroundPoints(FMath::Min(NumPoints, MaxNumShards));
}

TargetDataUnderMouse가 반환하는 데이터 핸들을 직접 받아 바닥에 포인트 컬렉션을 생성하는 작업까지 진행한다.

 

 

(2) 타이머 설정 함수

void UArcaneShards::ReadyToSpawnShards()
{
	AActor* AvatarActor = GetAvatarActorFromActorInfo();

	// 범위 표시기 제거
	if (AvatarActor->Implements<UPlayerInterface>())
	{
		IPlayerInterface::Execute_HideMagicCircle(AvatarActor);
	}

	// UI 범위기 메시지 제거
	RemoveRangeSpellHelpMessage(AvatarActor);
	
	if (UWorld* World = GetWorld())
	{
		World->GetTimerManager().SetTimer(ShardSpawnTimer, this, &UArcaneShards::SpawnShards,SpawnShardsDeltaTime, true);
	}

	// 쿨다운, 코스트 적용
	CommitAbility(GetCurrentAbilitySpecHandle(),
		GetCurrentActorInfo(),
		GetCurrentActivationInfo());
}

범위 표시기를 제거하고 타이머를 생성하여 DeltaTime 변수에 따라서 파편 기둥을 소환한다.

 

 

(3) 파편 기둥 소환 - 타이머 핸들의 콜백 함수

void UArcaneShards::SpawnShards()
{
	AActor* AvatarActor = GetAvatarActorFromActorInfo();
	
	if (Idx < GroundPoints.Num())
	{
		// 파편 위치 계산
		ShardSpawnLocation = GroundPoints[Idx]->GetComponentTransform().GetLocation();
		ShardSpawnRotation = GroundPoints[Idx]->GetComponentTransform().GetRotation().Rotator();

		TArray<AActor*> ActorsToIgnore;
		ActorsToIgnore.Empty();
		ActorsToIgnore.Add(AvatarActor);
		
		TArray<AActor*> OutOverlappingActors;
		OutOverlappingActors.Empty();

		// 데미지 입힐 플레이어 계산
		UAuraAbilitySystemLibrary::GetLivePlayersWithinRadius(
			AvatarActor,
			OutOverlappingActors,
			ActorsToIgnore,
			RadialDamageOuterRadius,
			ShardSpawnLocation);

		for (AActor* DamagedActor : OutOverlappingActors)
		{
			if (!IsValid(DamagedActor))
				continue;

			// 아군이라면 건너뛰기
			if (!UAuraAbilitySystemLibrary::IsNotFriend(DamagedActor, AvatarActor))
			{
				continue;
			}

			// 클래스 디폴트를 이용하여 데미지 이펙트 파라미터 생성
			FVector DirectionOverride = DamagedActor->GetActorLocation() - ShardSpawnLocation;
			FDamageEffectParams Params = MakeDamageEffectParamsFromClassDefaults(
				DamagedActor,
				ShardSpawnLocation,
				true,
				DirectionOverride,
				true,
				DirectionOverride,
				true,
				45.f);

			// 데미지 적용
			UAuraAbilitySystemLibrary::ApplyDamageEffect(Params);
		}
		
		// 아케인 파편 기둥 이펙트 생성
		FGameplayCueParameters CueParams;
		CueParams.Location = ShardSpawnLocation;
		CueParams.Normal = UKismetMathLibrary::GetRightVector(ShardSpawnRotation);
			
		K2_ExecuteGameplayCueWithParams(FGameplayTag::RequestGameplayTag("GameplayCue.ArcaneShards"), CueParams);
	}
	
	if (Idx >= GroundPoints.Num())
		EndSpawnShards();
	else
		Idx++;
}

파편 기둥을 하나씩 생성하면서 데미지를 입히고, 기둥 플레이 큐를 생성한다.

 

 

(4) 파편 소환 종료

void UArcaneShards::EndSpawnShards()
{
	PointCollection->Destroy();

	EndAbility(
		GetCurrentAbilitySpecHandle(),
		GetCurrentActorInfo(),
		GetCurrentActivationInfo(),
		true, false);
}

포인트 컬렉션을 파괴하고 어빌리티를 종료한다.

 

 

(5) EndAbility

void UArcaneShards::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
                               const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
	Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);

	auto* AvatarActor = GetAvatarActorFromActorInfo();
	if (AvatarActor->Implements<UPlayerInterface>())
	{
		IPlayerInterface::Execute_HideMagicCircle(GetAvatarActorFromActorInfo());
	}
	
	// 데미지 입힘 종료
	if (UWorld* World = GetWorld())
	{
		World->GetTimerManager().ClearTimer(ShardSpawnTimer);
	}

	// 포인트 컬렉션이 아직도 파괴되지 않았으면 파괴
	if (IsValid(PointCollection))
		PointCollection->Destroy();
}

범위 표시기가 사라지지 않았을 경우를 대비하여 범위 표시기를 제거한다. 포인트 컬렉션도 마찬가지이다.

 

 

3. 아케인 파편 어빌리티 - 블루프린트 이벤트 그래프

기존의 범위 지정 데칼과 자동 이동 중지 로직은 유지하고, 나머지 로직은 C++ 함수로 대체한다.

EndAbility 이벤트도 제거한다.