【Unity】キャラクタの手の高さに比例して手の形状を切り替える
キャラクタがダンスなどのモーションを行っているときにずっと手がパーの状態だと寂しいので,動的になるように施す.よく忘れるのでメモ.
Env.
- Unity2019.1.12f1
- UniVRM 0.55
Methods
手のアニメーションクリップを用意
手の高さに応じてグーとパーを切り替えたい.今回であれば手を上に上げたときにグーに,手を下げたときにパーになるようにしたいので,1秒間でグーとパーを切り替えるだけのアニメーションクリップを用意した.左右の手を別々に制御させたいので,左手と右手それぞれのアニメーションを作成した.
名前は適当にHand_LとHand_Rにした.それぞれ0:00にパーの状態,1:00にグーの状態を保存.
animファイルを新規作成するのは面倒なので,以下に作成したものを置いておく.
https://github.com/nmxi/Unity_pth_HandsController/releases/tag/v0.1
AnimationMaskを作成
左と右の手のマスクをそれぞれ作成しておく.
上記は左の手のマスクの例.これと反転させてものも作成する.
プロジェクトタブはこのような感じになる.
AnimationControllerを用意
適当な名前でアニメーションコントローラーを作成して,当該キャラクタオブジェクトに付いているAnimatorコンポーネントにアタッチしておく.
上記のように何もアニメーションが入っていない適当な名前のAnimationLayerを作成して,左手と右手それぞれのAnimationLayerを作成.先程作成したAnimationMaskをそれぞれのレイヤーにアタッチする.
左手と右手のレイヤーのDefaultAnimation(オレンジのノード)がそれぞれの1秒間のアニメーションクリップを設定しておく.
アニメーション(手のグーとパー)が勝手に動かないようにするため,DefaultAnimationのノードの設定はインスペクタから以下のように設定しておく.
とりあえず,Speedを0にしておく.
Animationの再生位置をスクリプトからコントロール
HandController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class HandController : MonoBehaviour { [SerializeField] private float _l_HandParam; [SerializeField] private float _r_HandParam; [Space(15f), SerializeField] private Animator _targetAnimator; private void Update() { _targetAnimator.Play("Hand_L", 1, _l_HandParam); _targetAnimator.Play("Hand_R", 2, _r_HandParam); } } |
適当なGameObjectに貼り付けて以下のようになる.

あとはTargetAnimatorに当該キャラクタモデルのAnimatorをアタッチしてPlay中に_l_HandParamと_r_HandParamを0.0~1.0もしくは0.2~0.8など範囲を調整しながら,変更させるだけ.
このように手が動くようになる.
キャラクタの手の高さを取得
上記のHandController.csを改造して,モデルの手の位置からグーとパーが切り替わるようにした.
以下のコードだとしゃがんだときや,ジャンプしたときなどに対応できないので,もっときれいに自然に動かすのであれば,HipsのlocalPositionからの手のlocalpositionの相対距離を取得したりする必要がある.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
using UnityEngine; public class HandController : MonoBehaviour { [SerializeField] private float _handMaxPos; [SerializeField] private float _handMinPos; [SerializeField] private Axis _referenceAxis; [Space(15f), SerializeField] private Animator _targetAnimator; private enum Axis { X, Y, Z } private void Update() { var lhPos = _targetAnimator.GetBoneTransform(HumanBodyBones.LeftHand).position; var rhPos = _targetAnimator.GetBoneTransform(HumanBodyBones.RightHand).position; if(_handMaxPos > lhPos[(int)_referenceAxis] && _handMinPos < lhPos[(int)_referenceAxis]) { var param = Remap(_handMinPos, _handMaxPos, lhPos[(int)_referenceAxis]); _targetAnimator.Play("Hand_L", 1, param); } else if(lhPos[(int)_referenceAxis] < _handMinPos) { _targetAnimator.Play("Hand_L", 1, 1); } else if(lhPos[(int)_referenceAxis] > _handMaxPos) { _targetAnimator.Play("Hand_L", 1, 0); } if (_handMaxPos > rhPos[(int)_referenceAxis] && _handMinPos < rhPos[(int)_referenceAxis]) { var param = Remap(_handMinPos, _handMaxPos, rhPos[(int)_referenceAxis]); _targetAnimator.Play("Hand_R", 2, param); } else if (rhPos[(int)_referenceAxis] < _handMinPos) { _targetAnimator.Play("Hand_R", 2, 1); } else if (rhPos[(int)_referenceAxis] > _handMaxPos) { _targetAnimator.Play("Hand_R", 2, 0); } } private float Remap(float min, float max, float x) { var a = x - min; var b = max - min; var res = a / b; return res; } } |
ReferenceAxisはpositionのx, y, zどの値を参照するかを変更できる.特になにも考えない場合,Yで大丈夫だと思う.
HandMaxPosとHandMinPosは手をグー,パー切り替える高さのブレイクポイントを指定する.
これらを設定して,手の高さがが高くなったときにグー.低くなったときにパー.といったようにアニメーションができるようになる.リアルタイムでのモーションの入力にも対応できる.
動作の見本を載せたかったが,全身見せられるモデルを用意する時間がなかったので,また今度.
あー.手のモーキャプ装置ほしい.