1. N_Hidano

    Posted

    N_Hidano
Changes in title
+【Unity】オブジェクトをカメラから見た前後左右に向かせる
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,153 @@
+##前置き
+先日、UnityでCharacterControllerを使う場合、オブジェクトを動かすためのMove()とSimpleMove()では**基本的にはMove()を使おう**という[記事](https://qiita.com/N_Hidano/items/08497fed83cef1702724)を書きました。
+
+そちらのサンプルスクリプトではキャラクターのオブジェクトが前後左右に平行移動するだけでした。
+折角だからちゃんと3Dゲームで使える感じの動きにしてみようと思って回転処理を実装したらまたしても沼にハマったため、最終的に出来上がったスクリプトと解説をまとめることにしました。
+
+新年度からの目標は、戦いの記録を積極的にアップしていくことですね。
+
+##要件
+- 今回は私がこれまでの人生で一番長く遊んだ3DACTのスーパーマリオ64(DS)に従うことにします。
+- 十字キーが入力されると、**カメラから見て**前後左右の方向にキャラクターが回転します。
+- キャラクターの移動は別スクリプトのCharacterController.Move()で実装していますが、今回のスクリプトはtransform.rotationを弄っているだけなので、他にも応用は効くと思います。
+- 向いている方向を分かりやすくする為、ユニティちゃんには人柱になってもらいます。
+- 最終的には移動に合わせてカメラも追従させないとゲームになりませんが、本記事ではとりあえず回転するだけで。
+- 入力に合わせたアニメーションの設定もまた別の機会にしましょう。
+
+###スクリプト
+```cs:PlayerRotateBaseCamera.cs
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class PlayerRotateBaseCamera : MonoBehaviour{
+
+ public float rotateSpeed = 10.0F;
+
+ private Transform player;
+
+ // Use this for initialization
+ void Start () {
+ //Playerタグのつけ忘れに注意!
+ player = GameObject.FindGameObjectWithTag("Player").transform;
+ //見つからない場合は自身を設定
+ if(player == null){
+ player = transform;
+ }
+ }
+
+ // Update is called once per frame
+ void Update () {
+ float vertical = Input.GetAxis("Vertical");
+ float horizontal = Input.GetAxis("Horizontal");
+ //アナログスティックのグラつきを想定して±0.1以下をはじく
+ if(Mathf.Abs(horizontal) + Mathf.Abs(vertical) > 0.1F){
+ // π/2 - atan2(x,y) == atan2(y,x)
+ float targetAngle = Mathf.Atan2(horizontal,vertical) * Mathf.Rad2Deg;
+ //カメラの向きが変わってもここで対応できる
+ targetAngle += Camera.main.transform.eulerAngles.y;
+ Quaternion targetRotation = Quaternion.Euler(0, targetAngle, 0);
+ //deltaTimeを用いることで常に一定の速度になる
+ player.rotation = Quaternion.Slerp(player.rotation, targetRotation, Time.deltaTime * rotateSpeed);
+ }
+ }
+}
+
+```
+
+##結果
+こうなりました
+![rota.gif](https://qiita-image-store.s3.amazonaws.com/0/229970/2ed068b8-e86b-c8f4-afcf-3274a949021b.gif)
+*© UTJ/UCL*
+
+瞬間的に方向が変わるのではなく、ぐるっと回転しているのがポイントです。
+また~~面倒なので~~GIFはありませんが、カメラを違う位置と角度に向けても正しく動く事は確認済みとなっています。
+
+Hierarchyビューはこんな感じになっています。
+![無題.png](https://qiita-image-store.s3.amazonaws.com/0/229970/cb6ce2e6-0994-b31d-5d65-69f2057d24c6.png)
+
+PlayerRotateBaseCamera.csはPlayerオブジェクトのInspectorに入っています。CharacterControllerオブジェクトにまとめてもよいのでは? という気もしますが、移動と回転は分けておいた方が後々ややこしいことにならないかなぁと考えました。
+
+ちなみにModelオブジェクトはユニティちゃんの親オブジェクトにしてあるだけで、Transform以外のコンポーネントは入っていません。モデルに対して何かスクリプト処理を入れたい場合はここに追加するつもりです。
+というのも、原則として外部から持ってきたプレハブには手を加えない方がいいですからね。
+
+スクリプト内で用いているPlayerタグは、同名のPlayerオブジェクトに設定しています。そのため、入力に合わせて回転するのはPlayerオブジェクトから下の階層のみで、CharacterControllerオブジェクトは一切回転しないことになります。
+MainCameraをPlayerより下の階層に置いてしまうと大変賑やかなことになるので、興味があればやってみてください。失敗もまた経験です。
+
+以下は細かい解説となります。スクリプトにもコメントが入っていますし、それで十分だぜって方はここでお別れとなります。
+
+***
+
+##スクリプト解説
+スペースが空いたのでもう一度貼りますね。
+
+```cs:PlayerRotateBaseCamera.cs
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class PlayerRotateBaseCamera : MonoBehaviour{
+
+ public float rotateSpeed = 10.0F;
+
+ private Transform player;
+
+ // Use this for initialization
+ void Start () {
+ //Playerタグのつけ忘れに注意!
+ player = GameObject.FindGameObjectWithTag("Player").transform;
+ //見つからない場合は自身を設定
+ if(player == null){
+ player = transform;
+ }
+ }
+
+ // Update is called once per frame
+ void Update () {
+ float vertical = Input.GetAxis("Vertical");
+ float horizontal = Input.GetAxis("Horizontal");
+ //アナログスティックのグラつきを想定して±0.01以下をはじく
+ if(Mathf.Abs(horizontal) + Mathf.Abs(vertical) > 0.1F){
+ // π/2 - atan2(x,y) == atan2(y,x)
+ float targetAngle = Mathf.Atan2(horizontal,vertical) * Mathf.Rad2Deg;
+ //カメラの向きが変わってもここで対応できる
+ targetAngle += Camera.main.transform.eulerAngles.y;
+ Quaternion targetRotation = Quaternion.Euler(0, targetAngle, 0);
+ //deltaTimeを用いることで常に一定の速度になる
+ player.rotation = Quaternion.Slerp(player.rotation, targetRotation, Time.deltaTime * rotateSpeed);
+ }
+ }
+}
+
+```
+
+privateな**player**について、こちらはpublicにしてエディタから該当のオブジェクトを設定しても問題ありません。その場合は***Start()***での処理を消去しておくべきでしょう。
+今回はオブジェクトの**transform**しか使わないので、メンバ変数の型もTransformにしています。エディタから設定する場合は普通にオブジェクトをドラッグしてくることで、そのオブジェクト内のTransformコンポーネントが勝手に入ってくれます。
+
+***GetAxis()***は十字キーやアナログスティックの入力値から-1~1の値を出力します。
+***Mathf.Abs()***は絶対値を出力する関数なので、**horizontal**と**vertical**の入力の絶対値を足しても0.1に満たない場合は処理が行われないことになります。
+使いこまれたアナログスティックではニュートラルポジションにあっても若干の傾きが検出されることがあるので、ある程度の閾値を設けておくことで「操作してないのにジリジリ動く」事態を防ぐことができます。
+※GetAxis()の内部でも同じ処理がされており、閾値もエディタから変更することができます(Edit>ProjectSetting>Input>Axes>Vertical/Horizontal>Dead)。しかしUnity以外の環境でも全て同様の親切設計とは限りませんし、float型を用いた条件分岐で「float != 0」を使うのは躊躇われることもあり、現在の仕様になっています。
+
+if文の内部、***Mathf.Atan2()***のAtanはアークタンジェントの略です。
+これは逆三角関数といって、y,x座標から角度を逆算してくれます。逆三角関数は大学の分野ですが、ゲーム開発では非常によく使われるので、まだ習っていない方はアークタンジェントだけでも覚えておくとよいでしょう。
+
+そしてコメントにも書きましたが、この計算はAtan2()の引数に従うと
+`90-Mathf.Atan2(vertical,horizontal)*Mathf.Rad2Deg;`
+と書くべきなのですが、`π/2-atan(x,y)`と`atan(y,x)`が等価であるため、短く書くことにしました。
+こういった変換は高校数学でもありましたね。私は頭で考えるより覚えろと教えられたので、あまり楽しい思い出ではありませんでした。
+なお、最後に掛けている**Mathf.Rad2Deg**はラジアンを度数に変換する定数です。ぶっちゃけると180/πです。
+
+ここまでで得られるのはy軸の角度なので、丁度プレイヤーを真上から見た際の回転角になります。ここにカメラのy軸を足すことで、カメラの回転にも対応できるようになります。
+
+その後***Quaternion.Euler()***でVector3からQuaternionに変換します。
+Unityのプログラミングを始めると多くの人が回転角の扱いで躓きますが、Vector3とQuaternionの変換が自在にできるようになるかがここの分水嶺だと思います。ちなみに反対の変換は**[Quaternionの変数].eulerAngles**でいけます。
+
+最後に***Quaternion.Slerp()***という関数を使っていますが、これは二つの回転角の中間を取得する関数です。これがゆっくり回転させる処理の正体です。
+Slerpで現在の角度と目的の角度の中間を取得し、現在の角度に置き換えています。
+中間の割合は**Time.deltaTime**の倍数にしています。ここは0から1までの値であれば0.1とか0.5とか他の数でもいいのですが、deltaTimeにすることでフレームレートが変わった時や処理落ちが発生した時にも常に同じ速さで回転してくれます。
+
+***Slerp()***はFloatクラスやVector3クラスにも実装されており、様々な場面で使うことができます。
+手軽に動きのクオリティをアップさせられるありがたい関数なので、積極的に活用したいですね。
+
+以上がスクリプトの解説となります。お付き合いいただきありがとうございました。