Chapter 13-11. 미니 RPG : 몬스터 자동 리스폰

Date:     Updated:

카테고리:

태그:

인프런에 있는 Rookiss님의 [C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click

Chapter 13. 미니 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
    • monsterCountkeepMonsterCount에 다다를 떄까지 몬스터 생성
      • reserveCount가 쓰이는 이유는 아래에.
  • ReserveSpawn
    • 이 코루틴 함수는 한 마리의 몬스터 스폰을 위한 함수다. 랜덤으로 0 ~ _spawnTime 시간 내의 1️⃣ 랜덤한 시간을 “대기”한 후에 2️⃣ 몬스터를 생성하고 3️⃣ 스폰된 몬스터의 위치를 랜덤하게 세팅하는 함수다.
      • _reserveCount 1 증가
        • _reserveCount을 증가시키는 이유
          • 이 코루틴이 yield return을 한 후 WaitForSeconds로 인하여 대기하는 시간 동안 코루틴의 호출점이였던 Update의 while문이 마저 실행된다. (코루틴은 대기 시간동안 코루틴 호출점을 마저 실행한다. 그리고 대기 시간이 다 끝나면 다시 코루틴 함수 내부로 돌아온다.)
          • _monsterCount는 대기시간을 가진 이후에 📜GameManager의 Spawn 함수의 OnSpawnEvent.Invoke을 통해 세팅된다. 따라서 📜SpawningPool의 Updatewhile 문에서는 아직 이
          • 따라서 대기시간을 가지는 동안 코루틴을 나와 while문을 다시 실행할 때는 _monsterCount가 아직 1 증가가 되지 않은 상태이기 때문에 while문이 무한 루프로 돌게 된다. _monsterCount < _keepMonsterCount 이 조건이 만족하니까 ReserveSpawn 을 또 호출 하고 또 호출하고.. 대기 시간동안 ReserveSpawn 을 엄청 많이 호출하게 될 것이다.
          • 따라서 아직 증가되기 전인 _monsterCount을 1 증가 후로 반영하여 while문 조건에 넣기 위해 _reserveCount를 1로 세팅하고 while문 조건을 while (_reserveCount + _monsterCount < _keepMonsterCount)로 해주는 것이다!
          • 이러면 증가되야 하는 수만큼만 ReserveSpawn 코루틴 함수가 호출이 될 수 있다.
      • 랜덤한 대기 시간 가지기
      • 몬스터 스폰 📜GameManager의 Spawn 호출
      • 스폰한 몬스터를 랜덤한 위치로 설정 (= 랜덤한 위치에 스폰)
        • 랜덤으로 뽑은 위치가 갈 수 있는 곳으로 나올 때까지 랜덤 뽑기
          • 랜덤 위치 뽑기
            • Random.insideUnitSphere
              • (0, 0, 0) 위치를 기준으로 반지름 1 의 반경 원에서 랜덤한 Vector3 값을 리턴함
            • 2D 게임이므로 랜덤 y값은 반영 X (이렇게 안해주면 하늘이나 땅에서 스폰될 수도 있는 것이다. ㅠ)
          • 뽑은 위치가 갈 수 있는 곳인지
            • nma.CalculatePath(randPos, path) 가 True 이면 랜덤뽑기 그만 하고 break.
      • 다시 _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좌표도 반영될 수 있다. 목표 방향인 diry까지 포함되면 플레이어가 조금 붕 뜰 수도 있기 때문에 dir.y은 반영하지 않도록 0 으로 세팅한다.


❤ 완강

이 글을 보시는 분이 계시다면.. 인프런에서 이 강의를 꼭 들어보시기를 추천드리고 싶습니다. 단순히 유니티 엔진 사용법만을 익히는 것에 그치지 않고 프레임워크를 구축하는 코드를 짜기 때문에 더욱 더 명강의라고 생각합니다. 😭 강추..



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Unity Lesson 2 카테고리 내 다른 글 보러가기

댓글 남기기