언리얼 엔진 5/개발 일지

[UE5] 블루아카이브 TPS게임 개발일지 (28) - 적 연발 사격, 겹침 방지

ciel45 2024. 1. 4. 22:14

전투의 양상을 구현할 수는 있었지만, 고칠 점이 있다.

 

  • 적이 자동소총을 들고있음에도 단발사격만 함
  • 자기들끼리 서로 진로를 방해함

 

우선 연발 사격을 구현하기 위해,

BP_Enemy의 이벤트 그래프에 만들어놓았던 Shoot 이벤트를 One Shot이라는 이름의 함수로 바꿨다.

 

또한 적이 너무 정확하게 사격하지 않도록 발사 방향에 약간의 랜덤성을 부여하였다.

함수 노드는 다음과 같다.

 

또한 Play Sound at Location에 Attenuation Setting을 달아줌으로써 적의 총성에서 거리감을 느낄 수 있도록 하였다.

이에 대한 자세한 설명은 https://docs.unrealengine.com/5.0/ko/sound-attenuation-in-unreal-engine/

 

사운드 어테뉴에이션

언리얼 엔진 4 에서 소리의 attenuation, 감쇠 제어에 쓰이는 여러가지 디스턴스 모델에 대한 레퍼런스입니다.

docs.unrealengine.com

 

 

이제 기존의 Shoot 이벤트가 이 One Shot 함수를 여러번 호출하게 하여 연발 기능을 만들 것이다.

 

이를 가능하게 해주는 노드는 Set Timer by Function Name이다.

Set Timer by Function Name의 Function Name에 One Shot이 들어가있는 것을 볼 수 있다. Looping에 꼭 체크해야한다.

 

이렇게 함으로써 Shoot 이벤트를 호출하면 0.1초마다 One Shot 함수를 호출하게된다.

 

사격을 멈추고싶다면, 타이머를 Clear시켜주어야한다. 이를 위해 Clear and Invalidate Timer by Handle을 사용한 것을 볼 수 있다.

 

간단히 Clear Timer로 부르자면, Clear Timer의 파라미터로는 멈추고자하는 타이머의 핸들이 들어가야한다. Set Timer 노드의 리턴 값이 바로 자기 자신 타이머의 핸들이므로, Set Timer의 리턴 값을 Clear Timer의 인풋으로 넣어주면 된다.

 

Handle을 이용해 타이머를 끄는 것에 대한 내용은 여기에서 참고하였다. 

https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Timers/

 

게임플레이 타이머

설정된 주기마다 동작을 수행하는 타이머 구조체입니다.

docs.unrealengine.com

 

Clear and Invalidate Timer by Handle이 Stop Shooting, Cease Fire 이벤트에 각각 연결되어있는 것을 볼 수 있다.

 

Stop Shooting은 사격 중단을 위해 새로 만든 BTTask_StopShooting에서 호출하는 이벤트이다.

BTTask_StopShooting

 

그리고 이 태스크는 비헤이비어 트리에서는 Cover 시퀀스의 첫 단계에 들어가있다.

 

CeaseFire는 C++에서 선언만 해놓고, 블루프린트에서 구현하여 사용하는 BlueprintImplementableEvent 함수이다.

이렇게 한 이유는, C++ 코드 상의 Die에서 호출하여 사용할 수 있도록 하기 위해서이다.

void AEnemy::Die(FDamageEvent const& DamageEvent)
{
	//죽을 때 래그돌 효과 (함수가 너무 길어져서 따로 떼어냄)
	RagdollEffect(DamageEvent);

	//AI 컨트롤러 떼기
	GetController()->UnPossess();

	CeaseFire();
}

 

이 결과로 적은 사격 중 죽을 시 사격을 멈춘다.

 

이렇게 하고도 버그가 있었다. 원인을 찾는데 조금 걸렸는데, BTTask_Attack을 수행하고 실제로 Shoot 이벤트를 호출하기 전 잠깐의 시간 사이에 죽으면 죽은 뒤에도 총을 쏘는 버그였다.

 

해결을 위해 C++ 코드에 IsDead 함수를 BlueprintCallable로 추가하고, 블루프린트에서는 사격 바로 전 IsDead가 false인 경우에만 Shoot 이벤트를 호출하도록 하였다.

// Enemy.h
UFUNCTION(BlueprintCallable)
bool IsDead();
    
// Enemy.cpp
bool AEnemy::IsDead()
{
	return Attributes->IsDead();
}

 

 

 

다음은 적끼리 서로 진로를 방해하는 문제를 손보았다.

언리얼 엔진 공식 문서로 이에 대한 해결책이 존재하여, 많이 참고하였다.

https://docs.unrealengine.com/5.0/ko/using-avoidance-with-the-navigation-system-in-unreal-engine/

 

내비게이션 시스템으로 회피 사용하기

이 가이드에서는 내비게이션 시스템으로 회피를 사용하는 방법을 설명합니다.

docs.unrealengine.com

 

해결 방법은 두가지가 있다.

  • RVO 회피 설정
  • AIController 부모클래스를 DetourCrowdAIController로 지정

 

현재 적들의 MoveTo는 모두 내비게이션 메시를 사용하여 작동하므로, 크라우드 우회 매니저, 즉 두번째 방법을 사용하기로 결정했다.

 

방법은 매우 간단하다. AIController 블루프린트(여기서는 AI_Enemy)의 부모 클래스를 바꿔주면 된다.

 

 

 

다음은 테스트 영상이다.

https://www.youtube.com/watch?v=Y9RBcIOW9YE&ab_channel=Ciel45

 

 

 

프로젝트가 진행될수록 점점 C++ 코드는 안짜고 블루프린트로만 해결하게 되는 것 같다..

 

원활한 관리를 위해서 한번 시간을 내어서 블루프린트 모듈들 중 몇개는 C++ 코드로 옮겨야 할 것 같다.