今回は「Blocks」というプロジェクトを完成させます。Blocksは、ブロックをつかんで積み上げられるような遊べるVRコンテンツを目標としています。

前回の内容では、以下の手順まで完了しています。

  • VR対応のシーンを作成
  • コントローラの先からレーザーポインターを表示
  • 床と大きい箱をシーンに配置
  • クリックに反応するスクリプトを実装

今回作るVRコンテンツ。コントローラーでブロックをつかんで積み上げられる

残りの実装項目は次のとおりです。

  • トリガーでブロックをつかめるようにする
  • ブロックを生成する仕組みを作る
  • シーンを再起動する

今回は、以上の3項目を順番に実装していきます。

「ブロックをつかむ」という挙動を考えてみる

前回の最後に作成したスクリプトでは、トリガーを押し込んだ時に「DoClick」が呼び出され、トリガーを離した時にDoUnclickが呼び出されるようになっています。

// Use this for initialization
void Start () {
    var trackedController = gameObject.GetComponent();
    if (trackedController == null) {
        trackedController = gameObject.AddComponent();
    }

    trackedController.TriggerClicked += new ClickedEventHandler(DoClick);
    trackedController.TriggerUnclicked += new ClickedEventHandler(DoUnclick);
}

void DoClick(object sender, ClickedEventArgs e) {
    Debug.Log("click!!");

 }

void DoUnclick(object sender, ClickedEventArgs e) {
    Debug.Log("unclick!!");

}

「つかむ」という挙動は、DoClick呼び出し時にコントローラと「操作対象ブロック」の相対位置を固定し、DoUnclick呼び出し時にその固定を解除する、という2ステップで実現可能です。

レーザーポインターが当たっているオブジェクトを取り出す

まず「操作対象ブロック」を探す必要があります。

UnityではRayという直線とオブジェクトの衝突判定を行う仕組みがありますのでこれを利用します。

  1. DoClick呼び出し時のメソッドを以下のように書き換えます。
void DoClick(object sender, ClickedEventArgs e) {
   Ray raycast = new Ray(transform.position, transform.forward);
   RaycastHit hit;
   bool bHit = Physics.Raycast(raycast, out hit, 10);

   if(bHit){
       string targetName = hit.transform.name;
       Debug.Log("grab!! "+targetName);

       // ↓これが操作対象オブジェクト.
       GameObject pointingObject = hit.transform.gameObject;
   }
}

書き換えた後、Unityを実行して、レーザーポインターがCubeに当たっているときにトリガーを引いてみてください。

コンソールに “grab!! Cube” と出力されれば問題なく上記コードが動作していることになります。

ブロックを「つかむ」

次は実際に「つかむ」機能について考えます。Unityでは、「Fixed Joint」という機能(コンポーネント)で相対位置の固定が可能ですので、このコンポーネントの設定を切り替えることで「つかむ」が実現可能です。

  1. 操作するコントローラにFixed Jointを適用します。

  2. 「Hierarchy」タブの「[CameraRig]/Controller (right)」を選び、「Inspector」タブの下部にある「Add Component」ボタンを押し、「Fixed Joint」を追加します。

「Fixed Joint」を選択

  1. Fixed Jointコンポーネントを使うとき、つなげようとする両オブジェクトには、同時に「Rigidbody」というコンポーネントも必要になりますので、自動的にRigidbodyコンポーネントも追加されます。

「Rigidbody」コンポーネントも追加される

Rigidbodyについて

Rigidbodyとは物理挙動を与えるコンポーネントです。

重力によって落下する、とか、床にあたって転がる、というような挙動をゼロからプログラムしようとすると大変です。しかし、UnityではRigidbodyを適用するだけでこのような振る舞いを実現出来ます。

いくつかパラメータがありますが、今回必要な項目を説明します。

  • Use Gravity : これは重力の影響を受けるかどうか、というフラグになります。チェックを入れると、重力に応じて落下していきます。
  • Is Kinematic : 直訳すると「運動学に従う」というフラグになります。完全に物理シミュレーションに従って動くオブジェクトではなく、プログラムやアニメーションで動かしたいオブジェクトではチェックを入れておきます。

今回のコントローラに適用したRigidbodyでは「Use Gravity」のチェックは外し、「Is Kinematic」のチェックを入れておきます。

また、操作対象となるCubeについてもRigidbodyを入れておく必要があります。そのためには、Cubeを選んで「Add Component」ボタンを押してRigidbodyを追加します。CubeのRigidbodyは「Use Gravity」はチェックを入れ、「Is Kinematic」はチェックを外しておきます。

スクリプトでFixedJointの切り替えを行う

スクリプトでFixedJointの設定を切り替え、つかむ機能を実装します。

  1. DoClick内のヒットオブエジェクト周りの処理を以下のように書き換えます。
if(bHit){
    GameObject pointingObject = hit.transform.gameObject;
    this.grabObject(pointingObject);
}
  1. どのオブジェクトを掴んでいるのかを把握しておくために「targetObj」という変数を用意しておきます。
private GameObject targetObj = null;
  1. トリガーを離したときの処理は、DoUnclick内を以下のように書き換えておきます。
void DoUnclick(object sender, ClickedEventArgs e) {
    if( this.targetObj != null ){
        this.releaseObject();
    }
}
  1. 最後に以下のような2つのメソッドを追記します。
void grabObject( GameObject obj ) {
    this.targetObj = obj;

    FixedJoint fj = gameObject.GetComponent();
    fj.connectedBody = obj.GetComponent();
}

void releaseObject() {
    FixedJoint fj = gameObject.GetComponent();
    if( fj != null ){
        fj.connectedBody = null;
    }

    this.targetObj = null;
}

これで実装は完了です。問題なく実装が完了していればブロックをつかみ、離すことができるようになります。

コントローラーでトリガーを引くとブロックを掴み、離すとブロックが重力に従って落ちる(GIFアニメーション)

ブロックを生成できるようにする

複数のブロックがあるといろいろとアイディアが湧いてきそうですので、ブロックを生成できるようにします。

Unityではオブジェクトのテンプレートのような仕組みとして「プレハブ(prefab)」という機能があります。同じオブジェクトを複数生成したりするときによく使われます。既につかめるようになっているCubeのプレハブを作成しておき、それを次々と生成(複製)できるようにしたいと思います。

  1. Projectタブ内で「/Assets/Prefabs/」となるようにフォルダを作成します。

  2. このフォルダ内に、Hierarchy上のCubeをドラッグします。

Hierarchy上のCubeを /Assets/Prefabs/にドラッグ

これで既存のCubeオブジェクトをベースにした「prefab」が生成されました。

  1. Projectタブ内を見ると、生成されたPrefabは「Assets/Prefabs/Cube」となっていますが、Cubeを「CubePrefab」とリネームしておきましょう。

続いて、「VRController.cs」にCubeを生成する機能を追加します。「ViveController」の丸いパッド部分(Pad)をクリックしたときにこのCubePrefabをベースにオブジェクトを生成するよう、次の手順でコードを追加します。

  1. Padクリック時に指定メソッドが呼ばれるように、「Start()」内に以下の一文を追加します。
trackedController.PadClicked += new ClickedEventHandler(DoPadClick);
  1. 続いて、以下の変数宣言とメソッド宣言を追加します。
public GameObject cubePrefab;

void DoPadClick(object sender, ClickedEventArgs e) {
    Vector3 cubePos = new Vector3(0,1,1);
    Instantiate(cubePrefab, cubePos, Quaternion.identity);
}

Unity Editorに戻って、Hierarchyタブの「[CameraRig]/Controller(right)」を選択し、Inspectorタブ内にある「VR Controller」エリア見てみると、「Cube Prefab」という項目があり、「None (Game Object)」となっているはずです。これが先ほどソースに追加した「public GameObject cubePrefab;」に相当する項目となります。Unity Editor上で、「Assets/Prefabs/CubePrefab」を選んでこの項目にドラッグするか、もしくは右側の◎を選んで出てくるパネルから「CubePrefab」を選ぶか、いずれかの方法で「CubePrefab」を指定します(次画面の円選択部分)。

CubePrefabを指定

問題なく実装されていれば、Padをクリックする度に次々とCubeが生成されます。うまく操作すれば最初の画像のように積み上げることも可能です。

事例紹介

ランシステムが、VR機器を大量導入した複合カフェ「スペースクリエイト自遊空間NEXT 蒲田西口店」を2016年7月22日にオープンすることを発表しました。

写真は自遊空間福島店でのVR機器貸出サービス。プレスリリースから

  • 店名 : スペースクリエイト自遊空間NEXT 蒲田西口店
  • 所在地 : 〒144-0051 東京都大田区西蒲田 7-2-3 第二醍醐ビル3・4F
  • 電話番号 : 03-5480-6800
  • 開店日 : 2016年7月22日(金)
  • 営業時間 : 24時間年中無休

GearVR等を使うと自分のスマホでどこでもVR体験ができるのは事実ですが、それでも周りからどう見られているかはやはり気になります。自宅やオフィスでも長時間VRコンテンツを体験していると、ちょっと周りがどうなっているか気になって没入しきれないことがあります。複合カフェでは、このような他人の視線を一切気にせずコンテンツに没入しきれることが大きなメリットです。

「スペースクリエイト自遊空間NEXT 蒲田西口店」では、Gear VRやViveが置かれるようです。おそらくルームスケールのコンテンツは体験できませんが、最新のVRコンテンツをさまざまな方に体験してもらう導入として今後のVRコンテンツの充実にも期待したいです。

著者紹介


山田宏道 (YAMADA Hiromichi) - 株式会社トルクス 代表取締役

千葉大学工学部卒業。ゲームプログラマーを経て、2005年よりフリーランス。2012年 株式会社トルクスを設立し、コンシューマー向け、ビジネス向けを問わず、さまざまなアプリを受託開発している。

現在、VR関連技術に注力中。2016年4月より島根県奥出雲町に在住。山陰エリアでUnityやVRに興味ある方を募集しています。