アイキャッチVR1280x670

3時間でできるはじめてのOculus Goプログラミング「VRブロック崩し」

こんにちは。木村です。
みなさん、Oculus Goは買いましたか?
持っていなかったら、23,800円のお手頃価格で他に何も要らずに手軽にVRが楽しめるのでぜひ買ってみてください!
初めて試すなら無料アプリのMerry SnowballsNextVRスライスアンドダイスあたりがおすすめです。一緒に試す人がいるならOculus Roomsもぜひ試してみてください!

でも、アプリで遊ぶだけでなく、作ってみるともっとVRのおもしろさを体験できます。今回は、Unityを使って、Oculus Goで動く下記動画のようなVRブロック崩しゲーム(キャッチしてるけど)を作っていきましょう!


1. 準備するもの

・Oculus Go(23,800円〜)
・Unity Personal(無料)
・Android Studio(無料)

Oculus Goは以下のリンクから購入してください。

2. Oculus Goを開発者モードに設定

Oculus開発者ダッシュボードにアクセスし、適当な名称で団体を作成します。

Oculus Goのモバイルアプリを起動し、[設定]タブを開きます。

ペアリングしているOculus Goを選択し、[その他の設定] > [開発者モード]をONにします。

3. Unityをインストール

上記リンクからUnity Personalをダウンロードして、最低でも「Unity」「Visual Studio for Mac」「Android Build Support」にチェックをして、インストールしてください。

Unityを起動すると、Unityアカウントでログインを要求されるので、ログインします。アカウントを作っていない場合は、アカウントを作成してください。

4. Android Studioをインストール

上記リンクからAndroid Studioをダウンロードして「Standard」を選択してインストール。

Welcome画面の[Configure]より[SDK Manager]を開き、「Android 5.0(Lollipop)API Level 21」をインストール。

5. Unityプロジェクトの作成

ここからゲームを作っていきますが、ステップバイステップで作っていきますので、面倒な方は末尾にある「※5〜21の手順が面倒な方向け手順」を御覧ください。途中詰まった場合もソースコード丸ごと置いてありますので、そちらを参考にしていただければと思います。

では、作っていきましょう!

以下の設定でプロジェクトを作成します。

・Project name: VR Breakout
・Template: 3D
・Location: 適当なフォルダ
・Enable Unity Analytics: OFF

こんな画面が表示されます。

6. Unityの設定を変更する

[Unity] > [Preferences](Windowsは[Edit] > [Preferences])を開き、[External Tools]タブを選択し、[Android]セクションのSDKの[Browse]ボタンをクリックします。以下のように聞かれるはずなので、[Yes]とします。

続いてJDKの欄です。
JDKをインストールしていない場合は、[Download]をクリックして、先にJDKをインストールしてください。
[Browse]ボタンをクリックすると、同じようにJDKが見つかったと言われるはずなので[Yes]をクリックします。

[File] > [Build Settings]を開き、PlatformでAndroidを選択し、[Switch Platform]をクリックします。

続いて[Switch Platform]の右の[Player Settings]をクリックします。
[XR Settings]セクションの[Virtual Reality Supported]にチェックを入れ、[+]アイコンから[Oculus]を追加します。

同じく[Player Settings]の[Other Settings]セクションで、[Package Name]に適当な値を設定し、[Minimum API Level]で「API level 21」を選択します。

7. この時点で動作確認

作業しているコンピュータに電源を入れたOculus GoをUSBでつなぎます。その状態でOculus Goを被るとPCからの接続の許可を求める画面が出ているので、許可します。

[File] > [Build & Run]をクリックすると、apkファイルの保存先を聞かれますので、適当に指定します。
しばらく待つとOculus Go内でアプリが自動的に起動します。
何も置いてないので地平線が表示されるだけです。

その後は[ライブラリ]の[提供元不明]からアプリを起動することができます。

8. Oculus Utilities for Unityを追加

以下のリンクからOculus Utilities for Unityをダウンロードして解凍します。
これを入れると簡単にVR用カメラ、Oculus Goコントローラを扱えるようになります。

[Assets] > [Import Package] > [Custom Package]で先程解凍した「OculusUtilities.unitypackage」をインポートします。
途中で「Update Oculus Utilities Plugin」というダイアログが出たら[Yes]をクリックして、指示通りUnityを再起動します。

無事インポートできると、AssetsにOculusフォルダが追加されます。

Projectウィンドウは右下のスライダーを左にずらして、ファイル名が見やすいようにAssetsをリスト表示にしておきます。

9. VR用カメラの追加

ProjectウィンドウのAssetsの[Oculus] > [VR] > [Prefabs] > [OVRCameraRig]をHierarchyウィンドウにドラッグ&ドロップします。

[Main Camera]は必要ないので、右クリック > [Delete]で削除します。

10. Oculus Goコントローラの追加

[Oculus] > [VR] > [Prefabs] > [TrackedRemote]をHierarchyウィンドウの[OVRCameraRig] > [TrackingSpace] > [LeftHandAnchor]および[RightHandAnchor]にドラッグ&ドロップします。

[LeftHandAnchor]と[RightHandAnchor]の両方にドロップするのを忘れないでください。

さらに、[LeftHandAnchor] > [TrackedRemote]を選択し、Inspectorの[OVR Tracked Remote (Script)] > [Controller]を「L Tracked Remote」に変更します。

こうすることでOculus Goを左利き設定にしてても大丈夫になります。

11. 再度動作確認

ここまでで一度保存しておきましょう。Command + S(WindowsはCtrl + S)で保存できます。保存については今後明記しませんが、度々保存しておいてください。

Oculus Goをつないだ状態で、[File] > [Build & Run]を実行します。

Oculus GoコントローラがVR画面内の手元に表示され、現実の動きに追随することが確認できます。

以降は動作確認ついては特に書きませんので、好きなタイミングで動作確認してください。

12. ファイル・フォルダの整理

これからいろいろなコンポーネントを作っていきます。その前にファイルとフォルダを整理します。

SampleSceneも格好悪いので、名前をMainSceneに変更します。Projectウィンドウで[Assets] > [Scenes] > [SampleScene]を右クリックして[Rename]で「MainScene」に変更します。

続いてフォルダを作成します。[Assets]の空いているところを右クリックして[Create] > [Folder]から作成できます。

以下のような構成になるように[Main]フォルダ以下を作成してください。

13. GameManagerを作る

今回のゲームのメインスクリプトを持たせるための空オブジェクトを作ります。

Hierarchyウィンドウで右クリックし、 [Create Empty]をクリック。

作成された[GameObject]を右クリックして、[Rename]し、「GameManager」に名称変更します。

Inspectorウィンドウの一番下の[Add Component]ボタンをクリックし、「script」と入力して表示された[New script]をクリックします。「GameManager」と入力してスクリプトを作成します。
ちなみにわかりやすさのためにオブジェクトを作っていますが、Stageにつけようが、カメラにつけようが問題なく動きます。

[Assets]直下に作成されているので、[Main] > [Scripts]の下にドラッグ&ドロップで移動します。

[Scripts] > [GameManager]をダブルクリックするとVisual Studioで開かれます。今後、スクリプトを修正する場合は、Visual Studioで編集します。

14. ステージを作る

ブロック崩しの壁を作っていきます。今回は直方体の部屋を作ってその中でゲームをするように作ります。

Hierarchyウィンドウで右クリックし、[Create Empty]をクリック。作成された[GameObject]を右クリックして、[Rename]し、「Stage」に名称変更します。これはただのフォルダ代わりのオブジェクトです。

右の壁から作っていきます。[Stage]を右クリックして、[3D Object] > [Cube]をクリック。

作成された[Cube]を以下のように変更します。
・名前: Right
・Position: X:3, Y:2, Z:0
・Rotation: X:0, Y:0, Z:0
・Scale: X:20, Y:5, Z:1

左の壁(Left)を作ります。作成した[Right]を右クリックして[Duplicate]すると少し作業が楽です。

天井(Roof)を作ります。

地面(Ground)を作ります。

前面(Front)を作ります。

背面(Back)を作ります。

Physic Materialを作ります。

以下の設定で作成します。ボールが壁に当たったときに摩擦なし、エネルギー損失なしで跳ね返るように設定しています。
・名前: Default Physic Material
・Dynamic Friction: 0
・Static Friction: 0
・Bounciness: 1
・Friction Combine: Minimum
・Bounce Combine: Maximum

作成した壁を全部選択した状態で、Projectウィンドウの[Default Physic Material]をInspectorウィンドウの[Box Colider] > [Material]にドラッグ&ドロップします。

カメラの位置を少し移動します。Hierarchyウィンドウで[OVRCameraRig]を選択し、Positionを変更します。

15. ブロックを作る

崩す対象のブロックを作成します。

Hierarchyウィンドウで[MainScene]を選択し、右クリック > [3D Object] > [Cube]をクリックします。

作成した[Cube]を以下のように変更します。Physic Materialは先程と同じようにProjectウィンドウからドラッグ&ドロップします。

ブロックを半透明にしたいので、透明にできるMaterialを設定します。
[Assets] > [Main] > [Materials]で右クリックし、[Create] > [Material]をクリックします(Physic Materialと間違えないようにしてください)。
作成したら、以下のように変更します。
・名前: Fade Material
・Rendering Mode: Fade

[Albedo]の右の白い四角をクリックすると、Colorダイアログが表示されるので、[A]の欄を128に変更して、白の半透明にします。

[Block]のInspectorウィンドウの[Mesh Renderer] > [Materials] > [Element 0]に[Fade Material]をドラッグ&ドロップします。

これでブロックはできましたが、たくさん作る必要があるので、プログラムで簡単に増やせるようにPrefab(プレハブ)にします。

Projectウィンドウで[Assets] > [Main] > [Prefabs]を開きます。Hierarchyウィンドウから[Block]を[Prefabs]にドラッグ&ドロップします。

これでプログラムからブロックを大量作成する準備ができました。Hierarchyウィンドウの[Block]はもう必要ないので、右クリック > [Delete]で削除してください。

続いて、ブロックにボールが当たったらブロックが消滅するようにしましょう。

Projectウィンドウで[Assets] > [Main] > [Prefabs] > [Block]をクリックし、Inspectorウィンドウの一番下の[Add Component]ボタンをクリックし、「script」と入力して表示された[New script]をクリックします。「BlockController」と入力してスクリプトを作成します。
(※Inspectorウィンドウの配置をスクショ撮りやすいよう変えています。)

[Scripts] > [BlockController]をダブルクリックして、Visual Studioで開きます。以下のコードをコピーして、BlockController.csのコードを置き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class BlockController : MonoBehaviour
{
    public UnityEvent OnDestroy;

    // Use this for initialization
    void Start()
    {
        if (OnDestroy == null)
            OnDestroy = new UnityEvent();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnCollisionEnter(Collision collision)
    {
        // ブロックを消す
        Destroy(this.gameObject);
        // ブロック削除イベントを発生させる
        OnDestroy.Invoke();
    }
}

Startは最初のフレーム(パラパラ漫画でいうコマのような。ちょっと違うけど。)の直前に呼ばれます。Updateは毎フレーム呼び出されます。OnCollisionEnterは他の物体にぶつかったときに呼ばれます。
ここでは何かぶつかったら自ブロックを消してます。ブロック削除イベントは後で使います。

続いて、[Scripts] > [GameManager]をダブルクリックして、Visual Studioで開きます。以下のコードをコピーして、GameManager.csのコードを置き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    [SerializeField]
    private GameObject blockPrefab;

    // ブロックの残数
    private int blockCount;

    // Use this for initialization
    void Start()
    {
        CreateBlocks();
    }

    // Update is called once per frame
    void Update()
    {
    }

    private void CreateBlocks()
    {
        // 適当にブロックの色を用意
        var colors = new Color[] {
            new Color(1.0f, 0.0f, 0.0f, 0.5f),
            new Color(0.0f, 1.0f, 0.0f, 0.5f),
            new Color(0.0f, 0.0f, 1.0f, 0.5f),
            new Color(1.0f, 1.0f, 0.0f, 0.5f),
            new Color(0.0f, 1.0f, 1.0f, 0.5f),
            new Color(1.0f, 0.0f, 1.0f, 0.5f),
            new Color(1.0f, 0.5f, 0.5f, 0.5f),
            new Color(0.5f, 1.0f, 0.5f, 0.5f),
            new Color(0.5f, 0.5f, 1.0f, 0.5f)
        };
        // ブロックの生成
        for (int x = 0; x < 5; x++)
        {
            for (int y = 0; y < 4; y++)
            {
                for (int z = 0; z < 4; z++)
                {
                    var position = new Vector3(-2.0f + x * 1.0f, 0.8f + y * 0.8f, -1.0f + z * 2.0f);
                    GameObject block = Instantiate(blockPrefab, position, new Quaternion());
                    block.GetComponent<Renderer>().material.color = colors[z];
                    // ブロックが削除されたときの処理を追加
                    block.GetComponent<BlockController>().OnDestroy.AddListener(HandleBlockDestroy);
                    blockCount++;
                }
            }
        }
    }

    private void HandleBlockDestroy()
    {
        blockCount--;

        if (blockCount == 0)
        {
            // ゲーム終了
        }
    }
}

これはStart(ゲーム開始時)でブロックを5 x 4 x 4 = 80個作っているだけですね。ブロックが削除されたらブロックの残数を減らしてます。0になったらゲーム終了です。

GameManagerを選択し、Inspectorで[Block Prefab]を設定します。

[Play]ボタンを押すとブロックがたくさん作られることが確認できます。

16. ラケットとボールを作る

ブロックを崩すためにはブロックに当てるボールと打ち返すラケット(?)が必要です。作っていきましょう。

発射前はラケットにボールがくっついて動くようにするために、[RacketArea]という親オブジェクトを作り、ラケットとボールはその子供になるように作っていきます。

[MainScene]を選択して右クリック > [Create Empty]をクリック。[RacketArea]を作成します。以下のようにPositionとScaleを変更します。

ボールを作ります。[RacketArea]を選択して右クリック > [3D Object] > [Sphere]をクリック。以下のように設定します。

ボールは物理法則に則って動くようにRigidbodyを追加します。[Add Component] > [Rigidbody]を追加します。

1kg、空気抵抗なし、重力無効、物理演算OFFに設定します。物理演算OFFにしているのは、最初ラケットにボールがくっついていてほしいためです。後でスクリプトからONにします。

ラケットを作ります。同じく[RacketArea]の下に[Cube]を作成し、以下のように設定します。

[Play]ボタンをクリックしてみると、ここまででこのようになっているはずです。

17. ボールに効果音をつける

ボールが何かに当たったときの効果音をダウンロードします。以下のリンクから「ボヨン」をダウンロードしてください。

ダウンロードした「boyon1.mp3」をProjectウィンドウの[Assets] > [Main] > [Audio]にドラッグ&ドロップします。

Hierarchyウィンドウで[Ball]を選択し、Inspectorウィンドウの一番下の[Add Component]ボタンをクリックし、「audio」と入力して表示された[Audio Source]をクリックして追加します。

追加された[Audio Source]コンポーネントの[Audio Clip]に先ほどのboyon1をドラッグ&ドロップし、[Play On Awake]のチェックを外します。

再び[Add Component] > [New Script]をクリック。「BallController」と名前をつけてスクリプトを作成します。

[Assets]直下に作成されているので、[Main] > [Scripts]の下にドラッグ&ドロップで移動します。

BallControllerをダブルクリックするとVisual Studioで開かれます

以下のコードをコピーして、Visual StuidoでBallController.csの中身を丸ごと置き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
    }

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突音を鳴らす
        GetComponent<AudioSource>().Play();
    }
}

ボールが何かに当たったら音を鳴らしているだけですね。

18. ラケットにプログラムをつける

[Racket]を選択し、[Add Component] > [New Script]から「RacketController」を作成します。

以下のコードをコピーし、置き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RacketController : MonoBehaviour
{
    [SerializeField]
    private Transform rightHandAnchor;

    [SerializeField]
    private Transform leftHandAnchor;

    [SerializeField]
    private Transform centerEyeAnchor;

    [SerializeField]
    private GameObject ball;

    // ボールの速度
    private float speed = 400.0f;

    // ボールを発射済みか
    private bool isBallMoving = false;

    private Transform Pointer
    {
        get
        {
            // 現在アクティブなコントローラーを取得
            if (OVRInput.IsControllerConnected(OVRInput.Controller.RTrackedRemote))
            {
                return rightHandAnchor;
            }
            else if (OVRInput.IsControllerConnected(OVRInput.Controller.LTrackedRemote))
            {
                return leftHandAnchor;
            }
            // どちらも取れなければ目の間
            return centerEyeAnchor;
        }
    }

    // Use this for initialization
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        MoveRacket();

        ShootBall();
    }

    void OnCollisionEnter(Collision collision)
    {
        CatchBall(collision);
    }

    private void MoveRacket()
    {
        // Oculus Go上で動いている場合は、Oculus Goのコントローラの少し先にラケットを表示する
        if (OVRManager.isHmdPresent)
        {
            var pointer = Pointer;
            if (pointer != null)
            {
                transform.parent.forward = pointer.forward;
                transform.parent.position = pointer.transform.position + (pointer.transform.forward * 2.5f);
            }
        }
        // 開発用にPCの場合は矢印キーで操作する
        else
        {
            float racketSpeed = 5.0f;

            if (Input.GetKey(KeyCode.UpArrow))
            {
                transform.parent.position += transform.up * racketSpeed * Time.deltaTime;
            }
            if (Input.GetKey(KeyCode.DownArrow))
            {
                transform.parent.position -= transform.up * racketSpeed * Time.deltaTime;
            }
            if (Input.GetKey(KeyCode.RightArrow))
            {
                transform.parent.position += transform.right * racketSpeed * Time.deltaTime;
            }
            if (Input.GetKey(KeyCode.LeftArrow))
            {
                transform.parent.position -= transform.right * racketSpeed * Time.deltaTime;
            }
        }
    }

    private void ShootBall()
    {
        if (Input.GetKeyDown(KeyCode.Space) || OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger))
        {
            if (!isBallMoving)
            {
                isBallMoving = true;
                // ボールが自由に動くように親を解除
                ball.transform.parent = null;
                // Rigitbodyに力を加えてボールを発射
                var rb = ball.GetComponent<Rigidbody>();
                rb.isKinematic = false;
                rb.AddForce(transform.forward * speed);
            }
        }
    }

    private void CatchBall(Collision collision)
    {
        // ラケットがボールと衝突したらボールをキャッチ
        if (collision.gameObject.name == "Ball")
        {
            var ballObj = collision.gameObject;
            // ボールがラケットについてくるようにRacketAreaの子にする
            ballObj.transform.parent = transform.parent;
            // ボールをラケットの手前に配置
            ballObj.transform.position = transform.position + (transform.forward * 0.5f);
            var rb = ballObj.GetComponent<Rigidbody>();
            // 物理演算を無効にして、ボールを止める
            rb.isKinematic = true;
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
            isBallMoving = false;
        }
    }
}

これはちょっと長いですね。詳しくはコード内のコメントを見てください。簡単に言うといか3つの処理をしています。
・Oculus Goコントローラに連動したラケットの移動処理
・ボールがラケットに当たったらボールをキャッチ
・トリガーボタンが押されたら、ボールを発射

RacketControllerの書き込み可能な項目を以下のようにドラッグ&ドロップして設定します。

[Play]ボタンで動かすと、ここまでの動作を確認できます。矢印キーでラケットが動き、スペースキーでボールを発射できます。ボールがブロックに当たれば、音がなってブロックが消えますね。

19. タイマーとクリア表示のUIを作る

もうちょっとゲームらしくするために経過時間を表示するタイマーとブロックが全部消えたときにCLEAR!!と表示してみましょう。

そういったUI系はCanvasコンポーネントの下に作成します。[MainScene]を選択して、右クリック > [UI] > [Canvas]をクリックし、Canvasを作成します。
作成したら、まず[Render Mode]を[World Space]に変更してから、以下のように設定します。

[Canvas]の下に[UI] > [Text]として[TimerText]を作成し、以下のように設定します。

同じく[Canvas]の下に[UI] > [Text]として[ClearText]を作成し、以下のように設定します。Colorは何色でも構いませんが、ここでは「FFFF00」に設定しています。
[Text (Script)]セクションのチェックを外すのを忘れないようにしてください。クリアしたときにプログラムでチェックを入れることによってクリア後だけ表示するようにします。

UIに関してはGameウィンドウで配置を確認すると、確認しやすいと思います。確認のためClearTextを有効にすると、以下のように表示されると思います。

20. タイマーやクリアの処理を作る

よりゲームらしくしていきましょう。

まずBGMをつけます。
BGM素材を以下のリンクからダウンロードします。

ダウンロードリンクがわかりにくいですが、最初に[音楽素材ダウンロードページへ]のボタンをクリックして遷移したページで、以下のピンクのボタンをクリックします。

ついでにクリア時のBGMもダウンロードしておきましょう。

[Assets] > [Main] > [Audio]にダウンロードした「急げ急げ!.mp3」と「Victory_March.mp3」をドラッグ&ドロップします。

Hierarchyウィンドウで[GameManager]を選択し、Inspectorから[Add Component] > [AudioSource]としてAudioSourceを追加し、以下のように設定します。
・AudioClip: 「急げ急げ!」をドラッグ&ドロップ
・Loop: チェックする
・Volume: 0.3

[Scripts] > [GameManager]をVisual Stuidoで開き、以下のコードに置き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    [SerializeField]
    private GameObject ball;

    [SerializeField]
    private GameObject blockPrefab;

    [SerializeField]
    private GameObject timerText;

    [SerializeField]
    private GameObject clearText;

    [SerializeField]
    private AudioClip clearBGM;

    // ゲームスタートしたか
    private bool isStarted = false;

    // ブロックの残数
    private int blockCount;

    // 経過時間
    private float totalTime = 0.0f;

    // Use this for initialization
    void Start()
    {
        CreateBlocks();
    }

    // Update is called once per frame
    void Update()
    {
        // トリガが押されたらゲームスタート
        if (Input.GetKeyDown(KeyCode.Space) || OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger))
        {
            isStarted = true;
        }

        // 戻るボタンが押されたらリセット
        if (Input.GetKeyDown(KeyCode.Return) || OVRInput.Get(OVRInput.Button.Back))
        {
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }

        CountTime();
    }

    private void CreateBlocks()
    {
        // 適当にブロックの色を用意
        var colors = new Color[] {
            new Color(1.0f, 0.0f, 0.0f, 0.5f),
            new Color(0.0f, 1.0f, 0.0f, 0.5f),
            new Color(0.0f, 0.0f, 1.0f, 0.5f),
            new Color(1.0f, 1.0f, 0.0f, 0.5f),
            new Color(0.0f, 1.0f, 1.0f, 0.5f),
            new Color(1.0f, 0.0f, 1.0f, 0.5f),
            new Color(1.0f, 0.5f, 0.5f, 0.5f),
            new Color(0.5f, 1.0f, 0.5f, 0.5f),
            new Color(0.5f, 0.5f, 1.0f, 0.5f)
        };
        // ブロックの生成
        for (int x = 0; x < 5; x++)
        {
            for (int y = 0; y < 4; y++)
            {
                for (int z = 0; z < 4; z++)
                {
                    var position = new Vector3(-2.0f + x * 1.0f, 0.8f + y * 0.8f, -1.0f + z * 2.0f);
                    GameObject block = Instantiate(blockPrefab, position, new Quaternion());
                    block.GetComponent<Renderer>().material.color = colors[z];
                    // ブロックが削除されたときの処理を追加
                    block.GetComponent<BlockController>().OnDestroy.AddListener(HandleBlockDestroy);
                    blockCount++;
                }
            }
        }
    }

    private void HandleBlockDestroy()
    {
        blockCount--;

        if (blockCount == 0)
        {
            // ゲーム終了
            isStarted = false;
            // クリア表示
            clearText.GetComponent<Text>().enabled = true;
            // 物理演算を無効にして、ボールを止める
            ball.GetComponent<Rigidbody>().isKinematic = true;
            // クリアBGM再生
            GetComponent<AudioSource>().Stop();
            GetComponent<AudioSource>().PlayOneShot(clearBGM);
        }
    }

    private void CountTime() {
        if (isStarted)
        {
            totalTime += Time.deltaTime;
            timerText.GetComponent<Text>().text = totalTime.ToString("F2");
        }
    }
}

前のGameManagerに以下の処理を追加しています。
・経過時間の表示
・ブロックがなくなったらゲーム終了表示
・戻るボタンが押されたらリセット

GameManagerスクリプトの書込み可能な項目を以下のようにInspectorでドラッグ&ドロップして設定します。

21. 完成

おめでとうございます!これで完成です!

[File] > [Build & Run]を実行し、Oculus Go実機で遊んでみましょう。

※5〜21の手順が面倒な方向け手順

5以降の手順が面倒な方向けにUnityプロジェクト丸ごとのソースコードも用意しました。以下の手順で動かすことができます。

1. 上記リンクからソースコードをダウンロードして、解凍したフォルダをUnityで開きます。
2. [Assets] > [Scenes] > [MainScene]を開く。
3. 「6. Unityの設定を変更する」をする。
4. 「7. この時点で動作確認」の手順でOculus Go実機にデプロイ。

途中でうまく動かなくなったときもこちらのコードを参考にしてください。

22. 終わりに

ブロック崩しと言いながら、毎回ラケットでボールをキャッチして投げるという動きになっています。これはそのままブロック崩しのルールにしてしまうとつまらなかったからです。BlockController#CatchBallをコメントアウトすると、いわゆるブロック崩しの動きになりますので、ぜひそれでも試してみてください。

遊んでいると、なかなかボールが戻ってこないときがあるとか、ライティングが暗いとか、いろいろ気になる点があるかと思います。そういった気になるところはぜひ自由にカスタマイズして、よりよくしてみてください!

この記事が気に入ったらサポートをしてみませんか?