はじめに
先日、Oculus Rift 用のアプリを作って公開してみました。
ミクさんがミクさんになった自分についてくるアプリです。Oculus Rift では、下を向くと自分の身体が無いことに違和感を感じるので、何かキャラクターの身体を表示してみたいなと思って作ってみました。
作業の間にハマった点等をメモとして残しておきます。
お借りした素材
- モーション
- 歩き&走りモーション(瞬きするように手を加えました)
- ステージ
- Sci-Fi Level
- Unity Asset Store より
- Sci-Fi Level
首の動きと VMD との連動について
アイドル時や歩行時のアニメーションは MMD を使って作成した VMD を吐き出して、そのアニメーションをしながら頭だけはカメラの方を向くようにしたかったのですが、最初はホラーのように頭がガクガク動いてしまって困ってました。結論としては VMD に頭の座標も入っていたからだったようです。気づくまでに 2 日くらい掛かりました…。
なお、頭は以下の様なコードで動かすことができます。
// 何らかの方法で頭とカメラのゲームオブジェクトを取得 GameObject head = GameObject.FindWithTag("MikuHead"); GameObject camera = GameObject.FindWithTag("MainCamera"); // クォータニオンと球面線形補間で滑らかに顔をカメラの方へ向かせる Quaternion headRotFrom = head.transform.rotation; Quaternion headRotTo = Quaternion.LookRotation(head.transform.position - camera.transform.position); head.transform.rotation = Quaternion.Slerp(headRotFrom, headRotTo, 0.04f);
そのままだと首があらぬ方向へ向いてしまうので、Lukalus のように前を向かせるか、本作のように歩いて自分の方を向き直すようにするのが吉だと思います。
コード全容は以下になります。
ミクさん
using UnityEngine; using System.Collections; public class MikuController : MonoBehaviour { private GameObject player_; private GameObject camera_; private GameObject head_; private GameObject miku_; private CharacterController controller_ = null; private bool isFollow_ = false; public float followDistance = 3.0f; public float waitDistance = 1.5f; public float speed = 5.0f; public float gravity = 3.0f; public float rotationSpeed = 1.0f; void Awake() { // Get player gameObject player_ = GameObject.FindWithTag("Player"); // Get Miku-san's head gameObject head_ = GameObject.FindWithTag("MikuHead"); Debug.Log (head_.name); // Get Character Controller controller_ = gameObject.GetComponent<CharacterController>(); // Get Miku miku_ = gameObject.transform.FindChild("Miku").gameObject; // Get main camera camera_ = GameObject.FindWithTag("MainCamera"); } void Update() { // Walk toward camera // -------------------------------------------------------------- float distance = Vector3.Distance(transform.position, player_.transform.position); if (distance > followDistance) { isFollow_ = true; miku_.animation.CrossFade("miku_walk"); } if (distance < waitDistance) { isFollow_ = false; miku_.animation.CrossFade("miku_idle"); } // Direction // -------------------------------------------------------------- Vector3 playerDirection = player_.transform.position - transform.position; float diffAng = Vector3.Angle(transform.forward, playerDirection); if (diffAng > 70) { isFollow_ = true; miku_.animation.CrossFade("miku_walk"); } // Face to camera position // -------------------------------------------------------------- Quaternion headRotFrom = head_.transform.rotation; Quaternion headRotTo = Quaternion.LookRotation(head_.transform.position - camera_.transform.position); head_.transform.rotation = Quaternion.Slerp(headRotFrom, headRotTo, 0.04f); // Follow // -------------------------------------------------------------- if (isFollow_) { Quaternion mikuRotFrom = transform.rotation; Quaternion mikuRotTo = Quaternion.LookRotation(player_.transform.position - transform.position); mikuRotFrom.x = 0; mikuRotFrom.z = 0; mikuRotTo.x = 0; mikuRotTo.z = 0; transform.rotation = Quaternion.Slerp(mikuRotFrom, mikuRotTo, 0.05f); controller_.Move(transform.forward * speed * Time.deltaTime); } } }
プレイヤー
using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public float speed = 5.0f; public float gravity = 3.0f; public float rotationSpeed = 1.0f; private OVRCameraController camera_ = null; private CharacterController controller_ = null; private GameObject miku_ = null; void Start() { // Get Oculus camera information (Ref: OVRPlayerController.cs) OVRCameraController[] CameraControllers; CameraControllers = gameObject.GetComponentsInChildren<OVRCameraController>(); if(CameraControllers.Length == 0) { Debug.LogWarning("OVRPlayerController: No OVRCameraController attached."); } else if (CameraControllers.Length > 1) { Debug.LogWarning("OVRPlayerController: More then 1 OVRCameraController attached."); } else { camera_ = CameraControllers[0]; } // Get character controller controller_ = gameObject.GetComponent<CharacterController>(); if (controller_ == null) Debug.LogError("No CharacterController detected!"); // Get Miku-san miku_ = gameObject.transform.FindChild("Miku").gameObject; if (miku_ == null) Debug.LogError("No MIKU found!"); } void Update() { // Get degrees from Oculus Vector3 angles = Vector3.zero; camera_.GetCameraOrientationEulerAngles(ref angles); // player direction bool moveForward = false; bool moveBack = false; bool moveLeft = false; bool moveRight = false; bool rotateLeft = false; bool rotateRight = false; Vector3 moveDirection = Vector3.zero; float rotation = 0.0f; if (Input.GetKey(KeyCode.W)) { moveForward = true; moveDirection += Vector3.forward; } if (Input.GetKey(KeyCode.S)) { moveBack = true; moveDirection += Vector3.back; } if (Input.GetKey(KeyCode.A)) { moveLeft = true; moveDirection += Vector3.left; moveDirection *= Mathf.Sin(Mathf.PI / 4); } if (Input.GetKey(KeyCode.D)) { moveRight = true; moveDirection += Vector3.right; moveDirection *= Mathf.Sin(Mathf.PI / 4); } if (Input.GetKey(KeyCode.Q)) { rotateLeft = true; rotation -= rotationSpeed; } if (Input.GetKey(KeyCode.E)) { rotateRight = true; rotation += rotationSpeed; } // speed moveDirection = transform.TransformDirection(moveDirection); // gravity moveDirection += Vector3.down * gravity; // animation if (moveForward || moveBack || moveLeft || moveRight || rotateLeft || rotateRight) { miku_.animation.CrossFade("miku_walk"); } else { miku_.animation.CrossFade("miku_idle"); } // move controller_.Move(moveDirection * speed * Time.deltaTime); // rotate transform.Rotate(Vector3.up * rotation * 100.0f * Time.deltaTime); } }
高速化について
GPU は Quadro 600 を使っているのですが、フレームレートが 40 FPS 強程度しか出なかったので調べてみた所、IK の処理が結構重くなっているようです。
そこで、髪の毛やネクタイ、足など IK を使用している所の「iteration」の値をできるだけ下げてみたところ、60 fps 出るようになりました。後は見えないプレイヤー側のミクさんの「Expression」(表情データ)のチェックボックスを OFF にして、少し早くなった気がします。
おわりに
次は Kinect 連動とか Razer Hydra 連動をやってみます。
Unity でゲーム創ってると、ロジックで動かしたいトコだけコード書けば良いのが楽で良いですね。