#はじめに
前回 (Unityで視線誘導のための矢印をつくる②)の続きです。
※HTC VIVEを使って開発しています。
実は前回の誘導矢印は不完全でした。
別の矢印3Dオブジェクトの場合、HMDを動かすと矢印の上側や下側の面が見えてしまします。
画像を用いた場合、画像が見えなくなります。今回はこの問題を解消します。
#実装
前回の実装では注視対象に矢印を向ける処理にTransform.LookAt
を使用していましたが回転を上手く制御できていませんでした。
今回は回転をいい感じになるよう実装していきたいと思います。
こちらの記事を参考にさせていただきました。
(もうこれでいいじゃん)
記事中、角度を求めるため内積と外積を使用していますが、左右判定のVector3.Cross
はどうやらHMDを動かしていると符号が度々入れ替わってしまい期待した結果が得られませんでした。
なので左右判定(今回は実装上Cameraの上半分か下半分か判定)はコライダーを用いて実装します。(※脳筋実装。)
ヒエラルキーは最終的に以下のような状態になります。
###【1】下準備
・Cameraの子階層に空のオブジェクトを作成し、名前を「Z_PlusOne」とします。
Camera の前方に矢印を配置したいのでのTransform
の Z 値を1に設定ます。
今後、この「Z_PlusOne」の子階層にオブジェクトを追加していきます。
###【2】下準備2
回転のための下準備が続きます。
・「Z_PlusOne」の子階層に空のオブジェクトを作成し、名前を「RotationBase」として前回作成したMyLookAtTarget2.cs
をアタッチし、注視先オブジェクトを指定します。
これで「RotationBase」は注視先オブジェクトを向くことができます。
・「RotationBase」の子階層にCubeを作成し、名前を「RotationCube」とします。
「RotationCube」は、Cameraの上半分かをコライダーで判定するために使用します。
「RotationCube」に以下を設定します。
・「Z_PlusOne」の子階層に空のオブジェクトを作成し、名前を「BaseObject」とします。
「BaseObject」は矢印の回転角度を求めるために使用します。
Rotation
の Y の値を 90 に設定します。
###【3】矢印の親オブジェクトを作る
矢印オブジェクトそのものをスクリプトで回転させても良いのですが、今回は矢印オブジェクトの親となるオブジェクトを作成し、その親オブジェクトを回転させようと思います。
・「Z_PlusOne」の子階層に空のオブジェクトを作成し、名前を「ArrowBase」とします。
「ArrowBase」にMyLookAtTarget3.cs
(今回新規作成)をアタッチします。
MyLookAtTarget3.cs
は、「baseObject」と「rotationCube」を参照して回転角度を求めます。
using UnityEngine;
/// <summary>
/// baseObject、rotationCubeから角度を求めアタッチしたオブジェクトに反映
/// </summary>
public class MyLookAtTarget3 : MonoBehaviour {
// Cameraから見て、矢印が上にあるか。
[HideInInspector] public bool arrowPosUp = false;
[SerializeField] private Transform baseObject;
[SerializeField] private Transform rotationCube;
void Update () {
// 内積を求める
var diff = (rotationCube.position - baseObject.position).normalized;
// 回転角を求める
var angle = Vector3.Angle(baseObject.forward, diff) * (arrowPosUp ? 1 : -1);
// 現在の角度を取得(クォータニオン)
Quaternion rotation = this.transform.localRotation;
// クォータニオン → オイラー角への変換
Vector3 rotationAngles = rotation.eulerAngles;
// Z軸回転
rotationAngles.z = angle;
// オイラー角 → クォータニオンへの変換
rotation = Quaternion.Euler(rotationAngles);
// 角度更新
this.transform.localRotation = rotation;
}
}
###【4】Cameraから見て矢印が上半分にあるか判定するコライダーを作成する
・「Z_PlusOne」の子階層にCubeを作成し、名前を「CollisionCube」とします。
「CollisionCube」に以下を設定します。
また、「RotationCube」と衝突判定するためにCollisionCube.cs
(今回新規作成)を「CollisionCube」にアタッチし、arrowBase に「ArrowBase」を指定します。
using UnityEngine;
public class CollisionCube : MonoBehaviour {
[SerializeField] private GameObject arrowBase;
// Camera の上半分に矢印がある
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.name == "RotationCube")
{
arrowBase.GetComponent<MyLookAtTarget3>().arrowPosUp = true;
}
}
// Camera の下半分に矢印がある
private void OnTriggerExit(Collider other)
{
if(other.gameObject.name == "RotationCube")
{
arrowBase.GetComponent<MyLookAtTarget3>().arrowPosUp = false;
}
}
}
###【5】矢印オブジェクトを作成する
・矢印オブジェクトを「ArrowBase」の子階層に作成します。名前は何でもよいですが、私は「Arrow」としました。
オブジェクトの作りによっては注視対象の方を向かないので、Unityエディタで実行しながら必要に応じRotationの値を調整してください。
#終わりに
脳筋実装が終わりました。
これで矢印オブジェクトが3Dでも2Dでも気にせず視線誘導を行えます。
数学が出来るともっとスマートに実装できることでしょう。勉強しないとなぁ。