[UE4] Enemy AI -2 : 랜덤 순찰, 근접 추적 (서비스, Task(Speed, Patrol))
Enemy가 순찰하다가, 플레이어를 발견하면 추적을 시작하고, 일정 거리 안으로 들어오면 공격을 수행하는 Enemy AI를 만들어보았다.
이때, 순찰은 랜덤한 위치로의 순찰 경로를 가지고, 해당 경로를 따라 움직이도록 구현하였다.
📚 Class 생성
먼저, BT_Melee(근접)이라는 비헤이비어 트리와, AI Controller, Enemy AI 블루프린트를 각각 생성해주었다.
비헤이비어 트리에서는 서비스라는 기능을 사용할 수 있는데, 서비스는 무언가 값을 제공하기 위해 사용된다.
블루프린트 클래스에 BTService_BlueprintBase를 상속받아 서비스 클래스를 생성해주었다.
🚩 Service Class
서비스는 Composites 노드에도 붙을 수 있고, Task에도 붙을 수 있는데 어디에 붙는지에 따라 실행이 달라진다.
서비스 클래스를 만들고 난 뒤, 오버라이드 함수를 확인해보면 AI가 붙은 것과 안붙은 것 두 종류가 있다.
비헤이비어 트리에서 서비스를 이용할 때는 AI가 붙은 함수로 사용하고, AI가 안붙은 것은 비헤이비어 트리 외부에서 (Controller등) 직접 서비스를 Call 해줄 때 사용한다.
서비스 클래스를 만들고 나면, 비헤이비어 트리에서 서비스 추가 속성에 방금 만든 Class가 나타나게 된다.
💡 Service 함수 호출 순서
Activation(활성화 함수)와, Start, Tick, Deactivation 함수가 각각 언제 실행되는지를 확인하기 위해 Test 코드를 작성하였다.
테스트 결과, Selector에 붙은 Service 노드는 Begin Play가 실행되기 이전에 Start가 먼저 실행되고, 이후 Activation 함수가 실행되었다.
그다음 Begin Play가 실행되고 Service 노드에 들어올때마다 Start -> Tick -> Tick... 이 반복되며 실행되었다.
이후 게임이 종료되는 End Play 이후에 Deactivation이 호출되며 끝이 났다.
반면, Wait에 붙은 Service 노드는 Begin Play가 될 때 Activation 함수가 실행되고, 이후에 계속 Tick 함수가 실행되다가, Tick이 끝나고 Wait 함수를 나갈 때 Deactivation이 실행되었다. 이후 같은 패턴으로 Activation->Tick->Tick...->Deactivation이 반복되며 Wait 함수의 Service 노드가 호출되었다.
즉, Wait에 Task로 붙은 서비스는 Start가 실행되지 않는다.
따라서 서비스(Service)라는 것은 컴포짓 노드나 태스크에 같이 붙어서, 해당 노드가 실행될 때 같이 실행되는 함수로써 노드가 실행될때마다 함께 상태를 바꿔주고 싶은 경우에 유용하게 사용될 수 있다.
BT Service_Melee 클래스는 서비스의 Tick 함수가 호출될 때마다 AI의 상태값을 알맞게 변경하기 위해 만들어졌다.
우선, AI의 상태값은 Enum 자료형으로 총 6가지 상태를 만들었다.
BTService_Melee 클래스의 이벤트 그래프에서 Tick 이벤트를 호출시키고, 커스텀 함수인 Tick AI를 만들어주었다.
서비스 클래스에선 블랙보드의 Key값을 마음대로 불러오지 못하기 때문에, 변수 유형이 Blackboard Key Selector인 변수 Target Key를 만들어주고, 인스턴스 편집 가능을 사용해 비헤이비어 트리에서 Target Key를 초기화해주었다.
Target이 존재하지 않는다면 감지가 안된 상태이므로, AI의 상태를 순찰(Patrol) 상태로 변경해준다.
이후 Target이 감지되었다면 Target과 플레이어의 거리가 Action Distance(150) 이내라면 Action 상태로 변경해주었다.
거리가 150이내가 아니라면, 감지는 됐는데 공격범위는 아니므로 추격(Approach) 상태로 변경해주었다.
이로써 서비스 클래스의 코드 작성은 마무리가 되었으니, 이제 다시 비헤이비어 트리에서 방금 만든 서비스 클래스를 등록하여 사용해주었다.
🚩 Behavior Tree - Melee
Behavior 키가 Wait과 같다면 Wait을 실행하게 된다. 이때 Wait을 실행하고 있는 도중에 Service_Melee에서 상태가 바뀌면, Wait에 할당된 시간이 전부 끝난 후에서야 Selector 노드로 돌아가게 된다.
이러한 불편사항을 해결하기 위해 Wait 노드에 관찰자 중단 항목을 Self로 설정하면, Wait 노드가 계속해서 실행될 때 상태값이 바뀌면 실패가 되어 Wait을 중단하고 나가도록 만들 수 있다.
Notify Observer 항목은 On Result Change와 On Value Change가 있는데,
Result Change는 값을 가져와서 두 상태를 비교했을 때 True였다가 False로 바뀌었거나, False였다가 True로 바뀌는 순간을 검사하고,
Value Change는 블랙보드에 존재하는 값 자체를 계속해서 관찰하여 값이 바뀌는 순간을 검사하는 것이다. (Value Change는 잘 쓸일 없다)
관찰자 중단이 수행되면, 데코레이터에 붙은 Task를 전부 종료시키고, 중단이기 때문에 결과 반환도 없이 루트로 이동해버린다.
🚩 Task : Speed
비헤이비어 트리에 존재하는 Speed는 Enemy의 상태에 따라 이동하는 속도를 조절하기 위한 Task 클래스이다.
BTTask를 상속받아 만들어진 클래스를 생성하여, Enemy의 Max Walk Speed 값을 변경하는 Set Speed 함수를 생성 및 호출해주었다.
모든 적용이 끝나면 Finish Excute 함수를 호출하여 Task가 끝났음을 알리고 성공 여부를 반환해주었다.
(Task는 반드시 성공인지 실패인지를 반환해주어야 한다. 아무것도 반환하지 않으면 계속 Task 상태에 머물러있는다.)
🚩 Task : Patrol
Enemy AI가 Patrol(순찰) 상태가 되면 랜덤 위치로 Enemy가 돌아다니며 순찰할 수 있도록 구현하였다.
Task가 실행될 때 Enemy의 위치에서부터 Get Random Location In Navigable Radius 함수를 통한 반경 내 랜덤한 위치 이동을 구현하였다.
Random Distance를 1500으로 설정하여 해당 반경안에서 랜덤한 위치값이 반환되고, 그 위치로 내비 메시가 이동 가능하면 Enemy의 위치를 변경하도록 하였다.
Enemy의 이동은 Move To Location 함수를 통해 목표지점으로 걸어가도록 하였고, Acceptance Radius를 20으로 설정하여 이동 종료를 구현하였다. Excute AI 에벤트에서 Location 변수를 설정한 뒤, Finish Excute를 호출하지 않았다. 그럼 Task는 대기상태가 되어 Tick을 지속적으로 호출하는데, 이를 이용해 이동 기능을 Tick 이벤트에서 구현한 것이다.
Move To Location 함수의 반환값이 Failed이면 못가는 위치이므로 Finish 시키고, Already At Goal이면 목표지점에 도착했으므로 마찬가지로 Finish 시켜주었다.
실행결과, 바닥에 랜덤 순찰 위치가 Sphere로 그려지고, Enemy가 해당 지점까지 도달하면 또다른 랜덤 위치가 반환되어 계속해서 순찰을 돌게 되었다.
도중에 Enemy의 시야각에 들어간다면 Enemy에게 감지되어 추적당하게 된다.