takeda_san’s blog

KotlinとVRを頑張っていく方向。

VRで焚火を眺めたい その2(焼きマシュマロ編)

成果発表


バーチャル焚火でマシュマロを焼こう

前回

焚火ができた。

takeda-san.hatenablog.com

今回やること

焚火でマシュマロを焼く。
無心でマシュマロを焼いているときが幸せ。

ライブラリ探し

さすがにね、VRで物をつかんだり、放したりが簡単にできるライブラリなんて専門的なものは、ないよね。よね…

VRTK - Virtual Reality Toolkit - [ VR Toolkit ] - Asset Store

あるよね。すごいよね。
テレポートとかUI作成とかまでできるのね。
しかも無料なのね。

自分でやってみよう

完璧なライブラリはあるが、Unityの勉強もしたいので物をつかむぐらいは自分で書いてみようと思う。

コントローラーの色々はここ
HTC Vive向けにアプリケーションを開発する〜コントローラでインタラクション編〜 - VOYAGE GROUP VR室ブログ

衝突判定についてはここを参考にしました。
衝突判定のあれこれ - Qiita

毎度ながら日本語情報の多さには助けられてます。

コードと動作

ほぼコピペです。

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

public class RightArmController : MonoBehaviour {

    GameObject grababbleObject;
    FixedJoint joint;

    private void Start()
    {
        joint = gameObject.GetComponent<FixedJoint>();
    }

    void Update ()
    {
        var trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        if (device.GetPress(SteamVR_Controller.ButtonMask.Trigger))
        {
            grab();
        }
    }

    void grab()
    {
        if (grababbleObject == null || joint.connectedBody != null)
        {
            return;
        }

        joint.connectedBody = grababbleObject.GetComponent<Rigidbody>();
    }

    void OnTriggerEnter(Collider other)
    {
     grababbleObject = other.gameObject;
    }
}

f:id:takeda_san:20180219212432g:plain

すごいつかんだものが、荒ぶってますが出来ました。
相変わらず十数行で立派なものができてしまう。

衝突判定とFixed Joint

今回初めて使ったのが衝突判定とFixed Joint、自分なりにまとめてみる。

衝突判定

Collision系とTrigger系の二つの衝突判定の仕方がある。
Collisionは、衝突の結果、物理挙動を起こしたいときに使う。
(物理挙動を起こすので、Rigidbodyを設定する必要がある)
Triggerは、衝突したことを検知したいときに使う。

なので、今回は手が物体に触れているかを判定したいだけなので、Triggerを使う。

Fixed Joint

一時的にオブジェクトをほかのオブジェクトとつなげる。
今回はCubeを右手につなげた。
で、接合部はFixedなのでくねくね動かない。
(すごい荒ぶってらっしゃいましたが)

よし、マシュマロ焼こう

なんとなくわかったような気がしているうちに、本題のものを作っていきます。

マシュマロモデリング

まずは、マシュマロを作りましょう。
モデリングなんてしたことないので、一からお勉強です。
ソフトはBlenderを使いました。
マシュマロ作成には過ぎたツールかもしれませんが、これもお勉強。

というわけで入門書を一冊。
これ最高にわかりやすくて、いちから丁寧に丁寧すぎるぐらいに解説がされている。
Blenderの一歩目としては最適ではなかろうか。
Blender 3DCG モデリング・マスター

久しぶりにペンダブを引っ張り出して、仕事から帰った後のお楽しみタイムにコツコツやって3日ほどで終わりました。
最終的にわんことかスクーターを作れるところまで行けますぞ。
今の自分なら、マシュマロ作るなんて余裕よ。まかせとけって。
そして、完成。
f:id:takeda_san:20180224000557p:plain

あ、うん。テクスチャマッピングはこの入門書の範囲外だからさ… 多少の粗さは目をつぶっていこうね。
blenderファイルとをUnityに読み込んで、大きさを調整してCylinderをズブリと刺します。
(そのままUnityに読み込めるのが驚き)

f:id:takeda_san:20180224105752p:plain

VRTK編

VRTKを使って物を持つ挙動を実装する。
更新が結構頻繁らしくて、ブログ記事とかを参考にしてるとスクリプトの名前が変わってたりで躓くので、公式を見に行く。
すごいドキュメントの充実ぶり。
とりあえず、Getting Startedで入門してみよう。

VRTKのセットアップ

結構苦労したので手順を書いておきます。
Getting Started · VRTK - Virtual Reality Toolkit

※このあたりの記事の設定(SteamVRでモデルを動かす)が完了している前提です。
takeda-san.hatenablog.com

  1. VRTKをアセットストアからダウンロードする
  2. 空のオブジェクト(名前は何でも、今回は[VRTK_SDKManager])を作って、VRTK_SDKManagerをアタッチする
  3. その子にさらに空のオブジェクト(名前は何でも、今回は[SDKSetup])を作って、VRTK_SDKSetupをアタッチする
  4. SteamVR三種の神器、[CameraRig]、[Status]、[SteamVR]をまるごと2.のオブジェクトに追加する
  5. VRTK_SDKSetupをアタッチしたオブジェクトのInspectorからVRTK_SDKSetupのSDK Selectionの値をSteam VRに変更する
  6. Object References内のActual Objectsの 各値を設定する(画像のような設定にする)
    f:id:takeda_san:20180224183211p:plain
  7. 空のオブジェクト(名前は何でも、今回は[VRTK_Scripts])を作る
  8. その子にさらに2つオブジェクトをつくる(Viveコントローラ用。名前は何でも、今回はLeftControllerRightControllerにする)
  9. VRTK_SDKManagerをアタッチしたオブジェクトのInspectorからSetups内のAuto Populateボタンを押してSetupsにVRTK_SDK(VRTK_SDKSetup)が追加されたのを確認する
  10. さらに、Script AliasesのLeft ControllerとRight Controllerに8で作ったオブジェクトをそれぞれ設定する
  11. 頭のカメラの位置がちょっとずれてるので調整

参考までに私の設定後の画像。
f:id:takeda_san:20180224183810p:plain f:id:takeda_san:20180224183843p:plain

物をつかめるようにする

あとはやりたいことをドキュメントの中から見つけて、Examplesの中のシーンを眺めて使い方を真似するとよさそう。
今回は物をつかんだり放したりする、005_Controller_BasicObjectGrabbingを真似っこする。

VRTK_SDKManagerをアタッチしたオブジェクトの子のコントローラーオブジェクトに次の3つをアタッチ。

  • VRTK_ControllerEvents
  • VRTK_InteractTouch
  • VRTK_InteractGrab

VRTK_InteractGrabのController Attach Pointに[CameraRig]配下の方の左手のコントローラを設定します。
その後、VRTK_SDKManagerをアタッチした方のオブジェクトにRigidbodyを設定します。
これがないと物がつかめない。
f:id:takeda_san:20180224193759p:plain

こんな感じで、つかめます。
手の位置とviveコントローラの位置がどうしても一致しなかったので、作成中はこんな感じでコントローラを表示ながらやってました。
f:id:takeda_san:20180224194815g:plain

焚火との当たり判定編

マシュマロを焚火で炙っていきましょう。
ここまで、長かった…

焚火にBox Colliderを新規に作成して当たり判定の範囲を設定します。
f:id:takeda_san:20180224220317p:plain

マシュマロには、005_Controller_BasicObjectGrabbingのつかめるオブジェクトと同様に以下の設定を追加します。

  • Rigidbody
  • Box Collider
  • VRTK_SwapControllerGrabAction
  • VRTK_FixedJointGrabAttach
  • VRTK_InteractableObject(Is Grabbableにチェック)

で、焚火に当たっている時間に応じてテクスチャを切り替えるようにスクリプトを書きましょう。

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

public class MashuOnHit : MonoBehaviour
{

    // テクスチャ
    const string TEXTURE_FILE_BAKE = "bake";
    const string TEXTURE_FILE_BLACK = "black";

    Texture2D bake;
    Texture2D black;

    // 焚火に触れている間のカウント
    private int count = 0;

    void Start()
    {
        bake = Resources.Load<Texture2D>(TEXTURE_FILE_BAKE);
        black = Resources.Load<Texture2D>(TEXTURE_FILE_BLACK);
    }

    void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "Fire")
        {
            count++;

            // 焼けたかな
            if (count == 1000)
            {
                this.GetComponent<Renderer>().material.mainTexture = bake;
            }
            else if (count == 2000)
            {
                this.GetComponent<Renderer>().material.mainTexture = black;
            }
        }
    }
}

テクスチャを読み込んで、当たり判定に入っているカウントによって、焼き目が付いたり、焦げが付いたりという脳筋スクリプト
Resourcesフォルダというフォルダにテクスチャを入れて、Loadで読みだすとスクリプトの中で各種リソースが使えるらしい。

UnityのResourcesの使い方 - Qiita

調整編

マシュマロをちゃんと手に持つ

現状、マシュマロを持つとviveコントローラの位置にマシュマロがくるので、フォースの使い手のようになってしまう。
これをちゃんと手の位置に固定したい。
f:id:takeda_san:20180224233302p:plain

ものをつかむときの挙動がVRTKでは、数種類準備されている。
そのなかでもVRTK_ChildOfControllerGrabAttachというつかんだものを子要素に設定するというスクリプトを使って手に持てるようにする。
VRTK_ChildOfControllerGrabAttach · VRTK - Virtual Reality Toolkit

VRTK_ChildOfControllerGrabAttachクラスを継承しStartGrabメソッドで自分のモデルの手首の位置にマシュマロが来るようにする。
今回私が使っているNaokoさんは、手首がWRist_LとRなのでそれをパラメータとして受け取って親子関係を紐づける。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VRTK.GrabAttachMechanics;

public class GrabCharactor : VRTK_ChildOfControllerGrabAttach
{
    // Naokoさんの手首を設定する
    [SerializeField]
    GameObject rist;

    public override bool StartGrab(GameObject grabbingObject, GameObject givenGrabbedObject, Rigidbody givenControllerAttachPoint)
    {
        if (base.StartGrab(grabbingObject, givenGrabbedObject, givenControllerAttachPoint))
        {
            // つかんだものの親に手首を設定する
            givenGrabbedObject.gameObject.transform.parent = rist.gameObject.transform;

            // 手首の位置につかんだものを移動する
            Vector3 ristPos = rist.gameObject.transform.position;
            givenGrabbedObject.gameObject.transform.position = new Vector3(ristPos.x, ristPos.y + 0.25f, ristPos.z + -0.05f);

            grabbedObjectScript.isKinematic = true;
            return true;
        }
        return false;
    }


}

f:id:takeda_san:20180225003238p:plain

フォースの力は失われ、それっぽくなりました。
f:id:takeda_san:20180225003322g:plain

いま焼けてんのか、焼けてないのかわからない

マシュマロを焚火にしばらく近づけていると、焼き目が付くんですが、焼いている最中に今、焼けているのか焼けてないのが見た目で判別つかないんですね。
なので、焼けてる判定のときはパーティクルで湯気を出すようにしたい。
マシュマロの子として煙パーティクルを追加。

f:id:takeda_san:20180225132618p:plain

焚火の当たり判定にパーティクルのON/OFF処理を追加。

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

public class MashuOnHit : MonoBehaviour
{

    // テクスチャ
    const string TEXTURE_FILE_BAKE = "bake";
    const string TEXTURE_FILE_BLACK = "black";

    [SerializeField]
    GameObject smoke;

    Texture2D bake;
    Texture2D black;

    // 焚火に触れている間のカウント
    private int count = 0;

    void Start()
    {
        bake = Resources.Load<Texture2D>(TEXTURE_FILE_BAKE);
        black = Resources.Load<Texture2D>(TEXTURE_FILE_BLACK);

        // パーティクルオフ
        smoke.GetComponent<ParticleSystem>().Stop();
    }

    void OnTriggerEnter(Collider other)
    {
        // パーティクルオン
        if (other.gameObject.tag == "Fire")
        {
            smoke.GetComponent<ParticleSystem>().Play();
        }
    }

    void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "Fire")
        {
            count++;

            // 焼けたかな
            if (count == 1000)
            {
                this.GetComponent<Renderer>().material.mainTexture = bake;
            }
            else if (count == 2000)
            {
                this.GetComponent<Renderer>().material.mainTexture = black;
            }
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Fire")
        {
            // パーティクルオフ
            smoke.GetComponent<ParticleSystem>().Stop();
        }
    }
}

ParticleSystem.Start()ParticleSystem.Stop()でパーティクルのON/OFFができました。

f:id:takeda_san:20180225132812g:plain

うんうん、いい感じ。
その後、ちょっと照明を調整したものが動画の内容です。

次回予定

モデリング楽しかったので、人型モデルをひとつ作ります。
そして、挫折予定。

Youtubeに読書感想動画上げてます。
チャンネル登録してもらえると、すごく喜びます。

www.youtube.com