몬스터 자동 생성

CJB_ny·2022년 1월 20일
0

Mini_MMO_RPG

목록 보기
15/16
post-thumbnail

우리가 지금

몬스터 spawn을



이렇게
GameScene에서 하고있었다.


여기서 spawn함수로 게임을 실행을 할때 만들어 주었다.

그래서 한마리만 만들것이 아니라 몬스터를 죽일 때마다 리스폰을 시켜야 할텐데

이렇게 몬스터의 리스폰을 담당하는 그런 코드를 추가를 해주도록 하겠다.

1. 구현 내용

그런데 이런 리스폰을 하는 것은 프로젝트마다 이름이 다 다르긴한데

보통 spawningPool을 많이 사용했던 기억이 있다.

아무튼 용어는 그리 중요하지 않고

몬스터가 생성될 범위를 설정을 해둔 다음에

몬스터가 죽을 때마다 몬스터의 갯수를 유지를 시켜주는 것을 할것이고

그리고 약간 랜덤하게 스폰하게 되는 부분도 구현을 하게 될 것이다.

참고로 이부분은 나중에 서버가 붙게되면은 서버쪽에서 처리를 해줄 부분이다.

서버에서 몬스터를 생성을 한다음에 그것을 "클라"에게도 알려주는 방식으로 동작을 할것이다.

우리는 일단 서버가 없으니까

일단 클라베이스에 간단하게 구현을 하도록 하자.

그리고 사실

최종적으로 이렇게 인벤토리도 붙이는 것을 목표로 하고있었는데

인벤 처리는 나중에 서버가 붙으면 할것이다.

그리고 잠깐 UI창이

윈도우 크기를 늘리면 이렇고

줄이면


이런식으로 바뀌는데

유니티자체의 버그는 아니고

GameScene가서 자동으로 뜨게 하던부분은 삭제를 해주도록하자


나혼자 UI만지다가 복구가 안됨 좆됫다.

이렇게 되었다.

나중에 다시 수정을 해야함


GameScene에서


이부분 주석처리를 해서 사용을 그만 하자.

그리고

유니티로 돌아와서 테스트를 해보면

이렇게

안뜬다.


1차 커밋 222.01.19


2. SpawningPool 구현

그럼이제 유니티에서 Contents산하에

스포닝풀 넣자.

이녀석은 모노를 붙인 일반 컴포넌트로 만들것이다.


그리고

게임씬에서 몬스터를 셀 변수와

필드에서 유지시킬 _keepMonsterCount를 만들어주자.

참고로 나중에 서버에서 구현을 하게되면은

몬스터마다 유지해야되는 개체수가 다를테니까

이부분이 좀 세분화는 될 것이다.


그리고 순서대로

스폰을 할 위치 벡터

한자리에서 계속 스폰 되면 재미없으니까 위치를 랜덤하게 해줄 Radius

그리고 스폰 시간을 랜덤하게 해줄 스폰Time

이렇게 만들어 주도록 하자.

그리고 지금 몬스터를 어디서 스폰하고있었는지 생각을 해보면

GameManagerEx에서 하고있었다.

그래서 결국에는 몬스터가 한마리 추가가 되거나


한마리가 삭제가 될때


여기 왼쪽 스포닝풀에도 전달을 해주기 해야할 것이다.

그래서 de스폰인지 그냥 스폰인지 알고싶으면

이렇게 델레게이트 만들어서

필요하다면 들고갈 수 있게 Action을 하나 파주도록 하자.

그러면은 결국

여기서 몬스터가 추가가 될 때

spawn함수에서

null이 아니라면 1을 뿌려줄 것이고


Despawn에서

몬스터가 한마리 줄었다면은

1말고 -1을 넣어주도록 하자.


그래서 결국 ACtion에 int를 넣어준 것은 늘어난 숫자를 넣어 준것이다.

그것을 OnSpawnEvent를 통해서 전달을 하면 될 거같다.


그러면 이제 SpawningPool에서 Start부분에서 뭔가를 해주면 될거같은데

Action으로 전달받으면 _monsterCount를 늘려주어야 하니까

늘려줄 함수인

AddMonsterCount라는 함수를 하나 파주어서 _monsterCount는 value만큼 늘어난다고 해주도록 하자.


그리고 이렇게 연결을 시켜주면 될 것이다.

-=, += 은 습관상 실수로 두번 호출 할 수도 있으니까 이렇게 해주는 것임


그리고 KeepMonsterCount도 하나 만들어서

유지해야할 몬스터 수도 나중에 받으면 여기서 관리를 하도록 만들어 주자.


그러면 Update문에서는 무엇을 해야하냐면은

_monsterCount가 _keepMonsterCount보다 작은지 아닌지 봐가지고

작다면은 뭔가를 만들어 주어야 한다는 말이 된다.


그래서 작다면 뭔가를 해주어야 하는데

if 문 말고 while문으로 뺑뺑이를 돌면서 부족한 만큼 만들어 주도록 해주자.


그럼 여기서 이제 스폰을 이렇게 하면 될것인데

스폰을 한다는 것은 어떤 의미냐고 하면은

GameScene에서 기존에 Knight를 어떻게 생성하고 있었는지 보면은

이렇게 했는데

똑같이


Spawn함수안에서 이렇게 하기는 해야 할 것이다.

그리고 참고로


Spawn함수안에서 _monsterCount를 직접 늘려주지 않는다고 하더라도

Managers.Game.Spawn을 따라 가보면은

여기서 Invoke를해서 역으로 하나를 늘려줄 테니까

_monsterCount를 늘리는 부분은 SpawningPool의 Spawn함수에 굳이 안 넣어주어도 된더,

그래서 이제 Spawn을 하는데

_spawnTime을 사용을 하고싶은데

1~5초사이에 랜덤한 값으로 하고싶은데

음 어떻게 시간을 기다렸다고 스폰을 할지 잘 모르겠다.

그럴때 유용하게 사용하던것이 있었는데

그게 바로

2-1 코루틴

코루틴 인데 이것을 이용하면 굉장히 편리하게 사용할 수 있다라는 것을 알고있었으니까

코루틴을 사용하도록 하자.

그래서 Spawn함수앞에


IE뉴머레이터 뭐시기를 붙여 주도록 하자.


그리고 함수명도 앞에 Raserve를 앞에 붙여 주도록 하자.

왜냐하면 애당초

바로 스폰을 하는 경우도 있겠지만

Spawn을 하는 함수는

_spawnTime 따라 0~5초사이에 딜레이를 주어서 만들 것이니까

어느정도 예약이 들어가니까 함수 이름을 Reserve를 붙여주자.


그리고 Start에서는 StartCoroutine으로 인자에 "ReserverSpawn"을 넣어 주도록 하자.

그리고 ReserveSpawn에 빨간줄이 뜨는 것은 반환 값이 없어서 그런 것인데

이것은


시작하자마자 yield return null을 해주면 기분나쁜 빨간줄은 일단 없어진다.

그리고 우리가 코루틴을 사용한 이유는

waitTime을 조금 두고 _spawnTime을 사용하고 싶었던 거니까

사실은 yield return null;을 할 것이 아니라

yield return new WaitForSeconds라는 몇초 동안 기다려라하는

WaitForSeconds라는 것이 있었다.

그래서 여기 인자에 몇초인지를 넣어 주고 싶은데

0~_spawnTime 사이였다.

그런데 유니티에서 랜덤을 사용할 수 있게 굉장히 편리한 기능을 만들어 주었었는데


지금 여기의 Random은 유니티의 Random이다.

C#문법이 아니라!


그래서 Range안에다가 min, max를 넣어주게되면 작동을 하게된다.


그리고 Range 함수에 커서를 올려보면은

mininclusive, maxinclusive 라고있는데

min, max포함 값이라는 것이다.

그러면 이제 0~5초사이에 랜덤으로 선택을 하게 되가지고

     GameObject obj = Managers.Game.Spawn(Define.WorldObject.Monster, "Knight");

이부분으로 넘어오게 되는 것이다.

3. 랜덤위치에 생성시키기

그리고 이제 이 다음에 해야 할 것이 무엇이냐 하면은


obj를 생성을 했으면 obj를 랜덤 위치에다가 생성을 해주고 싶다.

그런데 랜덤하게 위치를 하게되는데

말도안되는 위치 -> 집안이나

그런데 위치를 시키면 안된다라는 문제가 있다.

그래서 그 부분을 생각을 해주어야 하는데

그부분을 생각을 해보도록 하자.


그래서 randPos라는 것을 만들어 주는데

어떠한 값을 쓸지 살짝 고민이 되기는 한다.

그런데 유니티에서 또 사용할 수 있는 기능중에 이것이 하나 있는데

Random.InsideUnit 시리즈가 있는데

Circle은 2D 이고 Spere는 3D인데


이녀석은 한마디로

어떤 원을 그려가지고 거기에 있는 랜덤 좌표를 뽑아오게 된다.

예를 들어 (0, 0, 0)좌표를 기준으로 크기가 1인 구를 그린 다음에

거기 안에있는 랜덤좌표를 하나 뽑아오는 기능을 하는데

이녀석을 이용을 하면은 우리가 굉장히 쉽게 구현을 할 수 있는것이 있는데

바로 랜덤으로 방향 벡터를 만드는 것을 쉽게 할 수 있다.


그래서 랜덤Dir이라고 해가지고 randDir를 만들 것인데

randDir은 insideUnitSphere를 한다음에

이렇게 아까 우리가 만들어준 _spawnRadius라는 거리를 곱해주면은

랜덤으로 뽑은 방향벡타가 나오기는 할 것이다.

그런데 지금같은 경우 우리가 3D게임이기는 하지만

평면에다가 몬스터를 배치를 시키고 있었다(땅바닥에다가)

사실상 2D처럼 동작을 하고 있었으니까

이녀석을 땅을 뚫고 아래에다가 만들기 싫다면은


이렇게 y축을 0으로 밀어 넣어 주어야 한다.

자! 그래서 이까지 했으면

randPos를 계산할 수 있는데...

_spawnPos는 무조건 갈 수 있는 위치이고 여기다가

randDir을 더해주면 이것이 randPos가 되는데

randPos가 무조건적으로 생겨도되는 그런 위치에 생긴다라는 보장이 없다 지금은 (예를들어 집안에 생길 수도 있고 그런거임 랜덤으로 위치를 설정해주기 때문에)

그러면 randPos를 길찾기를 이용을해서 갈 수 있는 길인지 아닌지 체크를 해주어야 한다.

우리는 이것을 NavMeshAgent로 체크를 했었었다.


그래서 obj에서 이렇게 GetorAddComponent로 NVA를 추출을 하도록 하자.

이제 추출을 했다면 nma를 기준으로 갈 수 있는 영역인지 아닌지를 체크를 해야된다.


그래서 while문을 계속 돌면서

밑에서 이제 randPos가 갈 수 있는 곳인지 아닌지를 체크를 계속 해주어야한다.

그래서 NavMeshAgent의 여러가지 기능중에서

CalculatePath라는 것이 있는데

갈 수 없는 곳이라면 false를 뱉어 줄 것이다.

여기 인자로 가고싶은 randPos를 넣어주면 될 것이다.

그리고 CalCulatePath가 인자를 하나 더 받는데


커서를 올려서 보면은 NavMeshPath라는 것을 더 받는다.


그래서 NavMeshPath라는 것을 파주어서 여기다가 길을 받아 주어야 하는데


그래서 이녀석이 true라고 하는것은 갈 수 있는 영역이니까

바로 break를 때리면 된다.

만약 갈 수 없는 곳이라서 if문안에 안들어 갔다라고 하면은

다시 함수를 도는 형태가 되어서 갈 수 있는 곳일때까지 돌게 될 것이다.

자 이렇게 하면은

randDir방향으로
_spawnRadius만큼 했는데

거리 도 랜덤하게 했으면 좋겠다라는 생각이 든다..

그래서 거리도


Random.Range를 사용해서 이렇게 바꿔주도록 하자.

그러면 이제

randDir는 방향도 랜덤이지만 거리도 랜덤인 그런 애가 만들어 질 것이고

그래서 while문까지 계산이 다되었으면 빠져나온다음에

우리가 계산한

randPos에다가 넣어주도록 하자.

그리고

이녀석은 이제 없애도 된다.

우리가 yield return을 한번이라도 해주면 아까와같이 불평을 하지 않는다.

그래서 이제 테스트를 해볼까했는데

여기서 문제가 되는점이

Update문 안에서 StartCoroutine으로 예약을 하고있는데


이 녀석이 당장 실행이 되지 않고

ReserveSpawn함수 안의 내용이 3초 or 5초?후에 실행이 될 수도 있다는 이야기인데

그때까지


_monsterCount는 증가되지 않은 상태이다.

왜냐하면 _monsterCount는


Spawn하는 부분에서 늘려줄 테니까

여기

_monsterCount가 늘어나지 않는다면

이 Update문을 계속계속 실행을 하는 상태가 될 것이다.

그래서 지금은 굉장히 위험한 코드이고

그렇다는 것은

그렇다는 것은 ReserveSpawn을 몇개를 예약을 했는지도 코드로 관리를 해주어야 하니까


대충 이쯤에다가 _reserveCount를 넣어주도록 하겠다.

그래서 실제로 내가 예약을 한 숫자

_monsterCount가

여기 _keepMonsterCount보다 작을때까지만 돌게 해주고

ReserveSpawn()함수의 경우에는 들어오자마자 ++를 해주어 가지고
하나를 예약을 한 다음에


이렇게 하고

그 다음에

이 코드들을 실행을 해주고

이 코드가 실행이 되었으면은


그다음에서 이렇게 _reserveCount를 하나 감소를 시키면 될거 같다라는 생각이 든다.


근데 이까지 먼말인지 모르겠다


조금 자다가 일어나서 보니까 조금 이해가 되는데


이까지 완료를 했다면은

얼추 완성이 된거같다.

그래서 이부분을 이제 GameScene에서 만들어 주도록 하자.

그래서 GameScene에서

이전처럼 이렇게 만들어 주는 것이 아니라


주석처리를 해주고

go라는 GameObject를 만드는데 name = "SpawningPool"로 만들어 주고

사실은 Addcomponent로 해도 상관은 없는데

일단 GetOrAddComponent를 해주어서 SpawingPool을 넣어 주도록하자.

그다음에


이렇게 추가를 하자마자


SetKeepMonsterCount를 해주면

인자로 넣어준 숫자만큼 생성이 될 때까지

SpawningPool을 돌려주게 될 것이다.


그다음에 이제 유니티로 돌아가서 실행을 해보면

이제

잘 된다.

그리고 한마리를 죽이고나서 조금 기다리면 한마리가 또 다시 생성이 되는 것을 볼 수 있다.

그런데 rayCasting하는 부분에서 오류가 조금있는데

예를들어 몬스터들 사이에 있을때 몬스터를 누르면

먼가 위로 올라가려고하는데

이부분은 y값을 0으로 해주지 않고 클릭한 위치로 dir를 레이를 쏘고 움직이게 해서

그런부분인거 같다.

PlayerController에서 OnMouseEvent_IdleRun

함수에서 switch문안에서

hit.point한 부분에서 _destPos를 설정을 해주고 있었다.


그리고 위로 올려서

_destPos안에서 뭔가를 이동을 하려고 하고있었다.

그래서 이동을하는 dir부분에 dir.y = 0으로 날려주어야만 정상적으로 작동을 할 것이다.


이렇게.

그런데 y좌표가 차이가 난다면 비빌때 뭔가 위로 올라가려고하는 그런 부분이 생길 것이다.

그리고 플레이어가 죽고나서는 유니티의 문제가 아니라

컨텐츠 적인 부분의 문제이니까

여기서 마무리를 짓도록 하겠다.

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

0개의 댓글