본문 바로가기

Unity/로봇 체스 개발 일지

[Unity] 몬스터, 아이템 스포너

요약

아이템과 몬스터를 소환하기 위한 스포너를 만들기 위한 알고리즘을 작성하였다. 아이템 스포너는 맵 전체에 랜덤으로 소환하므로 간단하였다. 몬스터는 소환할 때 맵 끝 쪽에 소환 구역을 지정하여 소환하는데 소환할 때 로봇과 겹치지 않기 위해 중복되지 않은 랜덤을 사용하였다.


아이템 스포너

공중 지원 아이템은 단순히 맵 전체에 랜덤으로 소환하는 방식이기 때문에 조건이 까다롭지 않고 코드가 단순하다.

ItamSpawner.cs

public class ItamSpawner : MonoBehaviour
{
    bool start = false;
    int turnCount = 7;
    private void Update()
    {
        if(Instance.GameTurnCount % turnCount == 1)
        {
            start = true;
        }
        if (start && Instance.playerTurn && Instance.GameTurnCount % turnCount == 0)
        {
            SpawnItem(1);
            start = false;
        }
    }

스폰 개수와 소환 턴 수를 결정하는 알고리즘이 작성되었다.

 

    private void SpawnItem(int count)
    {
        for (int i = 0; i < count;)
        {
            Vector2Int ItemPosition = new Vector2Int(Random.Range(1, Instance.MapSizeX - 1), Random.Range(1, Instance.MapSizeY - 1));

            if (Instance.Map2D[ItemPosition.x, ItemPosition.y] != (int)MapObject.player)
            {
                if (Instance.Map2D[ItemPosition.x, ItemPosition.y] != (int)MapObject.moster)
                {
                    Instance.poolManager.SelectPool(PoolManager.Prefabs.RangedAttackObject).Get().transform.position = new Vector3(ItemPosition.x, ItemPosition.y, 0);
                    i++;
                }
            }
        }
    }
}

아이템을 소환하는 조건은 겹치지 않도록 설정되어 있다.


몬스터 스포너

몬스터를 소환하기 위해 스폰 포인트를 맵 끝쪽에 지정하도록 포인트를 만들었다.

이러한 스폰 포인트를 활용하여 코드를 작성하였다.

SummonMonsterStage.cs

public class SummoneMonsterStage
{
    public SummoneMonsterStage(int _Number, Prefabs _MonsterPrefabs) { Number = _Number; MonsterPrefabs = _MonsterPrefabs; }
    public int Number;
    public PoolManager.Prefabs MonsterPrefabs;
}

스폰할 몬스터의 수와 몬스터 종류를 저장하는 클래스를 만들었다.

 

MonsterSpawner.cs

public class MonsterSpawner : MonoBehaviour
{
    public Vector3Int startPosition;

    private Vector2Int size;
    private List<MyObject> points = new List<MyObject>();
    private List<List<SummoneMonsterStage>> summoneMonsterStage = new List<List<SummoneMonsterStage>>()
    {
        new List<SummoneMonsterStage> { new SummoneMonsterStage(3, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(2, Prefabs.RobotPistol), new SummoneMonsterStage(3, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(1, Prefabs.RobotSniper), new SummoneMonsterStage(2, Prefabs.RobotPistol), new SummoneMonsterStage(2, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(1, Prefabs.RobotSniper), new SummoneMonsterStage(2, Prefabs.RobotPistol), new SummoneMonsterStage(4, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(1, Prefabs.RobotSniper), new SummoneMonsterStage(4, Prefabs.RobotPistol), new SummoneMonsterStage(4, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(2, Prefabs.RobotSniper), new SummoneMonsterStage(4, Prefabs.RobotPistol), new SummoneMonsterStage(5, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(3, Prefabs.RobotSniper), new SummoneMonsterStage(5, Prefabs.RobotPistol), new SummoneMonsterStage(6, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(5, Prefabs.RobotSniper), new SummoneMonsterStage(5, Prefabs.RobotPistol), new SummoneMonsterStage(8, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(5, Prefabs.RobotSniper), new SummoneMonsterStage(7, Prefabs.RobotPistol), new SummoneMonsterStage(10, Prefabs.RobotKnife) },
        new List<SummoneMonsterStage> { new SummoneMonsterStage(7, Prefabs.RobotSniper), new SummoneMonsterStage(7, Prefabs.RobotPistol), new SummoneMonsterStage(12, Prefabs.RobotKnife) }
    };
	... 생략 ...

SummoneMonsterStage 클래스를 이용하여 단순하게 몬스터 리스트를 만들어 소환하도록 하였다.

 

private void Update()
{
    ... 생략 ...
    if (start && Instance.playerTurn)
    {
        if (Instance.GameTurnCount % 5 == 1)
        {
            int summone = 0;
            int monsterindex = 0;
            int availableCount = AvailableCount();
            int monstersSummonCount = MonstersSummonCount(Instance.StageCount);
            int count = randomNum.Count;
            if (availableCount < monstersSummonCount)
            {
                count = points.Count;
            }

AvailableChecks(): 소환할 수 있는 공간 확인
MonstersSummonCount: 스테이지에 소환할 총 몬스터 수 확인
소환 구역 수와 스테이지의 몬스터 수를 측정하여 몬스터를 소환할 수를 조정한다.

 

            for (int index = 0; index < count; index++)
            {
                if (summone >= summoneMonsterStage[Instance.StageCount][monsterindex].Number)
                {
                    summone = 0;
                    monsterindex++;
                }
                if (availableCount < monstersSummonCount)
                {
                    if (available[index])
                    {
                        SummoningMonsters(index, summoneMonsterStage[Instance.StageCount][monsterindex].MonsterPrefabs);
                    }
                }
                else
                {
                    SummoningMonsters(randomNum[index], summoneMonsterStage[Instance.StageCount][monsterindex].MonsterPrefabs);
                }
                summone++;
            }
            AvailableChecks();
            NonDuplicatedRandom(MonstersSummonCount(Instance.StageCount + 1));
            PredictingMonsterSpawnAreas();
        }
        AvailableChecks();
        PredictingMonsterSpawnAreas();
        start = false;
    }
}

AvailableChecks(): 소환할 수 있는 공간 확인
SummoningMonsters(): 몬스터 소환
NonDuplicatedRandom(): 중복되지 않는 랜덤
PredictingMonsterSpawnAreas(): 몬스터가 실제로 소환되는 공간 예측
해당 코드 역시 이전 코드와 같이 측정된 값을 기준으로 몬스터를 소환하고 위치를 예측한다.


중복되지 않는 랜덤

중복되지 않는 랜덤이 필요한 이유는 소환할 구역을 정하기 위해 랜덤으로 구역을 선택하다 보면 중복되어 소환이 적게 되거나 이중으로 소환되기므로 이를 방지하기 위해 중복되지 않는 랜덤이 필요하다.

MonsterSpawner.cs

public class MonsterSpawner : MonoBehaviour
{
    ... 생략 ...
    bool[] available;
    List<int> randomNum = new List<int>();
    ... 생략 ...
    private void NonDuplicatedRandom(int count)
    {
        randomNum.Clear();
        int availableCount = AvailableCount();
        int monstersSummonCount = MonstersSummonCount(Instance.StageCount + 1);
        if (availableCount > monstersSummonCount)
        {
            // 사용할수 있는 인덱스 값을 저장할 리스트
            List<int> temp = new List<int>();
            int index = 0;
            for (index = 0; index < available.Length; index++)
            {
                // 해당 공간을 사용할 수 있으면
                if (available[index])
                {
                    // 사용할수 있는 인덱스 값에 저장
                    temp.Add(index);
                }
            }

            for (index = 0; index < count; index++)
            {
                int num = Random.Range(0, temp.Count);
                // 뽑은 랜덤 값을 통해 available 인덱스 값을 랜덤 번호로 사용하고 
                // temp를 리스트에서 제거함으로 중복을 제거함
                randomNum.Add(temp[num]);
                temp.RemoveAt(num);
            }
        }
        else
        {
            print("랜덤 값을 가지기에는 소환할 공간이 부족하여 의미가 없다.");
        }
    }
    ... 생략 ...
}

코드를 보면 임의의 리스트를 만들고 그 안에 사용할 수 있는 공간을 넣어둔다. 그 후 리스트 안에 숫자를 랜덤으로 선택하고 선택된 인덱스는 임의의 리스트에 제거하여 중복되지 않게 하였다.