目次
参考
- モデル作成からUnityへの取り込みまで
- OculusTouchとモデルを連動させる
- 【Unity】Oculus RiftとFinalIKを使ってUnityちゃんになる。
- Unity3D:ライトを特定のオブジェクトのみに当てる
- Oculus TouchでUnityちゃんの指を動かして遊ぼう
環境
- Unity 2019.1.0f2
- SteamVR Plugin 2.2.0
- Unity:VRゲームを作ってみたい/05-SteamVR:ものを掴んで投げてみる の続き
前提
- 有償Assetの FinalIK 利用
- Oculusの操作系は無理やりSteamVRのAPIで再実装する
1.とりあえずUnityにモデルを読み込む。詳細な内容は本項では除外する。
- 1.1.詳細は本wikiのVRChatのページで
2.とりあえずRigidbodyやcolliderを持ったPlayerの下にモデルを配置。

- 2.1.Cameraとか余計なものがモデルに含まれてたらチェックを外すなりして無効化すること。Colliderとモデルが重なるようにする。
- 2.2.まずは静止状態のモデルにする
- 2.2.1.AssetStore からIdle MoCapを入手、インポートする
- 2.2.2.Projectで適当な所に、[Create]→[Animator Controller]を作成
- 2.2.3.Animator Controllerをダブルクリックなりで開き、ここに [Idle MoCap]→[Animations]の[Idle_Stance_02_MB_v01]あたりをドロップする

- 2.2.4.ヒエラルキーからモデルを選択し、Animatorコンポーネントを追加。先程作成したAnimator ControllerをControllerに設定する。

棒立ちちゃん
3.モデルをVRヘッドセット、コントローラーに同期させる
- 3.1.SteamVRのPlayerオブジェクト配下にある[SteamVRObjects]の[LeftHand]と[RightHand]の下にそれぞれ空のオブジェクトを作成し、分かりやすい名前にしておく。 ※なおRightHandやLeftHandのUse Hover Sphereで表示される緑色のワイヤーフレームの球体の位置がコントローラーの中心位置のようなのでここに合わせると良い。

- 3.2.同様にPlayerが持つ[FollowHead]に空のオブジェクトを作成し、分かりやすい名前にしておく。

- 3.3.Assetストアからおもむろに有償Assetの FinalIK を導入、インポートする
- 3.4.モデルに[Assets]→[Plugins]→[RootMotion]→[FinalIK]→[IK Components]の[VR IK]を設定する
- 3.5.VR IKのインスペクターの設定で、[Spine]と[Left Arm]と[Right Arm]のそれぞれのHead Targetに、先程3.1.と3.2.で作成した空オブジェクトを指定する。

- 3.6.この時点で概ね動くと思われるが、あとはひたすらオブジェクト間の親子関係と相対位置の調整、カメラ位置の調整などを実施する。手のひらの向きが同期していなかったらLeftHandやRightHandに作成た空オブジェクトのRotateを調整しよう。※もう少し自動でなんとかしてくれる何かがあるような気もするが…
- 3.7.足の動き周りは、VR IKの[Locomotion]あたりを弄ると若干まともになる・・・・かも。以下の画像は今回試したケースでの値だが、モデルの特性によって異なるはずなので要調整。

4.モデルの状態確認に便利な鏡を用意する。
- 4.1.適当な鏡にするPlaneを配置
- 4.2.適当に鏡に写す内容を撮影するカメラを配置
- 4.3.Projectに適当に[Render Texture]を新規作成、Sizeの値が解像度になるのでちょっと上げておく。

- 4.4.設置したCameraを選択し、インスペクターの[Camera]の[Target Texture]に先程作成したRender Textureを指定する。
- 4.5.同じくCameraのインスペクターでAudioListenerを無効化、TargetEyeはNoneにしておく。※VRのカメラと競合するため

- 4.6.あとはProjectから先程作成したRender Textureを、ヒエラルキーの先程作成したPlaneにドロップで適用して完了。

- 4.7.光の当たり具合でPlaneに映る映像が暗くなってしまう場合があるので、自分はセットでDirectionalLightを設置し、専用のレイヤーを割り当てて周りには影響しないように設定した。

本当はシェーダー設定でUnlitに設定するらしい
5.せっかくなので指も動かしたい
- 5.1.参考サイトのスクリプトをお借りして、モデルに適用する
- Oculus TouchでUnityちゃんの指を動かして遊ぼう
- 5.2.なお機能の全容を理解しないままにSteamVRのAPIに変更したため、機能を損ねている可能性が高い…が一応ソースコードを載せておく。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using Valve.VR;
-
- // OculusFinger Ver.0.21 - zlib License
- // (C) NISHIDA Ryota, ship of EYLN http://dev.eyln.com
-
- // 流用元
- // -Oculus TouchでUnityちゃんの指を動かして遊ぼう
- // --http://eyln.hatenablog.com/entry/2016/12/19/003435
-
- // 更にSteamVR2.2.0のInputAPI向けに変更したが、既存機能の理解ができていないため機能を損ねている可能性あり。自分でつくろう(白目)
-
- public class FingerController : MonoBehaviour
- {
- public SteamVR_Input_Sources handType;
- public SteamVR_ActionSet myAction;
- public SteamVR_Action_Single Trigger;
- public SteamVR_Action_Boolean Trigger_Touch;
- public SteamVR_Action_Single Grip;
- public SteamVR_Action_Vector2 JoyStick;
- public SteamVR_Action_Boolean JoyStick_Touch;
- public SteamVR_Action_Boolean JoyStick_Click;
- public SteamVR_Action_Boolean Button_X;
- public SteamVR_Action_Boolean Button_X_Touch;
- public SteamVR_Action_Boolean Button_Y;
- public SteamVR_Action_Boolean Button_Y_Touch;
- public SteamVR_Action_Boolean Button_A;
- public SteamVR_Action_Boolean Button_A_Touch;
- public SteamVR_Action_Boolean Button_B;
- public SteamVR_Action_Boolean Button_B_Touch;
-
- [TooltipAttribute("Awake時に現在のFingerTypeにあわせて自動設定を行うか")]
- public bool isAwakeAutoSetup = true;
- [TooltipAttribute("Oculus Touchによるタッチ入力を有効にするか")]
- public bool isEnableTouchControl = true;
-
- public enum FingerType
- {
- Custom, // 独自設定
- Auto, // Awake時に指の名前から自動的設定
- L_Thumb, // 左手親指
- L_Index, // 左手人差し指
- L_Middle, // 左手中指
- L_Ring, // 左手薬指
- L_Little, // 左手小指
- R_Thumb, // 右手親指
- R_Index, // 右手人差し指
- R_Middle, // 右手中指
- R_Ring, // 右手薬指
- R_Little, // 右手小指
- }
-
- [TooltipAttribute("指のタイプ")]
- public FingerType fingerType = FingerType.Auto;
-
- [HeaderAttribute("Oculus Touch Settings")]
-
- [TooltipAttribute("触れると指を半分曲げる近接センサーボタン類")]
- public List<SteamVR_Action_Boolean> touchButtonPool;
- [TooltipAttribute("握ると指を曲げるトリガー(touchButtonPool指定時は半分~最後まで)")]
- public SteamVR_Action_Single trigger = null;
- [Range(0, 0.99f), TooltipAttribute("トリガーを使うとき、指を曲げ始める開始位置")]
- public float triggerStart = 0.0f;
- [TooltipAttribute("触れていてなおかつトリガーを少し曲げたときに指を半分曲げる近接センサーボタン")]
- public SteamVR_Action relatedTouchButton = null;
-
- [HeaderAttribute("Joint Settings")]
-
- [SerializeField, TooltipAttribute("根本から指先につながっていく関節を設定(未指定のときAwakeで自動設定)")]
- private List<Transform> jointPool;
- [SerializeField, TooltipAttribute("各関節が根本の角度に対してどの割合で曲がるかを設定(未指定のときAwakeで自動設定)")]
- private List<float> jointLevelPool;
- private List<Quaternion> jointBaseRotPool; // 各関節の初期姿勢(Awake時に自動記憶)
-
- [HeaderAttribute("Finger Angle")]
- [TooltipAttribute("指関節の回転軸")]
- public Vector3 axis = Vector3.up;
- [TooltipAttribute("現在の回転角度(実行時に動的に変わる)")]
- public float angle = 0.0f;
- [TooltipAttribute("最大の回転角度")]
- public float maxAngle = 90.0f;
- [Range(0, 1), TooltipAttribute("指を開くときの補間の速さ")]
- public float openLerpLevel = 0.3f;
- [Range(0, 1), TooltipAttribute("指を閉じるときの補間の速さ")]
- public float closeLerpLevel = 0.15f;
- [TooltipAttribute("親指か(親指を立てられるようにするか)")]
- public bool isThumb = false;
- private bool isThumbsUp = false; // 親指を立てているか
-
- void Awake()
- {
- if (isAwakeAutoSetup) { AutoSetup(); }
-
- foreach (Transform joint in jointPool)
- {
- jointBaseRotPool.Add(joint.transform.localRotation);
- }
- }
-
- // 現在のFingerTypeにあわせて各種設定を自動設定(コンポーネントのメニューからも実行可能)
- [ContextMenu("Automatic Setup")]
- void AutoSetup()
- {
- AutoSetupJoint();
- SetupFingerType(fingerType);
- }
-
- // 指の根本にOculusFingerコンポーネントをつけたとして、その子供の最初にあるものを
- // 順に関節として、末端の前まで自動設定する(手動設定済みの場合はそのまま)
- // あわせて、関節を曲げる量も個別に設定可能にする
- void AutoSetupJoint()
- {
- if (jointPool.Count <= 0)
- {
- Transform t = transform;
- while (t && t.childCount > 0)
- {
- jointPool.Add(t);
- jointLevelPool.Add(1.0f);
- t = t.GetChild(0);
- }
- }
- }
-
- // GameObjectの名前が指の名前のとき指タイプとボタンや回転軸を自動設定
- void AutoSetupFingerType()
- {
- FingerType type = FingerType.Custom;
- string name = transform.name.ToLower();
- string[] typeNames = { "thumb", "index", "middle", "ring", "little" };
- int typeIndex = (int)FingerType.L_Thumb;
- if (name.IndexOf("right") >= 0) { typeIndex = (int)FingerType.R_Thumb; }
-
- for (int i = 0; i < typeNames.Length; i++)
- {
- if (name.IndexOf(typeNames[i]) >= 0)
- {
- type = (FingerType)(typeIndex + i);
- break;
- }
- }
- SetupFingerType(type);
- }
-
- void Start()
- {
- myAction.Activate(SteamVR_Input_Sources.Any);
- }
-
- void Update()
- {
- if (!isEnableTouchControl) return;
-
- // Oculus Touchの近接センサー付きボタンを指定していて、触れていたら指を半分曲げる
- float touchLevel = 0.0f;
- foreach (SteamVR_Action_Boolean touch in touchButtonPool)
- {
- if (touch.GetState(handType)) { touchLevel = 0.5f; break; }
- }
-
- // 近接センサーを指定していて、なおかつ(この指の)トリガーを少しでもひいていたら指を半分曲げる
- bool isRelatedTouch = false;
- {
- if (((SteamVR_Action_Boolean)relatedTouchButton).GetState(handType))
- {
- if ((trigger != null) && trigger.GetAxis(handType) > 0.1f)
- {
- isRelatedTouch = true;
- }
- }
- }
- if (isRelatedTouch) touchLevel = 0.5f;
-
- // 近接センサーに触れてるか、近接センサーを指定していないとき、トリガーで曲げる
- if (touchLevel > 0.0f || touchButtonPool.Count <= 0)
- {
- float triggerLevel = 0;
- if (trigger != null) triggerLevel = (trigger.GetAxis(handType) - triggerStart) / (1 - triggerStart);
- triggerLevel = Mathf.Clamp01(triggerLevel);
- if (touchLevel > 0.0f) { triggerLevel *= 0.5f; } // 近接センサーを使っているときは残りをトリガーで曲げる
- touchLevel += triggerLevel;
- }
-
- // 親指のとき、(中指、小指などの)指定のトリガーを引いていて、(親指の)近接センサーから指が離れていたら親指を立てるモードにする
- isThumbsUp = false;
- if (trigger != null) isThumbsUp = (isThumb && touchLevel <= 0.05f && trigger.GetAxis(handType) > triggerStart);
- if (isThumbsUp) { touchLevel = -0.5f; }
-
- // 指を補間量にあわせて補間
- float lerpLevel = (touchLevel <= 0.0f) ? openLerpLevel : closeLerpLevel;
- if (touchLevel > 0.0f && touchButtonPool.Count > 0 && trigger != null && trigger.GetAxis(handType) < 0.1f) { lerpLevel *= 0.5f; } // ちょっと近接センサーに触れただけのときはゆっくり補間する
- angle = Mathf.Lerp(angle, maxAngle * touchLevel, lerpLevel);
- }
-
- void LateUpdate()
- {
- if (jointPool.Count != jointBaseRotPool.Count && jointPool.Count != jointBaseRotPool.Count)
- {
- Debug.LogError("jointData Error.");
- return;
- }
-
- // 指の角度にあわせて各関節の姿勢を決定
- for (int i = 0; i < jointPool.Count; i++)
- {
- Transform joint = jointPool[i];
- float jointLevel = jointLevelPool[i];
- if (isThumbsUp) { jointLevel = 0.5f - i * 0.1f; }
- float rot = angle * jointLevel;
- Quaternion jointBaseRot = jointBaseRotPool[i];
- joint.localRotation = jointBaseRot * Quaternion.AngleAxis(angle * jointLevel, axis);
- }
- }
-
- // 指タイプにあわせてボタンや回転軸を自動設定
- // (キャラクターによって調整が必要な場合は下記内容を書き換えるか、Inspector上で手動設定する)
- void SetupFingerType(FingerType type)
- {
- this.fingerType = type;
- if (type == FingerType.Custom) return;
- if (type == FingerType.Auto) { AutoSetupFingerType(); return; }
-
- touchButtonPool.Clear();
- relatedTouchButton = null;
- triggerStart = 0.0f;
- isThumb = false;
-
- switch (fingerType)
- {
- case FingerType.L_Thumb:
- touchButtonPool.Add(JoyStick_Touch);
- touchButtonPool.Add(Button_X_Touch);
- touchButtonPool.Add(Button_Y_Touch);
- trigger = Trigger;
- isThumb = true;
- break;
- case FingerType.L_Index:
- trigger = Trigger;
- triggerStart = 0.5f;
- relatedTouchButton = Trigger_Touch;
- break;
- case FingerType.L_Middle:
- trigger = Grip;
- triggerStart = 0.95f;
- break;
- case FingerType.L_Ring:
- trigger = Grip;
- triggerStart = 0.1f;
- break;
- case FingerType.L_Little:
- trigger = Grip;
- break;
-
- case FingerType.R_Thumb:
- touchButtonPool.Add(JoyStick_Touch);
- touchButtonPool.Add(Button_A_Touch);
- touchButtonPool.Add(Button_B_Touch);
- trigger = Trigger;
- isThumb = true;
- break;
- case FingerType.R_Index:
- trigger = Trigger;
- triggerStart = 0.1f;
- relatedTouchButton = Trigger_Touch;
- break;
- case FingerType.R_Middle:
- trigger = Grip;
- relatedTouchButton = Trigger_Touch;
- triggerStart = 0.95f;
- break;
- case FingerType.R_Ring:
- trigger = Grip;
- triggerStart = 0.1f;
- break;
- case FingerType.R_Little:
- trigger = Grip;
- break;
- }
-
- if (jointLevelPool.Count >= 3)
- {
- float[,] levels = {
- { 0.05f, 0.5f, 0.9f }, // 親指
- { 0.9f, 1.0f, 1.2f }, // 人差し指
- { 1.0f, 0.8f, 1.6f }, // 中指
- { 1.0f, 0.7f, 1.6f }, // 薬指
- { 1.0f, 0.7f, 1.6f } // 小指
- };
-
- int fi = (int)fingerType;
- int levelType = (fi >= (int)FingerType.R_Thumb) ? fi - (int)FingerType.R_Thumb : fi - (int)FingerType.L_Thumb;
- jointLevelPool[0] = levels[levelType, 0];
- jointLevelPool[1] = levels[levelType, 1];
- jointLevelPool[2] = levels[levelType, 2];
-
- }
- }
- }
-
- 5.3.モデルのbody部を開いていって、各指の根本のオブジェクトに対して作成したControllerのスクリプトを適用する。HandTypeは右手と左手をそれぞれ設定、他は全部アクションを入れとく(これAction系の設定自動で取れないかな・・・取れるよな・・・まぁいいか)


とりあえず、なんとなくバ美肉した気分になれる程度にはできたのでこの辺にしておく。
気になるのは、かがむと顔の位置がずれてカメラに写り込んでしまったり、相変わらずプレイエリアごと回転させてるもんで旋回時に位置がずれるのが気になるが・・・
気になるのは、かがむと顔の位置がずれてカメラに写り込んでしまったり、相変わらずプレイエリアごと回転させてるもんで旋回時に位置がずれるのが気になるが・・・
モデルの貫通で何が怖いって、カメラが顔に貫通するのが顔の内側的な意味で怖いな・・・