Raspberlyのブログ

Raspberlyのブログ

Unityネタをメインとした技術系ブログです。にゃんこ大戦争や日常なども。そろそろブログタイトル決めたい

【Oculus Touch開発メモ】拳で物を殴る、指でつっつく【Unity】

 

今回はOculus Touchの開発ネタをやっていきます。
使う機材はOculus Questですが、Oculus Rift Sでもそのまま使うことができる内容です。

 

Oculus Questをお持ちの方は公式チュートリルアプリであるFirst Stepsをプレイ済みかと思われます。
ãFirst Stepsãoculusãã®ç»åæ¤ç´¢çµæ

www.oculus.com


このコンテンツは物を掴んで投げるだけでなく、拳(握った状態)で殴ることができ、
人差し指を立てた状態でツンツンすることもできます。

f:id:Raspberly:20190817175200g:plain


これは今までやってきたAvatarGrabなどにあるOVR Grabberではできないことです。

f:id:Raspberly:20190817175249g:plain



そこで今回は拳で物を殴ったり、指でつんつんするなど、手を使った物理挙動を作ってみます

f:id:Raspberly:20190826133057g:plain

f:id:Raspberly:20190828231129g:plain

 

 

 

 

 

 

 

 

開発環境

Windows 10

Unity 2018.4.5f

Oculus Integration ver1.39

Oculus Quest

ALVR

 

 

 

アプローチ

OVR Grabber(とそれを継承しているDistance Grabberなど)では
掴むといった判定はすべてIsTriggerで行われます。
つまり、CustomHandやAvatartGrabでは物理衝突を行うことができません

 

探してみたところ、OculusIntegration内に物を殴るといった機能はないようです。(あったらごめん)
そのため、手に沿ったコライダーを付け、物理衝突を制御するコンポーネントを独自実装していきます。

 

 

セットアップ

始めてVR開発をする方はこちらを参考にしてみてください

raspberly.hateblo.jp

既にこちらを終えてあることを前提として進めていきます。

 

 

 

サンプルシーンの確認

Assets/SampleFramework/Usageに様々なサンプルシーンがあります。
学習や挙動の確認にとても有益です。見てみましょう。

今回はお馴染み、AvatarGrabシーンを使っていきます。

f:id:Raspberly:20190816190502p:plain

 

【追記】 2020/02/18

AvatarGrabシーンが削除されました。詳しくはこちら。

raspberly.hateblo.jp

 

 

 

OVR Fist

今回はOculusIntegrationにない機能ですので独自コンポーネントを実装していきます。
その名もOVRFistコンポーネントです。

github.com

インスペクターパラメーター

f:id:Raspberly:20190828233101p:plain

Controller

HandやOVRGrabberにあるものと同じです。
右手であればR Touch左手であればL Touchを選択します。

 

 

 

 

 

実際に使ってみる

手の用意

あらかじめ手を用意しておきましょう。

raspberly.hateblo.jp

 

手っ取りばやく用意するのであれば、CustomHandプレハブをそのまま使いましょう。

f:id:Raspberly:20190817184608p:plain

AvatarGrabにあるLocalAvatarWithGrabの中に右手と左手を加えます。
最初から入っているAvatarGrabber2つは非アクティブにしておきましょう。

f:id:Raspberly:20190817223758p:plain

 

ついでにLocalAvatarWithGrabの中にあるOvrAvatarコンポーネントも無効にしておきます。

f:id:Raspberly:20190828232236p:plain





 

 

余談ですが、デフォルトのCustomHandプレハブは左右の手でマテリアルが違います
見た目を統一したい場合はマテリアルを差し替えましょう。

変え方は前回の記事で紹介しています。

 

 

 

 

 

手に当たり判定をつける

まずは手に当たり判定をつけましょう。
AvatarGrabなどの手には最初からCapsuleColliderがついていますが、これはGrab用のIsTriggerなものです。
当然物理衝突はしません。

f:id:Raspberly:20190818174456p:plain


そのため物理衝突をするコライダーを手の形に沿ってつけていきます。
手にはSkinnedMeshColliderがついているのでMeshColliderをつけたいところですが、
MeshColliderはメッシュの変形に対応していないので、プリミティブなコライダーを組み合わせて再現します。

 

使うのはこちらのアセット「SAColliderBuilder」

複雑な形の3Dモデルでも簡単に当たり判定をつけることができるアセットです。

詳しくはこちらで解説されています。

kan-kikuchi.hatenablog.com

 

 

SAColliderBuilderの設定

SA Bone Collider Builderのアタッチ

このコンポーネントはAnimatorのあるオブジェクトにアタッチする必要があります。
CustomHand内にある、hand_skeltal_lowersにSA Bone Collider Builderをアタッチします。

f:id:Raspberly:20190825125038p:plain

 

Animatorがアタッチされているのが目印。

f:id:Raspberly:20190825131840p:plain

 

 

コライダーの生成

SABoneColliderBuilderのインスペクターを編集します。

・Shape TypeをCapsule
・Optimize Rotationのチェックを外す
・Rigidbody IsCreateのチェックを外す
最後にProcessを押して生成

f:id:Raspberly:20190828081729p:plain

これでいい感じにコライダーが生成されます。
片手が終わったらもう片方の手もやりましょう。

f:id:Raspberly:20190825145601p:plain

 

 

テスト段階で偶然見かけましたが、コライダー生成時にRigidbodyをつけ、IsKinematicを外すと。
手のメッシュが崩れとんでもないことになります。
似たような現象が起きた方はRigidbodyあたりを確認してみましょう。

f:id:Raspberly:20190827083621g:plain

 

 

 

動作確認

ここまでできたら実際に動かしてみたいところですが、このままでは問題があります。
なんとせっかくつけたコライダーが実行時に無効化されてしまいます。

 

犯人はHandコンポーネントです。
Handがアタッチされている子オブジェクトのコライダー(IsTriggerでないもの)はStart時に無効化されます

// Collision starts disabled. We'll enable it for certain cases such as making a fist.
m_colliders = this.GetComponentsInChildren<Collider>().Where(childCollider => !childCollider.isTrigger).ToArray();
CollisionEnable(false);

 

OVRFistではここも考慮しています。

 

 

 

 

OVRFistの設定

GitHubからOVRFistを持ってくる

GitHubからOVRFistを持ってきます。

github.com

Handオブジェクトにアタッチ

これをHandコンポーネントやOVRGrabberコンポーネントがある同じオブジェクトにアタッチします。
ちなみにOVRGrabberコンポーネントは必須ではありません。

f:id:Raspberly:20190828233209p:plain

インスペクターのControllerパラメーターを変更します。
右手であればR Touch左手であればL Touchに設定します。

 

これで終わりです。

 

 

 

 

 

操作方法

First Stepsとほとんど同じ操作感です。

HandTrigger(中指のボタン)を握っている間のみ拳の当たり判定が有効になります。
ただし、OVRGrabbableが掴める範囲内にある場合は掴むことを優先します。

f:id:Raspberly:20190826133057g:plain

物を放り投げて殴り飛ばすこともできます。

f:id:Raspberly:20190828231129g:plain

 

 

 

 

まとめ

SAColliderBuilderを使えば簡単に手に当たり判定をつけることができる。

OVRFistコンポーネントで拳に物理的な衝突判定を実装できる。

 

 

 

 

 

 

 

 

参考文献

 

movie-impression.com

kan-kikuchi.hatenablog.com

 

 

 

おまけ OVR Fistのコードについて

正直OVR Grabberのm_grabCandidatesをPublicにして直接参照すればOnTriggerなどは不要になりますが、
できるだけOculusIntegrationのコードに手を加えたくないためこういう書き方になっています。

 

これに限らずOculusIntegrationのコードにいろいろ手を加えている方は、
アップデートのたびに手直しする必要がでてくるので注意しましょう。
あるいはアップデートしないというのも手です。

 

 

 

 

 

おまけ Handコンポーネントのバグ?

OculusIntegrationに含まれているHandコンポーネントにはバグがあります。
Handには子オブジェクトにあるIsTriggerではないコライダーを全て無効化するCollisionEnable()があります。
このメソッドが子オブジェクト全てのコライダーの有効化無効化を切り替えていますが、
同時にコライダーのあるオブジェクトのスケールも変更しています。

 


この時、有効化時にはCOLLIDER_SCALE_MAXを適応しなければならない所がCOLLIDER_SCALE_MINになっています。m_collisionScaleCurrentもなんかおかしい。
このままでは、例えコライダーが有効化されてもスケールが小さいままです。。

private void CollisionEnable(bool enabled)
{
    if (m_collisionEnabled == enabled)
    {
        return;
    }
    m_collisionEnabled = enabled;

    if (enabled)
    {
        m_collisionScaleCurrent = COLLIDER_SCALE_MIN;
        for (int i = 0; i < m_colliders.Length; ++i)
        {
            Collider collider = m_colliders[i];
            collider.transform.localScale = new Vector3(COLLIDER_SCALE_MIN, COLLIDER_SCALE_MIN, COLLIDER_SCALE_MIN);
            collider.enabled = true;
        }
    }
    else
    {
        m_collisionScaleCurrent = COLLIDER_SCALE_MAX;
        for (int i = 0; i < m_colliders.Length; ++i)
        {
            Collider collider = m_colliders[i];
            collider.enabled = false;
            collider.transform.localScale = new Vector3(COLLIDER_SCALE_MIN, COLLIDER_SCALE_MIN, COLLIDER_SCALE_MIN);
        }
    }
}

 

正しくはこうなるはずです。

if (enabled)
{
    m_collisionScaleCurrent = COLLIDER_SCALE_MAX;
    for (int i = 0; i < m_colliders.Length; ++i)
    {
        Collider collider = m_colliders[i];
        collider.transform.localScale = new Vector3(COLLIDER_SCALE_MAX, COLLIDER_SCALE_MAX, COLLIDER_SCALE_MAX);
        collider.enabled = true;
    }
}
else
{
    m_collisionScaleCurrent = COLLIDER_SCALE_MIN;
    for (int i = 0; i < m_colliders.Length; ++i)
    {
        Collider collider = m_colliders[i];
        collider.enabled = false;
        collider.transform.localScale = new Vector3(COLLIDER_SCALE_MIN, COLLIDER_SCALE_MIN, COLLIDER_SCALE_MIN);
    }
}

それとも、このままの方が正しいのだろうか、よくわからん。

 

 

 

 

 

今までGistなどを使ってましたが、今回初めてGitHubでコードを公開しました。
READMEの書き方とかおかしい所、他間違っている箇所がありましたらコメントにお願いします。