전체 페이지뷰

2017년 9월 21일 목요일

Unity Tutorial: 2D Roguelike, part 2


Writing the Board Manager



이번 시간에는 플레이 시마다 다르게 각 레벨을 생성하는 board manager 스크립트를 작성할 것입니다.

Assets 폴더에 Scripts 폴더를 생성하고 그 안에 두 개의 빈 스크립트를 생성, 각각의 이름을 BoardManager, GameManager라고 정합니다.


그와 동시에 Hierarchy에 GameManger라는 빈 오브젝트를 하나 생성해 둡시다.

그리고, BoardManager 스크립트를 에디터로 열겠습니다.

보드매니저는 플레이어가 새로운 레벨을 시작할 때마다 현재의 레벨 넘버를 기반으로 레벨을 랜덤 생성할 것입니다.

네임스페이스 선언을 시작으로 스크립트를 작성합니다.

using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
cs

using System은  Serializable 어트리뷰트를 사용하여 인스펙터 상에 프로퍼티를 보여줄지 결정하기 위한 것이며, System.Collections.Generic은 리스트, Random은 유니티엔진의 랜덤 클래스를 Random이라는 이름으로 사용하기 위한 것입니다.

[Serializable]
public class Count
{
    public int minimum;
    public int maximum;
    public Count(int min, int max)
    {
        minimum = min;
        maximum = max;
    }
}
cs

Count라는 최소, 최대값의 정수형 필드를 가지는 퍼블릭 클래스를 하나 작성하고 계속해서 퍼블릭 변수들을 선언합니다.

public int columns = 8// 게임 보드의 행 수
public int rows = 8// 게임보드의 열 수
public Count wallCount = new Count(59); // 레벨 당 벽의 하한,상한값
public Count foodCount = new Count(15); // 레벨 당 음식 하한,상한값
public GameObject exit; // exit 프리팹 레퍼런스
//각 타일들에 대한 레퍼런스
public GameObject[] floorTiles; 
public GameObject[] wallTiles; 
public GameObject[] foodTiles;
public GameObject[] enemyTiles;
public GameObject[] outerWallTiles;
cs

private 변수들로 이어집니다.

private Transform boardHolder; // 보드의 tranform 레퍼런스
// 타일이 놓일 수 있는 장소의 리스트
private List<Vector3> gridPositions = new List<Vector3>();
cs

다음으로 우리의 보드게임에 사용될 리스트를 초기화할 메소드를 하나 작성합니다.

void InitialiseList()
{
    gridPositions.Clear();
    for(int x = 1; x < columns - 1; x++)
    {
        for(int y = 1; y < rows - 2; y++)
        {
            gridPositions.Add(new Vector3(x, y, 0f));
        }
    }
}
cs
이 리스트는 벽, 적, 음식이나 소다등이 놓일 수 있는 자리입니다.

0과 가장 바깥 줄을 빼놓은 이유는 그곳에는 혹시나 우연히 벽으로 둘러싸여 깰수 없는 판이 생길 것을 방지하기 위해서 입니다.

그림과 같이 가장 바깥줄을 제외시킨 상태에서 리스트를 작성합니다.

다음으로 보드의 외벽과 바닥을 설정할 메소드를 작성합니다.
// outerWall과 floor 세팅
void BoardSetup()
{
    // 새 Board 오브젝트를 인스턴스화하고 그 트랜스폼을 보드홀더에 저장
    boardHolder = new GameObject("Board").transform;
        
    for (int x = -1; x < columns + 1; x++)
    {
        for(int y = -1; y < rows + 1; y++)
        {
            // 바닥타일 8종 중 하나의 프리팹을 랜덤으로 고름
            GameObject toInstantiate = floorTiles[Random.Range(0, foodTiles.Length)];
            // 만약 위치가 테두리라면 외벽타일 중 골라 다시 저장
            if (x == -1 || x == columns || y == -1 || y == rows)
            {
                toInstantiate = outerWallTiles[Random.Range(0, outerWallTiles.Length)];
            }
            // 고른 프리팹을 현재 순회중인 위치에 인스턴스화하여 저장
            GameObject instance = Instantiate(
                toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;
            // 생성된 인스턴스의 트랜스폼을 boardHolder의 자식으로 둠
            instance.transform.SetParent(boardHolder);
        }
    }
}
cs

다음은 랜덤으로 배치할 적, 음식 등을 설정하는 메소드 차례입니다.

먼저 랜덤으로 위치를 고르는 메소드를 작성합니다.
// gridPosition에서 랜덤으로 하나 고를 수 있게 함
Vector3 RandomPosition()
{
    // gridPositon으 길이 내에서 랜덤으로 인덱스 형성
    int randomIndex = Random.Range(0,gridPositions.Count);
    // 랜덤으로 형성한 인덱스에 해당하는 gridPositons를 저장하는 변수선언
    Vector3 randomPosition = gridPositions[randomIndex];
    // randomIndex에 해당하는 gridPositions값 삭제하여 사용가능하게 함
    gridPositions.RemoveAt(randomIndex);
    return randomPosition;
}
cs

다음으로, 주어진 범위 내에서 정해준 타일을 랜덤으로 생성해주는 메소드입니다.
// 주어진 상/하한 값 내에서 랜덤으로 지정한 타일을 생성해줌
void LayoutObjectAtRandom(GameObject[] tileArray,int minimum, int maximum)
{
    // 랜덤으로 개수 선택
    int objectCount = Random.Range(minimum, maximum + 1);
    // 정한 개수만큼 오브젝트 생성
    for (int i = 0; i < objectCount; i++)
    {
        Vector3 randomPosition = RandomPosition();
        // 주어진 타일 종류 내에서 골라 저장
        GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
        // 고른 타일을 인스턴스화
        Instantiate(tileChoice, randomPosition, Quaternion.identity);
    }
}
cs

이제 실제적으로 게임매니저에서 호출하여 보드를 세팅할 수 있게 해주는 퍼블릭 메소드를 작성합니다.

public void SetupSecene(int level)
{
    //외벽과 바닥 세팅
    BoardSetup();
    // 리스트 초기화
    InitialiseList();
    // 내벽 세팅
    LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
    // 음식 세팅
    LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum);
    // 적의 수는 레벨에 따라 로그함수적으로 결정
    // 따라서 레벨 2에는 적 1, 4에는 적 2, 8에는 적 3
    int enemyCount = (int)Mathf.Log(level, 2f);
    LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
    // 최종적으로 exit를 우상단에 생성
    Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
}
cs


전체 BoardManager 스크립트는 아래와 소개하고 이 글을 마칩니다.


using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
 
public class BoardManager : MonoBehaviour
{
    [Serializable]
    public class Count
    {
        public int minimum;
        public int maximum;
 
        public Count(int min, int max)
        {
            minimum = min;
            maximum = max;
        }
    }
 
    public int columns = 8// 게임 보드의 행 수
    public int rows = 8// 게임보드의 열 수
    public Count wallCount = new Count(59); // 레벨 당 벽의 하한,상한값
    public Count foodCount = new Count(15); // 레벨 당 음식 하한,상한값
    public GameObject exit; // exit 프리팹 레퍼런스
    //각 타일들에 대한 레퍼런스
    public GameObject[] floorTiles; 
    public GameObject[] wallTiles; 
    public GameObject[] foodTiles;
    public GameObject[] enemyTiles;
    public GameObject[] outerWallTiles;
 
    private Transform boardHolder; // 보드의 tranform 레퍼런스
    // 타일이 놓일 수 있는 장소의 리스트
    private List<Vector3> gridPositions = new List<Vector3>();
 
    // gridPositions 초기화
    void InitialiseList()
    {
        gridPositions.Clear();
 
        for(int x = 1; x < columns - 1; x++)
        {
            for(int y = 1; y < rows - 2; y++)
            {
                gridPositions.Add(new Vector3(x, y, 0f));
            }
        }
    }
 
    // outerWall과 floor 세팅
    void BoardSetup()
    {
        // 새 Board 오브젝트를 인스턴스화하고 그 트랜스폼을 보드홀더에 저장
        boardHolder = new GameObject("Board").transform;
        
        for (int x = -1; x < columns + 1; x++)
        {
            for(int y = -1; y < rows + 1; y++)
            {
                // 바닥타일 8종 중 하나의 프리팹을 랜덤으로 고름
                GameObject toInstantiate = floorTiles[Random.Range(0, foodTiles.Length)];
 
                // 만약 위치가 테두리라면 외벽타일 중 골라 다시 저장
                if (x == -1 || x == columns || y == -1 || y == rows)
                {
                    toInstantiate = outerWallTiles[Random.Range(0, outerWallTiles.Length)];
                }
 
                // 고른 프리팹을 현재 순회중인 위치에 인스턴스화하여 저장
                GameObject instance = Instantiate(
                    toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;
 
                // 생성된 인스턴스의 트랜스폼을 boardHolder의 자식으로 둠
                instance.transform.SetParent(boardHolder);
            }
        }
    }
 
    // gridPosition에서 랜덤으로 하나 고를 수 있게 함
    Vector3 RandomPosition()
    {
        // gridPositon으 길이 내에서 랜덤으로 인덱스 형성
        int randomIndex = Random.Range(0,gridPositions.Count);
        // 랜덤으로 형성한 인덱스에 해당하는 gridPositons를 저장하는 변수선언
        Vector3 randomPosition = gridPositions[randomIndex];
        // randomIndex에 해당하는 gridPositions값 삭제하여 사용가능하게 함
        gridPositions.RemoveAt(randomIndex);
        return randomPosition;
    }
 
    // 주어진 상/하한 값 내에서 랜덤으로 지정한 타일을 생성해줌
    void LayoutObjectAtRandom(GameObject[] tileArray,int minimum, int maximum)
    {
        // 랜덤으로 개수 선택
        int objectCount = Random.Range(minimum, maximum + 1);
 
        // 정한 개수만큼 오브젝트 생성
        for (int i = 0; i < objectCount; i++)
        {
            Vector3 randomPosition = RandomPosition();
            // 주어진 타일 종류 내에서 골라 저장
            GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
 
            // 고른 타일을 인스턴스화
            Instantiate(tileChoice, randomPosition, Quaternion.identity);
        }
    }
    
    public void SetupSecene(int level)
    {
        //외벽과 바닥 세팅
        BoardSetup();
        // 리스트 초기화
        InitialiseList();
        // 내벽 세팅
        LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
        // 음식 세팅
        LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum);
        // 적의 수는 레벨에 따라 로그함수적으로 결정
        // 따라서 레벨 2에는 적 1, 4에는 적 2, 8에는 적 3
        int enemyCount = (int)Mathf.Log(level, 2f);
        LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
 
        // 최종적으로 exit를 우상단에 생성
        Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
    }
}
cs

댓글 없음:

댓글 쓰기