今回の記事はUnityで3Dエンドレスランゲームを制作する講座の第5回目です。今回がいよいよ最終回です。
前回は3D障害物ランゲームを本格的な無限ランゲームに改造していきました。BGMや効果音も付けてスコアシステムも実装しゲームとして形になってきました。
前回の記事:
ですが、まだ少々足りていないところがあります。ゲームオーバー処理が実装できていません。今回はまずゲーム終了&リスタート処理を開発し、その後ビルドして実機でゲームを遊べるようにしていきます。
ゲームオーバー・ゲーム終了処理の実装
これはエンドレスランナーのゲームなのでプレイヤーが障害物にぶつかるとゲーム終了となります。
ゲーム終了判定のためにCharacterに別のコライダーを追加しましょう。
Characterプレハブを開き、Playerを選択します。そのインスペクタにBoxColliderを追加します。
Collisionを検出するために、必要に応じてサイズを変えてください。そしてIs Triggerをオンにします。
次に”PlayerMovement”スクリプトを開いてください。ゲームがプレイ中かどうかのチェック判定処理を追加します。
PlayerMovement.csを以下のように修正します。
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class PlayerMovement : MonoBehaviour { public float moveSpeed = 2f; public float acceleration = 0.01f; public float leftRightSpeed = 4f; public float limit = 5f; public Animator animator; public Vector3 jump; public float jumpForce = 2.0f; public bool isGrounded; Rigidbody rb; public bool isGamePlaying; // Start is called before the first frame update void Start() { rb = GetComponent<Rigidbody>(); jump = new Vector3(0.0f, 2.0f, 0.0f); isGamePlaying = true; } void OnCollisionStay() { isGrounded = true; } void OnCollisionExit() { isGrounded = false; } // Update is called once per frame void Update() { if (isGamePlaying) { transform.Translate(Vector3.forward * Time.deltaTime * moveSpeed, Space.World); moveSpeed += Time.deltaTime * acceleration; if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { if (transform.position.x > -limit) { transform.Translate(Vector3.left * Time.deltaTime * leftRightSpeed); } } if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { if (transform.position.x < limit) { transform.Translate(Vector3.left * Time.deltaTime * leftRightSpeed * -1); } } if (Input.GetKeyDown(KeyCode.Space)) { animator.SetTrigger("a_Jump"); } if (Input.GetKeyDown(KeyCode.Space) && isGrounded) { animator.SetTrigger("a_Jump"); rb.AddForce(jump * jumpForce, ForceMode.Impulse); isGrounded = false; } } else { StartCoroutine(nameof(EndGame)); } } IEnumerator EndGame() { animator.SetBool("a_Idle", true); yield return new WaitForSeconds(2); SceneManager.LoadScene(0); } } |
まずゲームプレイ中かどうかを判定する変数を追加します。
1 |
public bool isGamePlaying; |
Start()メソッドでゲームプレイ中であることを表すtrueを代入します。
1 |
isGamePlaying = true; |
Update()メソッドでは、まず全てのコードの前にif文をが適用されるようにします。ゲームプレイ中の時のみ実行されるようにします。
1 2 3 4 5 |
if (isGamePlaying) { ... ... } |
そして、Updateメソッドの中の元々のコードの下で括弧を閉じます。
閉じたif文の下にelse文を追加し、nameofを使ってメソッド名を取得&コルーチン呼び出しを行い、ゲームを終了させます。
1 2 3 4 |
else { StartCoroutine(nameof(EndGame)); } |
EndGame()では、idleアニメーションを再生している2秒間待機し、その後シーンを読み込み直してゲームを再起動します。
1 2 3 4 5 6 |
IEnumerator EndGame() { animator.SetBool("a_Idle", true); yield return new WaitForSeconds(2); SceneManager.LoadScene(0); } |
SceneManagerを使うためにUnityEngine.SceneManagementの名前空間を追加しておきます。
1 2 3 4 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; |
Ctrl+Sでスクリプトを保存しUnityエディターに戻りましょう。
障害物に衝突した時のアニメーション変更処理を追加
Animator Controllerオブジェクトを選択し、Animatorパネルで、右クリックで “Run “から “Idle “へのトランジションを作成します。
AnimatorのParametersタブで、Boolパラメータを追加し、名前を “a_Idle “とする。
runからidleへの遷移矢印を選択し、インスペクタでa_Idleパラメータがtrueになったときにこの遷移が起こるという条件を追加します。終了トランジションは必要ありません。
(もしキャラクターアセットにゲーム終了時に適したアニメーションがあれば、それを使ってもOKです)
3Dランゲームの障害物衝突を検知するObstacleCrush.csを作成
最後は衝突を検知してゲームを終了させる処理を追加するだけです。
Scriptsフォルダに新しいC#スクリプトを作成します。”ObstacleCrush”という名前を付けてスクリプトを開いてください。
Start()メソッドとUpdate()メソッドを削除し、新しい関数を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ObstacleCrush : MonoBehaviour { private void OnTriggerEnter(Collider other) { if (!other.gameObject.CompareTag("Collectable")) { gameObject.GetComponentInParent<PlayerMovement>().isGamePlaying = false; } } } |
OnTriggerEnterメソッドは、Playerが何かに衝突したことを検出し、それがCollectableかどうかをチェックし、そうでない場合はPlayerMovementスクリプトを見つけ、isGamePlaying boolを変更します。
つまり、収集用アイテム以外にぶつかったらゲーム終了にするということです。
Ctrl+Sでスクリプトを保存し、Unityエディタに戻ります。
Characterプレハブを開き、ObstacleCrush.csをPlayerオブジェクトにドラッグ&ドロップしてアタッチします。
最後に、Collectableプレハブを開きます。インスペクタで、Levelsと同じように新しいタグを追加します。名前は「Collectable」とします。
Collectable タグを Collectable プレハブに割り当てます。
Unityエディターでゲームを再生してテストプレイしてみましょう。
このようにCollectableタグが付いていない障害物と衝突するとゲームが最初からリスタートします。
もし衝突判定がうまくいかない場合はPlayerのBoxColliderの形を再調整しましょう。プレハブに戻って編集してください。サイズ調整やアタッチがちゃんとできていれば障害物入りの3Dエンドレスランゲームが完成しているはずです。
これで障害物ありの3D無限ランゲーム開発は完了です!お疲れ様でした!
このあとはビルド処理やアンドロイドで遊ぶための追加変更を行います。
エンドレス3Dランゲームをビルドしてみよう
このゲームは現在PCまたはブラウザ上で動作するように作られています。
ゲームをビルドしたい場合は、Ctrl+Sでシーンを保存し、メニューからFile → Build Settingsを選択してください。
ビルド設定ではまずゲームに使うシーンをAdd Open Scenesで追加するのを忘れないでください。
ビルドするプラットフォームを変更すればPC版以外の実行ファイルを作ることもできます。例えば、WebGLプラットフォームを選択すると、ブラウザ上でゲームが実行できるようになります。
他のプラットフォームに変更したい場合は、スクリプトやゲームプログラムに変更を加える必要が出てくるでしょう。
ビルドするには、Buildボタンを押して、.exeファイルをコンピューターに保存します。その後、そのファイルを共有したり、開いてゲームをプレイすることができます。
ビルドされるプロダクトをカスタマイズしたい場合は、Player Settingsを適宜変更してください。
おめでとうございます!3Dランゲームの完成ですね!PC版で開発を終了したい方はここまでで終了です。
このあとはおまけとしてアンドロイドで実機ビルド&遊べるようにする方法を解説します。
3Dランゲームをアンドロイドで遊べるように改造してみよう
このゲームをAndroid用にビルドしたい場合はいくつかの変更を加える必要があります。
まず、ビルド設定でプラットフォームをAndroidに切り替えます。
次に、ゲームビューをスマホのアスペクト比、またはデバイスを接続している場合はデバイスのアスペクト比になるように変更します。
さて、このゲームではこれまでキーボード操作でキャラを動かしてきました。アンドロイドで遊ぶためには入力処理の変更のためPlayerMovementスクリプトを修正する必要がある。
スクリプトを開き、Update()内に以下のように修正します。
Our whole code is following:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class PlayerMovement : MonoBehaviour { public float moveSpeed = 2f; public float acceleration = 0.01f; public float leftRightSpeed = 4f; public float limit = 5f; public Animator animator; public Vector3 jump; public float jumpForce = 2.0f; public bool isGrounded; Rigidbody rb; public bool isGamePlaying; // Start is called before the first frame update void Start() { rb = GetComponent<Rigidbody>(); jump = new Vector3(0.0f, 2.0f, 0.0f); isGamePlaying = true; } void OnCollisionStay() { isGrounded = true; } void OnCollisionExit() { isGrounded = false; } // Update is called once per frame void Update() { if (isGamePlaying) { transform.Translate(Vector3.forward * Time.deltaTime * moveSpeed, Space.World); moveSpeed += Time.deltaTime * acceleration; #if UNITY_ANDROID if (Input.touchCount > 0) { Touch touch = Input.GetTouch(0); if (touch.deltaPosition.x < 0) { transform.Translate(Vector3.left * Time.deltaTime * (leftRightSpeed)); } else if (touch.deltaPosition.x > 0) { transform.Translate(Vector3.left * Time.deltaTime * (leftRightSpeed) * -1); } } #elif UNITY_EDITOR || UNITY_EDITOR_WIN if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { if (transform.position.x > -limit) { transform.Translate(Vector3.left * Time.deltaTime * leftRightSpeed); } } if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { if (transform.position.x < limit) { transform.Translate(Vector3.left * Time.deltaTime * leftRightSpeed * -1); } } if (Input.GetKeyDown(KeyCode.Space)) { Jumping(); } #endif } else { StartCoroutine(nameof(EndGame)); } } IEnumerator EndGame() { animator.SetBool("a_Idle", true); yield return new WaitForSeconds(2); SceneManager.LoadScene(0); } public void Jumping() { if (isGrounded) { animator.SetTrigger("a_Jump"); rb.AddForce(jump * jumpForce, ForceMode.Impulse); isGrounded = false; } } } |
まず、最初に追加したif Unity_Androidとelif~から始まるコードは現在対象とするプラットフォームがアンドロイドもしくはUnityエディタかWindowsPCでの操作かどうかを検出しています。もしアンドロイドなら画面のタッチとタッチの位置を検出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void Update() { if (isGamePlaying) { transform.Translate(Vector3.forward * Time.deltaTime * moveSpeed, Space.World); moveSpeed += Time.deltaTime * acceleration; #if UNITY_ANDROID if (Input.touchCount > 0) { Touch touch = Input.GetTouch(0); if (touch.deltaPosition.x < 0) { transform.Translate(Vector3.left * Time.deltaTime * (leftRightSpeed + 2f)); } else if (touch.deltaPosition.x > 0) { transform.Translate(Vector3.left * Time.deltaTime * (leftRightSpeed + 2f) * -1); } } #elif UNITY_EDITOR || UNITY_EDITOR_WIN |
プレイヤーが左にスワイプすると、キーボードのボタンと同じように左に移動し、右にスワイプすると右に移動するように変更を加えています。
また、以前のコードからジャンプの行を切り取り、それを使って新しいメソッドを作ります。
1 2 3 4 5 6 7 8 9 |
public void Jumping() { if (isGrounded) { animator.SetTrigger("a_Jump"); rb.AddForce(jump * jumpForce, ForceMode.Impulse); isGrounded = false; } } |
if文のInput.GetKeyDown(KeyCode.Space) && isGroundedのisGroundedチェックだけを残しました。
また、PC版の処理の後に#endifを追加します。
これでPCでプレイしてるときに実行されるコードは次の部分になっているはずです。
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 |
#elif UNITY_EDITOR || UNITY_EDITOR_WIN if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { if (transform.position.x > -limit) { transform.Translate(Vector3.left * Time.deltaTime * leftRightSpeed); } } if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { if (transform.position.x < limit) { transform.Translate(Vector3.left * Time.deltaTime * leftRightSpeed * -1); } } if (Input.GetKeyDown(KeyCode.Space)<del> && <span style="color: #000000;" data-mce-style="color: #000000;"><del>i</del>sGrounded</span></del>) { Jumping(); } #endif } else { animator.SetBool("a_Idle", true); } } |
Ctrl+Sでスクリプトを保存してUnityエディタに戻ります。
続いて、スマホ版のジャンプボタンを作成します。ヒエラルキー画面でCanvasを選択し、その上で右クリックしてUI > Buttonを作成します。
JumpButtonと名付け、画面内の使いやすい場所に配置し、Anchor Presetsを使ってCanvas内での相対位置を決定します。
Rect TransformのScaleを(0.25, 1.5, 1)に設定しました。
ヒエラルキー画面でJumpButtonオブジェクトを展開し、Textコンポーネントを削除します。
ボタンの画像と外観はインスペクタ画面で変更できます。
Colorを選択し、ボタンが押されたときに表示する「Pressed」の色を選択します。
次に、インスペクタのOnClickイベントで+をクリックし、Characterオブジェクトをドラッグ&ドロップします。
ボタンクリック時に実行するメソッドを設定します。PlayerMovementを選択し、先ほど分離したJumpingメソッドを選択します。
Ctrl+Sで再びシーンを保存します。これでAndroidビルドができるようになりました。
File → Build Settingsでゲームに用いるシーンが含まれていることを確認してPlatformをAndroidに変更しましょう。
この状態でUnityエディタでゲームをプレイするとわかりますが、プラットフォームが「UNITY_ANDROID」になっているため、キーボードのボタンを押してもキャラクターが動きません。
アプリをビルドしてデバイスに直接インストールしたい場合は、アンドロイドをUSBでPCに接続し、アンドロイドのUSBデバッグを有効にして、UnityのBuild SettingsのRun Deviceを選べば実機で実行できます。
必要に応じてPlayer Settingsで実行環境の条件を修正できます。
直接ビルドしてインストールするには、ビルドして実行を選択します。そうでなければ、ビルドしてapkをPCに保存するだけです。
apkファイルを転送または共有することで、どのAndroidデバイスにもインストールできるようになります。
また、もしiOSで実機テストしてみたい場合はif Unity_ANDROIDをif UNITY_IPHONEに変更してあげればOKです。ただしiOSでのテストにはXcodeが必要で実機テストまでの道のりがやや煩雑なので今回は割愛します。iOSテストに挑戦してみたい方は以下の記事を参考にしながら取り組んでみてください。
以下が実際にAndroid上で完成したゲームの実機プレイ動画です:
お疲れ様でした。ここまででアンドロイドでも遊べる3Dエンドレスランゲームができました。
ここからはあなたオリジナルの3Dランゲームを開発してもらえたらと思います。
Unity入門の森では他にも様々なゲームの作り方講座を用意しているのでぜひ開発に挑戦してみてください。
コメント