상세 컨텐츠

본문 제목

pond 프로젝트 11 - AI without NavMesh

Unreal Engine 4/Project

by hwano 2015. 4. 30. 19:08

본문

 

프로그래밍 개요 강좌를 살펴봤는데 공부할 분량이 장난이

아니다..  마야 플러그인을 만들어봐서 대충 적응하면 되겠지 했는데

프로그래밍 지식이 전혀 없으니 봐도 난감하네.. 

 

BP안에서 해결할 방법을 좀 찾아봐야겠다.

 

 

 

 

1. 서치

 

 

전에도 살펴봤던 FlyingAI가 가장 대표적인 3d pathfinding일거다.

https://www.youtube.com/watch?v=xjAKqioNZaE&list=PLV0qBSMhzPz5DUog3QEbFjDFc6z93D7I7

 

아니면 fish schooling

https://www.youtube.com/watch?v=XgpIKNK2gDM

 

이건 BT없이 배럭에서 생산된 캐릭터들이 정렬하는 예제

https://www.youtube.com/watch?v=2uJWr1qe73Q

 

위 샘플들을 보면 bt를 쓰지 않고 고유의 pathfinding 을 짜서 쓰고 있다.

bt를 기반으로 하는 방법이 없을까. 그냥 z축 하나만 추가해 주면되는디..

....................................................................

UE4로 BT가 아닌 FSM을 써서 작업한 예제가 없을까? 만약 BT안쓰고 하려면

어떻게 접근해야 되지?

 

 

 

 

2. 갈아엎기

 

 

기본적인 길찾기 알고리즘을 BP로 구현하여 아예 그냥 BT없이 물고기 움직임을 구현하고자 한다.

어차피 내 프로젝트같은 경우에는 크게 길이 막히는 경우나 점프, 횡이동 같은 복잡한 구현이 필요없으므로

어느 정도 구현 가능하리라는 생각이다. 또, 물고기가 최대 15이하 정도로 생각하고 있기때문에

쓸데없이 알고리즘이 복잡해지더라도 계산상에서 그리 큰 부담이 없으리란 생각이다.

 

BP만으로 가능할지 모르겠는데..  한번 테스트 삼아

해보는걸로,,

 

 

 

 

 

3-1. 길찾기 알고리즘 기초

 

 

http://cozycoz.egloos.com/9748811

설명이 가장 잘 되어있던 이 블로그의 내용을 요약해보자.

 

 

녹색에서 빨간색까지 이동하는 경로를 사각형으로 도식화 해보자.

 

F = G + H

F = 비용

G = 시작점에서 다음열린공간으로의 이동 비용

H = 현재에서부터 최종 목적지까지의 예상 이동 비용

 

F가 가장 적은값을 갖는 사각형을 선택하는것을 반복하여 최단거리의 경로를 찾아낸다.

 

 

 

 

G 비용 얻기

 

 

내가 현재 속한 공간을 당힌 공간, 내가 현재 속한 공간에서 검색되는 사방의 8개의

사각형은 열린 공간이라고 정의하자. 한번 닫힌 공간은 이동과정에서 누적되어

다시는 열린 공간에 포함되지 않는다.

 

위아래 양옆의 이동은 10 대각선은 14의 비용을 할당한다.

 

 

 

H 비용 얻기

 

맨하탄 방식이라고 부른다고 한며,

중간에 장애물은 무시하고 대각선을 제외한 가로세로로만 이동할 수 있다고 했을때 그 이동

값에 10을 곱한다.

 

그림에 나와 있듯 시계방향으로 F H G값들을 정리하면 저렇게 된다.

저 값대로 중심에서 오른쪽으로 한칸 이동하게 된다.

 

 

계속 검색

 

 

 

계속해서 같은 방식으로 계산해보자. 오른쪽으로 한칸이동 후 위 아래 모두

F값이 54로 동일하다. 어느방향을 선택해도 상관없고 여기선 아래로 이동하였다.

위 그림은 두칸 이동 후의 모습이다.

 

현재 녹색의 대각선 오른쪽 아래에 있는건데 거기서 주위 사각형을 서치에 보면 장애물의

아래사각형이 걸려야 한다. 하지만 여기서는 캐릭터가 몸의 부피가 있다는 가정이므로 대각선으로

이동하게되면 몸의 부피가 장애물에 걸리게 되므로 대각선 이동은 불가한 것으로 정의했다.

( 이건 그냥 예제에서의 택일 )

 

 

최종 경로

 

 

이 과정을 마지막 목표 빨간색 사각형까지 열린목록에 추가될때까지 반복한다.

그 결과가 위 그림이다. 이제 반대로 빨간색 사각형에서 사각형 안에

남겨진 핀의 방향을 거꾸로 타고 온다.

 

그러면 위와 같은 경로가 나오게 된다.

 

 

 

 

3-2. 길찾기 알고리즘

 

 

http://theory.stanford.edu/~amitp/GameProgramming/AStarComparison.html

이 과정을 좀 더 사실적인 경로로 그려본다면 아래와 같다.

 

 

 

 

4. 아이디어

 

 

공식말고 직관적으로도 생각해보자.

 

아무런 장애물이 없다고 한다면 곡선의 이동경로를 가지게 하는건 그리 큰

문제가 안될거 같다. 물고기의 몸통이 회전하는 문제도 마야의 hCurve만들때

normal 문제를 해결했던 데로 하면 뭐 가능할거 같다.

 

거기에 만약 회전방향대로 이동하는 과정에서 벽을 만난다고 하면 중간에 있는 그림처럼

물고기 시야앞으로 충돌 감지할수 있는  스피어를 달아 놓아서 tick 계산으로 계속해서

충돌이 없는 지역으로 이동하도록 하면 되지 않을까??

 

 

 

 

 

 

 

 

 

5-1. fishCharacter 수정

 

먼저 연못이라는 공간을 무시하고 텅빈 공간에 임의의 포인트들을 생성해서 따라 움직이는 물고기

까지만 만들어보자.

 

BT를 꼭 사용안할필요가 있을까? task와 일반 블루프린트에서 만들수 있는 노드들이 다른가?

BT를 사용하지만 navigation mesh만 사용하지 않는 방법으로 하면 되지 않을까?

 

 

////////////////

이동을 하는거야 두 포인트끼리의 거리가지고 쉽게 구할 수 있는데

점차 바라보게 하는게 어렵다. A axis에서 B axis로 회전값을 구해서 물고기가

점차 target쪽을 바라보게 해야한다. 근데 그 방법을 모르겠다. 블루프린트에는

이계산을 지원하는 노드가 안보인다.

 

그래서 오브젝트가 roll회전은 하지 않으니

yaw와 pitch 계산을 따로 구해서 add local rotation으로 적용하면 되지 않을까 싶다.

 

yaw를 구하는거는 물고기와 target의 위치를 높이(z) 0 으로 고정 시켜서 구하면된다.

pitch를 구하는거는 local XZ 평면을 만들고 타겟의 위치가 그 평면에 직각으로 떨어지는 지점을 구하여

계산하면 될거다.

 

 

 

 

 

5-2. Yaw 계산 

 

하다보니 생각보다 복잡하다. 물고기가 바라보는 방향 벡터를 get actor forward vector로 뽑아내고

물고기가 target을 바라보는 방향의 벡터는 target의 위치와 물고기의 위치를 빼서 구한다.

두 벡터를 z는 0으로 고정하고 노말라이즈한후 내적을 구한다. 내적을 rotation 각도로

바꾸려면 acos(degree)를 적용해 주면 된다고 한다. 이를 yaw값으로 add 시켜준다.

 

작동해보면 돌기는 하지만 타겟이 물고기의 오른쪽에 있는지 왼쪽에 있는지 판단하는

과정이 또 필요하다. 두 벡터의 내적으로는 그냥 각도밖에 구할 수 없기 때문에 이를 더해줘야하는지

빼줘야 하는지 모르기 때문이다. 왼쪽 오른쪽을 판단하는 함수도 짜 넣어야 한다.

오른쪽 왼쪽 판단함수도 완료.

 

 

 

5-3. Pitch 계산 

 

pitch계산하는게 약간 헷갈리는데

왼쪽 처럼 물고기 세로 평면으로 target을 투영시키는게 아니다 그렇게 되면 물고기가 뒤집힌다.

그냥 물고기와 타겟이 떨어진 x, y의 거리 만큼을 물고기 방향으로 가져온다.

그 다음 옮겨진 타겟과 물고기와의 내적을 구해 회전값을 구하면 된다. 되겠지?

 

음..  근데 어떻게 수학적으로 옮기지??

get right vector를 이용해서 고정축인 pitch회전축을 구하고 월드업 벡터와 외적을 하면

물고기 앞쪽을 향하는 벡터를 구할수 있다. 그 벡터를 노말라이즈하고

타겟과의 거리 x를 곱한것을 물고기 위치 벡터에 더하면 아마 옮길 수 있을 듯..

 

 

이래이래저래요래해서 어쨌든 타겟을 바라보는 물고기가 완성되었다. 이제 target으로 이동하도록하자.

 

 

 

5-4. 이동 계산 

 

이동할 때 곡선의 경로를 그리도록 하려면 어떻게 해야하나?

전에 만들어 봤던 Jiggle 강좌의 알고리즘을 다시 봐보자.

근데 Jiggle은 외부에서 움직임이 주어지고 이를 바탕으로 current position과

previous position을 바탕으로 계산을 해나가는거라서 이 경우에는 맞지 않는거 같다.

 

음..  생각해보니 그냥 현재 위치와 골 위치의 distance를 적당히 나눈값을

계속 direction방향으로 더해만 주면 되는거 같다/?

 

회전 계산을 별도로 들어가니 앞쪽으로 velocity만 더해주면 점차 곡선 궤적을 그리며 가게 되는게 아닌가?

 

-_- 아니지..  그냥 FishMove Speed라는 변수를 하나 만들어서 매프레임 적용하면 되지...

거리 따져서 distance를 나누면 속도가 차이가 생기니까...

 

 

 

 

 

 

6-1. 충돌계산

 

뭐 당연 플레이 해보고 수정은 해야겠지만 기본적인 움직임 시스템은 만들어졌다.

wayPoint에 충돌구를 하나만들어서 물고기가 가까이 다가가 충돌을 인식하게 해보자.

 

레벨 블루프린트에서 보면 event overlap같은 이벤트가 지원되서 오버랩될때

딱 실행되도록 할 수 있는데 task 블루프린트에서는 그런걸 어떻게 하는지 모르겠다.

우선 그냥 wayPoint와 overlapping을 일으키는 fishCharacter의 이름을 확인하도록 했는데

저러면 매프레임 계산이 들어가니 속도가 느려질거 같은데..  어쩔수 없지 우선 저렇게 하자.

 

 

 

 

6-2. 움직임 개선

 

여기까지 하고 플레이를 해보면 물고기가 wayPoint에 도달했을 때 뚝뚝 끊어지는 모습을 볼 수 있다.

지금 물고기의 move값은 FishMove Speed라는 항상 일정값을 direction방향으로 더해주고 있기 때문에

move때문에 끊길 이유는 없다.  회전때문에 끊기는거 같다. 현재 회전이 물고기각도와 목표물의 각도를

일정한 변수로 나누어 계산하기 때문에 물고기와 목표물의 각이 적어질수록 회전은 느려진다.

그러다 목표물이 변경되면 갑자기 회전이 빨라지기 때문에 끊겨보이는게 첫번째 이유일 것이다.

 

 

물론 속도도 목표물에 가까워지거나 다음 목표물로 이동을 시작할때 베지어로 자연스럽게 가면 더

좋을 것이나 우선 회전값부터 끊어지지 않도록 블렌딩을 시켜보자.

 

 

 

 

 

 

6-3. 회전값 블렌딩

 

앞서 얘기한데로 다음 목표물로 변경시 갑자기 회전 속도의 최고치까지 올라가지 않고 점차 속도가 올라갔다가 내려오게 해야한다. 어찌해야할까??  이전 프레임과 블렌딩을 해야하나? tick으로 해버리면

컴퓨터마다 블렌딩 구간의 폭이 차이가 생겨버릴것이다.

 

......   그렇게 생각해보니 내 물고기의 속도도 delta time을 곱했어야 하는데 안했구나...

이건 바로 수정하자.

 

회전 속도가 올라갔다가 내려오게 해야한다. 어찌해야할까?? 

이전 프레임과 블렌딩을 해야하나? tick으로 해버리면 컴퓨터마다 블렌딩 구간의 폭이 차이가

생겨버릴것이다.

.....................................

물론 실행때마다 블렌딩 폭의 차이는 생기겠지만 대충 10개정도의 이전프레임값들을 저장해놓고

평균을 때리면 자연스럽게 블렌딩이 되지 않을까? UE4에서는 배열같은건 어떻게 쓰는건지 알아봐야겠다.

 

 

생각해보면 블렌딩 폭도 거의 일정하게 유지 할수 있다. 블렌딩폭 변수을

지정할 변수를 만들고 delta값을 나누어 반올림한다.

그렇게 해서 구해진 수의 array length 만큼의 수들을 가져와 평균을 때리면 어느정도 일정한

blend폭을 유지할 수 있다.

 

왜나면 델타값이 적으면 (fps가 높으면) 더 여러개의 array들을 블렝딘해야하고

델타값이 높으면 (faps가 적어 더 듬성듬성 계산이 되니) 더 적은 수의 array들을 블렌딩해야한다.

 

moveToTarget노드를 simpleParallel로 나누고 카운트 태스크를 별도로 붙였다.

Printcount안에 counter라는 변수를 만들고 0으로 지정해준다음 tick마다 1을 더해서 출력한다.

내가 걱정했던게

spawnWayPoint -> moveToTarget -> destroyWayPoint라는

루프를 한바퀴 끝내면 PrintCount에 들어있는 변수는 메모리에서 날라가고

다시 default값인 0으로 돌아갈줄 알았다. 하지만 플레이 해보니 그렇지는 않다.

 

이 AI가 계속되는한 메모리에서 날아가지 않고 계속 살아있는거 같다. 다행이다. 그러면

굳이 BB에 기록할 필요가 없지.

 

------------------------------------------------------------------------

이런식으로 블렌딩을 했는데도 약간씩 끊어지는 느낌이 든다.

아마도 moveToTarget노드가 끝나고 destroyWayPoint와 spawnWayPoint계산되는 동안

tick을 소비하기 때문에 그런거 같다. 그래서 spawnWayPoint 태스크에 loop 데코레이터를

붙여봤다. 600정도 루프를 돌려서 실행해봤는데 역시 루프하는 동안 움직임이 멈춘다.

 

그래서 simple parallel을 이용해서 moveToTarget은 계속 실행시키고

background계산으로 wayPoint를 만들고 지우는 과정을 해야한다.

 

 

 

 

 

6-4. simple parallel의 에러

 

 

여기서 좀 애매해지는데 딱히 말로 설명을 하지는 못하겠지만

계산이 계속 꼬여서 프로그램이 다운된다.

 

구조를 생각한게 이건데

이게 왜 그런지 계속 에러가 난다. 무한루프에 걸리는거 같다.

 

simple parallel의 오른쪽 부분은 왼쪽 태스크가 끝나고 실행되는게 아니고 동시간대에

실행되는 컨셉이다. 그래서 spawnWayPoint에서 false -> true가 되고

다시 true -> false가 되고 무한반복이 되는게 아닐까??  나도 몰라.

어쨌든 계속 에러나서 이 방법은 안될듯.

 

 

 

 

6-5. 구조변경

 

 

이런식으로 구조를 더 단순화시켜봤다.

simple parallel의 왼쪽에서 이동은 이동대로 계속진행하고

simple parallel의 오른쪽에서는 fish가 wayPoint에 도착했는지 곁눈으로

확인하면서 새로운 wayPoint로 변경해주는 일을 동시에 진행한다.

 

개념상 이렇게 해야 꼬이는 일이 없을거 같다.

 

 

 

7-1. 다시 새로운 구조

 

 

위와 같은 방법으로 조금씩 조금씩 수정해 나가다 보면

계속 빵구나고 또 수정하는데 시간이 뺏길거 같다. 물고기가 이동하다가

벽을 만나는 경우도 생각해야한다. 모든 경우를 포함해서

전체적인 로직을 다시 구성한 후 한번에 수정해야할거 같다.

 

 

 

 

7-2. 마우스 이벤트

 

 

마우스를 클릭한 곳에 물고기들이 모여드는거나 중간에 장애물이 있으면

피해가는 것 등을 모두 포함한 포괄적인 AI를 구성하려고 한다.

먼저 마우스를 클릭 했을때 물고기들이 모여드는 FingerVolume이

생성되도록 하자.

 

마우스커서는 playerController 블루프린트 설정에서 켤 수 있다.

https://docs.unrealengine.com/latest/KOR/Resources/ContentExamples/MouseInterface/MouseControlSetup/index.html

아래 강좌를 참고했다.

Blueprints - How to Create a Custom Cursor - Unreal Engine 4

 

물표면에 마우스로 클릭한 위치를 얻어오기 위해서

get hit result under cursor by channel을 사용했는데 물표면 오브젝트를 하나 만들어 배치하고

아웃라이너에서 눈을 끄고 detail에서도 actor hidden in game을 켜줘서 눈에는 안보이고 계산에서만

이용하도록 했다.

 

 

 

7-3. 속도체크

 

 

여러가지 로직을 정리 중인데 물고기를 50마리만 뿌려도 속도가 상당히 느려진다..

계산상의 무거운 부분을 어떻게 찾는거지?

 

 

 

7-4. 보간

 

 

물고기의 움직임을 개선 하기 위해 난 그냥 프레임들을 직접 array로 만들어 블렌딩

시켜줬는데 보니까finterp to라는 보간용 노드가 제공된다. 이쪽을 이용하는게

퍼포먼스 상의 이득이 있을거 같다.

 

-> 이건 내가 방법을 잘 몰라서 그러는 건지 결과물이 더 이상해 져서 그냥 안썼다.

 

 

 

7-5. 도망가기 

 

 

물고기가 도망가게 하기 위해

FingerPoint의 위치에서 물고기를 바라보는 위치벡터를 구한다.

그 벡터를 FearValue와 곱해주어 TargetPoint에 반영해준다. 이런식으로 손가락을 피해 도망가는

물고기의 모션을 잡을 수 있다.

 

해봤는데 내가 원하는  움직임이 안나온다. 그냥 인위적이지만 손가락의 반대편으로 도망가는 모션으로

잡으려고 한다.

 

 

 

 

7-6. HUD

 

 

작업을 더 진행하기 전에 여러변수들과 로직들을 확인하고 컨트롤하는데 있어 print string같은걸로

그때 그때 확인하는게 한계가 있다.

변수들을 모아서 보고 관리할수 있는 기본적인 hud정도는 하나 만들고 가야 할거 같다.

블루프린트 타임어택 튜토리얼을 참고했다

 

 

7-7. 패키징

 

 

물고기를 50마리까지 spawn하니 play시 매우 느려졌다. 그래서 shipping버젼으로 패키징해보았다.

배포파일로 굽는거는 file / cook contents ~~로 먼저 쿠킹(어셋을 한번 배포용 파일로 굽는다)

을 해주고 file / pakage project를 해주면 된다.

 

패키징을 해도 50마리를 스폰하니 엄청 느리다....   니미 아무것도 없는 빈공간에

꼴랑 물고기 50마리 뿌렸는데 느려지면 어쩌라는거야...

 

 

 

 

 

 

 

 

8-1. 또 다시 새로운 구조 

 

 

물고기 마리 수가 늘어나면 느려지는 문제를 수정해야한다.

 

지금 방식이 wayPointVolume액터에서 fishCharacter를 스폰하면 fishAI들이 각각 개별적으로 생겨서 계산

(이건 확실치 않다) 되는게 아닌가 싶다.

 

그렇게 되면 각각의 AI들마다 함수들이나 변수들을 개별적으로 갖게되어 퍼포먼스 저하가

일어날수 있다. play를 시켜보면 world outliner에 AI블루프린트들이 물고기마다 개별적으로 따로따로

생긴다. 맞네 AI가 하나만 생기는 방법으로 수정해야한다.

 

------------------------------------------------------------------------------------

고민해 보다가 이번 프로젝트에서는 navigation도 쓰지 않는데 계속 굳이 Behavior Tree를 가지고

진행할 필요가 있나 싶어(BB를 한번 거쳐오느라 쓰기도 상당히 불편하다)

그냥 일반 블루프린트에서 AI를 구현하고자 한다.

 

 

--------------------------------------------------------------------------------------

어떻게 해야되나/???

fishCharacter의 AI Controller Class인 FishAI에서 FishCharacter를 spawn하면 되나????

그럼 AI가 하나만 생길까?

 

아니지 Fish가 navigation시스템을 이용하지 않으니 부모 클래스를 character가 아닌

그냥 일반 pawn으로 만들어도 되니 AI가 없어지는 건가?? ㅁ;닝럼;ㅣㄴ얼;ㅁㄴㅇㄹ 아우

머리아파.

 

 

 

 

8-2. 형변환에 대한 정리 

 

 

다른 액터를 레퍼런싱하려면

변수를 Actor타입으로 만들수도 있고

변수를 FishCharacter(예를들어)로 만들수도 있다.

 

이때 FishCharacter안의 component에 접근한다면

 

Actor로 만든것은 FishCharacter로 한번 cast하고 다시 component로 cast해야한다.

FishCharacter로 만든것은 바로 component로 접근이 가능해진다.

근데 그냥 actor로 해서 cast해서 쓰자. 안그러면 더 헷갈린다.

 

 

 

 

 

9. 또 또 또 다시 새로운 구조 

 

 

물고기의 회전 움직임들을 수학적 계산들로 직접 하나씩 구현했었는데 알고보니

그냥 이렇게 하면 끝난다..

 

헷헷. 염병할.

 

 

관련글 더보기