2020/11/09
今更聞けないUnityのちょっとした疑問として、「キャラクターの首 (顔) を特定方向に向ける」というものがあります。もっと言えば「特定のものを注視する」ような動作ですね。
3DゲームのNPCに近づいた時によく見かける動きです。
この手の話題はぐぐればあっさり解決しそうですが、意外としっくりくる情報がヒットせず、他のアセットを用いて実現している所が多かったです。
私は (なるべく) アセットは使わずに実現したいので、実装方法を模索してみました。
boneのTransformをスクリプトから制御する
Unity的にはインポートしたArmature (bone) とSkinned Mesh Renderer
でモデルをアニメーションさせています。
boneの位置や回転、大きさでモデルの形状が決まるので、首に相応するboneを回転させてあげれば実現できそうです。
ただし、単にUpdate()
に書いただけでは上手くいかないので、LateUpdate()
内に記述する必要があります。これはAnimatorのbone更新処理が終わってからTransformを変更する必要があるためです。
ちなみに該当のboneにキーフレームを設定していなかったとしてもUpdate()
内では動作しませんでした。Skinned Mesh Renderer
に対応付けられている (=頂点グループで指定されている) とキーフレームの有無に関わらず更新されるのかもしれません。
スクリプトを設定するオブジェクトはboneの参照が取れればどこでも大丈夫です。キャラクター制御用のクラスがあればそちらに記述するのが手っ取り早いかと思います。
今回はneckBone
フィールドにインスペクターから設定してしまいます。
以下はスクリプトの関数部分です。
public Transform neckBone; protected virtual void LateUpdate() { if (neckBone != null) { neckBone.Rotate(30f, 20f, 0f); } }
下の画像は何も行っていない状態の首です。
上のスクリプトを実行すると、こんな感じの向きになりました。
実際に動かすことが多いのはX軸とY軸で、Z軸はほとんど必要ないかと思います。何れにしても、固定の方向を向けるだけなら簡単にできます。
特定のオブジェクトを注視する
次に「特定のオブジェクトを見続ける」という処理を考えます。
ここで曲者なのは「首用boneはキャラクターとArmatureの子オブジェクト」ということ。つまりキャラクターやArmatureの各種親boneの回転情報が継承されてしまうので、それを考慮しなければなりません。例えばlocalRotation
を弄ってもキャラクター自体が回転していたら継承されて向きが変わってしまいます。
Armatureの構成上親子関係を切るわけにもいかないわけで、首用boneにQuaternion.identity
を突っ込むことで親の回転を無視し、必要な分だけRotate()
で回して実現します。
public Transform neckBone; public GameObject watchTarget; protected virtual void LateUpdate() { if (neckBone != null) { if (watchTarget != null) { Vector2 target_pos = new Vector2(watchTarget.transform.position.x, watchTarget.transform.position.z); Vector2 char_pos = new Vector2(transform.position.x, transform.position.z); Vector2 direction = target_pos - char_pos; float angle = -(Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg) - 90f; neckBone.rotation = Quaternion.identity; neckBone.Rotate(0f, -angle - 90f, -90f); } } }
数学さっぱりダメな人なので角度計算の正当性に疑問が残りますが、ぐぐった感じではこれでいけそう。XZ軸の水平的な角度の差を取るので、ここではVector2
型を使用しています。
また、Quaternion.identity
を突っ込んだ際に首用boneが折れ曲がってしまうため、最後のRotate()
で90度マイナスし強引に補正しています。このあたりはトライ&エラーで試して調整するしかなさそうです。
動かす際は注視する対象を (watchTarget
) をインスペクターから設定しておきます。
実行してみると以下のような感じ。
動き回るキャラクターの方を見続けてくれているので、問題なさそうですね。
上のスクリプトのwatchTarget
に設定するオブジェクトをSphereCollider
あたりを使って取得すれば、それっぽい雰囲気になります。
ただ、このままでは角度制限がないので体の向きに反して真後ろも向いてしまいます。Mathf.Clamp()
あたりで制限できそうですが、私のArmatureはboneの軸がブレブレで実装が困難でした。
Blenderの使い方がもうちょっとマシになったらまた考えてみます。