とあるプログラマの備忘録

都内某所に住むプログラマが自分用に備忘録を残すという趣旨のブログです。はてなダイアリーから移動しました!

【Unity 入門】DrawCallが多かった原因

とりあえず動くものを作る事を目標に今までつくってみましたが
他の方の記事をみていたらDrawCallなる物がFPSおよび負荷に多大な影響を与えているとのこと。

自分のつくった物を見てみたらなんと最大値160!これはちょっと多すぎる。

基準としてはスマホも対応させるなら40が程度に押さえるのが良いそうです。
で、なにかこんなにDrawCallを増やしているのかしらべてみました。
色々調べた結果遂に原因がわかりました。原因はライトです。


今回プレイヤーの周りだけを明るくする為に追尾カメラにPointLightを入れていたのですが、
このライトが壁のプレハブを照らすのが良くない様です。

ライトが照らす壁やオブジェクトが多いとDrawCallが多くなってしまうのでしょうか。
これは予想ですが、照らされている場所と暗い場所は別にCallしてるのかもしれません(どなたかわかる方教えてください。)

兎にも角にもこのライトが原因だったのでこのpointLightをやめて全体を照らすDirectional Lightにしてみました。

その結果最大値9!

そんなに違うんですか!そうですか!
もしかしたらUnityユーザーの方達の間では当たり前の事なのかもしれませんが、
劇的に値が違ったので備忘録として残しておきます。

DrawCallライトには気をつけろ。
肝に命じておく事にします。

【Unity 入門】ダンジョンゲーを作ってみたい 第一回「ダンジョン作ってみる」

私達アラサーとしてはやっぱりダンジョンという言葉に胸躍らされてしまいます。
ってことでトルネコウィザードリーの中間位の3Dダンジョンゲーを作ってみたいなと思います。

1.床となるフロアを作りたい
1-1.床を作る
何にせよ床が無いと始まらないので床を作ります。

Hierarcy > Create > Plain
で床を作ります。

名前をfloorとかにしておきましょう。

positionのyが-0.5になっているは1のスケールの壁を作ると半分が地下に埋まってしまうので床を少し下に下げているという事です。
これを0で作りたい場合は壁のy軸を+0.5してやると良いと思います。

1-2.床にテクスチャ貼る
Assets > Create > Materialで床にテクスチャ貼ります。
今回は「WebTreats_NaturalGrunge3」のテクスチャを貼りたいと思います
(もしかしてこのテクスチャUnity入門の本について来たテクスチャダッタかも知れないので無い場合は適当に床っぽい奴を貼ってください)
このマテリアルをHierarchyのさっき作った床オブジェクトにドラッグ&ドロップしておきましょう。

こんな床が出来上がりました

1-3.一応これをプレハブ化
今のところ使ってないですけどこれを一応プレハブ化しておきましょう。
Assets > Create > Prefabで空のプレハブを作って
Hierarchy > floorをドラッグ&ドロップしておきます。プレハブ化して置けば例えばマップサイズを変更したときなどにサイズを可変にできるので
便利です。

2.周りに壁を作る
プレイヤーが落ちてしまわない様に、周りは全て壁で埋めてあげたいと思います。
一個ずつオブジェクトを置いていては日が暮れるので、スクリプトで周りの壁を生成したいと思います。

2-1.とりあえず周りの壁の元になるプレハブを作る
スケールが1の壁プレハブを作成します(プレハブの作り方は何度もやっているので割愛)
こんなプレハブを作ります。名前は「DWall」とでもしておきます。

2-2.周りの壁を作るスクリプトを書く
今まではjsで書いていたんですが、どうやらUnityの主流となる言語の第一党はC#の様なのでこれからはC#で書いて行こうと思います。

using UnityEngine;
using System.Collections;

public class MapGenerator : MonoBehaviour {

	public GameObject wallPrefab;
	private int default_x_max = 19;
	private int default_z_max = 19;

	// Use this for initialization
	void Start () {
		//枠を作るメソッド呼び出し
		CreateMapWaku();
	}
	
	//枠を作るメソッド
	void CreateMapWaku()
	{
     //ループしながらz軸の上と下2列に枠を作ります
		for(int dx = 0; dx <= default_x_max; dx++){
			Instantiate(defaultWallPrefab, new Vector3(dx, 0, 0), Quaternion.identity);
			Instantiate(defaultWallPrefab, new Vector3(dx, 0, default_z_max), Quaternion.identity);
		}
		
     //同じくx軸に右と左に枠を作ります
		for(int dz = 0; dz <= default_z_max; dz++){
			Instantiate(defaultWallPrefab, new Vector3(0, 0, dz), Quaternion.identity);
			Instantiate(defaultWallPrefab, new Vector3(default_x_max, 0, dz), Quaternion.identity);
		}
		
	}

このスクリプトはMapGeneratorと名付けます

2-3.Hierarchyに適応
メニュー > GameObject > Create EmptyでHierarchyに空のゲームオブジェクトを作ります。
名前は「mapGenerator」とかにしておきましょう
さっき作ったスクリプトをこれにドラッグ&ドロップします。

InspectorのDefaultWallPrefabにさっきプレハブかしておいたDWallをセットします
(※まだWallPrefabはまだ出てきてないと思います。)

さて実行してみましょう
こんな感じで枠の壁ができあがりましたでしょうか?できたら成功です。

3.マップの中身を作る
3-1.中身のマップ用のプレハブを作る
名前は「Wall」としておきましょう

3-2.マップ生成スクリプトを書く
中身のマップを生成するスクリプトを作りたいとおもいます。
とは行ってもさっき作ったマップジェネレーターにマップ用のメソッドを追加するだけですしおすし。

using UnityEngine;
using System.Collections;

public class MapGenerator : MonoBehaviour {

    //こっちは中身のマップ用のプレハブ
	public GameObject wallPrefab;
    //こっちは枠様のプレハブ
	public GameObject defaultWallPrefab;
	private int default_x_max = 19;
	private int default_z_max = 19;

	// Use this for initialization
	void Start () {
		//これがマップの元になるデータ
		string map_matrix  = "022520000000000000:022220000000000000:022250000000000000:025251111111111110:022220000000000010:000100000000000010:000110000000000010:000010022500000010:000010032200000010:000010052200000010:000011152200000010:000000022200000010:000000000000000010:000000000000002522:000000000000015222:000000000000002222:000000000000000000:000000000000000000";
		
		//wakucreate
		CreateMapWaku();
		
		// 引数にこれを入れてマップ生成する
		CreateMap(map_matrix);
	}
	
	//CreateWaku
	void CreateMapWaku()
	{
		for(int dx = 0; dx <= default_x_max; dx++){
			Instantiate(defaultWallPrefab, new Vector3(dx, 0, 0), Quaternion.identity);
			Instantiate(defaultWallPrefab, new Vector3(dx, 0, default_z_max), Quaternion.identity);
		}
		
		for(int dz = 0; dz <= default_z_max; dz++){
			Instantiate(defaultWallPrefab, new Vector3(0, 0, dz), Quaternion.identity);
			Instantiate(defaultWallPrefab, new Vector3(default_x_max, 0, dz), Quaternion.identity);
		}
	}
	
	//マップを作るメソッド
	void CreateMap(string map_matrix)
	{
                //「:」をデリミタとして、mapp_matrix_arrに配列として分割していれます
		string[] map_matrix_arr = map_matrix.Split(':');

     //map_matrix_arrの配列の数を最大値としてループ
		for(int x = 0; x < map_matrix_arr.Length; x++){
        //xを元に配列の要素を取り出す
			string x_map = map_matrix_arr[x];
        //1配列に格納されている文字の数でx軸をループ
			for(int z = 0; z < x_map.Length; z++){
                                //配列から取り出した1要素には022520000000000000こんな値が入っているのでこれを1文字づつ取り出す
				int obj = int.Parse(x_map.Substring(z, 1));
				
                                //もしも0だったら壁ということで壁のプレハブをインスタンス化してループして出したx座標z座標を指定して設置
				if(obj == 0){
					Instantiate(wallPrefab, new Vector3(x + 1, 0, z  + 1), Quaternion.identity);
				}
			}
		}
		
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

こんなかんじでしょうか、このスクリプトは既にHierarchyのマップジェネレーターに組み込まれているので、
さっきなかったwallPrefabのセットだけで動く筈です。

さっき無かったWall Prefabをさっき作ったプレハブをセットします。

さてそれではシュミレートしてみましょう。
おおーダンジョンっぽいマップができました!

ちなみにマップの右上の通路が途中で途切れているのはマップテキストがちょっと不完全だっただからです、
通路をつくってもよし、部屋をつくってもよしでマップテキストを適当に変更してみましょう。

今回は0は壁というものだけでしたが、マップテキストを見ると0の他に2,3,5などあると思います。
これは後々階段とかモンスターとかアイテムなんかを配置する為にこうなっているだけです。

そんなこんなで今回はここまでです。
次回はキャラクターを置いて、ダンジョン内をうろうろさせたいと思います。

関連記事
【Unity 入門】ダンジョンゲーを作ってみたい 第一回「ダンジョン作ってみる」
【Unity 入門】ダンジョンゲーを作ってみたい 第二回「キャラクターをダンジョン内で動かす」

【Unity 入門】ダンジョンゲーを作ってみたい 第二回「キャラクターをダンジョン内で動かす」

前回ダンジョンっぽいマップが出来上がり早くダンジョン内を動かしたいと思うのですが、
前にFPS系のゾンビゲーを作りたい!なんて事をしていたときは動かすキャラクターはコンポーネントに頼っていたのですが、
今回は動く時のスクリプトは自前で書きたいとおもいます。

では早速キャラクターを置きたいのですが、色々探したのですが、いい感じのモーションがついていて、
無料のキャラクターを探したのですが、ない。。。orz
しょうがないのでBlenderインスコして自分で作ってやるよっ!って思ったのですが、
すいません3Dモデリングなめてました。適当な物すらモデリングできずに一旦挫折(この奮闘記は又の機会に)
そんで結局行き着いたのが極力安くてモーションが沢山ついているアセットを購入するという事になりました。
こういう時いい方法があるよ!って方是非教えてください、攻撃モーションだけ拾ってきて、適当なボーンに適応できたりするの?

(もしも攻撃モーションがなくてよくて無料がいい人はこちらを)➡ Dokebimusa - GhostWarrior

今回は$10つかって(900円しないくらいだしこのくらいならお小遣いからなんとか)
これを購入しました。

デモはこちら
http://kimys2848.nayana.com/npc/blade/blade.html

まぁ前からこれ欲しかったんですけど、購入をためらっていましたが、アニメーション作ってる時間を買えるなら購入してしまおう!
ということで今回初有料物を使ってみたいと思います。

1.キャラクター周りの配置
1-1.キャラ置いてみる
インポートしたキャラクターファイルのプレハブをマップに適当に置いてみます。
キャラがデフォルトだと大きいのでスケールを調整します今回は0.5にしておきました

うん、いい感じですね

こいつは後で動かすのでCharacter Contorollerを追加してきます
PlayerのInspector>Add ComponentからPhysics>Character Contoroller
緑の枠をCharacter ContorollerのHeightとRadiusを調整してキャラに合わせます

1-2.カメラとライトの設置
キャラクターとの位置関係はこんな感じで配置して

ライトはカメラの子として置きます。

カメラはPlayer Camera1として置きます


2.カメラの自動追尾スクリプトを書く
前はplayerにカメラをつけてFPS視点でのカメラダッタのですが、
今回は絶対位置により固定方向カメラなので外出しにしています。
今のままだとキャラクターを動かしてもカメラは一切動かないので、プレイヤを自動追尾するスクリプトを書きます。

適当にcamera.csとでも名前をつけて

using UnityEngine;
using System.Collections;

public class camera : MonoBehaviour {
	
	public GameObject player;
	
	// Update is called once per frame
	void Update () {
		
		//プレイヤーどの位置に置くか
		transform.position = new Vector3(player.transform.position.x, player.transform.position.y + 2, player.transform.position.z - 1);
	}
}

真上だとせっかくのキャラクターの頭しか見えないので少し傾けてカメラをセットします
カメラの傾きはx軸に45で設定されています。
このスクリプトをPlayer Camera1にアタッチします。
オブジェクトにはプレイヤーをセット

3.プレイヤーを動かす
3-1.プレイヤーに登録されているアニメーションを見てみる
デフォルトでこんな感じでアニメーションが設定されているようです
結構あります、うれしい事にアイテムを拾うのとか、死亡時、コンボまでアニメーションがあるようです。
1000円でこれなら結構満足かも!

3-2.基本動作スクリプトを書く
とりあえずこんなスクリプトを書いてみました

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {
	
	public Vector3 targetDirection;

	// Use this for initialization
	void Start () {
	}
	
	// Update is called once per frame
	void Update () {
		
		targetDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
		
                //クリックで攻撃
		if (Input.GetButton("Fire1")){
			animation.Play("Attack");
			
     //Eでアイテム拾う
		}else if(Input.GetKey(KeyCode.E)){
			animation.Play("Pickup");
			
     //WASDで移動
		}else if (targetDirection.magnitude > 0.1) {
			animation.Play("Run");

        //プレイヤーの向きを変えて
			transform.rotation = Quaternion.LookRotation(targetDirection);
			//CharacterControllerコンポーネントを呼び出し
			CharacterController conroller = GetComponent<CharacterController>();
         //移動
			conroller.Move(transform.forward * Time.deltaTime * 3f);
		}else{
        //何も押されてない場合はIdle
			animation.Play("Idle");
		}
	}
}

これをPlayerMotor.csとして保存して、
スクリプトをプレイヤーにアタッチします。

さてさてどうでしょう、シュミレートするとダンジョン内を動き回る感じになるはず。
カメラはプレイヤーを固定位置からずっととり続けています。FPSみたいな視点では無く、ちゃんと動いているようです。

関連記事
【Unity 入門】ダンジョンゲーを作ってみたい 第一回「ダンジョン作ってみる」
【Unity 入門】ダンジョンゲーを作ってみたい 第二回「キャラクターをダンジョン内で動かす」