じゅころぐAR

AR/VRメインのブログ。時々ドローン。

ARKitでARモグラ叩きを作ってみた

久し振りのARネタです。簡単かつ応用が効く方法なのでまとめておく。

はじめに

iOS11.3と同時に提供されるARKit1.5で垂直面の検知ができるようになるそうです。
それなら、"以前にTangoで作ったARモグラ叩きが作れるのでは?"と思い、現時点で実装可能なARKit版(水平面のみ)を実装してみました。

上記のTango版は透明なメッシュをリアルタイムで作って、メッシュの下からモグラを表示させています。
同じことをARKitやARCoreでやろうとしてもTangoのようにメッシュを作成するのは容易ではないので、透明な箱に隠しておく実装で代用しました。

開発環境

以下の環境で作りました。ARKit1.5にはまだ対応していません。

  • macOS High Sierra
  • Unity 2017.3.0f2
  • Unity ARKit Plugin version 1.0.14

モグラ役には、こちらも久し振りのクエリちゃんSD版モデルを使います。
クエリちゃんアセットは"Creative Commons Attribution 4.0 international License(CC-BY)"で提供されています。

作り方

Unity ARKit Pluginで基本的なシーンを構成する方法は割愛します。
以下の記事などを参考にしてください。

qiita.com

モグラ叩きの処理は、

  • ランダムにキャラクターを出現させる
  • 平面から出てくるように見せる

の順で実装しています。

ランダムにキャラクターを出現させる

Unity ARKit Pluginに同梱されているExamplesのUnityARBallzで使われているBallMaker.csが参考になります。

上記スクリプトは、"画面をタップして平面にGameObjectを置く処理"なので、これを改変してランダムにモグラを配置していきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;

public class GameController : MonoBehaviour {

    public GameObject moguraPrefab;

    private float timeInterval = 2.0f; // モグラの出現間隔(秒)
    private float timeElapsed; // 経過時間を保存する変数

    void Update () {
        // 一定時間が経過したらHitTestを実行する
        timeElapsed += Time.deltaTime;
        if(timeElapsed >= timeInterval) {
            _executeHitTest ();
            timeElapsed = 0.0f;
        }
    }

    private void _executeHitTest() {
        // 画面上のランダムな座標を元にARPointをセット
        var screenPosition = Camera.main.ScreenToViewportPoint(new Vector3 (Screen.width * Random.value, Screen.height * Random.value));
        ARPoint point = new ARPoint {
            x = screenPosition.x,
            y = screenPosition.y
        };

        // HitTestを実行する
        List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface ().HitTest (point, ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent);

        // 平面と衝突した位置にモグラを出現させる
        if (hitResults.Count > 0) {
            foreach (var hitResult in hitResults) {
                Vector3 position = UnityARMatrixOps.GetPosition (hitResult.worldTransform);
                _createMogura(position);
                break;
            }
        }
    }

    // モグラの生成
    private void _createMogura(Vector3 position) {
        GameObject mogura = Instantiate (moguraPrefab, position, Quaternion.identity);  
    }
}

Update()でタイマー処理を実装し、上記の例では2秒おきにHitTestを実行しています。

_executeHitTest()はExampleのHitTest処理とほぼ同じですが、HitTestを行う座標のscreenPositionを作成する処理が異なります。
Random.valueは0.0〜1.0の乱数を返すため、画面の幅・高さに掛け合わせることで画面上の任意の座標をランダムで取得することができます。

このスクリプトを任意のGameObjectにセットして、moguraPrefabを指定すると、キャラクターが平面上にランダムで出現するはずです。

平面から出てくるように見せる

せっかくARなので、平面からニョキニョキと現れるようにしたいですよね。
これを実現するため、GameObjectを置いた時点ではキャラクターを透明な箱に隠しておき、キャラクターだけを箱の外に移動させるスクリプトを書きます。

まず、親のGameObjectを作成し、Cubeとキャラクターを配置します。

f:id:jyuko49:20180212145247p:plain

次に、以下の位置合わせを行います。

  • 親のGameObjectの位置を(0, 0, 0)で固定
  • キャラクターが収まるようCubeのサイズを調整
  • Cubeの上面がy=0になるようy座標を調整

位置合わせ後はこんな感じになります。調整の際は、CubeのMeshRendererをON/OFFすると確認しやすいです。

f:id:jyuko49:20180212145751p:plain

位置が決まったらCubeのMeshRendererにオクルージョン用のマテリアルをセットします。
"Create" > "Material"で新規マテリアルを作成して、Shaderに"VR/SpatialMapping/Occlusion"を選択します。

f:id:jyuko49:20180212145737p:plain

エディタ上では単に透明な箱ですが、実機ではARカメラ の映像が表示されるため、中身はそこにあるけれど見えない状態になります。

最後にキャラクターだけが箱の外に出てくるスクリプトを追加します。親のGameObjectではなく、箱の中のキャラクターに"Add Component"で適用されるようにします。

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

public class MoguraController : MonoBehaviour {

    private float moveDistance = 0.5f; // 移動させる距離
    private float movedDistance = 0f; // 移動した距離
    private float moveSpeed = 0.02f; // 移動させる速さ

    void Update () {
        if (movedDistance < moveDistance) {
            this.transform.position += this.transform.up * moveSpeed;
            movedDistance += moveSpeed;
        }
    }
}

スクリプトを保存したら、親のGameObjectごとPrefab化して、最初に作成した"モグラをランダムに出現させるスクリプト"にmoguraPrefabとしてセットします。

ビルドして実行すると、今度は平面からニョキニョキと生えてくるはずです。

冒頭のTango版では、これに"一定時間でモグラが消える処理""画面タップでボールを投げる処理""ボール衝突時のアニメーション&パーティクル""スコア計算"などを追加して、ミニゲームっぽくしています。

応用編

今回使ったロジックを少し改変すると、色々なシーンで使えます。

平面に消えていく表現

今回作ったモグラの初期状態と終了状態を入れ替えるだけです。
最初の位置合わせをキャラクターが箱の上に乗っている状態にして、箱の中に隠れていくスクリプト(移動方向をマイナスにするだけ)を書けばOKです。

水面に見立てて沈んでいく表現や忍者(床や天井に消えるイメージ)っぽい表現で使えると思います。

徐々に出てくる表現

キャラクターを箱から出すのではなく、箱の方をスクリプトで上下に移動させることで、キャラクターが徐々に転送されてくるような表現になります。GANTZみたいなやつです。

ARはてなボックス

「?」のテクスチャを貼って、宙に浮かせているだけです。箱を出た後に、スクリプトで移動の方向を変えてそれっぽく見せています。
キノコ(の妖精)のMMDが使いたかったため、UnityではなくWebAR(three.ar.js)で実装しました。

まとめ

一見難しいことをやっているようで、方法を思い付いてからは簡単でした。平面検知さえできれば実装でき、プラットフォームを問わないので使い勝手がよいです。

平面の上を動かすだけでなく、平面から出たり消えたりさせることで、ARKitで手軽に現実感を増すことができます。

垂直面を扱う場合、Tango版のように配置する平面の応じてモグラの向きを変える必要があります。これはHitTestの際に衝突した平面の法線(normal)が取得できれば実現可能です。
このあたりはUnity ARKit Pluginが垂直面に対応してから実装します。