0. 개요

이제 서버가 클라이언트에서 수행되는 클릭에 대한 정보를 수신받을 수 있게 되었다.

이제 투사체의 방향을 클릭 방향으로 진행하도록 투사체를 정렬할 것이다.

 

 

1. 투사체 회전

(1) 투사체 소환 함수 수정 - AuraProjectileSpell의 SpawnProjectile 함수

이제 이 함수는 타겟의 위치를 사용하여 투사체를 회전시킬 것이다.

	UFUNCTION(BlueprintCallable, Category="Projectile")
	void SpawnProjectile(const FVector& ProjectileTargetLocation);
void UAuraProjectileSpell::SpawnProjectile(const FVector& ProjectileTargetLocation)
{
	ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAvatarActorFromActorInfo());

	if (CombatInterface)
	{
		const FVector SocketLocation = CombatInterface->GetCombatSocketLocation();
		
		// 소켓에서 타겟까지의 벡터의 각도만 가져옴
		FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation();
		
		// 지면과 평행하게 진행, 중력을 적용하려면 값을 올리면 됨
		Rotation.Pitch = 0.f;

		FTransform SpawnTransform;
		SpawnTransform.SetLocation(SocketLocation);
		SpawnTransform.SetRotation(Rotation.Quaternion());

타겟 위치의 벡터와 소켓의 위치를 빼서 소켓에서 타겟까지의 벡터를 구하고 각도를 취한다.

이후에 바닥과 평행하게 직진하도록 Z축 각도를 0으로 고정한 후 투사체에 적용한다.

 

 

(2) 발사체 블루프린트 수정 - GA_FireBolt

(2-1) 변수로 승격

HitResult의 Location을 변수로 승격한다.

 

(2-2) 노드 연결

기존의 PlayMontageAndWait 노드부터 End Ability 노드를 연결하고 수정했던 Spawn Projectile 노드에 Target Location을 전달한다.

 

(3) 문제 확인

클라이언트에서 적을 클릭하면 파이어볼트가 발사되지 않는 버그가 발생한다.

이는 투사체를 소환한 즉시 어빌리티를 종료시키기 때문이다.

EndAbility 노드를 제거한다.

 

 

2. Shift키를 눌러 제자리에서 투사체 발사하기

적을 클릭하지 않아도 제자리에서 마우스 방향으로 투사체를 발사하도록 하고 싶다.

입력 액션 IA_Shift를 생성하고 1D(float)으로 설정한 후 IMC에서 양쪽 쉬프트키를 할당한다.

 

(1) Shift키 눌림 판정 - AuraPlayerController

	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputAction> ShiftAction;

	void ShiftPressed() { bShiftKeyDown = true; }
	void ShiftReleased() { bShiftKeyDown = false; }
	bool bShiftKeyDown = false;

에디터에서 입력 액션을 받을 수 있도록 설정하고 키가 눌렸을 때와 떼어졌을 때 불리언을 변경하는 함수를 생성한다.

void AAuraPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();

    // 커스텀 입력 컴포넌트 유효성 확인
    UAuraInputComponent* AuraInputComponent = CastChecked<UAuraInputComponent>(InputComponent);

    AuraInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAuraPlayerController::Move);
    AuraInputComponent->BindAction(ShiftAction, ETriggerEvent::Started, this, &AAuraPlayerController::ShiftPressed);
    AuraInputComponent->BindAction(ShiftAction, ETriggerEvent::Completed, this, &AAuraPlayerController::ShiftReleased);

이어서 Shift 입력 액션에 대해 콜백 함수를 바인딩한다.

 

(2) 왼쪽 클릭이 눌려있는 경우 - AbilityInputTagHeld

void AAuraPlayerController::AbilityInputTagHeld(FGameplayTag InputTag)
{
//~~~~~~~~~~~~
    // 타겟
    if (bTargeting || bShiftKeyDown)
    {
        if (GetASC())
            GetASC()->AbilityInputTagHeld(InputTag);
    }

타겟팅이 되어있는지 확인하는 부분을 or를 이용하여 Shift키가 눌려있는지 확인한다.

 

(3) 왼쪽 클릭이 때진 경우 - AbilityInputTagReleased

void AAuraPlayerController::AbilityInputTagReleased(FGameplayTag InputTag)
{
    // 더 이상 왼쪽 클릭 태그가 아닐 경우
    if (!InputTag.MatchesTagExact(FAuraGameplayTags::Get().InputTag_LMB))
    {
        if (GetASC())
            GetASC()->AbilityInputTagReleased(InputTag);

        return;
    }

    if (GetASC())
        GetASC()->AbilityInputTagReleased(InputTag);

    // 타겟
    if (!bTargeting && !bShiftKeyDown)
    {
        // 경계값보다 짧게 눌렀으면 목적지로 길 찾기 시작
        const APawn* ControlledPawn = GetPawn();
        if (FollowTime <= ShortPressThresold && ControlledPawn)
        {
            if (UNavigationPath* NavPath = UNavigationSystemV1::FindPathToLocationSynchronously(this, ControlledPawn->GetActorLocation(), CachedDestination))
            {
                // 각 경로 점을 스플라인에 추가
                Spline->ClearSplinePoints();
                for (const FVector& PointLoc : NavPath->PathPoints)
                {
                    Spline->AddSplinePoint(PointLoc, ESplineCoordinateSpace::World);
                }
                CachedDestination = NavPath->PathPoints[NavPath->PathPoints.Num() - 1];
                bAutoRunning = true;
            }
        }
        FollowTime = 0.f;
        bTargeting = false;
    }
}

이전 코드는 bTargeting == true일 경우 ASC의 TagReleased 함수를 실행했고, false일 경우 길 찾기를 실행했다면

Shift키를 도입하게 되면서 Shift키가 눌려있지 않을 때 길 찾기를 실행하여야 하므로 조건문의 변화가 발생한다.

 

(4) 블루프린트 수정 - BP_AuraPlayerController

 

 

3. 문제점 해결

(1) 캐릭터와 카메라가 충돌함 - AuraCharacterBase

AAuraCharacterBase::AAuraCharacterBase()
{
	PrimaryActorTick.bCanEverTick = false;

	GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
	GetMesh()->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);

캡슐과 메쉬의 카메라 콜리전 체널을 Ignore로 변경한다.

 

 

4. 모션 워핑(Motion Warping) - 투사체 발사 시 캐릭터 회전

환경에 따라 캐릭터의 애니메이션을 동적으로 수정하는 기능이다. 언리얼 5 이상 버전에서 사용할 수 있다.

 

(1) 모션 워핑을 사용하기 위한 조건

- 애니메이션이 루트 모션(Root Motion)일 것

애니메이션의 디테일에서 루트 모션이 활성화되어 있어야 한다.

 

- 모션워핑 플러그인을 추가할 것

에디터의 플러그인에서 모션 워핑 플러그인이 활성화되어야 한다.

 

- 캐릭터에 모션 워핑 컴포넌트가 추가되어야 함

플러그인이 구성되었다면 캐릭터 블루프린트에서 모션 워핑 컴포넌트를 추가한다.

 

 

(2) 몽타주에 모션 워핑 적용하기 - AM_Cast_FireBolt

애니메이션의 처음부터 캐릭터가 회전하기를 바라므로 노티파이 스테이트(Motion Warping)를 0초에 지정한다.

쉬프트키를 누르면서 애니메이션을 보며 애님 노티파이 직전까지 오른쪽 끝을 늘린다.

 

 

(3) 모션 워핑 설정 - 디테일

- Warp Target Name : 워프 타겟 이름을 지정한다.

- Warp Translation : 본의 위치 또는 크기를 변경하지 않으므로 체크를 해제한다.

- Warp Rotation : 루트 본의 회전을 원하므로 체크한다.

- Rotation Type : Target을 바라보도록 Facing으로 체크한다.

 

 

(4) 모션 워프 타겟 설정 - BP_AuraCharacter, GA_FireBolt

워프 타겟을 설정하는 블루프린트 이벤트를 생성한다.

이어서 파이어볼트 어빌리티에서 해당 함수를 호출한다.

캐릭터가 향해야 하는 Facing Target에 투사체 타겟 위치를 전달하는 방식이다.

이 방법을 이용하면 GA_FireBolt가 BP_AuraCharacter에 종속적이게 되므로 해당 캐릭터가 아닌 다른 캐릭터(적)은 파이어볼트를 발사하는 어빌리티를 사용할 수 없게 된다. 따라서 전투 인터페이스에서 이를 구현한다.

 

 

(5) 전투 인터페이스에서 구현 - ICombatInterface

UINTERFACE(MinimalAPI, BlueprintType)

블루프린트에서 인터페이스를 꺼내어 쓸 수 있도록 지정한다.

	UFUNCTION(BlueprintImplementableEvent, BlueprintCallable)
	void UpdateFacingTarget(const FVector& Target);

어빌리티의 의존성을 제거하기 위해 인터페이스에서 구현한다.

해당 함수는 블루프린트로 구현할 것이며, 블루프린트에서 호출할 것이다.

 

 

(6) 타겟 바라보기 함수 수정 - BP_AuraCharacter, GA_FireBolt

인터페이스에서 구현한 함수를 활용한다.

이어서 GA_FireBolt 블루프린트를 수정한다.