미니 RPG
몬스터 자동 리스폰
서버 측에서 한다. 몬스터가 죽으면 몬스터의 전체적인 숫자를 맞춰주기 위해 랜덤한 위치에서 스폰을 한다. 이렇게 서버측에서 스폰을 한 후 클라이언트에게 알려준다.
📜 GameManager
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;
case Define.WorldObject.Player:
_player = go;
break;
}
return go;
}
public void Despawn(GameObject go)
{
Define.WorldObject type = GetWorldObjectType(go);
switch(type)
{
case Define.WorldObject.Monster:
{
if (_monsters.Contains(go))
{
_monsters.Remove(go);
if (OnSpawnEvent != null)
OnSpawnEvent.Invoke(-1);
}
}
break;
case Define.WorldObject.Player:
{
if (_player == go)
_player = null;
}
break;
}
Managers.Resource.Destroy(go);
}
if (OnSpawnEvent != null)
OnSpawnEvent.Invoke(1);
//...
if (OnSpawnEvent != null)
OnSpawnEvent.Invoke(-1);
OnSpawnEvent
이 액션에 어떤 함수들이 등록되어 있을진 모르겟지만! 몬스터를 스폰할 때, 디스폰할 때 이 액션을 실행한다. 이 액션에는 파라미터만큼 몬스터를 증가 및 감소시키는 함수가 등록될 것이다.
스폰시 👉 몬스터 1마리 증가 OnSpawnEvent.Invoke(1);
디스폰시 👉 몬스터 1마리 감소 OnSpawnEvent.Invoke(-1);
📜 SpawningPool
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class SpawningPool : MonoBehaviour
{
[SerializeField] int _monsterCount = 0; // 현재 몬스터 수
[SerializeField] int _keepMonsterCount = 0; // 월드 상에 유지되야 하는 몬스터 수
int _reserveCount = 0; // ReserveSpawn 코루틴 함수 당 곧 생성할 몬스터 수
[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)
{
StartCoroutine(ReserveSpawn());
}
}
IEnumerator ReserveSpawn()
{
_reserveCount++;
yield return new WaitForSeconds(Random.Range(0, _spawnTime));
GameObject obj = Managers.Game.Spawn(Define.WorldObject.Monster, "Knight");
Vector3 randPos;
NavMeshAgent nma = obj.GetOrAddComponent<NavMeshAgent>();
while(true)
{
Vector3 randDir = Random.insideUnitSphere * Random.Range(0, _spawnRadius);
randDir.y = 0;
randPos = _spawnPos + randDir;
NavMeshPath path = new NavMeshPath();
if (nma.CalculatePath(randPos, path))
break;
}
obj.transform.position = randPos;
_reserveCount--;
}
}
AddMonsterCount : 몬스터를 파라미터만큼 누적 증가시키기 위해 _monsterCount 업뎃
게임 시작시 📜GameManager의 OnSpawnEvent에 등록
SetKeepMonsterCount : 몬스터를 파라미터만큼 증가시키기 위해 _keepMonsterCount 업뎃
Update
monsterCount가 keepMonsterCount에 다다를 떄까지 몬스터 생성
reserveCount가 쓰이는 이유는 아래에 서술한다.
ReserveSpawn
이 코루틴 함수는 한 마리의 몬스터 스폰을 위한 함수다. 랜덤으로 0 ~ _spawnTime 시간 내의 1️⃣ 랜덤한 시간을 “대기”한 후에 2️⃣ 몬스터를 생성하고 3️⃣ 스폰된 몬스터의 위치를 랜덤하게 세팅하는 함수다.
1. _reserveCount 1 증가
1 - 1. _reserveCount을 증가시키는 이유
- 이 코루틴이 yield return을 한 후 WaitForSeconds로 인하여 대기하는 시간 동안 코루틴의 호출점이였던 Update의 while문이 마저 실행된다. (코루틴은 대기 시간동안 코루틴 호출점을 마저 실행한다. 그리고 대기 시간이 다 끝나면 다시 코루틴 함수 내부로 돌아온다.)
- _monsterCount는 대기시간을 가진 이후에 📜GameManager의 Spawn 함수의 OnSpawnEvent.Invoke을 통해 세팅된다. 따라서 📜SpawningPool의 Update의 while 문에서는 아직 이 함수가 정상적으로 설정되지 않았기 때문에 무한루프가 돌게 된다.
- 따라서 대기시간을 가지는 동안 코루틴을 나와 while문을 다시 실행할 때는 _monsterCount가 아직 1 증가가 되지 않은 상태이기 때문에 while문이 무한 루프로 돌게 된다. _monsterCount < _keepMonsterCount 이 조건이 만족하니까 ReserveSpawn 을 또 호출 하고 또 호출하고.. 대기 시간동안 ReserveSpawn 을 엄청 많이 호출하게 될 것이다.
- 따라서 아직 증가되기 전인 _monsterCount을 1 증가 후로 반영하여 while문 조건에 넣기 위해 _reserveCount를 1로 세팅하고 while문 조건을 while (_reserveCount + _monsterCount < _keepMonsterCount)로 해주는 것이다!
- 이러면 증가되야 하는 수만큼만 ReserveSpawn 코루틴 함수가 호출이 될 수 있다.
2. 랜덤한 대기 시간 가지기
3. 몬스터 스폰 📜GameManager의 Spawn 호출
4. 스폰한 몬스터를 랜덤한 위치로 설정 (= 랜덤한 위치에 스폰)
4 - 1. 랜덤으로 뽑은 위치가 갈 수 있는 곳으로 나올 때까지 랜덤 뽑기
4 - 2. 랜덤 위치 뽑기 (Random.insideUnitSphere)
- (0, 0, 0) 위치를 기준으로 반지름 1 의 반경 원에서 랜덤한 Vector3 값을 리턴한다.
- 2D 게임이므로 랜덤 y값은 반영 X (이렇게 안해주면 하늘이나 땅에서 스폰될 수도 있다.)
4 - 3. 뽑은 위치가 갈 수 있는 곳인지
- nma.CalculatePath(randPos, path) 가 True 이면 랜덤뽑기 그만 하고 break.
5. 다시 _reserveCount 1 감소
📜 GameScene
public class GameScene : BaseScene
{
protected override void Init()
{
//...
//Managers.Game.Spawn(Define.WorldObject.Monster, "Knight");
GameObject go = new GameObject { name = "SpawningPool" };
SpawningPool pool = go.GetOrAddComponent<SpawningPool>();
pool.SetKeepMonsterCount(5);
}
“GameScene”씬이 시작되면 “SpawningPool” 객체를 생성하고 이에 📜SpawningPool 를 바로 붙인다. 그리고 몬스터는 5 마리를 유지하도록 SetKeepMonsterCount(5) 호출.
수직으로 이동하려는 버그
📜PlayerController
protected override void UpdateMoving()
{
// 몬스터가 내 사정거리보다 가까우면 공격
//...
// 이동
Vector3 dir = _destPos - transform.position;
dir.y = 0;
클릭 위치에 따라 y좌표도 반영될 수 있다. 목표 방향인 dir에 y까지 포함되면 플레이어가 조금 붕 뜰 수도 있기 때문에 dir.y은 반영하지 않도록 0 으로 세팅한다.
완강. 😎
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 4-1-1. 서버 OT 및 환경 설정 (1) | 2023.09.03 |
---|---|
Part 4. 게임 서버 (0) | 2023.09.03 |
Part 3-13-8. 미니 RPG : 레벨업 (0) | 2023.09.02 |
Part 3-13-7. 미니 RPG : Destory (0) | 2023.09.01 |
Part 3-13-6. 미니 RPG : 몬스터 AI (0) | 2023.09.01 |