目次
1. BlenderでVroidのモデルを編集しよう
2. Oculus Quest 2の準備をしよう
3. UnityにVRMモデルを入れてみよう
4. VeryAnimationで遊んでみよう
5. DynamicBoneでいろいろ遊んでみよう
6. UnityのAnimationでいちゃいちゃしよう
デモとパッケージ
6.UnityのAnimationでいちゃいちゃしよう
最後のセクションではUnityのAnimationを利用していちゃいちゃしてみたいと思います。
その前にVR上で二次元の女の子といちゃいちゃする場合の問題点について、考えてみたいと思います。
ここから、VR上でも問題点について考察が長々と続くので時間がない方は次のStepまで飛んでください。ただの設計思想の話なので、ただいちゃいちゃするだけなら、読まなくても大丈夫です。
VR上で胸の中に手が突っ込めてしまう問題。
現実世界でいちゃいちゃする場合には、物理法則が働きます。つまり、動いている胸に対して、動いている手が当たった場合、胸と手の両方に対して作用反作用の法則が働き、物体が移動します。
UnityでいうならばColliderとRigidBodyがアタッチされているもの同士が衝突するのと同じ働きです。
言い換えるならば、胸に触っても手が胸の内部にまでは侵入はしないということです。
しかし、これをOculusHandに実装しようとする場合様々な問題点が生じます。
以下に実際のテストでの失敗を上げていきます。
初めにSABoneColliderでColliderを設置するときに、Oculus HandとモデルのisKinematicを作用反作用を有効にするために、無効にしてみました。
すると、PlayModeで、指先が吹っ飛んだり、髪の毛が大暴れしたり、とても面白い現象が起きます。
原因はColliderの重複でした。Colliderの重複により、OculusHand中のColliderが衝突判定され、勝手に指とかが吹っ飛んだようです。RigidBodyそのものにTagNameなどから場合分けなどができるわけではないので、両方のRididBodyの有効化は不可能となりました。
次に考えられるのは胸のみに物理法則を適用する方法です。つまり、胸のisKinematicだけを無効にするやり方です。実際やってみるとわかるのですが、胸がへこみます。移動した後で、手がColliderの範囲を離れたときに元のTransformに瞬間移動しても、違和感しか残りませんでした(笑)。つまり、安直に物理法則を適用しただけでは、実際の胸のように弾性体の動きをしません。
これにより、DynamicBoneを採用する流れになりました。
以上の考察から、両方にColliderを設置することができず、また、片方だけのColliderを設置するのも不可能となるので、あとはTriggerによって強制的に移動させる方法が残りました。
つまり、胸と手が接触した場合、Triggerが動作し、手のTransformを利用して強制的に移動させる方法です。
実際にやってみると、確かに胸の中に手が入らないようにはできますが、実際の手の位置と画面中の手の位置がずれます。これはとても強烈な違和感を生じ、また、元の位置に手を戻すことが何故か出来なかったので、不採用となりました。
ならば、画面の手の位置と実際の手の位置が離れないように、カメラごと移動すればいいのではないか、という話になります。つまり、手の移動量と同じだけの移動量を手の代わりにカメラが移動すればいいのかということです。
ですが、実際にやってみるとカメラと手の移動量が同じはずなのに、カメラがより多く移動しているように感じられます。どうも、Oculus内でCameraのTransformを変化させることは想定されていないみたいで、バグが生じているようでした。
以上をまとめると、手が胸に貫通するのは現在のところ防げない、もしくは防ぐのにデメリットが大きすぎるということです。
しかし、何故手が胸を貫通してはいけないのかという根本的な問題に立ち返ってみたいと思います。
胸に触ったと同時に胸が揺れていれば、クロスモーダル現象が起き、実際に触ったかのような感覚が多少なりとも得られます。ところが次に感じるのは胸に手がすり抜けているという感覚。つまり、相異なる感覚がほぼ同時に感じられるために、違和感が生じます。また、アバター本人からのフィードバックがユーザーに返ってこないことが、更に違和感に拍車をかけています。
なので、今回の設計では胸に触れた場合にアバターにAnimationを取らせることで、この違和感を回避しようとしています。つまり、手が胸に触れて、胸が揺れた直後に別の動作があることで、ユーザーの手がすり抜けているという感覚が生じる前に、アバターの行動に対しての意識が移るからと考えているからです。
実際には全身にColliderを設置し、Animationを取らせるのは大変なので、今回はユーザーがよく振れるであろう箇所の、胸・上腕・顔・頭頂部・臀部にColliderを設置し、Animationを取らせています。
では、実際の実装に入ります。
頭と顔のColliderを設置しよう!
もうすでに5.DynamicBoneでいろいろ遊んでみようのところで、上腕と胸と臀部にcolliderが設置されていると思います。
J_Bip_C_UpperChestを用いて、Coll.J_Bip_C_UpperChestを設置したやり方と同様にして、頭部にColl.J_Bip_C_Headを設置します。これを親が同じになるようにコピぺして、名前をColl.J_Bip_C_TopOfTheHeadにします。写真のように大きさを調整してもらえればOKです。
Animatiorを設定しよう!
Animatorに関しては4.VeryAnimationで遊んでみよう!
とやり方は全く同じです。
Projectのところで右クリックをして、Create > AnimationでAnimationClipの作成。それぞれの名前をArm, Chest, Face, Hip, Headとして、4.VeryAnimationで遊んでみよう!
で使用した、AnimatorにDrag&Dropします。
AnimatorのParamatorsにactionというint型の変数を追加します。
つぎに、それぞれのAnimationClipとWaitingを相互にMake Transitionで結びます。
Waitingから、それぞれのAnimationへの矢印をクリックして、Inspector中のConditionsを action Equals 1 ~ 5 までの数字 というようにします。
次に各AnimationClipのノードをクリックして、AddBehaviorから4.VeryAnimationで遊んでみよう!
で使用した、ExitAnimationをアタッチします。
Collider用のScriptをアタッチしよう
Projectタブのところで、右クリックから、Create > C# ScriptでAnimation_Actionを以下のように作成します。
using System;
using System.Collections;
using System.Collections.Generic;
using Oculus.Platform.Models;
using UnityEngine;
public class Animation_Action : MonoBehaviour
{
[SerializeField] int ActionIndex = 0;
[SerializeField] Animator animator;
private void OnTriggerEnter(Collider other)
{
if (ActionIndex == 0)
{
Debug.Log("Action Index is Invalid!");
}
if (other.tag == "LeftHand" || other.tag == "RightHand")
{
animator.SetInteger("action", ActionIndex);
}
}
}
これを胸・上腕・頭頂部・顔・臀部のColliderのあるオブジェクトのところにアタッチします。
Animation_ActionのAction IndexにはAnimatorで設定した数字をそれぞれ入れて、Animatorのところには先ほど使ったAnimationControllerを入れてください。
最後にOculus Handのオブジェクトにタグを設定したいので以下のChangeAllTagをOVRCustomHandPrefab_RとOVRCustomHandPrefab_Lにアタッチしてください。
using System.Collections.Generic;
using UnityEngine;
public class ChangeAllTag : MonoBehaviour
{
[SerializeField] String tagname;
[SerializeField] GameObject gameobject;
// Start is called before the first frame update
[ContextMenu("Change Tag Name")]
void ChangeName()
{
var allChildren = GetAll(gameObject.gameObject);
foreach (GameObject obj in allChildren) {
obj.tag = tagname;
}
}
private List<GameObject> GetAll (GameObject gameObject)
{
List<GameObject> allChildren = new List<GameObject> ();
GetChildren (gameObject, ref allChildren);
return allChildren;
}
//子要素を取得してリストに追加
private void GetChildren (GameObject obj, ref List<GameObject> allChildren)
{
Transform children = obj.GetComponentInChildren<Transform> ();
//子要素がいなければ終了
if (children.childCount == 0) {
return;
}
foreach (Transform ob in children) {
allChildren.Add (ob.gameObject);
GetChildren (ob.gameObject, ref allChildren);
}
}
}
Inspectorから、左手にはTagnameをLeftHand, GameObjectをOculusHand_Lとして、右手にはTagnameをRightHand, GameObjectをOculusHand_Rとします。
名前のところを右クリックして、Change Tag Nameを実行し、左手と右手のそれぞれのTagをすべて変更します。
タグが左手と右手のそれぞれがLeftHand, RightHandになっていれば大丈夫です。
これでモデルのColliderに手が接触したときにモデルがアニメーションを取るようになりました。
###AnimationClipを編集しよう!
4.VeryAnimationで遊んでみよう!
でTimeLineとVeryAnimationを用いて、AnimationClipを音声付きで編集する方法をやりました。
同様のやり方で、胸・上腕・頭頂部・顔・臀部のそれぞれのAnimationClipを編集してください。
また、AnimationClipで呼び出す関数はActionPlayBackとし、ActionPlayBackに設定した音声を呼び出してください。
以下に今回のセリフを載せておきます。
部位 | セリフ |
---|---|
頭頂部 | えへへ |
顔 | なんか、恥ずかしいよ |
胸 | ひゃー、変態ですね。 |
上腕 | ふふふ、どうしたの? |
臀部 | ひゃー、エッチですね。 |
アプリをBuildして、試してみてください。
実際にフィードバックが得られると、ただ胸が揺れるだけよりも数倍楽しくなること請け合いです。
最後に
2次元のキャラクターと3次元でのいちゃいちゃに挑戦してみました。
調整してはやり直し、調整してはやり直しを繰り返したために、Vroidのモデルの調節に1か月程度、Oculusのアプリのほうに1か月程度かかりました。
それでも、無上に好きなキャラクターといちゃいちゃできたので、やる価値は相当あったと思います。
また今回、初Qittaだったのですが、書いた理由が、
1.Qculusの個人開発アプリは承認されない。
2.もっと2次元の女の子といちゃいちゃできるようなアプリが増えてほしい。
3.一夜先輩のことをもっと知ってもらって、好きになってもらいたい。
という、個人的な理由で書いたので、もっとVRでいちゃいちゃできるアプリが増えたらとてもうれしいです。