TKSPのUnity関連ブログ

Unityに関することで書きたいことを書く.

道路の自動生成

ちょっと描きたくなったのでメモ

実は第六回のゲームジャムに向けて裏でフライングスタートしていたのですが,お題が「スペース」だったので没になったものをせっかくなので公開しちゃいます.

道路を自動生成するというだけですが発展させれば街の自動生成ができちゃうかもですね.まさかお題がスペースになるとは思いもしなかったよ.

道路の自動生成について解説していきます.


f:id:ymtkyorosiku:20171123143129g:plain

© UTJ/UCL


一番最後に完成形のスクリプトを置いておくのでコピペするなりして照らし合わせてください.

もっと簡単な書き方,効率の良い書き方があると思いますが,学生が考えるコードなんで大目に見てくださいww

まず,こちらにいいアセットがあるのでダウンロードしてきます.

このアセットには道路がパーツごとに分かれて入っているのでprefabから連続で生成することにより繋がる道をランダムで自動生成することなどが可能になります.

 

アセットの中に入っている道路のうち今回使うのはこちらの3つになります.これを使って配列で道路を自動生成していきます.

 
f:id:ymtkyorosiku:20171123103259p:plain
 

 ではスクリプトをゴリゴリ書いていきます.

まず,考え方として

width,heightの箱を用意します.

そこに1~10の番号を割り振ります.

番号に応じた道路を生成します.

図で書くとこのような感じです.
f:id:ymtkyorosiku:20171123121327p:plain
まず変数の宣言から

        //roadのプレハブ
	public GameObject road;
	public GameObject road_intersection_L;
	public GameObject road_intersection_T;
	//床
	public GameObject plane;
	//道路を管理するためのクラス
	public class ArrayController
	{
		public int type = 0;
		public bool sub = false;
		//0:未決定,1:結合あり,2結合なし
		public int left = 0;
		public int right = 0;
		public int up = 0;
		public int down = 0;
	}
	//上記のクラスを配列で用意	
	public ArrayController[,] array;
	//自動生成する幅と高さ
	public int width;
	public int height;

ArrayControllerでは全ての配列の箱分用意してそこにあると仮定したオブジェクトの状態を管理します.
例えば次の図のように1がある場合はそこと結合する道路を必ず生成しなければなりません.
生成することが確定した場合subをtrueに,生成しない場合はfalseにしておけばあとで確認して別のオブジェクト(建物など)を置くことができます.
typeは先ほど解説した1~10の数値が入ります.

次に配列を定義して,左上,左下,右上,右下の角の道路を指定します.

void CreateCity ()
	{
		//配列
		array = new ArrayController[width, height];
		//床の大きさを幅,高さに応じて変更
		plane.transform.localScale = new Vector3 (width / 2, 1, height / 2);
		plane.transform.position = new Vector3 (width, 0.02f, height);
		plane.GetComponent<Renderer> ().material.color = Color.white;
		//配列の中身初期化
		for (int i = 1; i < width - 1; i++) {
			for (int j = 1; j < height - 1; j++) {
				array [i, j] = new ArrayController (){ type = 0, sub = false };
			}
		}
		//円周
		//左上の角は右と下をつなぐ曲線
		array [0, 0] = new ArrayController (){ type= 1 };
		//左下の角は右と上をつなぐ曲線
		array [0, height - 1] = new ArrayController (){type = 2 };
		//右上の角は左と下をつなぐ曲線
		array [width - 1, 0] = new ArrayController (){ type = 3 };
		//右下の角は左と上をつなぐ曲線
		array [width - 1, height - 1] = new ArrayController (){ type = 4 };


これで次の図のような道路が生成されたと仮定されました.
f:id:ymtkyorosiku:20171123121437p:plain

次に辺を埋めていきます.
分岐点がある場合,その方向に1進んだ道路は必ず結合しなければなりません.なので,それぞれの向きにあった変数に1を代入します.また,分岐点がないただの直線の場合は必ず結合してはならないのでそれぞれの向きにあった変数に2を代入します.

                //一時的な数値格納
		int temp;
		for (int j = 1; j < height - 1; j++) {
                        //左端から1右に進んだところで左に結合するなら1,しないなら2を代入
			if (Random.Range (0, 2) == 0) {
          //tempはtypeに代入される.typeは上の図のように道路の形を表している.
				temp = 5;
				array [1, j].left = 2;
			} else {
				temp = 9;
				array [1, j].left = 1;
			}
                        ////左端の定義
			array [0, j] = new ArrayController (){ type = temp, sub = true };
			//右端
			if (Random.Range (0, 2) == 0) {
				temp = 5;
				array [width - 2, j].right = 2;
			} else {
				temp = 7;
				array [width - 2, j].right = 1;
			}
			array [width - 1, j] = new ArrayController (){ type = temp, sub = true };
		}
		for (int i = 1; i < width - 1; i++) {
			//上端
			if (Random.Range (0, 2) == 0) {
				temp = 6;
				array [i, 1].up = 2;
			} else {
				temp = 10;
				array [i, 1].up = 1;
			}
			array [i, 0] = new ArrayController (){ type = temp, sub = true };
			//下端
			if (Random.Range (0, 2) == 0) {
				temp = 6;
				array [i, height - 2].down = 2;
			} else {
				temp = 8;
				array [i, height - 2].down = 1;
			}
			array [i, height - 1] = new ArrayController (){ type = temp, sub = true };
		}

これで道路で四角が作られます.
f:id:ymtkyorosiku:20171123121513p:plain

ここからは分岐の続くゴリ押しになります.左上から順に操作し,結合できる部分があればそこに結合するような道路を生成していきます.

f:id:ymtkyorosiku:20171123121533p:plain

重要なのは上が結合するのかしないのかと,左が結合するのかしないのかになります.最初の行(箱の配列)では右と下には何も定義していませんので.


ソース長いので割愛.中身は一番下に置いてあるスクリプトを参照してください.


最後に一番下の行(下端の辺)を一番下から1つ上の行を走査して,結合するような形に上書きします.

そしたら後は道路を生成するのみです.最初の図にある形に合うようにprefabを回転させながら生成していきます.

                //下の列を作る
		for (int i = 1; i < width - 1; i++) {
			if (array [i, height-1].up == 1 && array [i, height-2].sub) {
				temp = 8;
				array [i, height-2].down = 1;
			} else {
				temp = 6;
				array [i, height-2].down = 2;
			}
			array [i,height-1] = new ArrayController (){ type = temp, sub = true };
		}
                //作成
		GameObject tempobj;
		for (int j = 0; j < height; j++) {
			for (int i = 0; i < width; i++) {
				switch (array [i, j].type) {
				case 1://(下右)
					tempobj = Instantiate (road_intersection_L, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 180, 0)));
					tempobj.name = i + "," + j;
					break;
				case 2://(上右)
					tempobj = Instantiate (road_intersection_L, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 90, 0)));
					tempobj.name = i + "," + j;
					break;
				case 3://(下左)
					tempobj = Instantiate (road_intersection_L, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 270, 0)));
					tempobj.name = i + "," + j;
					break;
				case 4://(上左)
					tempobj = Instantiate (road_intersection_L, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.identity);
					tempobj.name = i + "," + j;
					break;
				case 5://(上下)
					tempobj = Instantiate (road, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.identity);
					tempobj.name = i + "," + j;
					break;
				case 6://(右左)
					tempobj = Instantiate (road, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 90, 0)));
					tempobj.name = i + "," + j;
					break;
				case 7://T(上左下)
					tempobj = Instantiate (road_intersection_T, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.identity);
					tempobj.name = i + "," + j;
					break;
				case 8://T(上左右)
					tempobj = Instantiate (road_intersection_T, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 90, 0)));
					tempobj.name = i + "," + j;
					break;
				case 9://T(右下上)
					tempobj = Instantiate (road_intersection_T, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 180, 0)));
					tempobj.name = i + "," + j;
					break;
				case 10://T(右左下)
					tempobj = Instantiate (road_intersection_T, new Vector3 (i * 2, 0, (height- j) * 2), Quaternion.Euler (new Vector3 (0, 270, 0)));
					tempobj.name = i + "," + j;
					break;
				case 11://ここから建物とか
					
					break;
				}
			}

後はシーンに空のオブジェクトとPlaneを作って,空のオブジェクトに下記のスクリプトをアタッチし,prefabに合う道路をAssetから,Planeに今作ったPlaneを指定し,width,heightを20くらいに設定すれば道路が自動生成されます.

これを使うとこのゲームのような感じでRボタンを押せば自動で道路が生成されます.

自動生成された道路に落ちてるものを1000枚集めるゲーム | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

こちらのゲームでは下記のアセットも使用しています.

  • BGM/SE



  • バイク

  • スカイボックス


  • 右下でプレイヤーを示すConeの生成