#この解説は
Unityのサイトに昔からあるチュートリアルに、以下があります。
「はじめてのUnity」(旧)
http://tutorial.unity3d.jp/archive/my-first-unity/
これを独自にC#化したものをベースとしています。以下を
この解説は以下の段階があります、前の内容を実行した上でのものとして参照して下さい。現時点(20151026)では、新しい「はじめてのUnity」の玉転がしゲームが公開されています。本サイトのブロック崩しと併せて学習すると広がりがあり、初期の学習にはとても有効です。
【基礎】
- [超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(1)ステージ配置
- [超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(2)色を変える
- [超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(3)動くボール
- [[超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(4)ラケットを動かす]
(http://qiita.com/JunShimura/items/5fac20966bba6b223427) - [超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(5)消えるブロック
- [超初心者向け]Unityチュートリアル「はじめてのUnity」のブロック崩しと同等をC#で::(6)ブロックを手作業で並べる
【応用】
PrefabとInstantiateの基本(1)ブロックをScriptで配置:「はじめてのUnity」のブロック崩しを改造しながら学ぶ
#効率的にBlock
を並べる
前回のターンでBlock
をPrefabにしてAssetに置いた形から、Instantiate
関数を使ってScene
に配置しました。これを繰り返すことで、複数の配置を簡単に実行出来るようになります。
##位置を指定して配置する
Instantiate
関数でPrefabを配置するときに、オーバーロードされた形を用いると、位置の指定が可能になります。
以下を参考にして下さい。
Unity - スクリプトリファレンス:Object.Instantiate
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
が配置されます。
内容を解説します。
Vector3 placePosition = new Vector3(-4,0,9);
Vector3
型は3次元のベクトルで、中の数はそれぞれ、x座標、y座標、z座標を示します。これはBall
に力を加える時にも用いているものです。placePosition
という名前で新たに生成し、座標(-4,0,9)
を代入しています。
Quaternion q = new Quaternion();
q= Quaternion.identity;
Quaternion
は4元数という型で、3次元の座標にwというパラメータがついているもので、回転する軸と回転する角度を表したものを指定します。詳しくは以下を参考にしてください。
Unity - スクリプトリファレンス:Quaternion
今回のプログラムでは回転角度を変えないので、無回転状態を示すQuaternion.identity
を指定しています。
上記の二つを用いて、Instantiate
を実行します。
Instantiate(blockPrefab,placePosition,q);
##位置を変えて複数を実行する
配置するときに、その座標を変えていくと、並べて配置することが可能になります。例えば、横に並べる場合、Block
の幅の分、x座標を加算してずらしてからInstantiate
を実行すると並びます。
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);
これを連続で実行すると、横に並べられます。
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);
}
これをまとめた処理にすると、以下になります。
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分の1になります。
####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エディタで指定します。
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;
}
}
これで、topWall
に張り付いた状態で自動的に配置されるようになりました。試しにUnityエディタで'TopWall`を移動してみましょう。
###並べる個数の自由度を上げる
今回のプログラムでは5個を並べています。これは、block
の幅を2にし、TopWall
の幅を10にしているため、ちょうど並ぶのが5個になるように想定していたからです。この仕様を、自由度を上げる場合は以下の候補が考えられます。
-
Block
の大きさは変えない -
TopWall
の幅に交錯しない可能な数だけ等間隔に並べて隙間が空く -
TopWall
の幅に隣接して可能な数だけ並べて、左端、右端に隙間が空く -
Block
の大きさを変えて指定数分でならべる。
ゲーム性を考えて選択することになりますが、ここでは「Block
の大きさを変えて指定数分でならべる」という方法を採用します。他の方法に転用しやすいのと、緊密な方がきれいに見えるからです。
個数は機能的な部分では無くレベルデザイン面のものなので、Unityエディタ側で指定できるようにします。
変数placeX
を宣言し、Unityエディタ側で設定出来るようにしました。
合わせて、繰り返す処理で5にしていたところを、placeX
に変えています。これで並ぶ個数は設定可能になりました。
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個にしています。
実行を一時停止し、並んでるblockPrefab
を選択して状態を確認します。
この状態では、横に足らない場合は隙間が出来、多い場合は横にはみ出して並ぶ状態になります。これを、並べる個数とtopWall
の幅を使って適正な大きさに変えて等間隔で密接する並び方に変えます。
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;
}
}
幅を変える場合、blockPrefab.localScale.x
だけをを変えることになります。この時、xだけを代入出来る仕様になっておらず、Vector3
型で代入する必要があるため、以下の手順をとっています。
//幅を調整
Vector3 localscale = blockPrefab.localScale;
localscale.x = topWall.localScale.x / placeX;
blockPrefab.localScale = localscale;
- ローカル変数
localscale
に一度、代入 - xだけを書き換える
-
blockPrefab.localScale
へ代入
###奥行きの個数を指定する
奥行き方向をは現在、一つだけなので、これをpublic
で宣言し、その数で並べることが可能になります。この時、縦の限界値は存在しないので、それもエディタから指定できるようにします。
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;
}
}
}
エディタ側で個数と座標を指定します。
実行して確認します。
奥行きの個数、座標の大きさをそれぞれ指定します。
public int placeZ; ////奥に並べる個数
public float totalDepth; ////奥に並べる座標
奥行きの大きさを、上記の変数から算出します。
localscale.z = totalDepth / placeZ;
奥行き方向への配置の処理をするので、横の処理を奥行き方向の座標を変えながら指定回数、繰り返すことで配置します。
//配置
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座標を戻しやすくしています。
#まとめ
- 値をそのまま書いているとあとから修正しにくいので使わない
- 固定値は何かに依存した数になっているので、算出するようにしておく
- 算出によって求められるものでない場合は、設計上の調整に用いる値なので、エディタで操作出来るようにしておく
今回の話は少し面倒と考えて避けたいことがあると思いますが、動けばそれでいい、という作り方をしていると、レベルデザインを繰り返すうちにそれが面倒に感じられます。レベルデザインは本来、楽しめる部分なので、そこを楽しむためにも、こうした部分に気を付けておくとよいです。そして更に、多人数で制作する場合には必ず求められることになります。