LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

Vuforiaを使って楽しいARを作ろう!

Last updated at Posted at 2017-06-22

1. 前置き

AR爆上げ

2016年はVR元年と呼ばれておりますが、最近ではむしろARの方がとんでもない勢いで流行っています。
もはや、顔に画像を重ねてユニークな映像を撮るようなアプリは珍しくなくなってきています。

市場的な規模でいうと、AR市場はこの5年間で数倍~数十倍にも伸びると言われており、調査によってはAR市場はVR市場を超えると言われております。もちろん調査によってその結果はまちまちですが、それだけARが注目されている技術であるということには変わりはありません。

このカリキュラムをやる意味

・注目のARを簡単に楽しく作れる!
・せっかくTECH::CAMP VRにいるのだからXR(VR、AR、MR...)技術に触れたくない?

What is Vuforia?

Vuforiaは世界中の幅広い技術者に使われているARプラットフォームです。基本はマーカーを使用し、認識したマーカーを基準にして実際の映像にCGを重ねることでARを作ります。
最近ではAppleからARKitが発表されましたが、機能的にはそれに似ています。誰しも(個人開発なら)無料で、ARアプリに必要な技術を手に入れることができます。
口で説明するよりも実際に見た方が早いでしょう。
以下の動画を見てみてください!

動画1
動画2

ワクワクしてきましたか!?

今回作るもの

今回は簡単なイライラ棒みたいなものを作ります。マーカーを使用して行います。

alt

2. 前準備

Vuforiaのシステムを使うには、少々前準備が必要です。
そんなに時間はかからないので、大丈夫です!

Vuforiaにdeveloper登録

こちらからdeveloper登録しましょう。「Register」というところから名前、パスワード、メールアドレスを登録してください。
alt

ライセンスキーを登録

developer登録したら、もう一回Vuforiaのサイトに行き、ログインしてください。
ログインしたら、サイト上部の「Develop」を押してください。
すると以下のようなページに移動したと思います。
alt
(※初めてVuforiaを使用する方の場合、License ManagerのName欄にはまだ何も登録されておりません。)
基本、一つのプロジェクトにつき一つのライセンスキーを取得します。それでは、ライセンスキーを取得しましょう。

「Add License Key」を押してください。すると以下のページに移動します。
alt

今回は「Project Type」に「Development」を指定します。アプリを他者に販売しない開発の場合、このタイプを選びます。それ以外の場合だと、料金が発生します。指定後、「Project Details」欄が出てきますので、適当にApp Nameを指定してください。
alt
こんな感じで。
打ち終わったら、「Next」を押してください。
そしたら確認ページができるので、規約同意にクリックして、「confirm」を選択してください。
License Managerの欄にリダイレクトされるので、ちゃんとLicense Keyが作成されてるかを確認してください。

データベースを登録

それでは次に、ARアプリを使う上でのマーカーを登録します。ARアプリでは現実世界に画像を重ねます。ソフトウェアが描画作業を行う上で重要なのは、描画する画像と現実世界のスケールを合わせることです。スケール合わせの基準を作るためにマーカーを使用します。

それでは、マーカーを登録しましょう。
上のタブから、「Target Manager」をさらに「Add DataBase」を押してください。
すると以下のようなポップアップが出てくるので、適当に名前をつけて、Typeは「Device」を指定します。
alt

新しくDatabaseが追加されたと思うので、そのDatabase名をクリックしてください。
するとTarget(マーカーのこと)を登録するページに来たので、「Add Target」を押してください。以下のページが出ます。
alt
Vuforiaのマーカーとして使えるのは4種類あって、画像・直方体・円柱・3Dオブジェクトのどれかです。どれでも良いのですが、今回はオライリーの画像を使用しました。(これにした理由は、たまたま本を持っててマーカーとして使えたからです。お好きなもの使ってください。)
Type・マーカーにするFile・Width(マーカーの大体の幅(m)、大雑把で良い)・名前を入力してAddしてください。
すると、マーカーが登録されました!
akt
上の画像の「Rating」という欄は、「検知のしやすさ」を表しています。星4つ以上あれば良いと思いますので、足りなかったら違うマーカーを試してみましょう。

データベースをダウンロード

データベースをダウンロードします。上の画像の「Download Database(All)」を選択してください。
alt
「Unity Editor」をチェックしてダウンロードしましょう。「???.unitypackage」みたいなやつがダウンロードされてたらokです!

ふー前準備が終わりました。これから実装に入っていきます!

3. 実装

それでは実装に入っていきます。
ちなみに動作環境は
Unity 5.6.2f1
Vuforia 6.2.10
です。

特にUnityは最新版にアップデートしてください!(僕は5.6.0でやってたら上手くいかなかったです...)

またマーカーがトラッキングされているかどうかを確認するときに、webカメラを使うと楽になるかもしれないので、もし必要だったら神宮まで言ってください。(なくてもできるっちゃできます)

必要なものをインポート

それでは新規プロジェクトを作ってください。Asset Storeで「Vuforia AR Starter Kit」を検索してインポートしといてください。Vuforiaを使うための基本的な機能が入ってます。(sampleとかはチェック外してokです)
alt
例の「API Update Required」が出て来ますが、Go Ahead!

そして、先ほどVuforiaからダウンロードして来た「???.unitypackage」みたいなやつもプロジェクトにインポートしてください。
Image Targetを登録した方はダウンロードしたマーカー(今回は Editor/QCAR/ImageTargetTexture/??? にあります)のTexture Shapeが「Cube」になってるかもなので、「2D」に変えといてください。

alt

またEthanとモバイル用のVirtualPadも使用するので、Standard AssetsからCharactersCrossPlatformInputもインポートしといてください。

迷路ジェネレーターのアルゴリズム

まずVuforiaに入る前に、ゲームのシステムを普通に作ってしまいましょう!
システムの概要としては、「mazeボタンを押したら迷路が作られ、startボタンを押したらプレイヤーが現れ、ゲームが始まる」というものです。

MazeGenerator.cs
using UnityEngine;

public class MazeGenerator : MonoBehaviour {
    [SerializeField] int mazeSize;
    [SerializeField] GameObject wallPrefab;

    private GameObject Maze{ get; set;}
    private int MazeSize{ get{ return mazeSize; }}
    private int MazeW{ get; set;}

    public void Generate(){
        if (Maze) {
            Destroy (Maze);
        }
        GenerateMaze ();
    }


    void GenerateMaze(){
        MazeW = MazeSize * 4 + 2;
        bool[,] mdata = GenerateMazeData ();

        Maze = new GameObject ("maze");
        Maze.transform.SetParent (this.transform.parent);

        for (int i = 0; i < MazeW; i++) {
            for (int j = 0; j < MazeW; j++) {
                if (mdata [i, j]) {
                    Vector3 pos = new Vector3 ((float)i, 0.5f, (float)j);
                    GameObject wall = Instantiate(wallPrefab, pos, Quaternion.identity);
                    wall.transform.SetParent (Maze.transform);
                }
            }
        }   
        CreatePlane ();
    }


    bool[,] GenerateMazeData(){
        System.Random rnd = new System.Random (System.Environment.TickCount);
  
        bool[,] mdata = new bool[MazeW, MazeW];
                                
                //①周囲の値を全てtrueに
        for (int i = 0; i < MazeW; i++) {
            for (int j = 0; j < MazeW; j++) {
                if(i == 0 || i == (MazeW - 1) || j == 0 || j == (MazeW - 1)){
                    mdata[i, j] = true;
                }
            }
        }

                //②進んでいく方向の選択肢を提示
        int[,] arw = new int[,] {
            { 0, -1 }, { 0, 1 }, { -1, 0 }, { 1, 0 }
        };

                //③ランダムにブロックを選んで、壁を直線上に配置していく
        for (int i = 0; i < (MazeSize / 2) * (MazeSize / 2); i++) {
            while (true) {
                int x = rnd.Next (1, MazeSize) * 4;
                int y = rnd.Next (1, MazeSize) * 4;

                if (mdata [x, y]) {
                    continue;
                }

                mdata [x, y] = true;

                int n = i % 4;
                while (true) {
                    x += arw [n, 0];
                    y += arw [n, 1];
                    if (mdata [x, y]) {
                        break;
                    } else {
                        mdata [x, y] = true;
                    }
                }
                break;
            }
        }
        return mdata;
    }


    void CreatePlane(){
        GameObject obj = GameObject.CreatePrimitive (PrimitiveType.Plane);
        obj.transform.localScale = new Vector3 ((float)MazeW/10, 1f, (float)MazeW/10);
        obj.transform.position = new Vector3 ((float)MazeW/2 - 0.5f, 0f, (float)MazeW/2 - 0.5f);
        obj.transform.SetParent (Maze.transform);
    }
}

迷路ジェネレータのアルゴリズムです。mazeSizeを変えることで自分の好みの大きさで迷路を作ることができます。
しょっぱなから面倒臭そうですが、今回のナレッジで理解しにくいのはここだけです。 
わかりにくいのはvoid GenerateMaze(){}bool[,] GenerateMazeData(){}ですね。

public void Generate(){}は単純に迷路を作成してるだけ(あとでGenerateメソッドとUIのボタンとを紐付け)で、もし迷路が既にあったら元のやつをdestroyしてます。void CreatePlane(){}では、作った迷路の壁に合う大きさの床を作成してます。

後の二つは分けて解説します。

bool[,] GenerateMazeData(){}

上のスクリプトに①~③で通し番号を降ったので、それに従って解説します。
このメソッドはbool型の二次元配列(true>>ブロックを置く、 false>>ブロックを置かない に対応)を返します。

まず最初に全ての区画をfalseにします。(⓪に対応)
次に一番外側の区画をtrueにします。(①に対応)
続いて、falseのブロックの中からランダムにブロックを選び、そのブロックを起点として、直線上にブロックを配置していきます。(③に対応) ブロックの進む方向はあらかじめ四通り用意しておきます。(②に対応)

alt

要するに、

ランダムに区画を選び、その区画を起点にして、四方向のうちのどれかに向かって壁を増やしていく。ブロックに当たったらやめる。またランダムに区画を選ぶ。

の繰り返しです。
その方法だったら、場合によっては閉ざされた区画ができてしまいそうですが、できません。
なぜなら、区画をランダムに決める時、 x,yの選び方を

int x = rnd.Next (1, MazeSize) * 4;
int y = rnd.Next (1, MazeSize) * 4;

としているので、xとyは「4 ≦ i <MazeSize * 4 を満たす4の倍数」です。つまり、ランダムに選ばれる区画はお互い隣同士になることはなく間引かれているので、閉ざされた区画は生じません。

また、Sytem.Random名前空間に関してはこちらを参照してください。

void GenerateMaze(){}

このメソッドでは、GenerateMazeDataメソッドの返り値であるbool型の二次元配列に従って、迷路の壁(wallPrefab)の配置を決めます。

具体的には、
・mdata[i, j] == true の場合、((float)i, 0.5f,(float)j)の位置にwallPrefabを配置します。

・mdata[i, j] == false の場合、((float)i, 0.5f,(float)j)の位置には何も置きません。

また、壁を配置した後に床も配置してます。
...迷路ジェネレータのアルゴリズムはこんな感じです。

この「wall」というPrefabは、単にプリミティブのCubeをprefab化しただけなので、特別な設定はいらないです。

alt

また現時点のヒエラルキー上では「MazeGenerator」と「PlayerGenerator」は「Generator」(Emptyに名前つけただけ)の子要素に一時的になってます。「MazeGenerator」と「PlayerGenerator」は後ほどVuforiaのアセットの子要素になります。(「Generator」はいずれ用済みになるのでその時はdeleteしてok。)

Player(Ethan)を登場させる

PlayerGenerator.cs
using UnityEngine;

public class PlayerGenerator : MonoBehaviour {

    [SerializeField] GameObject playerPrefab;
    private GameObject Player{ get; set;}

    public void Generate(){
        if (Player) {
            Respawn ();
            return;
        }

        Player = Instantiate (playerPrefab, Vector3.one * 2, Quaternion.identity);
        Player.transform.SetParent (this.transform.parent);
    }

    public void Respawn(){
        Player.transform.position = Vector3.one * 2;
    }
}

GenerateメソッドではPlayerをInstantiateします。Playerがすでにいる場合はPlayerを初期位置に移動するだけにして、処理を軽くします。

あとで、GenerateメソッドとUIのボタンとを紐付けます。

alt
シーンに「PlayerGenerator」オブジェクトを作って、スクリプトをアタッチしましょう。
「playerPrefab」の欄には、StandardAssetsからThirdPersonControllerをアタッチしてください。

UI

迷路の作成とプレイヤーの作成をボタンでできるようにします。
下のようにUIを適当に配置してください。

alt

↓ヒエラルキー上ではこんな感じです。
alt

それではスクリプトを書いて、ボタンとメソッドとを紐付けます。

UIManager.cs
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using UniRx.Triggers;

public class UIManager : MonoBehaviour {

    [SerializeField] Button 
    maze_button,
    player_button;

    [SerializeField] MazeGenerator m_generator;
    [SerializeField] PlayerGenerator p_generator;

    public void SetupUI(){
        maze_button.OnClickAsObservable ()
            .Subscribe (_ => m_generator.Generate());

        player_button.OnClickAsObservable ()
            .Subscribe (_ => p_generator.Generate ());
    }
}

ボタンのクリックイベントには、UniRxを使用しております。別に使わなくてもできますが、まあせっかくなので笑 UniRxはインポートしといて下さい。また、UniRxについてはこちらとかこちらとかを参照していただければと思います!

[SerializeField]属性がついているオブジェクトには、ヒエラルキー上からそれぞれ同じ名前のものをアタッチしてください。(雰囲気でわかると思います!)

また、Game全体のマネージャーであるGameManager.csも作ります。

GameManager.cs
using UnityEngine;

public class GameManager : MonoBehaviour {

    [SerializeField] UIManager uiManager;

    void Start () {
        uiManager.SetupUI ();
    }

}

GameManager.csによって、ゲーム開始時にUIがセットされます。ちなみに今回のプロジェクトではStart関数はこのGameManager.csでしか使っておりません。(デフォルトで組み込まれているScriptを除く)個人的にはStart関数を全体で一つだけにすると、あとで見直したときにゲームシステムが理解しやすいかなと思います。

それではシーンを再生してみましょう!
ここまで上手く行ってれば下の動画みたいな感じになってると思います。(mazeボタンを押してからplayerボタンを押してください、Ethanが奈落の底に落ちていきます)
alt

マーカーの上に表示させる

ここからやっとVuforiaに入ります!
ProjectビューのVuforia>PrefabsからARCameraプレファブImageTargetプレファブをシーンに置いてください。

そして、もともとシーンにあるMainCameraを削除してください。また、MazeGeneratorオブジェクトとPlayerGeneratorオブジェクトはImageTargetオブジェクトの子要素に置いて、Generatorオブジェクトは削除しましょう。
VuforiaのImageTargetを使う場合(つまりは画像をマーカーにする場合)、ARとして表示したいオブジェクトは全てImageTargetオブジェクトの子要素として配置します。そのため、mazeGeneratorやPlayerGeneratorでは、生成した迷路やプレイヤーがImageTargetオブジェクトの子オブジェクトになるように実装しています。

↓ヒエラルキー上ではこうなってると思います。(ただし、マーカーはまだ指定していないので、Sceneビューにはマーカーは表示されていません)
alt

それではマーカーの設定をしていきましょう。VuforiaのLicense ManagerでLicense Keyをコピーしてください。
alt

次にUnityでARCameraオブジェクト(ヒエラルキー上)>Vuforia Behaviourコンポーネント>「Open Vuforia configuration」をクリックして、VuforiaConfigurationを開きます。

先ほどコピーしたLicenseKeyをAppLicenseKeyにペーストし、更にDatasetsの欄内にある「Load <自分がダウンロードしたdatabase名> Database」 と「Activate」にチェックをつけます。
alt

次にImageTargetオブジェクト>ImageTargetBehaviourコンポーネント内の「Database」と「ImageTarget」を、それぞれ自分が使用するDatabase名・Target名に変更します。
alt

ちゃんとトラッキングできてるかどうかを表示

mazeボタンを押すと、マーカーの上に迷路が作られます。マーカーがちゃんとトラッキングされてない状態でmazeボタンを押すと変な挙動をしてしまいます。
そのため、マーカーをちゃんとトラッキングできてるかどうかを画面に表示させます。
↓こんな感じのイメージです。
alt

↓hierarchyではこんな感じ。「tracking_state」は普通のTextです。
alt

alt

トラッキング状態の表示のため、ImageTargetオブジェクトにアタッチされているDefaultTrackableEventHandlerスクリプト内に記述していきます。このスクリプトはImageTargetプレファブにもともとアタッチされているスクリプトでその内部をいじるのはあまり好ましくないかな?とも思いますが、元のスクリプトには確実に影響が出ないぐらいの付け足しなので、今回は許してもらいましょう。

DefaultTrackableEventHandler.cs
using UnityEngine;
using UnityEngine.UI;

namespace Vuforia
{
    /// <summary>
    /// A custom handler that implements the ITrackableEventHandler interface.
    /// </summary>
    public class DefaultTrackableEventHandler : MonoBehaviour,
                                                ITrackableEventHandler
    {
                   
        [SerializeField] Text tracking_text; //追加

        #region PRIVATE_MEMBER_VARIABLES

        private TrackableBehaviour mTrackableBehaviour;

        #endregion // PRIVATE_MEMBER_VARIABLES



        #region UNTIY_MONOBEHAVIOUR_METHODS

        void Start()
        {
            mTrackableBehaviour = GetComponent<TrackableBehaviour>();
            if (mTrackableBehaviour)
            {
                mTrackableBehaviour.RegisterTrackableEventHandler(this);
            }
        }

        #endregion // UNTIY_MONOBEHAVIOUR_METHODS



        #region PUBLIC_METHODS

        /// <summary>
        /// Implementation of the ITrackableEventHandler function called when the
        /// tracking state changes.
        /// </summary>
        public void OnTrackableStateChanged(
                                        TrackableBehaviour.Status previousStatus,
                                        TrackableBehaviour.Status newStatus)
        {
            if (newStatus == TrackableBehaviour.Status.DETECTED ||
                newStatus == TrackableBehaviour.Status.TRACKED ||
                newStatus == TrackableBehaviour.Status.EXTENDED_TRACKED)
            {
                OnTrackingFound();
            }
            else
            {
                OnTrackingLost();
            }
        }

        #endregion // PUBLIC_METHODS



        #region PRIVATE_METHODS


        private void OnTrackingFound()
        {
            Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
            Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);

            // Enable rendering:
            foreach (Renderer component in rendererComponents)
            {
                component.enabled = true;
            }

            // Enable colliders:
            foreach (Collider component in colliderComponents)
            {
                component.enabled = true;
            }

            Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " found");
            tracking_text.text = "Tracked";//追加
        }


        private void OnTrackingLost()
        {
            Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
            Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);

            // Disable rendering:
            foreach (Renderer component in rendererComponents)
            {
                component.enabled = false;
            }

            // Disable colliders:
            foreach (Collider component in colliderComponents)
            {
                component.enabled = false;
            }

            Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " lost");
            tracking_text.text = "Lost";//追加
        }

        #endregion // PRIVATE_METHODS
    }
}

少しビビりますが、付け足すのはたった3行だけです。

・メンバ変数に[SerializeField] Text tracking_text;を追加
・OnTrackingFoundメソッドの最終行にtracking_text.text = "Tracked";を追加
・OnTrackingLostメソッドの最終行にtracking_text.text = "Lost";を追加

DefaultTrackableEventHandlerスクリプトはマーカートラッキングに関するイベントを取り扱っています。マーカーを検知した時はOnTrackingFoundメソッドを、マーカーを見失った時はOnTrackingLostメソッドを発動します。

[SerializeField]属性のついたtracking_textには、ヒエラルキー上の「tracking_state」をアタッチしましょう。

また、これは余力があったら構わないのですが、ImageTargetのPositionやScaleを好みで変えてください。僕の場合は迷路が実世界のマーカーのちょうど真上に来るように調整しております。

ここまで上手く行ってれば、下のような感じになると思います。(真ん中上部の「20s」という時間はまだ実装してません)
マーカーが認識できないときは「Lost」と表示され、ちゃんとマーカーを認識したら表示が「Tracked」になっています。

akt

ここまでうまくいってたらこんな感じに迷路作成・プレイヤー作成できると思います。
alt

モバイル用にビルド

このままノートパソコンでアプリを作ることも可能ですが、せっかくなのでスマホとかiPadとかにビルドしましょう~~

まずはこちらのエキスパートカリキュラム「Unity標準のアセットで作成する場合」~「再生して操作をしましょう」に従って、バーチャルパッドを作ってください。ちなみに、カリキュラムに出てくる「Mobile Input」で「Enable」を押すと、Gameビュー上でのマウスのクリックが検知されなくなる(つまりボタンが押せない)みたいなので、把握しといてください。

多分今Gameビューがこんな感じになってると思います。
alt

では、ビルドに入りましょう。こちらのカリキュラムを参考にして、それぞれが使用している端末にビルドしてください。
再度確認ですが、Unityは最新版にしといてください。

以上で、とりあえずの実装は終了です!今回はVuforiaを使ったアプリのビルドが目的だったので、一応これで終わりでも良いと思うのですが、「もうちょっとちゃんとゲーム作りたいよ!」って人は4に進んでください!

4. さらに頑張る人たちへ

はい~それでは実装をさらに進めて、ゲームっぽくしたいと思います。

アセットを変える

こちらのアセットとか使ったりして、雰囲気変えたり。装飾はご自由に。

ゴールを作る

あとは、せっかくの迷路なのでゴールもつけましょうか!

Goalオブジェクトは最初offにしといて、maze_buttonが押されたタイミングで、一緒にGoalオブジェクトも作成しようと思います。
GoalManager.csとGoal.csを作ります。

UIManager.cs
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using UniRx.Triggers;

public class UIManager : MonoBehaviour {

    [SerializeField] Button 
    maze_button,
    start_button;

    [SerializeField] MazeGenerator m_generator;
    [SerializeField] PlayerGenerator p_generator;
    [SerializeField] GoalActivator goalActivator;


    void SetupUI(){
        maze_button.OnClickAsObservable ()
            .Subscribe (_ => m_generator.Generate());

        //追加
        maze_button.OnClickAsObservable ()
            .Subscribe (_ => goalActivator.ActivateGoal());

        start_button.OnClickAsObservable ()
            .Subscribe (_ => p_generator.Generate ());

    }
}

GoalActivator.cs
using UnityEngine;

public class GoalActivator : MonoBehaviour {

    [SerializeField] GameObject goal;

    public void ActivateGoal(){
        //最初はgoalをoffにしておく
        goal.SetActive (true);
        goal.GetComponent<Goal> ().SetGoal();
    }
}

Goal.cs
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using UnityEngine.UI;

public class Goal : MonoBehaviour {

    //ResultManagerはあとで作ります。
    [SerializeField] ResultManager resultManager;

    public void SetGoal(){
                //playerがゴールに到達したら、「Clear!」って出します。
        this.OnTriggerEnterAsObservable ()
            .Where (x => x.GetComponent<Collider>().name == "ThirdPersonController(Clone)")
            .Subscribe (_ => resultManager.Clear());
    }
}

流れとしては、ヒエラルキーにGoalManagerオブジェクトとGoalオブジェクト(ImageTargetの子要素)を置いて、maze_buttonが押されたタイミングでゴールも作成。Playerがゴールに到達したら、「Clear!」と表示します。

ヒエラルキーではこんな感じです。(僕の場合はゴールをゴリラにしましたが、みなさんお好きなアセット使ってください)
alt

ResultManagerは書くまでもないですがこんな感じです。

ResultManager.cs
using UnityEngine;
using UnityEngine.UI;

public class ResultManager : MonoBehaviour {

    [SerializeField] Text result_text;

    public void Clear(){
        result_text.text = "CLEAAAAAAR!";
    }
}

制限時間を作る

時間無制限だとゲームにならないので、制限時間を作りましょう。
流れとしては、Startボタンを押されたタイミングで、タイマーをStartします。

UIManager.cs
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using UniRx.Triggers;

public class UIManager : MonoBehaviour {

    [SerializeField] Button 
    maze_button,
    start_button;

    [SerializeField] MazeGenerator m_generator;
    [SerializeField] PlayerGenerator p_generator;
    [SerializeField] GoalActivator goalActivator;
    [SerializeField] TimeManager timeManager;


    void SetupUI(){
        maze_button.OnClickAsObservable ()
            .Subscribe (_ => m_generator.Generate());

        maze_button.OnClickAsObservable ()
            .Subscribe (_ => goalActivator.ActivateGoal());

        start_button.OnClickAsObservable ()
            .Subscribe (_ => p_generator.Generate ());

        //追加
        start_button.OnClickAsObservable ()
            .Subscribe (_ => timeManager.SetTimer());


    }
}

TimeManager.csは以下のような感じです。

TimeManager.cs
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using UnityEngine.UI;
using System;

public class TimeManager : MonoBehaviour {
    [SerializeField] Text time_text;
    [SerializeField] int default_time = 20;
    private int Time{ get; set;}
    private bool timerFlag;


    public void SetTimer(){
        Time = default_time;

        if (timerFlag) {
            return;
        } else {
            timerFlag = true;
        }

        Observable.Timer (TimeSpan.FromSeconds (1), TimeSpan.FromSeconds(1))
            .Select(_ => Time--)
            .TakeWhile(x => x >= 0)
            .Subscribe (x => time_text.text = x +"s");
    }


}

startボタンが押されるとタイマーがリセットされ、どんどん秒数が減ってく感じです。startボタンが押されるたびに、Observable.Timerファクトリメソッドが登録されるのが嫌なので、timerFlagを使って一回だけ登録されるようにしました。(もっとスマートな方法あったら教えて下さいmm)

また、「~~Manager」はヒエラルキーでこんな感じにまとめるといいかもです。
alt

以上の箇所までできたら、こんな感じでできると思います。

akt

クリアしたときはこんな感じです。
alt

さらにさらに頑張る人へ

...とこんな感じでやれば色々不備はありますが一応ゲームっぽくなったと思います(^^;
これ以上の実装は各々任せますが、

・Assetにもっと凝る
・Photonを使って複数プレイできるようにする
・HPや敵キャラなどを設定する

など、やりようによってどんどん面白いゲームになっていくと思います!!

ぜひやってみて下さい~~!

一応僕のgithubも載せときますのでご参照してください〜
https://github.com/ajingu/Vuforia_app

5. 参考

Vuforia
Vuforia公式サイト
今回はImageTargetを使用しましたが、Vuforiaには他にも様々な技術があります。
なんか面白いやつできたら見せて下さいね!

Kudan
Vuforiaと同じくARエンジンを開発する企業です。
マーカーレスなARが可能ということでしたので試してみましたが、かなり映像が不安定だったので今回は取り扱いませんでした。ARKitに期待します。
誰かうまくできたら教えてください!!!

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up