몬스터 AI#2

CJB_ny·2022년 1월 6일
0

Mini_MMO_RPG

목록 보기
11/16
post-thumbnail

이 다음으로는 진짜 진짜 몬스터 AI를 만들기 위해서

리얼로 MonsterController에 돌아 가가지고


여기서 이제 고민을 해보도록 하자.

일단 몬스터가 기본적으로 Idle상태이니까 Idle부터 챙기도록하자.

1. 몬스터 Idle 상태부터 구현

Idle인 상태일때 무엇을 해야 하냐면..

가만히 있다가 Player가 사정거리 범위 안에 들어오면 Player를 추적을 하기 시작해야 한다.

어쨋든 내 사정거리 범위 안에만 들어오는지를 체크를 해야 한다.

그 범위의 수치를

이렇게 _scanRange로 만들어 주자.

그리고 만드는 김에

_attackRange = 2;로 만들어 주자.

이 _scanRange, _scanRange와 같은 수치들은 나중에 Data에서 따로 빼놓아서 관리를 하다가


여기서 init을 할때 추출을 해서 사용하는 식이면 될 것이다.

그래서 이제 몬스터가 Idle인 상태에서 Player를 찾아와야 하는데

우리가 아직은 Player나 Monster를 총괄하는 매니저는 사용하지 않고 있었다.

그래서 그 부분은 나중에 매니저가 생기면 달라질 수 있다.

그래서 몬스터가 Idle인 상태에서 플레이어를 쉽게 가져올 수 있는 방법이 있었는데

바로 Tag를 사용하는 것이였던 것이였던 것이였다.

그래서 유니티짱의 Tag를

이렇게 바꾸면

Tag를 이용해서 찾을 수 있었다.

이런 간단한 기능이 있었었다.
(프리팹에서 하는것이 더 좋으니까 거서 하자)

이렇게 해주자.

그래서 이제 Tag를 이용해서 Player를 찾아 보도록 하자.


FindGameObjectWithTag를 통해서 player를 찾은 다음에

만약

이상태라면 딱히 할게 없으니까 계속 기다리도록 하자.

만약 player가 있다면 나와 player사이의 거리를 체크를 하면 될 것이다.

distance를 체크를 하는 방법은 상대방의 좌표에서 나의 좌표를 뺀 다음에

빼서 나온 그 방향 벡터 나온거에다가 Magnitude(사이즈)를 추출 하면 된다.


그래서 이렇게 distance를 추출 하면 될거같고요


<< 참고 >>

magnitude를 사용할때는 어쨋든 루트값을 한번 적용을 하기 때문에

연산하는데 시간이 조금 더 걸리기 때문에

정말정말 나는 성능의 최적화를 하고 싶다라고 하면은


magnitude말고 sqrMagnitude를 사용한다음에


distance의 제곱이다 라고 생각을 해서 계산을 하면 될 것이다.

근데 이렇게 까지 챙기기는 싫으니까 그대로 두자 ㅎㅎ


그래서 이제 거리가 나의 _scanRange보다 작으면

이렇게 몬스터의 _lockTarget을 이제 player로 변경해서

player를 주시를 하는것이다.

그다음에 이제 State를

로 바꿔주면 될 것이다.

그러면 이제 Idle상태에서는 뭔가를 추적하는 상태로 바뀌게 되는 것이다.

그리고 이것이 잘 되는지 유니티에서 테스트를 해보도록 하자.

_scanRange안에 들어오면 Moving상태로 잘 바뀐다.

근데 유니티 툴에서 설정을 해주어야 됨...

코드로 시리얼라이즈 필드로 설정을 해주었는데 안된다.(해결해야함)

아무튼 이제 Moving부분을 구현을 해주어야 한다.
(나를 추적을 하는 부분을 만들어 줘야한다)


사실 이부분은 우리 PlayerController와 어느정도 비슷하다고 볼 수 있다.


PlayerController의 Moving부분인데

이런식으로 특정 거리까지는 따라오다가

뭐 이런식으로 이동해서 공격하는 부분이 있었는데

그렇다는 것은 이것을 복사를 해서 MonsterController로 옮기도록 하자.


이딴식으로 주석 수정하고 붙여 넣자.


이부분까지는 똑같은데

지금 distance가 <= 1 일때 State를 Define.State.Skill이렇게 했는데

몬스터는 _attackRange로 두었으니까


이렇게 해주고

이제 이동 하는 부분에서 Player와 다른점이


Player는 Ray를 찍어가지고 해당 벡터좌표로 이동을 했는데

몬스터는 Ray를 찍는 부분이 없다.

공격을 하는 상태가 아니라면 _attackRange안이 아니라면

// 이동하는 부분 의 코드가 실행이 될것이다.


몬스터는 레이를 찍는 부분이 없으니까

이부분 지워주고


우리는 이 NavMeshAgent를 이용해가지고 직접 길찾기를 이용을 해주어야 한다.


이렇게 다 지워주고

이제 드디어 nma의 SetDestination이라는 녀석을 사용을 해볼 것인데

얘는 그냥 간단합니다. -> 그냥 내가 갈 상대 목표를 지정하면 된다.


우리는 목표하는 지점을 _destPos로 관리를 하고있었으니까

인자에 _destPos를 넣어주도록 하자.


이렇게 해주고

더 넣어 줄것이 없나 생각해보고 nma.speed를 _stat의 MoveSpeed로 넣어주자.

그래서

이런식으로 SetDestination에 넣어주는 순간에 우리가

우리가 이제 딱히 이동을 시키지 않더라도 코드처럼 목표지점이 찍혀있으니까 막 따라 올 것이다.

_destPos 의 위치를

위에서 설정을 해주어야함(안해놔서 안따라 오더라 처음에)

그래서 이까지 이제 _scanRange안에 들어오면 몬스터가 쫒아 와서 한대 때림

그래서 이제

UpdateSkill을 구현을 해주어야 한다.

그래서 우리가 PlayerCOntroller에서는 어떻게 UpdateSkill을 구현했는지 보면은

아하!

방향을 조절하게 만들어 주었었다.(때릴때 해당 방향으로 돌려서 때리게)

그래서 몬스터도 UpdateSkill에 똑같이 넣어주고


이렇게.

그게 아니라고 한다면은 다음에 한번 더 때릴지 아닐지는

PlayerController에서

OnHitEvent에서 관리를 이렇게 하였었다.

그래서 MonsterController에서 한번 때리고나서 그다음 더 때릴지 아닐지는

여기에서 구현을 해야 하는데


일단 _lockTarget이 있는지 없는지 확인을 해주자

그리고 항상 null체크를 해주는 것은 좋은 습관이다.

그럴 상황은 없겠지만 타겟이 없다면

바로 이렇게 Idle인 상태로 넘겨 주도록 하자.

그리고 체력을 깍는 부분은 우리가 PlayerController의 OnHitEvent부분에서 구현을 해주었었는데


이렇게 복사해서 넣어주도록 하자.

이렇게 한다음에 이다음에 할 것이

몬스터가 _lockTarget을 한 녀석이 죽었냐 안죽었냐에 따라가지고

더 때릴지 말지를 구현을 해야 한다.


이런식으로 if문으로 target이 죽었는지 살아있는지 확인을 해주도록 하자.


죽었다면 이렇게 Idle상태로 넘어가는데

if(targetStat.Hp > 0)일 경우에

다시 때린다? -> 무조건 다시 때릴 수는 없다! -> 왜??

사정거리가 있기 때문에 사정거리 안에 있는지 없는지 판별을 해서

_attackRange안에 있다면 다시 때리고

너무 멀다면 다시 쫒아가는 State로 바꾸어 주어야 한다.


이렇게 하면 방향 벡터가 나오고

여기서

magnitude를 추출하면 크기가 나오게 된다.


그래서 이렇게 distance를 구해서 _attackRange안에 있으면 Skill인 상태로, 아니라면 Moving상태로 해서 쫒아가게 만들어 주도록 하자.


근데 잘 따라오고 때리고 도망갔을때도 잘 따라와서 때리는데

2. 충돌시 밀리는것 왜그런지 && 수정

Player를 계속 미는 현상이 발생하는데...!

여러가지 가설이 있을 수 있지만 MonsterController가서 확인을 해보면은

이동하는 부분에서

SetDestination에서 _destPos가 찍혀 있으면 그 목표한 곳으로 계속 이동을 하려고 하는데

우리가 Skill상태일때 nma.SetDestination부분을 날려주지 않았으니까 그런거 같다라는 생각이 드는데

얘를 밀어 주도록 할게요.?(뭘 밀어 준다는 거고??)


이부분을 복사를 한다음에 UpdateSkill에 가서


이렇게 위에 if문에서 distance <= _attackRange일때 밑에 복붙 해주고
SetDestination에다가 나의 좌표를 넣어 주면은

목적지에 도달했으니까 멈춰 있을 것이다.

그래서 이제 밀처지는 문제가 이것 때문이였는지 확인을 위해서 유니티로 돌아가서 실행을 해보도록 하자.

근데 이제 몬스터는 Player를 밀지를 못하는데

Player는 몬스터를 밀쳐낼 수가 있는데 이게 왜그런지 함 보면은

그렇다는 것은 Player가 왜 Monster를 밀 수 있는지 약간 이상하기는 한데

우리가 지금 Player한테

Nav Mesh Agent를 붙여 놨었다.

그리고 이동을 할때도 NMA의 기능들을 이용해서(Move를 이용해서) 움직이고 있었는데

우리가 다시 Player를 이동하는 것을 복습을 해보면은

PlayerController로 돌아가서


이동하는 부분이 이거였는데

애당초 우리가 여기서 Debug.DrawRay를 해가지고 갈 수 있는지 없는지를 판별했었는데

굳이 Nav Mesh에서 사용하는 그런 기능들을 활용해서 Move를 사용할 필요가 전혀 없다.

그래서

이부분을 사용안하고 우리가 맨처음에 했던 방법대로 고쳐 보도록 하자


이렇게 바꿔보도록 하자.

dir에 normalized를 한다음에 _moveDist로 이동을 하도록 만들어 주자.

else안에 들어왔을때 Debug.Draw부터 해서 갈 수 없는 방향이면 저기서 처리 될 것이고

if문 통과해서 float _moveDist부터 설명을 하자면은


dir은 여기서 처럼 이동해야할 방향이였고

_moveDist가 내가 이동해야 할 거리였다.

이렇게 바꿔주고 유니티로 가서

유니티짱의 Nav Mesh Agent를 삭제를 해주자( 컴포넌트 없애도 무방하다)
Player의 경우에는 굳이 이것이 필요 없을 수도 있으니까

없애주고 다시 실행을 하면 Player가 몬스터 안 밀치게되고


이동할때 그대로 통과 하게된다.

충돌 안하고 쓱 지나가게 된다.

죽는것은 다음에 챙기도록 하고

이렇게해서 몬스터의 기본적은 AI는 다 만들어 주었다.

그리고 여기서

중요한 부분 복귀 를 해보면은

결국에는 Player랑 Monster랑 마우스를 이용해서 컨트롤 하거나 아니면 AI를 통해서 컨트롤 하거나의 차이는 있지만


이렇게 이런 기본적인 상태관리

즉 State 패턴을 이용한 관리는 매우 유사하기 때문에


이렇게 BaseController를 하나 만들어 주면은

몬스터 AI도 굉장히 쉽고 빠르게 만들 수 있는것을 볼 수 잇었다.

게임이 진짜 너무 커지지 않는 이상 이렇게 State를 이용해서 관리하면 굉장히 유용하기 때문에

꼭 기억을 하세요!!

-끝-


중간 유용한 질문글들

![](https://velog.velcdn.com/images%2Fstarkshn%2Fpost%2F6511e1b1-69fc-4b63-a4ed-7b88846f13bb%2Fimage.png)

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글