Unityは重いから使いたくない!と思っていたのですが、面白そうなので使ってみることにしました。
影響された動画はこれ。
上記動画を参考につくってみました。
Contents
フィールドをつくる
まず床と周囲の枠をつくります。
床も枠と同様にCubeでつくります。ヒエラルキーで右クリック 3Dオブジェクト ⇒ キューブを選択。Floorという名前にしてTransformをx,y,zはすべて0、拡大縮小を32, 0.5, 32にします。動画よりもブロックの数を多くします。そのまま真似しても面白くないので・・・。
周囲の枠をつくります。新しく別のCubeを4つ作成しわかりやすい名前(WallTop、WallLeft、WallRight、WallBotomにしました)をつけます。そして周囲を囲うように配置します。コードをいちいち書かなくて便利かもしれませんが、慣れないとちょっと面倒かも。
画像のような値にしました。
ブロック、ボール、パドルをつくる
次にブロックですが、これもCubeで拡大率を1.95, 1, 1にします。Transformは適当です。このブロックは仮であり、プログラムが開始されたら消してしまいます。ブロックは7種類、色は虹の色と同じにします。
次にマテリアルを作成します。Assetsで右クリック 作成 ⇒ マテリアルを選択すると作成されます。あとはわかりやすい名前にしてインスペクターのアルベドをクリックして適当な色を設定します。
そして作成したマテリアルを作成したオブジェクトの上にドラッグアンドドロップします。コードを書く量が少ない代わりにドラッグアンドドロップが多いです。説明も文章だとしづらいです。
それからボールとパドルが必要です。これも新しいCube(Ball、Player)を作成して以下のように値を設定します。色は新しくマテリアルをつくってもいいし、これまで作ったマテリアルを流用してもかまいません。
GameManagerをつくる
それからゲームとして成り立たせるためにはGameManagerを作る必要があります。ヒエラルキーを右クリックして空のオブジェクトを作成を選択して生成されたオブジェクトにGameManagerという名前をつけます。
そしてC#スクリプトを作成します。作成するスクリプトはGameManager.csとBall.csです。できるだけ1箇所で管理したいので基本的にGameManager.csに書きます。ボールの跳ね返りの処理をする都合上、Ball.csはしかたなくつくります。
そして例のごとくファイルをつくるだけではダメでこれをオブジェクトと紐付ける必要があります。ヒエラルキーでGameManagerを選択してインスペクターの下のほうにコンポーネントを追加するボタンがあるのでこれをクリックしてスクリプトを選択し、表示されたスクリプトのなかからGameManager.csを選択します。Ballも同様にBall.csを追加します。
Ballクラスの作成
簡単にできそうなBallクラスから考えます。
まずボールの動きを物理特性によって制御するためにRigidbodyを追加します。ヒエラルキーでBallを選択し、コンポーネントを追加で物理 ⇒ リジットボディを選択します。するとボールのインスペクターにリジットボディが追加されます。
ボールは枠の外に飛び出してほしくないので高さは変更されないようにします。位置を固定のY座標部分にチェックをいれます。回転もあまり関係がないのでXYZ座標にチェックをいれます。
それからボールが当たったときの反発係数を設定します。Assetsで右クリック、追加 ⇒ 物理マテリアルを選択します。適当な名前(Ball)をつけます。
追加した物理マテリアルを選択して、DynamicFrictionとStaticFrictionを0に、Bouncinessを1に、BounceCombineをMaximにFrictionCombineをMinimumに設定します。摩擦は0とする、反発係数は1とする、ふたつの物体間で異なる値をとる場合は摩擦は少ない方を採用する、反発係数は大きいほうを採用するという意味です。
そしてBallクラスのスクリプトを書きます。ようやくコーディングの開始です。
Startメソッドは最初におこなわれる処理です。自作メソッドのBallStartを呼び出しボールに力をかけて初速を与えます。ボールの初期座標は(2, 1, -4)とし、ボールがうごいているときは速度をゼロにして右斜め前に力をかけます。SpeedはpublicにしてUnity側でも設定できるようにします。1.0fにしていますが、実はUnity側で12を設定しています。
次に当たり判定ですが、自分で考える必要はなく、OnCollisionEnterメソッドのなかで自動的に処理をしてくれます。これはなにかに当たったときに呼び出されるメソッドでcollision.gameObjectでどのゲームオブジェクトに当たったかがわかります。
ボールがブロックに当たったらブロックを消す、一番下の枠に当たったらミス時の処理をするというのが基本ですが、パドルで跳ね返したときにも点数を加算したいとか当たったブロックの色で得点を変化させたいということで処理を切り分けています。
OnCollisionEnterメソッド内でcollision.gameObject.tagとやればなにに当たったかを知ることができますが、事前にタグを設定しておく必要があります。存在しないタグを書いてしまうとそこで例外発生です(ここで気づかずけっこうハマった)。
各オブジェクトのインスペクターでタグの項目があるのでタグの追加を選択します。そしてこんな感じに追加します。
あとは追加したタグを設定するだけです。設定するのはPlayerとWallBotomと各ブロックです。Playerタグはそのまま最初から用意されているものをそのまま使います。WallBotomには自分でつくったMissを各ブロックにも自分でつくったタグを設定します。
Ballクラスのスクリプトをしめします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Ball : MonoBehaviour { Rigidbody Rigidbody; public float Speed = 1.0f; public GameManager GameManager; void Start() { BallStart(); } public void BallStart() { gameObject.transform.position = new Vector3(2, 1, -4); Rigidbody = gameObject.GetComponent<Rigidbody>(); Rigidbody.velocity = Vector3.zero; Rigidbody.AddForce((transform.forward + transform.right) * Speed, ForceMode.VelocityChange); } private void OnCollisionEnter(Collision collision) { // ボールがなにかに当たったらtagからなにに当たったかを調べて適切な処理をする if (collision.gameObject.tag == "Miss") { GameManager.Miss(); // 後述 } if (collision.gameObject.tag == "Player") { GameManager.Hit(); // 後述 // だんだんボールの速度をアップする Rigidbody.AddForce((transform.forward) * 0.5f, ForceMode.VelocityChange); } string tag = collision.gameObject.tag; bool b = tag == "Red" || tag == "Orange" || tag == "Yellow" || tag == "Green" || tag == "Blue" || tag == "Blue2" || tag == "Purple"; if (b) { GameManager.BlockBreak(collision.gameObject); // 後述 } } } |
ボールが当たったときの処理をGameManagerクラスで行ないたいので、GameManagerへの参照も設定しておきます。ヒエラルキーでGameManagerクラスを選択して、インスペクターのスクリプトのGameManagerと書かれている項目にGameManagerオブジェクトをドラッグアンドドロップします。このときそのままやろうとしてもGameManagerオブジェクトを選択したときにインスペクターの表示が変更されてしまうので、インスペクターと書かれている場所の右にある鍵のような部分をクリックしてからおこないます。するとそのあいだはインスペクターの表示が変更されません。
GameManagerクラスの作成
次にGameManagerクラスを考えます。
とりあえずいま必要なフィールド変数だけ示します。フィールド変数を宣言するだけでは意味がないので上記の方法でPlayer、ball、redBlockなどへの参照を設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System.Collections.Generic; using UnityEngine; using System.Linq; using UnityEngine.UI; public class GameManager : MonoBehaviour { public List<GameObject> blocks = new List<GameObject>(); public Ball ball; public GameObject Player; public GameObject redBlock; public GameObject orangeBlock; public GameObject yellowBlock; public GameObject greenBlock; public GameObject blueBlock; public GameObject blueBlock2; public GameObject purpleBlock; } |
次にStartメソッドですが、以下のようになります。
Instantiateメソッドでインスタンスのコピーを生成してブロックの初期位置に配置しています。そしてこれをリスト blocksに格納しています。
この処理が終わったらフィールド変数で使ったオブジェクトは表示させる必要はないのでDestroyメソッドで削除しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class GameManager : MonoBehaviour { void Start() { // あとで書き足す予定なのでSetBlocksメソッドだけまとめる SetBlocks(); } void SetBlocks() { for (int i = 0; i < 7; i++) { GameObject block = redBlock; if (i == 1) block = orangeBlock; if (i == 2) block = yellowBlock; if (i == 3) block = greenBlock; if (i == 4) block = blueBlock; if (i == 5) block = blueBlock2; if (i == 6) block = purpleBlock; Instantiate(block, new Vector3(-7, 1, 11 - i), Quaternion.identity); for (int j = -15; j <= 15; j += 2) blocks.Add(Instantiate(block, new Vector3(j, 1, 11 - i), Quaternion.identity)); } Destroy(redBlock); Destroy(orangeBlock); Destroy(yellowBlock); Destroy(greenBlock); Destroy(blueBlock); Destroy(blueBlock2); Destroy(purpleBlock); } } |
次にパドルを左右に移動させる処理を示します。
Playerのtransform.positionを変更させているだけですが、それだけでは枠の外に移動したり上下に移動できてしまうので制限をかけています。ブロック崩しの場合、変なパターンに入ってしまうとボールが通る場所が限定されてしまいクリアできなくなるという問題があるかもしれないので、左右だけでなく前後にも移動できるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class GameManager : MonoBehaviour { void Update() { MovePlayer(); } void MovePlayer() { // 枠の外へ移動しないようにする if (Input.GetKey(KeyCode.LeftArrow)) { if (Player.transform.position.x > -14) Player.transform.position += Vector3.left * speed * Time.deltaTime; } if (Input.GetKey(KeyCode.RightArrow)) { if (Player.transform.position.x < 14) Player.transform.position += Vector3.right * speed * Time.deltaTime; } // 上下に移動しすぎないように調整する if (Input.GetKey(KeyCode.UpArrow)) { if (Player.transform.position.z < -10) Player.transform.position += Vector3.forward * speed * Time.deltaTime; } if (Input.GetKey(KeyCode.DownArrow)) { if (Player.transform.position.z > -14) Player.transform.position += Vector3.back * speed * Time.deltaTime; } } } |
ボールがブロックに当たったときの処理です。ブロックを非表示にします。
1 2 3 4 5 6 7 |
public class GameManager : MonoBehaviour { public void BlockBreak(GameObject obj) { obj.SetActive(false); } } |
実際にやってみるとわかりますが、ボールがほぼ垂直に落ちてきたり水平に近い動きをすることがあります。とくに端でまっすぐにしか落ちてこない場合、そのまま打ち返してもまっすぐにしか飛ばないので完全にハマり形になります。X軸方向やZ軸方向の速度が小さすぎる場合、修正をいれるプログラムを考える必要があるかもしれません。
以下はパドルでボールを跳ね返したときの処理です。
blocks.Count(x => x.activeSelf)とやれば消されていないブロックの数がわかります。0になったら落ちてきたボールを跳ね返したタイミングでブロックを再生します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class GameManager : MonoBehaviour { public void HitBack() { CorrectBallSpeed(); if (blocks.Count(x => x.activeSelf) == 0) { foreach (GameObject obj in blocks) obj.SetActive(true); } } void CorrectBallSpeed() { // これで速度はわかる。あとは各自で工夫してください・・・ Rigidbody rigidbody = ball.GetComponent<Rigidbody>(); float vx = rigidbody.velocity.x; float vz = rigidbody.velocity.z; } } |
ミスをしたときの処理です。Ball.BallStartメソッドを呼び出して、ボールを初期位置に戻して初速を与え、すぐにゲーム再開となります。
1 2 3 4 5 6 7 |
public class GameManager : MonoBehaviour { public void Miss() { ball.BallStart(); } } |
得点表示とゲームオーバー処理
これだとエンドレスで続いてしまうのでゲームオーバーの処理をいれます。
ヒエラルキーを右クリックしてUI ⇒ テキストを選択します。するとCanvasオブジェクトが生成されるのでGameOverCanvasという名前をつけます。そしてGameObject型のフィールド変数 gameOverCanvasをGameManagerクラスのなかに作成して、インスペクターでgameOverCanvasと書かれている部分にGameOverCanvasをドラッグアンドドロップします。
GameOverCanvasの子オブジェクトとしてTextが生成されているはずなので、これにインスペクターから位置を0, 40, 0、幅160、高さ30、テキストをGameOver、フォントサイズは48を設定します。このあたりは適当です。それから水平オーバーフローと垂直オーバーフローの設定をOverFlowに変更します。
ゲーム再開のためのボタンを表示させます。GameOverCanvasの子オブジェクトとしてボタンを生成(UI ⇒ ボタン)します。位置を0, -20, 0、幅160、高さ30します。ボタンの色もインスペクターから設定できます。適当に指定してください。ボタンの子オブジェクトのTextに表示させたい文字列、ここでは「Retry」を設定します。
クリック時にどうするかですが、Retryメソッドを作成します。これは以下の1行でOKです。シーンを新たにロードすることで最初の状態に戻ります。
1 2 3 4 5 6 7 |
public class GameManager : MonoBehaviour { public void Retry() { UnityEngine.SceneManagement.SceneManager.LoadScene("game"); } } |
ボタンのインスペクターのクリック時のところにこのメソッドを設定します。
ゲームが開始されるときはゲームオーバーの表示がされないようにしてゲームオーバーになったら表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class GameManager : MonoBehaviour { int restMax = 3; // とりあえず自機は3とする int rest = 0; public GameObject gameOverCanvas; // スコア表示用(後述) public GameObject scoreObject; void Start() { // 最初はゲームオーバー表示を非表示にする gameOverCanvas.SetActive(false); // 残機を設定 rest = restMax; // あとはこれまでどおり SetBlocks(); } } |
ミスをしたら残機を減らします。残機が0になったらボールをもとの位置に戻さずDestroyしてしまってGameOverCanvasのSetActiveメソッドの引数をtrueで呼び出しゲームオーバー表示をさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class GameManager : MonoBehaviour { public void Miss() { rest--; if (rest == 0) { // ゲームオーバーの場合 Destroy(ball.gameObject); gameOverCanvas.SetActive(true); } else { // ミスをしただけでゲームオーバーではない場合は // ボールを元の位置に戻してゲーム再開 ball.BallStart(); } } } |
次にスコア表示をさせます。ヒエラルキーで右クリックしてUI ⇒ テキストを選択します。子オブジェクトのTextをScoreTextに変更してインスペクターで位置を(0, 170, 0)に設定します。フォントサイズは24、水平オーバーフローと垂直オーバーフローの設定をOverFlowに変更します。
次にGameManagerのインスペクターでscoreObjectと書かれている部分にScoreTextをドラッグアンドドロップします。そのあとScoreプロパティを作成して、ここに値を設定すれば得点が表示されるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class GameManager : MonoBehaviour { int score = 0; int Score { get { return score; } set { score = value; Text score_text = scoreObject.GetComponent<Text>(); if(score_text != null) score_text.text = score.ToString(); } } } |
またBlockBreakメソッドに点数計算をする部分を追加します。点数は以下のとおり。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class GameManager : MonoBehaviour { public void BlockBreak(GameObject obj) { obj.SetActive(false); if (obj.tag == "Red") Score += 100; if (obj.tag == "Orange") Score += 70; if (obj.tag == "Yellow") Score += 60; if (obj.tag == "Green") Score += 50; if (obj.tag == "Blue") Score += 40; if (obj.tag == "Blue2") Score += 30; if (obj.tag == "Purple") Score += 20; } public void HitBack() { // ボールを跳ね返したときも10点追加する Score += 10; // 以下は変更なし CorrectBallSpeed(); if (blocks.Count(x => x.activeSelf) == 0) { foreach (GameObject obj in blocks) obj.SetActive(true); } } } |
実行結果。