이번엔 몬스터를 게임이 실행되기 전에 미리 배치하지 않고 게임이 실행되고 나서 자동으로 생성되게 해 볼 것이다.
먼저 몬스터를 스폰하는 스크립트를 작성해준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class SpawningPool : MonoBehaviour
{
[SerializeField]
int _monsterCount = 0;
int _reserveCount = 0; // 코루틴을 생성할 때 현재 예약된 코루틴이 몇개인지 판단하기 위해
[SerializeField]
int _keepMonsterCount = 0;
[SerializeField]
Vector3 _spawnPos;
[SerializeField]
float _spawnRadius = 15.0f;
[SerializeField]
float _spawnTime = 5.0f;
public void AddMonsterCount(int value) { _monsterCount += value; }
public void SetKeepMonsterCount(int count) { _keepMonsterCount = count; }
void Start()
{
Managers.Game.OnSpawnEvent -= AddMonsterCount;
Managers.Game.OnSpawnEvent += AddMonsterCount;
}
void Update()
{
while (_reserveCount + _monsterCount < _keepMonsterCount)
// 현재 몬스터의 수와 예약된 코루틴의 수가 _keepMonsterCount보다 적다면 코루틴 실행
{
StartCoroutine("ReserveSpawn");
}
}
IEnumerator ReserveSpawn()
{
_reserveCount++;
yield return new WaitForSeconds(Random.Range(0, _spawnTime));
GameObject obj = Managers.Game.Spawn(Define.WorldObject.Monster, "DogPolyart");
// _monsterCount를 이 함수에서 늘리지 않아도 GameManagerEx에서 Spawn함수가 실행될 때 Invoke로 _monsterCount를 늘려준다.
NavMeshAgent nma = obj.GetOrAddComponent<NavMeshAgent>();
Vector3 randPos;
while (true)
{
Vector3 randDir = Random.insideUnitSphere * Random.Range(0, _spawnRadius);
randDir.y = 0;
randPos = _spawnPos + randDir;
// 랜덤한 방향벡터를 생성한다.
// 몬스터는 평면에 위치해야 하기 때문에 생성된 랜덤벡터의 y좌표를 0으로 만들어준다.
NavMeshPath path = new NavMeshPath();
if (nma.CalculatePath(randPos, path))
break;
// 생성된 랜덤벡터에 갈 수 있나 없나 계산
}
obj.transform.position = randPos;
_reserveCount--;
}
}
코루틴을 사용해 랜덤 생성한 벡터에 적을 생성하도록 하였다.
필드 내에 총 몇마리의 적이 있어야 되는지 정하고, 그 정한 수보다 현재 몬스터가 적다면 몬스터를 생성한다. 생성되는 주기는 0~5초 사이의 랜덤한 값이 된다.
현재 몬스터 수의 계산은 GameManagerEx 클래스의 이벤트를 받아서 계산한다.
public class GameManagerEx
{
...
public Action<int> OnSpawnEvent;
...
public GameObject Spawn(Define.WorldObject type, string path, Transform parent = null)
{
GameObject go = Managers.Resource.Instantiate(path, parent);
switch (type)
{
case Define.WorldObject.Monster:
_monsters.Add(go);
if (OnSpawnEvent != null)
OnSpawnEvent.Invoke(1);
break;
...
}
...
public void Despawn(GameObject go)
{
Define.WorldObject type = GetWorldObjectType(go);
switch (type)
{
case Define.WorldObject.Monster:
{
if (_monsters.Contains(go))
{
if (OnSpawnEvent != null)
OnSpawnEvent.Invoke(-1);
_monsters.Remove(go);
}
}
...
}
GameManagerEx의 Spawn()함수가 실행될 때 몬스터가 스폰된다는 이벤트를 Invoke하며 인자로 1의 값을 보낸다. (몬스터가 1마리 늘었다는 뜻)
Despawn() 함수가 실행될 때는 Invoke하며 인자로 -1의 값을 보낸다.
public class GameScene : BaseScene
{
protected override void Init()
{
...
GameObject go = new GameObject { name = "SpawningPool" };
SpawningPool pool = go.GetOrAddComponent<SpawningPool>();
pool.SetKeepMonsterCount(5);
}
}
후에 GameScene 클래스에서 코드를 추가해준다.
이후 게임을 실행해보니 원하는대로 작동하는것을 확인했다.
하지만 몬스터 생성 관련 버그는 아니지만 버그를 하나 발견했는데, 몬스터를 클릭할 때 가끔식 몬스터 위를 걸어가는 버그가 발생한다. 이는 플레이어의 좌표를 변경할 때 y좌표를 0으로 처리하지 않아서 생기는 문제이다.
Vector3 dir = _destPos - transform.position;
dir.y = 0; // 추가
PlayerController 클래스의 UpdateMoving()함수를 이런식으로 수정하니 해결되었다.