dimeizaのブログ

興味のある技術(IoT/VR/Smart Speaker)とか、資格試験の話とか、日常で出会ったTechな話について書いています。

VR開発初めて民がOculusQuestとMMDを使って推しのリズムゲーもどきを作る話

 本記事はQiita Unity #3 Advent Calender 2020 12/16分の記事になります。

はじめに

Oculus Quest2

 2か月ほど前にOculus Quest2が発売されて、その低価格ゆえに、割と多くの人が手に取ったと聞いています。

[asin:B08GCM963G:detail]

 私自身は昨年からQuestを持っていて、少しだけUnity VR開発を触ってみたんですが、その時点ではOculus Integrationに含まれているサンプルを動かしただけで終わってしまったんですよ。

 何かを作りたい、というモチベーションがないと、人間なかなか取り組む意欲が持てないものです。

転機

 そんなある日、私がドハマりしていたHideo Kojima Gameつながりで、新しい世界を知る機会がありましてね。

www.youtube.com

 何やらホロライブとかいうVtuber集団が面白いことしとるやないけ、と。

 もうちょい調べていくと、彼らはMMDモデルを公開していて。

www.mmd.hololive.tv

 一方、私はOculus Quest2を買ったときに、同時にこのアプリを買っていたんですよ。

www.oculus.com

 Vtuberと言えば知らない人はないKizuna Aiをモチーフにしたゲームなんですが、このゲームのモデルはいたってシンプルで、

  • 前方のステージでアバターが踊っていて
  • 持っているペンライトで飛んでくるノーツに触ると得点

 というゲームだったんですよ。

ひょっとしてこの手のコンセプトのゲーム、自分で作れるのでは?

  • 加工可能なモデル
  • 開発環境
  • VRヘッドセット

 この辺が揃っていれば、あるいは可能なのではないか、とふと思いついてしまったのです。

 これに取り組めば、VRやAR開発への取っ掛かりになるし、何より楽しそうじゃないですか。

 というわけで、ゲームを作った経験もないVR初めて民が、無謀にもそれっぽいものを作ろうとしてみたという記録です 1

段取り

 とはいえ、徒手空拳どころか、自分のスキルすら当てにならない状態で未知の領域に戦いを挑むなら、相応の段取りというか、成果物を作るための流れを考えないと遭難してしまいます。

 私自身はこういう流れを考えていました(と、体よく目次を埋め込む)。

 この辺の流れを全部書いていると紙面がいくらあっても足りない上、例によって巨人の肩に乗りまくっているネタなので、必要に応じてガンガン外部リソースを参照していきます。

1. UnityでOculus Questのアプリを作るための環境と最小限技術の獲得

 開発環境としてはUnityを選択しました。Oculus Questアプリの開発環境としては他にも選択肢がありますが、とりあえず私が比較的よく耳にする方を選んでみました。

1.1. Unity環境構築

 これについては私が千言万語を費やすよりも、ここを見た方が100倍早いです。

framesynthesis.jp

 ここの『ビルド・実行する』まで完了すれば、とりあえずQuestのアプリを自分で作れる環境は構築できています。

 Unityのバージョンですが、私は2019.4で作りました。

1.2 チュートリアルによる学習

 で、これだけだと誰かが作ったアプリを動かしているに過ぎず、自分でUnity内で何かするための方法が分からないので、Oculusの公式ドキュメントに記述のあるチュートリアル2を実際にやってみました。

developer.oculus.com

 ここまで貫徹すると、

  • ゲームオブジェクトの配置
  • マテリアルの概念と設定
  • オブジェクトへのコンポーネントの追加
  • 独自ロジックの実装方法

 が体感できるはずです。

 私もUnityほとんどやったことがないので、最初は全く概念が分からなかったんですが、要は、

  • ゲームオブジェクトにC#のクラス(コンポーネント)を張り付けると、独自の振る舞いを付加できて、
  • コンポーネントはデフォルトでたくさん用意されている上、
  • 自分で独自のC#クラスを定義して貼り付けることもできる
  • あとはAPIを探してクラス内で所望の演算を実装すればいい

 ってことが分かれば、オブジェクトを好きに操作できるんだな、と。

 コンポーネントのプロパティを使ってゲームオブジェクトのパラメータを設定しながら、オブジェクト指向の考え方を3D世界のデザインに使うってのは、こんなにも親和性が高いんだなぁ、と、個人的には目からうろこでした。

2. MMDモデルをUnityに取り込むための方法の調査と習得

 では皆様お待ちかね、推しの3Dモデルをどうこうするお時間です。

 この辺は三者の著作物を使うことになるので、規約等を確認したうえでダウンロード、使用しましょう。

 と言っても、今回は商用、非商用含め第三者への配布は行わず、せいぜい操作時デモをYoutubeにアップロードする程度なので、悪意のある使い方をしない限り、規約上問題になることは少なかろうとは思います。

2.1 モデルとモーションの入手

 Unityちゃんとかを使ってもかまわないんですが、とりあえず私が推しているアキ・ローゼンタール(アキロゼ)のMMDモデルを使わせてもらいます。

3d.nicovideo.jp

 ホロライブの二次創作ガイドラインはこちら。

www.hololive.tv

 モーションについては、対象の曲に合った物を使うのがベストですが、今回はアキロゼのオリジナル曲である『シャルイース』に合わせてもらうので、

www.youtube.com

 当たり障りのない歌唱モーションを使わせていただきました。

bowlroll.net

 この2つをフォルダごと、Unityプロジェクトにドラッグアンドドロップしてインポートします。

2.2. MMD4Mecanim

 で、MMDモデルをUnityに導入する方法についてはある種鉄板的な方法があって。

stereoarts.jp

 ここの『チュートリアル(基本編)』を参照して、プロジェクトにMMD4Mecanimのスクリプト一式をインストールします。

f:id:dimeiza:20201211162236p:plain

MMD4Mecanimをダブルクリックして、

f:id:dimeiza:20201211162310p:plain

インポートします。

MMDモデルをプロジェクトフォルダのAssetに入れてから、pmxファイルを選択すると、こんなウィンドウが画面右側に出ます。

f:id:dimeiza:20201211162321p:plain

チェックボックスにチェックを入れた後、『同意する』を押すと、MMDモデルをインポートする画面が出るので、ここでモーションのVMDファイルを『VMD』にドラッグアンドドロップします。

f:id:dimeiza:20201211162339p:plain

この状態で『Process』を押すと、こんな画面が出てしばらくインポート処理が走るので待ちます(結構長いです)

f:id:dimeiza:20201211162354p:plain

インポート完了後

 インポートが完了したら、MMDモデルと同名の.fbxファイルが作成されます。そのファイルを選択すると右側にウィンドウが出るので、中央上部の『Rig』を選択し、Animation Typeを『Humanoid』に設定します。モーションを適用するために必要な操作です。

f:id:dimeiza:20201211162428p:plain

Sceneへの配置

 以上が完了したら、.fbxファイルをHierarchyウィンドウにドラッグアンドドロップすることで、画面上に配置することができるようになります。

3. ゲームロジックの構築と埋め込み

 ここから先は、ゲームとして動かすための実装に入ります。

3.1 ゲームコンセプトの確認

 基本的にはTouch the beatで出てくるような飛来系のリズムゲーにするわけですが、今回は商用開発するわけでもゲームを頒布するわけでもなく、単にVR開発の方法を体験して、自分だけのゲームを作りたいだけなので、

  • 前方から何らかのオブジェクトが、前方から等速直線運動で飛来してくる
    • オブジェクト生成位置は、プレイヤーの前方数メートルからランダムに座標が決定される
    • 生成タイミングは等間隔、なるべくBGMのリズムに合わせるように調整
      • これがいわゆる『譜面』に当たるが、特に曲に同期させてないのでリズムゲー"もどき"
  • コントローラでオブジェクトに触れると、オブジェクトが消滅してスコア獲得
    • プレイヤーの背後に抜けたらオブジェクトは勝手に消滅

 という、きわめてゆるっとしたコンセプトで作ってみます。

2. Oculus環境の準備

 Oculus IntegrationからOculus用のカメラとコントローラを持ってきます。

 HierarchyからMain cameraを削除したうえで、Oculus→VR→Prefabから、OVRCameraRigというPrefabを選択し、Hierarchyに貼り付けます。

 貼り付けたOVRCameraRigを選択し、右側に表示されたInspectorから、TrackingをFloor levelに設定します。

f:id:dimeiza:20201211163608p:plain

 さらに、HierarchyからOVRCameraRigを展開して、LeftControllerAnchorとRightControllerAnchorに、OVRControllerPrefabをドラッグアンドドロップします。

f:id:dimeiza:20201211162443p:plain

 これで、

  • Oculus Questの視点でUnity内の空間を視聴
  • コントローラをVR画面内に表示

 できるようになりました。

 このタイミングで、一度Oculus上で動作確認してみるといいと思います。インポートした3Dモデルが自分の目の前に来るよう、モデルのX,Y,Z座標やRotationを調整しておきましょう。

f:id:dimeiza:20201211162458j:plain

3. オブジェクトの操作と生成

 飛来するオブジェクトなんですが、我が推しであるアキ・ローゼンタールはリンゴが大好きという設定があるので、リンゴを生成してプレイヤーに飛ばしてもらうことにします。

 リンゴはAsset Storeから無料素材を取ってきます。

assetstore.unity.com

 このAssetsをインポートしたあと、Apple/Prefabフォルダに入っているApple.prefabをResourceフォルダにコピーしておきます。

 で、このAppleに動きと接触判定を与えるべく、ResourceフォルダにコピーしたApple.prefabをクリックし、右側のウィンドウから "Open prefab"を押してprefabの編集をします。

 まず、"Add Component"で"Sphere Collider"を選択します。選択すると相応のコライダー(当たり判定)がAppleに付きますが、必要であれば修正してもいいでしょう。

 次に、"Add Component"で"Rigid Body"を選択します。オブジェクトを物理特性によって制御するためのコンポーネントです。

 このRigid Bodyの中でGravityのCheckを外しておきます。これを外さないとニュートンが500年前に見たように、リンゴが地面に落ちてしまうので。

 次に、"Add Component""→"New Script"と選択して、新たにスクリプトを紐づけます。リンゴの動きを設定するためのものです。とりあえず名前は"AppleObject"とでもしておきましょうか。

f:id:dimeiza:20201211170822p:plain

 で、スクリプトの中身を以下のように記述してみます。

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

public class AppleObject : MonoBehaviour
{
    public float Z_speed;
    public float destroy_pos;

    // Start is called before the first frame update
    void Start()
    {
        Transform myTransform = this.transform;
        Vector3 localPos = myTransform.localPosition;
        this.GetComponent<Rigidbody>().velocity = new Vector3(0.0f,0.0f,Z_speed);
    }

    // Update is called once per frame
    void Update()
    {
        Transform myTransform = this.transform;
        Vector3 localPos = myTransform.localPosition;

        if(localPos.z < destroy_pos){
            Destroy(this.gameObject,0);
        }
    }

}

 リンゴが生成されてから消滅するまでの動きを定義していて、要は

  • 生成時にZ座標の移動速度としてZ_speedを与え
  • 特定のZ座標(destroy_pos)に来たら自動的に自分自身を消去する

 という振る舞いをリンゴに与えています。

 Appleの大きさは適宜Scaleで調整してみてください。

 一方で、アバターにリンゴを生成してもらうべく、Hierarchyのモデルを選択して、"Add Component""→"New Script"と選択して、スクリプトを紐づけます。中身はこんな感じで書きます。

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

public class AppleMaker : MonoBehaviour
{
    public float makePeriod;
    public float x_center;
    public float y_center;

    public float x_range;
    public float y_range;

    private float timeElapsed;
    GameObject obj;
    
    public float distance;
    // Use this for initialization
    void Start () {

        obj = (GameObject)Resources.Load ("Apple");
        timeElapsed = 0;

    }

    void Update(){

        timeElapsed += Time.deltaTime;
        print(Time.deltaTime);

        if(timeElapsed >= makePeriod){
            float x = Random.Range(x_center - x_range,x_center + x_range);
            float y = Random.Range(y_center - y_range,y_center + y_range);

            Instantiate (obj, new Vector3(x ,y,distance), Quaternion.identity);
            timeElapsed = 0.0f;
        };
    }
}
  • 生成するオブジェクトとしてResourceフォルダに格納した"Apple"を指定
  • オブジェクト生成時からの時間を記録
  • 一定時間が経過した場合、一定のX,Y範囲から乱数で生成位置を決めてオブジェクトを生成

 という動きを定義しています。これで、一定時間ごとに指定の領域からリンゴが生成されるようになります。

 これらが完了すると、以上示したスクリプトの動きが組み合わせられ、リンゴが何もない空間から生成されて、こっちに飛んでくるようになります。

f:id:dimeiza:20201211162530j:plain

4. コントローラによる接触とスコア

 飛んでくるリンゴにコントローラで接触して、スコアリングする操作を追加してみましょうか。

 まず、スコアを表示するスコアボードを作成します。

 Hierarchyで右クリックして、3D object→3D Textと選択して、3Dテキストオブジェクトを作ります。とりあえず名前は"ScoreObject"にしましょう。

 位置は適宜調整して、ゲーム画面から見られる場所に移動してみてください。

f:id:dimeiza:20201211162546p:plain

 で、”ScoreObject”にスクリプトを紐づけます。"Add Component""→"New Script"と選択して、以下のようなスクリプトを追加します。

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

public class ScoreObject : MonoBehaviour
{

    private int score;

    public int Score {
        get { return this.score;}
        set { this.score = value;}
    }
    // Start is called before the first frame update
    void Start()
    {
        score = 0;
        
    }

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

 スコアボード自身にスコアの値を覚えてもらいます。

 次に、コントローラに対して接触判定と、スコア加算処理を追加します。

 まず、LeftControllerAnchorとRightControllerAnchorにSphere Colliderを追加して、接触判定を追加します。

 次に、LeftControllerAnchorに新たにスクリプトを追加します。"Add Component""→"New Script"と選択して、以下のようなスクリプトを追加します。

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

public class touchController : MonoBehaviour
{
    public GameObject scoreBoard;
    public ScoreObject score;
    public AudioClip sound;
    // Start is called before the first frame update
    void Start()
    {
        scoreBoard.GetComponent<TextMesh>().text =  "Score:" + score.Score.ToString();
    }

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

    private void OnCollisionEnter(Collision col)
    {
          AudioSource.PlayClipAtPoint(sound, transform.position);
          scoreBoard.GetComponent<TextMesh>().text =  "Score:" + score.Score.ToString();
          score.Score += 100;

          GameObject targetGameObject = col.gameObject;
          Destroy(targetGameObject,0);
    }
}

 ポイントはOnCollisionEnterで、コントローラとリンゴのオブジェクトが接触した場合にこのメソッドが呼び出されます。

 で、メソッド呼び出し時に特定の音を鳴らしつつ、スコアを加算し、接触したリンゴのオブジェクトを消す、という塩梅です。

 このスクリプトをRightControllerAnchorにも設定してやります。

 で、このスクリプトはInpectorから設定したスコアボード、スコアオブジェクトを操作しつつ、Soundで設定された音を鳴らす操作をするので、左右両方にこれらを設定してやります。

 とりあえずSoundは適当にフリー素材のベルの音を設定することにしました。

f:id:dimeiza:20201211164625p:plain

 この辺でもう一度動作確認してみましょう。

 コントローラをリンゴにぶつけると音がしてオブジェクトが消え、スコアが加算されるようであれば問題ありません。

f:id:dimeiza:20201211162603j:plain

 まぁ何というか、この辺りでもうゲームロジックは実装できています。

 『こんな簡単な操作でVRゲームロジックが作れるUnityすげー』と思いました(小並感)。

5. モーションと音楽の設定

 そろそろいろいろと飾り付けをしていきます。

 現時点だと3Dアバターは微動だにしないので、動作を追加していきます。

 まず、Projectフォルダの適当な場所で右クリックし、Create→Animator Controllerと選択します。新しくNew Animation Controllerというファイルが生成されるので、これをクリックするとこんな画面になります。

f:id:dimeiza:20201211162634p:plain

 この画面が表示されている状態で、Projectフォルダから.fmxファイルを選択し、.fmxファイルに含まれているvmdファイルをドラッグアンドドロップします。するとこんな表示になり、New Animation Controllerにモーションが設定されます。

f:id:dimeiza:20201211162646p:plain

 次に、Hierarchy内の3Dアバターをクリックして、InspectorからAnimator内の、Controllerに対して、New Animation Controllerをドラッグアンドドロップします。

f:id:dimeiza:20201211162700p:plain

 これでモーションを起動時に動作させられるようになりました。

 ただし、まだモーション動作時にオブジェクトのめり込みや髪の毛、リボンの不自然な動作があるので、これらの調整を行います。

 MMD4 Mecanim ModelのPhysicsから、Generate Colliders右側の"Process"ボタンを押します。これを押すことでアバターの全身に接触判定が設定され、髪の毛が体にめり込んだりするのを防ぐことができます。

 加えて、Physics EngineをNoneからBullet Physicsに変更します。これでリボンや髪の毛がきちんと重力の影響を受けるようになります。

f:id:dimeiza:20201211162712p:plain

 最後に音楽の設定をしましょう。

 演奏したい音楽ファイルをプロジェクトにロードしたうえで、MMD4 Mecanim ModelのAnimを押し、AnimationsのAudio Clipに音楽ファイルをドラッグアンドドロップします。

f:id:dimeiza:20201211162724p:plain

 この状態で再生すると、3Dアバターがモーションを取りながら音楽が流れるので、リンゴ生成とコントローラ接触のタイミングが音楽に自然に合うよう、先ほど作成したAppleMakerのロジックやパラメータを変更して調整してやります。

6. 空間の飾り付け

 あとはおまけです。

 何もない殺風景な空間でプレーしているのも興がないので、空間を飾り付けてみます。

 アキちゃんはハーフエルフなので、せっかくだから森で歌ってもらおうと思って森のAssetを探してみたところ、割と豪勢なフリーのAssetがありました。

assetstore.unity.com

 このAssetにはサンプルSceneが付属しているので、このSceneからTreesとTerrainオブジェクトをコピーして貼り付けるとこんな感じになります。

f:id:dimeiza:20201211162741p:plain

動かしてみる

 こんな感じになります。冒頭部分だけ動画化してみました。

©2019 Cover Corp.

 今回は機械的に0.5秒単位でオブジェクトを生成していて、オブジェクトに接触するタイミングも特段制限していないので、音楽との同期は明示的に取られていないのですが、逆に言うとこの2点を押さえればリズムゲーになりえる構成になっているかと思います。

 結局リズムゲーでは譜面を作成するのが最も難しいのですが、VRで飛来系リズムゲーを構成する要素は、その点を除けば意外に簡単に構成できる、ということが示せればここでは十分です。

さいごに

 新しいガジェットを手に入れたタイミングと、新しい推しの世界を知ったタイミングが同期したので、体験がてらVRアプリ構築を試してみました。

 実際やってみて思ったのは、『作りたい』という意欲こそが技術学習にとっての最高の燃料である、といういつもの知見と同時に、

  • Unityを使ってOculus VRアプリを作るのはかなり簡単になっている。
    • 特にOOPに慣れているプログラマであれば、ぶっちゃけ雰囲気で作れる。
      • 私もC#ほとんど知らないまま実装してたもんで。
  • 飛来系リズムゲーを作る工程は、Unity VR入門者がアプリを作るためのチュートリアルとしても良さそう。
    • 技術難易度的も、つかみ的にも。

 という感触がありました。

 この後私はQuestに関係のない外部機器を使ってVR空間内オブジェクトを操作してみたり、ARに手を出したりできているので、XRに興味があるけど取っ掛かりが分からなかった私にとっては、非常に良い経験になったと思っています。

 もしそういう方がこの記事をご覧になるようであれば、せっかくの機会なので訓練がてら試してみてはいかがでしょうか。


  1. VR以外の経験は相応に積んでいてフル活用しています。

  2. 日本語で提供されているという僥倖。