PrefabとInstantiateの基本（2）効率的にBlockを並べる：「はじめてのUnity」のブロック崩しを改造しながら学ぶ

#この解説は
Unityのサイトに昔からあるチュートリアルに、以下があります。
[「はじめてのUnity」(旧）
http://tutorial.unity3d.jp/archive/my-first-unity/](http://tutorial.unity3d.jp/archive/my-first-unity/)
これを独自にC#化したものをベースとしています。以下を
この解説は以下の段階があります、前の内容を実行した上でのものとして参照して下さい。現時点(20151026)では、[新しい「はじめてのUnity」の玉転がしゲーム](https://github.com/unity3d-jp/FirstTutorial/wiki)が公開されています。本サイトのブロック崩しと併せて学習すると広がりがあり、初期の学習にはとても有効です。


【基礎】

* [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(1)ステージ配置](http://qiita.com/JunShimura/items/cbb0db8087a5cc75735e)
* [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(2)色を変える](http://qiita.com/JunShimura/items/1b2b4842e983022075de )
* [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(3)動くボール](http://qiita.com/JunShimura/items/dfb2bdbbc328869f62ec )
* [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(4)ラケットを動かす]
(http://qiita.com/JunShimura/items/5fac20966bba6b223427)
* [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(5)消えるブロック](http://qiita.com/JunShimura/items/867179092a2a564bc88c)
* [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(6)ブロックを手作業で並べる](http://qiita.com/JunShimura/items/1f39e52414171c67bc71)

【応用】

[PrefabとInstantiateの基本（1）ブロックをScriptで配置：「はじめてのUnity」のブロック崩しを改造しながら学ぶ](http://qiita.com/JunShimura/items/7e45fc6236cf97914041)

#効率的に`Block`を並べる
前回のターンで`Block`をPrefabにしてAssetに置いた形から、`Instantiate`関数を使って`Scene`に配置しました。これを繰り返すことで、複数の配置を簡単に実行出来るようになります。

##位置を指定して配置する
`Instantiate`関数でPrefabを配置するときに、オーバーロードされた形を用いると、位置の指定が可能になります。
以下を参考にして下さい。
[Unity - スクリプトリファレンス：Object.Instantiate](http://docs.unity3d.com/ja/current/ScriptReference/Object.Instantiate.html)

```csharp:BlockControl.cs

using UnityEngine;
using System.Collections;

public class BlockControl : MonoBehaviour {

    public Transform blockPrefab;

    // Use this for initialization
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(-4,0,9);
        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;
        //配置
        Instantiate(blockPrefab,placePosition,q);
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}
```

これを実行すると、座標`(-4,0,9)`に回転が無い状態で`Block`が配置されます。
内容を解説します。

```csharp
Vector3 placePosition = new Vector3(-4,0,9);
```
`Vector3`型は3次元のベクトルで、中の数はそれぞれ、x座標、ｙ座標、ｚ座標を示します。これは`Ball`に力を加える時にも用いているものです。`placePosition`という名前で新たに生成し、座標`(-4,0,9)`を代入しています。

```csharp
Quaternion q = new Quaternion();
q= Quaternion.identity;
```

`Quaternion`は４元数という型で、3次元の座標にwというパラメータがついているもので、回転する軸と回転する角度を表したものを指定します。詳しくは以下を参考にしてください。

[Unity - スクリプトリファレンス：Quaternion](http://docs.unity3d.com/ja/current/ScriptReference/Quaternion.html)

今回のプログラムでは回転角度を変えないので、無回転状態を示す`Quaternion.identity`を指定しています。

上記の二つを用いて、`Instantiate`を実行します。

```csharp
Instantiate(blockPrefab,placePosition,q);
```

実行すると、一つが右上に配置されます。
[![Gyazo](https://i.gyazo.com/d2438eebf51bede2cec4eb90aa54e34d.gif)](https://gyazo.com/d2438eebf51bede2cec4eb90aa54e34d)

##位置を変えて複数を実行する
配置するときに、その座標を変えていくと、並べて配置することが可能になります。例えば、横に並べる場合、`Block`の幅の分、x座標を加算してずらしてから`Instantiate`を実行すると並びます。

```csharp:BlockControl.cs(抜粋)
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(-4,0,9);
        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;
        //配置
        Instantiate(blockPrefab, placePosition, q);
        //x座標を変更し配置
        placePosition.x += blockPrefab.transform.localScale.x;
        Instantiate(blockPrefab, placePosition, q);

    }
```

以下の行を追加しています。

```csharp:BlockControl.cs(抜粋)
        placePosition.x += blockPrefab.transform.localScale.x;
        Instantiate(blockPrefab, placePosition, q);
```
[![Gyazo](https://i.gyazo.com/28937cf5c6ec5c650e5517727267711d.png)](https://gyazo.com/28937cf5c6ec5c650e5517727267711d)

これを連続で実行すると、横に並べられます。

```csharp:BlockControl.cs(抜粋)
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(-4,0,9);
        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;
        //配置
        Instantiate(blockPrefab,placePosition,q);
        //x座標を変更し配置
        placePosition.x += blockPrefab.transform.localScale.x;
        Instantiate(blockPrefab, placePosition, q);
        placePosition.x += blockPrefab.transform.localScale.x;
        Instantiate(blockPrefab, placePosition, q);
        placePosition.x += blockPrefab.transform.localScale.x;
        Instantiate(blockPrefab, placePosition, q);
        placePosition.x += blockPrefab.transform.localScale.x;
        Instantiate(blockPrefab, placePosition, q);
    }
```

[![Gyazo](https://i.gyazo.com/4d05e1fc42b46b84499994a52a5be89b.png)](https://gyazo.com/4d05e1fc42b46b84499994a52a5be89b)

これをまとめた処理にすると、以下になります。

```csharp:BlockControl.cs(抜粋)
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(-4,0,9);
        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;
        //配置
        for (int i = 0; i < 5; i++)
        {
            Instantiate(blockPrefab, placePosition, q);
            placePosition.x += blockPrefab.transform.localScale.x;
        }
    }
```

こうする事で、個数を変えて処理する自由度が上がります。

##座標の依存性を考えて処理の自由度を上げる
このままでも動くので問題は無いですが、これを変えて難易度を変える等の改造する場合などに、どの値を何によって求めているかが分らないと、修正が困難になります。このプログラムでは幾つかの定数を指定していますが、これらがどこからきているのかを考えて算出する部分を作っておくと、Unityエディタ側だけでカスタマイズが可能になります。

###最初に置く座標を考える
このプログラムで指定している座標`(-4,0,9)`は、`Scene`に在る`GameObject`である`TopWall`と、`BlockPrefab`で算出が出来る座標になっています。
positionに入ってる座標は、そのオブジェクトの中心点（ほぼ重心）になります。箱型の場合は、幅、高さ、奥行きの大きさのそれぞれ2分の１になります。

[![Gyazo](https://i.gyazo.com/3e5f3f0a6bde0a3dd9d5e5c01c5244be.png)](https://gyazo.com/3e5f3f0a6bde0a3dd9d5e5c01c5244be)

####x座標を算出する
`topWall`の左端の座標は、中心点から幅/2を減算で算出が出来るので、以下で表せます。

```
toWall.position.x-topWall.localscale.x/2
```

配置する座標は、`blockPrefab`の中心点を指定するので幅/2の分、右にするため加算します。

```
toWall.position.x-topWall.localscale.x/2+blockPrefab.localscale.x/2
```

####z座標を算出する
`topWall`の上端の座標は、中心点から高さ/2を減算で算出が出来るので、以下で表せます。

```
toWall.position.z-topWall.localscale.z/2
```

配置する座標は、`blockPrefab`の中心点を指定するので高さ/2の分、手前にするため減算します。

```
toWall.position.x-topWall.localscale.x/2-blockPrefab.localscale.x/2
```

####最初に置く座標を実装する
上記を踏まえて、座標を指定したScriptに書き換えます。`topWall`を参照しなくてはならないので、`public`で宣言し、Unityエディタで指定します。

```csharp:BlockControl.cs(抜粋)
public class BlockControl : MonoBehaviour {

    public Transform blockPrefab;
    public Transform topWall;   //上の壁の参照を追加

    // Use this for initialization
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(
            topWall.position.x-topWall.localScale.x/2+blockPrefab.localScale.x/2,
            0,
            topWall.position.z-topWall.localScale.z/2-blockPrefab.localScale.z/2);

        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;
        //配置
        for (int i = 0; i < 5; i++)
        {
            Instantiate(blockPrefab, placePosition, q);
            placePosition.x += blockPrefab.transform.localScale.x;
        }
    }
```

[![Gyazo](https://i.gyazo.com/1c46690ec72379ffe04eefb2e0b662c8.png)](https://gyazo.com/1c46690ec72379ffe04eefb2e0b662c8)

これで、`topWall`に張り付いた状態で自動的に配置されるようになりました。試しにUnityエディタで'TopWall`を移動してみましょう。
[![Gyazo](https://i.gyazo.com/e4b023c00a12f40d6e30614b42a5ba86.png)](https://gyazo.com/e4b023c00a12f40d6e30614b42a5ba86)

###並べる個数の自由度を上げる
今回のプログラムでは5個を並べています。これは、`block`の幅を2にし、`TopWall`の幅を10にしているため、ちょうど並ぶのが5個になるように想定していたからです。この仕様を、自由度を上げる場合は以下の候補が考えられます。

* `Block`の大きさは変えない
 * `TopWall`の幅に交錯しない可能な数だけ等間隔に並べて隙間が空く
 * `TopWall`の幅に隣接して可能な数だけ並べて、左端、右端に隙間が空く
* `Block`の大きさを変えて指定数分でならべる。

ゲーム性を考えて選択することになりますが、ここでは「`Block`の大きさを変えて指定数分でならべる」という方法を採用します。他の方法に転用しやすいのと、緊密な方がきれいに見えるからです。

個数は機能的な部分では無くレベルデザイン面のものなので、Unityエディタ側で指定できるようにします。
変数`placeX`を宣言し、Unityエディタ側で設定出来るようにしました。
合わせて、繰り返す処理で5にしていたところを、`placeX`に変えています。これで並ぶ個数は設定可能になりました。

```csharp:BlockControl.cs(抜粋)
public class BlockControl : MonoBehaviour {

    public Transform blockPrefab;
    public Transform topWall;   //上の壁の参照を追加
    public int placeX;          ////横に並べる個数

    // Use this for initialization
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(
            topWall.position.x-topWall.localScale.x/2+blockPrefab.localScale.x/2,
            0,
            topWall.position.z-topWall.localScale.z/2-blockPrefab.localScale.z/2);

        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;
        
        //配置
        for (int i = 0; i < placeX; i++)
        {
            Instantiate(blockPrefab, placePosition, q);
            placePosition.x += blockPrefab.transform.localScale.x;
        }
    }
```

今回は4個にしています。

[![Gyazo](https://i.gyazo.com/3191e92077bfee9fe992694a4b77b1d4.png)](https://gyazo.com/3191e92077bfee9fe992694a4b77b1d4)

実行を一時停止し、並んでる`blockPrefab`を選択して状態を確認します。
[![Gyazo](https://i.gyazo.com/68d22be3649ea7ffcb74f18f7c562aff.png)](https://gyazo.com/68d22be3649ea7ffcb74f18f7c562aff)

この状態では、横に足らない場合は隙間が出来、多い場合は横にはみ出して並ぶ状態になります。これを、並べる個数と`topWall`の幅を使って適正な大きさに変えて等間隔で密接する並び方に変えます。

```csharp:BlockControl.cs(抜粋)
public class BlockControl : MonoBehaviour {

    public Transform blockPrefab;
    public Transform topWall;   //上の壁の参照を追加
    public int placeX;          ////横に並べる個数

    // Use this for initialization
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(
            topWall.position.x-topWall.localScale.x/2+blockPrefab.localScale.x/2,
            0,
            topWall.position.z-topWall.localScale.z/2-blockPrefab.localScale.z/2);

        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;

        //幅を調整
        Vector3 localscale = blockPrefab.localScale;
        localscale.x = topWall.localScale.x / placeX;
        blockPrefab.localScale = localscale;
        
        //配置
        for (int i = 0; i < placeX; i++)
        {
            Instantiate(blockPrefab, placePosition, q);
            placePosition.x += blockPrefab.transform.localScale.x;
        }
    }
```

[![Gyazo](https://i.gyazo.com/a82d47ac3b0b2964489472a1ce9b3520.png)](https://gyazo.com/a82d47ac3b0b2964489472a1ce9b3520)

幅を変える場合、`blockPrefab.localScale.x`だけをを変えることになります。この時、xだけを代入出来る仕様になっておらず、`Vector3`型で代入する必要があるため、以下の手順をとっています。

```csharp
        //幅を調整
        Vector3 localscale = blockPrefab.localScale;
        localscale.x = topWall.localScale.x / placeX;
        blockPrefab.localScale = localscale;
```

0. ローカル変数`localscale`に一度、代入
1. xだけを書き換える
2. `blockPrefab.localScale`へ代入

###奥行きの個数を指定する
奥行き方向をは現在、一つだけなので、これを`public`で宣言し、その数で並べることが可能になります。この時、縦の限界値は存在しないので、それもエディタから指定できるようにします。

```csharp:BlockControl.cs(抜粋)
public class BlockControl : MonoBehaviour {

    public Transform blockPrefab;
    public Transform topWall;   //上の壁の参照を追加
    public int placeX;          ////横に並べる個数
    public int placeZ;          ////奥に並べる個数
    public float totalDepth;    ////奥に並べる座標

    // Use this for initialization
    void Start () {
        //配置する座標を設定
        Vector3 placePosition = new Vector3(
            topWall.position.x-topWall.localScale.x/2+blockPrefab.localScale.x/2,
            0,
            topWall.position.z-topWall.localScale.z/2-blockPrefab.localScale.z/2);

        //配置する回転角を設定
        Quaternion q = new Quaternion();
        q= Quaternion.identity;

        //幅と奥行きを調整
        Vector3 localscale = blockPrefab.localScale;
        localscale.x = topWall.localScale.x / placeX;
        localscale.z = totalDepth / placeZ;
        blockPrefab.localScale = localscale;

        //配置
        for (int i = 0; i < placeZ; i++)
        {
            Vector3 currentPlacePosition
                = placePosition
                - Vector3.forward * blockPrefab.localScale.z * i;
            for(int j = 0; j < placeX; j++)
            {
                Instantiate(blockPrefab, currentPlacePosition, q);
                currentPlacePosition.x += blockPrefab.transform.localScale.x;
            }
        }
    }
```

エディタ側で個数と座標を指定します。

[![Gyazo](https://i.gyazo.com/3aec3cfe4a2d68c2a8a45d4b28cdfee0.png)](https://gyazo.com/3aec3cfe4a2d68c2a8a45d4b28cdfee0)

実行して確認します。

[![Gyazo](https://i.gyazo.com/9edffa4eccbdc1f96caa5c3bd11d9265.png)](https://gyazo.com/9edffa4eccbdc1f96caa5c3bd11d9265)

奥行きの個数、座標の大きさをそれぞれ指定します。

```csharp
    public int placeZ;          ////奥に並べる個数
    public float totalDepth;    ////奥に並べる座標
```

奥行きの大きさを、上記の変数から算出します。

```csharp
        localscale.z = totalDepth / placeZ;
```

奥行き方向への配置の処理をするので、横の処理を奥行き方向の座標を変えながら指定回数、繰り返すことで配置します。

```csharp
        //配置
        for (int i = 0; i < placeZ; i++)
        {
            Vector3 currentPlacePosition
                = placePosition
                - Vector3.forward * blockPrefab.localScale.z * i;
            for(int j = 0; j < placeX; j++)
            {
                Instantiate(blockPrefab, currentPlacePosition, q);
                currentPlacePosition.x += blockPrefab.transform.localScale.x;
            }
        }
    }
```

配置する時の座標を算出しやすくするように、`Vector3`型でもう一つ、座標を作り、x座標を戻しやすくしています。

***
#まとめ
* 値をそのまま書いているとあとから修正しにくいので使わない
* 固定値は何かに依存した数になっているので、算出するようにしておく
* 算出によって求められるものでない場合は、設計上の調整に用いる値なので、エディタで操作出来るようにしておく

今回の話は少し面倒と考えて避けたいことがあると思いますが、動けばそれでいい、という作り方をしていると、レベルデザインを繰り返すうちにそれが面倒に感じられます。レベルデザインは本来、楽しめる部分なので、そこを楽しむためにも、こうした部分に気を付けておくとよいです。そして更に、多人数で制作する場合には必ず求められることになります。