BoardManager.cs를 분석해보겠습니다. 코드를 따라가며 의식의 흐름기법으로 작성되었습니다. 불완전한/틀린 내용은 지적해주시기 바랍니다.
[Serializable] public class Count { public int minimum; public int maximum; public Count (int min, int max) { minimum = min; maximum = max; } }
Count라는 클래스를 선언합니다. 나중에 랜덤으로 생성될 각종 지형들의 최소값과 최대값이 어느정도일거라고 준비해 저장해 놓는 그릇입니다.
public Count wallCount = new Count(5,9); // 안녕하세요 신입입니다. // new // 제가 관리할 물건들의 최소개수는 5, 최대개수는 9가 될 것입니다! // new Count(5,9); // 오호라 그렇게 하여라 너의 이름은 wallcount(벽의 개수를 정하는 자)이니라. // Count wallCount; // 나중에 짐이 최소값과 최대값을 바꾸라하면 그리 하여야 할것이니라 // public
한편 Count 클래스 안에는 클래스와 이름이 같은 두개의 int인자를 가진 매서드 Count가 존재합니다.
이렇게 클래스 이름과 같은 매서드를 생성자(Constructor, ctor)이라고 합니다. 생성자는 클래스가 생성될때 호출되며 클래스가 알고있어야 할것들의 값을 초기화 시켜줍니다. 매서드를 호출할때와 모양은 거의 같습니다.
가장 친숙한 것으로 다음과 같은것이 있지요.
new Vector3 (1.5, 0f, 0f); //Vector3를 간단하게 다시 클래스로 만들어보면 다음의 모양일지도 모릅니다. public class Vector3 { float x,y,z; float normalized, magnitude; static back = new Vector3 (0, 0, -1); ... public Vector3(float xInput, float yInput, float zInput){ x = xInput; y = yInput; z = zInput; normailized = //calculate vector normalize. magnitude = //calculate magnitude } ... }
Unity Scripting API를 보고 한번 상상해 보았습니다. 이참에 공식홈페이지의 Vector3항목을 읽어보는 것도 좋을 것 같습니다. Vector3에는 단순히 x,y,z변수만 있는것이 아닙니다. 사실 벡터는 x,y,z를 이용해 방향성을 정한다고 합니다. Vector3클래스 안에는 x,y,z를 각 축으로 1만큼씩만 이동할 수 있게 만든 상수도 존재하고, 정규화된 변수, 벡터의 길이도 저장된다고 합니다. 추후 벡터만을 따로 다뤄보도록 하겠습니다.
public int columns = 8; //Number of columns in our game board. public int rows = 8; //Number of rows in our game board. public Count wallCount = new Count (5, 9); //Lower and upper limit for our random number of walls per level. public Count foodCount = new Count (1, 5); //Lower and upper limit for our random number of food items per level. public GameObject exit; //Prefab to spawn for exit. public GameObject[] floorTiles; //Array of floor prefabs. public GameObject[] wallTiles; //Array of wall prefabs. public GameObject[] foodTiles; //Array of food prefabs. public GameObject[] enemyTiles; //Array of enemy prefabs. public GameObject[] outerWallTiles; //Array of outer tile prefabs.
Count 클래스를 이용해 벽의 개수를 정하는 객체와, 음식의 개수를 정하는 객체를 만들었습니다.(사실 실제로 벽과 음식의 개수를 정하는 것은 저 범위내에서 랜덤으로 골라내 다른 정수변수에 넣어서 결정하게 됩니다.) 여러가지 종류로 만들어진 타일오브젝트들의 배열을 선언해주고나면 이제 무서운 녀석들이 등장합니다.
private Transform boardHolder; //A variable to store a reference to the transform of our Board object. private List <Vector3> gridPositions = new List <Vector3> (); //A list of possible locations to place tiles.
Transform클래스와 List클래스를 이용해서 객체를 만들었네요. 그런데 List클래스 옆에는 이상한게 써있습니다. 문법적으로 저것이 가능한 것인지 의심이 되지만 빨간줄이 뜨지 않기에 일단은 안심하지요. 리스트인데 Vector3에 관한 리스트일 것이라 추측해보았습니다.
먼저 Transform 클래스부터 살펴보겠습니다. 뿌리찾기부터 시작하겠습니다. Transform은 Component의 자식이고, Component는 Object의 자식입니다.
Object : 모든 오브젝트의 가장 기본적인 클래스입니다. 유니티가 오브젝트에 접근이 가능하도록 해줍니다. 쉽게말해서 유니티가 인스펙터에서 public으로 선언된 오브젝트의 각종 값들을 조작할 수 있게 해주는 역할입니다.
이 클래스가 알고 있는 것은 자신의 이름과 존재유무입니다. 자신의 이름과 존재에 대해 고민하는 녀석이죠. 자신이 무엇을 하는지에 대해 고민하지 않습니다. 내가 존재하는것인가(bool)? 다른 오브젝트들이 자신과 같은지 다른지(!=. ==)를 판단합니다. 이름을 물어보면 알려주기도 합니다(ToString) 심지어 자신의 주민등록번호도 알려줍니다(getInstanceID). 분신술도 씁니다.(Instantiate) 무림(Scene)에 존재하지 않고 산속에 숨어있다가(prefab의 형태로 폴더내에 숨어있다가) 분신을 만들어내서 무림에 투입하기도 하죠. 명령을 내리면 스스로 죽기도 합니다(Destroy). 몇초후에 죽을지도 결정할수 있고. 로딩중엔 죽이지말라고 싹싹빌기도 하죠(DontDestroyOnLoad)
그런데 우리가 스크립트를 작성할떄 자주 보던것은 Object가 아니고 GameObject입니다. 이녀석은 정확히 뭘까요?
GameObject : Object의 자식입니다. 실제 씬뷰에 존재하는 녀석들이기도 하지요. 마치 그리스로마신화에서 신과 반신같은 느낌입니다. 아버지가 자신의 존재에 대해 고찰하던거와는 달리 실제 무엇을 할지 자아성취를 위한 노력을 시작합니다. 일단 아는게 많습니다. 자신이 필드에서 뛰고있나 벤치에서 쉬고있는지, 자신을 어떤모습으로 가꿀지, 물리적으로 어떻게 움직이는지, 어떻게 변형되는지에 대해 알고 있습니다. 자기 밑으로 새끼를 치기도 하죠(AddComponent)
Component : GameObject에 추가하여 게임오브젝트가 다양하게 활용될 수 있도록 해줍니다. 쉽게 말해 Inspector에서 볼 수 있는 것들입니다. transform. collision, rigidbody, camera, GUILayer등이 모두 Component입니다.
Transform : 가장 기본적인 Component입니다. 유니티의 모든 게임오브젝트는 이녀석 없이는 존재하지 못합니다. Position,Rotation,Scale에 관한 정보를 저장할 뿐만 아니라, 가장 중요한 것은 Hierarchy Panel에서 게임오브젝트들의 부모자식관계를 만드는 역할을 합니다. 이 예제에서는 랜덤으로 생성되는 바닥과 외벽 타일들을 하나의 폴더에 넣어두는 것 처럼 만들어 하이라키패널을 깔끔하기 정리하기 위해 사용됩니다.
여기서 질문이 하나 생깁니다. 게임오브젝트의 부모자식관계를 만드려면 그냥 GameObject를 쓰면 되지 뭐하러 Transform을 사용하는지에 대해 궁금합니다. 이와 관련된 영어 포럼 글입니다.
http://answers.unity3d.com/questions/627091/transform-vs-gameobject.html
GameObject는 반드시 transform을 포함하여야 하고 transform보다는 많은양의 정보를 가지고 있습니다. 하지만 위치정보만을 가지고 자식들을 가지기 위해서 굳이 GameObject를 사용할 필요는 없다는 것 같습니다. Transform이 가진 정보만으로도 부모자식관계를 만들 수 있습니다. 어찌되었던간에 Prefab을 특정위치에 자식으로 Instantiate하려면 스크립트 내에서 그 참조값이 필요합니다.
void BoardSetup () { boardHolder = new GameObject ("Board").transform; for(int x = -1; x < columns + 1; x++) { for(int y = -1; y < rows + 1; y++) { GameObject toInstantiate = floorTiles[Random.Range (0,floorTiles.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; instance.transform.SetParent (boardHolder); } } }
Transform borderHolder를 실제 사용한 코드를 보겠습니다.
boardHolder = new GameObject ("Board").transform;
먼저 GameObject클래스를 이용해서 객체를 하나 생성합니다. GameObject의 생성자는 이름을 필요로 합니다. boardHolder는 Board의 transform component를 참조합니다. 따라서 boardHolder를 통해 Board의 transform component에 접근해서 Board의 자식관계를 만들거나 할 수 있습니다. 그 밑으로는 for문을 이용해서 외벽과 바닥을 생성해주고 이를 boardHolder의 자식으로 만들어줍니다.
다시 필드로 돌아가겠습니다
private List <Vector3> gridPositions = new List <Vector3> (); //A list of possible locations to place tiles.
List는 컬렉션의 일종입니다. 컬렉션은 배열을 사용하면서 아쉬운점을 보완한 유동적인 배열입니다. 배열은 크기가 고정되어있어서 정해지지 않은 크기의 배열을 다뤄야 할 때는 조금 불편합니다. 하지만 컬렉션은 크기가 자유자재로 변하는 배열이라고 일단 생각해봅니다.
List는 System.Collections.Generic namespace에 정의되어 있기때문에 코드 상단에 다음과같이 선언해줍니다.
using System.Collections.Generic;
List뒤의 <>는 어떤 형태의 리스트를 사용할것인지에 알려주는 부분입니다. 예제의 gridPositions는 Vector3 객체들을 참조하는 명단이라고 할 수 있겠습니다. 이 리스트는 타일이 들어가야 하는 좌표 리스트입니다. 한 좌표에 타일을 깔면 해당 항목을 하나씩 지워나가는 식으로 사용합니다.(to-do list처럼 말이죠.)
리스트를 사용하는 법은 간단합니다.
Add, Remove, Insert(index,item), Contains, Clear 등을 사용해주면 됩니다. 다음의 예제코드와 하단에 링크된 페이지를 참고하시기 바랍니다.http://csharp.net-informations.com/collection/list.htm
void InitialiseList () { gridPositions.Clear (); for(int x = 1; x < columns-1; x++) { for(int y = 1; y < rows-1; y++) { gridPositions.Add (new Vector3(x, y, 0f)); }
리스트를 사용하기 위해 뭐가 써있었는지는 모르겠지만 Clear를 이용해 모조리 지워줍니다. 그 후 for문을 사용하여 (1,1,0),(1,2,0)...(6,6,0)까지 리스트에 기록해 주었습니다.