Infinite Fighter 개발일지 (13)

유영준·2023년 6월 11일
1

UE5 UNSEEN

목록 보기
13/18

23.06.05 ~ 2023.06.11 개발일지

개선한 점

스턴 상태 체크

먼저 저번주의 문제점이었던 스턴 인식 문제를 개선하였다 스턴 감지를 Decorator -> Service 로 바꾸게 되었다

BTService_StunCheck.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "AI/BTService_StunCheck.h"
#include "IFEnemy.h"
#include "IFEnemyAnimInstance.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"

UBTService_StunCheck::UBTService_StunCheck()
{
    Interval = 0.2f;
}

void UBTService_StunCheck::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

	APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
	if (nullptr == ControllingPawn)
	{
		return;
	}

	AIFEnemy* AIPawn = Cast<AIFEnemy>(ControllingPawn);
	if (nullptr == AIPawn)
	{
		return;
	}

	OwnerComp.GetBlackboardComponent()->SetValueAsBool(TEXT("IsStunned"), AIPawn->AnimInstance->GetStunState());
}

서비스로 변경하여 바로 바뀌는 것을 확인할 수 있었다

추가한 부분

패링의 완성

패링을 할때 바로 스턴 모션이 나오는 것에서 조금 더 연출적인 부분을 더해주었다

스턴하는 부분에 파티클을 추가해주었고, 약간의 슬로우 모션과 넉백 효과를 받게끔 구성하였다

패링 파티클은 스파크가 튀는 파티클을 조금 손봐서 만들어보았다

IFCharacter.cpp

if (bCanBeDamaged)
{
	if (bIsParryingPoint)
	{
		auto Enemy = Cast<AIFEnemy>(DamageCauser);
		if (::IsValid(Enemy))
		{
			// set timer
			UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 0.25f);
			GetWorld()->GetTimerManager().SetTimer(SlowTimer, [this]() { UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1); }, 0.05f, false);
				
			// set the animation and particle
			Enemy->ActivateStun();
			Parrying();
			FVector OffSet = (GetActorForwardVector() * 70) + (GetActorRightVector() * -20) + (GetActorUpVector() * 70);
			UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ParryingParticle, GetActorLocation() + OffSet, FRotator::ZeroRotator, true);
			Enemy->TakeDamage(0, DamageEvent, GetController(), this);
			return 0.0f;
		}
	}
       
    ...
        
}

기존의 코드에서 속도 제어와 파티클을 추가해주었다


추가한 부분

연출 강화

파티클 추가

도끼가 벽에 닿을때, 도끼를 손으로 잡을때, 상대를 공격할때 등 여러 모션에 파티클을 추가했다

  1. 도끼가 벽에 닿을때

    도끼가 벽에 닿을때는 아까 패링할때 만들 파티클의 원형이 되는 불꽃 스파크가 튀는 모습을 사용했다

  2. 도끼를 잡을때

    도끼를 잡는 순간 눈꽃이 튀는듯한 연출을 위해 파티클을 추가했다

    도끼 메쉬 자체에 파티클이 추가하기 위해 Skel Vert / Surf Location 을 사용했다

    이를 위해 원래는 static Mesh 였던 도끼 메쉬를 Skeleton Mesh로 바꿔주었고, 코드를 통해 파티클을 추가했다

    IFAxe.cpp

AIFAxe::AIFAxe()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	
    ...
    
	CatchParticleComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("CATCH_PARTICLE_COMPONENT"));
	CatchParticleComponent->SetupAttachment(Axe);

	static ConstructorHelpers::FObjectFinder<UParticleSystem>CATCH_PARTICLE
	(TEXT("/Game/InFiniteFighter/FX/Leviathon/P_AxeCatch.P_AxeCatch"));
	if (CATCH_PARTICLE.Succeeded())
		CatchParticleComponent->SetTemplate(CATCH_PARTICLE.Object);

	CatchParticleComponent->SetActorParameter(TEXT("VertSurfaceActor"), this);
	CatchParticleComponent->bAutoActivate = false;
    
    ...
}
  1. 공격 시

    성공적으로 공격을 하게 되면 (TakeDamage 함수가 0 초과를 return 하면) 피 파티클이 추가되도록 구현하였다

AnimNotifyState_AttackHitCheck.cpp

...

		if (bResult)
        {
            APawn* HitTarget = Cast<APawn>(OutHit.GetActor());
            if (::IsValid(HitTarget))
            {
                FDamageEvent DamageEvent;
                if (HitTarget->TakeDamage(1, DamageEvent, MeshOwner->GetController(), MeshOwner) > 0)
                    UGameplayStatics::SpawnEmitterAtLocation(MeshOwner->GetWorld(), BloodParticle, OutHit.ImpactPoint, FRotator::ZeroRotator, true);
            }
            
	...
    
}

Camera Shake 추가

공격을 하거나 도끼를 잡을때 카메라가 흔들리는 효과를 추가해주었다

IFCharacter.cpp

void AIFCharacter::SetCameraShake()
{
	APlayerCameraManager* PlayerCameraManager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0);

	if (::IsValid(PlayerCameraManager))
	{
		PlayerCameraManager->StartCameraShake(CameraShake);
	}
}

쉐이크 함수를 만들어두고, 이를 필요한 상황에 맞게 호출하는 방식으로 사용하였다

완성된 모습이다

Hit Stop 추가

Hit Stop, 우리말로 역경직을 추가해주었다

역경직은 액션게임의 손맛에서 굉장히 큰 영역을 차지한다고 생각하기에 추가해주었다

방식은 간단하게 공격대상과 피격대상의 애니메이션을 잠시 멈췄다가 실행하는 방식으로 해주었다

IFEnemy.cpp

		if (bCanBeAttacked)
		{
			// taking damage
			PlayerCharacter->SetCameraShake();
			AnimInstance->React(this, DamageCauser);
			bCanBeAttacked = false;

			// pause for hit stop
			PlayerCharacter->GetMesh()->GetAnimInstance()->Montage_Pause();
			AnimInstance->Montage_Pause();
			
			// setting the frame (4fps)
			float StiffFrame = 4.0f / 60.0f;

			// using timer to free animation
			GetWorld()->GetTimerManager().SetTimer(StiffTimer, [this]()
			{
				PlayerCharacter->GetMesh()->GetAnimInstance()->Montage_Resume(nullptr);
				AnimInstance->Montage_Resume(nullptr);
			}, 
			StiffFrame, false);
			
			return DamageAmount;
		}

완성된 모습은 다음과 같다


AI 개선

단순 공격을 반복하던 AI를 개선해 지금껏 자료에서 보았듯 추격하고, 회전하도록 구현하였다

BTTask_TurnToTarget.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "AI/BTTask_TurnToTarget.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"

UBTTask_TurnToTarget::UBTTask_TurnToTarget()
{
    NodeName = TEXT("Turn");
}

EBTNodeResult::Type UBTTask_TurnToTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);

	APawn* ControllingPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
	if (nullptr == ControllingPawn)
	{
		return EBTNodeResult::Failed;
	}

	APawn* TargetPawn = Cast<APawn>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(TEXT("Target")));
	if (nullptr == TargetPawn)
	{
		return EBTNodeResult::Failed;
	}


	FVector LookVector = TargetPawn->GetActorLocation() - ControllingPawn->GetActorLocation();
	LookVector.Z = 0.0f;
	FRotator TargetRot = FRotationMatrix::MakeFromX(LookVector).Rotator();
	ControllingPawn->SetActorRotation(FMath::RInterpTo(ControllingPawn->GetActorRotation(), TargetRot, GetWorld()->GetDeltaSeconds(), 2));

	return EBTNodeResult::Succeeded;
}

앞으로 할 일

적아 캐릭터를 보기 위해 회전하는 부분이 현재는 조금 어색하기 때문에, 회전에 관한 애니메이션을 추가할 것이며,

적이 단순하게 쫒아오는 것이 아닌 다양한 패턴을 가지도록 구현하고자 한다 (원거리 공격을 위해 거리를 일부로 벌리는 식)

profile
토비폭스가 되고픈 게임 개발자

0개의 댓글