legacy
切り替え案を拝見いたしました。よもやあのような単純な手で制限を回避できるとは信じがたかったのですが、確かにちゃんとビルド後でもゲーム上で設定したマッスル値がポーズに反映されているようですね。となると、なぜレガシーでないとカーブ設定を許さないようにしてあるのか謎ですね...
私の方では制限回避を狙う方針はのっけからあきらめてしまいまして、AnimationHumanStream とかいう機能が使えるかもしれない...と思って試してみました。
スクリプトは下記のようにして、
lang
1 using Unity.Collections;
2 using UnityEngine;
3 using UnityEngine.Animations;
4 using UnityEngine.Playables;
5
6 public class PoseModifier4 : MonoBehaviour
7 {
8 [Header("IK")]
9 public Transform leftHandGoal;
10 [Range(0.0f, 1.0f)] public float leftHandIk;
11 public Transform rightHandGoal;
12 [Range(0.0f, 1.0f)] public float rightHandIk;
13
14 [Header("Root")]
15 [Range(0.0f, 1.0f)] public float lieOnBack;
16
17 [Header("Muscles")]
18 [Range(-2.0f, 2.0f)] public float spineFrontBack;
19 [Range(-2.0f, 2.0f)] public float chestFrontBack;
20 [Range(-2.0f, 2.0f)] public float upperChestFrontBack;
21
22 private Animator animator;
23 private PlayableGraph graph;
24 private Transform root;
25 private Transform hips;
26 private Vector3 hipsInitialLocalPosition;
27 private Quaternion rootInitialLocalRotation;
28 private Quaternion hipsInitialLocalRotation;
29 private NativeArray<MuscleHandle> muscles;
30 private NativeArray<float> additiveMuscleValues;
31 private NativeArray<float> leftHandWeight;
32 private NativeArray<float> rightHandWeight;
33 private NativeArray<Quaternion> additiveRootRotation;
34 private NativeArray<Quaternion> previousHipsRotation;
35
36 private void OnEnable()
37 {
38 // Animatorを取得し...
39 this.animator = this.GetComponent<Animator>();
40
41 // PlayableGraphを作成し...
42 this.graph = PlayableGraph.Create("Unity-chan Animation");
43 var output = AnimationPlayableOutput.Create(this.graph, "Output", this.animator);
44
45 // マッスルハンドルとマッスル値の配列を作成する
46 var muscleHandles = new MuscleHandle[HumanTrait.MuscleCount];
47 MuscleHandle.GetMuscleHandles(muscleHandles);
48 this.muscles = new NativeArray<MuscleHandle>(muscleHandles, Allocator.Persistent);
49 this.additiveMuscleValues = new NativeArray<float>(HumanTrait.MuscleCount, Allocator.Persistent);
50
51 // IKウェイト入力用の配列を作成する
52 this.leftHandWeight = new NativeArray<float>(1, Allocator.Persistent);
53 this.rightHandWeight = new NativeArray<float>(1, Allocator.Persistent);
54
55 // ルート回転入力用の配列を作成する
56 this.additiveRootRotation = new NativeArray<Quaternion>(1, Allocator.Persistent);
57 this.additiveRootRotation[0] = Quaternion.identity;
58
59 // Hips回転入力用の配列を作成する
60 this.previousHipsRotation = new NativeArray<Quaternion>(1, Allocator.Persistent);
61 this.previousHipsRotation[0] = Quaternion.identity;
62
63 // Hipsボーンを取得し、ルートに対する相対姿勢を求めておく
64 // また、ルートの初期回転も保存しておく
65 this.hips = this.animator.GetBoneTransform(HumanBodyBones.Hips);
66 this.root = this.transform;
67 this.rootInitialLocalRotation = this.root.localRotation;
68 this.hipsInitialLocalPosition = this.root.InverseTransformPoint(this.hips.position);
69 this.hipsInitialLocalRotation = this.hips.rotation * Quaternion.Inverse(this.root.rotation);
70
71 // ポーズ制御ジョブを作成し、データを投入する
72 var poseControlJob = new PoseControlJob
73 {
74 LeftHandGoal = this.animator.BindSceneTransform(this.leftHandGoal),
75 LeftHandWeight = this.leftHandWeight,
76 RightHandGoal = this.animator.BindSceneTransform(this.rightHandGoal),
77 RightHandWeight = this.rightHandWeight,
78 Muscles = this.muscles,
79 AdditiveMuscleValues = this.additiveMuscleValues,
80 AdditiveRootRotation = this.additiveRootRotation,
81 InverseInitialHipsRotation = Quaternion.Inverse(this.hips.rotation * Quaternion.Inverse(this.root.rotation)),
82 PreviousHipsRotation = this.previousHipsRotation
83 };
84
85 // ポーズ制御ジョブを使ったAnimationScriptPlayableを作成し、グラフに接続する
86 var poseController = AnimationScriptPlayable.Create(this.graph, poseControlJob);
87 output.SetSourcePlayable(poseController);
88
89 // グラフを始動する
90 this.graph.Play();
91 }
92
93 private void OnDisable()
94 {
95 this.graph.Destroy();
96 this.muscles.Dispose();
97 this.additiveMuscleValues.Dispose();
98 this.leftHandWeight.Dispose();
99 this.rightHandWeight.Dispose();
100 this.additiveRootRotation.Dispose();
101 this.previousHipsRotation.Dispose();
102 }
103
104 private void Update()
105 {
106 // Update時点では直立させておく
107 this.root.localRotation = this.rootInitialLocalRotation;
108
109 // インスペクター上のマッスル値をデータ入力用配列に書き写す
110 this.additiveMuscleValues[0] = this.spineFrontBack;
111 this.additiveMuscleValues[3] = this.chestFrontBack;
112 this.additiveMuscleValues[6] = this.upperChestFrontBack;
113
114 // インスペクター上のIKウェイトをデータ入力用配列に書き写す
115 this.leftHandWeight[0] = this.leftHandIk;
116 this.rightHandWeight[0] = this.rightHandIk;
117
118 // 寝転がし回転を求める
119 this.additiveRootRotation[0] = Quaternion.Euler(Mathf.Lerp(0.0f, -90.0f, this.lieOnBack), 0.0f, 0.0f);
120 }
121
122 private struct PoseControlJob : IAnimationJob
123 {
124 public TransformSceneHandle LeftHandGoal;
125 [ReadOnly] public NativeArray<float> LeftHandWeight;
126 public TransformSceneHandle RightHandGoal;
127 [ReadOnly] public NativeArray<float> RightHandWeight;
128 [ReadOnly] public NativeArray<MuscleHandle> Muscles;
129 [ReadOnly] public NativeArray<float> AdditiveMuscleValues;
130 [ReadOnly] public NativeArray<Quaternion> AdditiveRootRotation;
131 public Quaternion InverseInitialHipsRotation;
132 [ReadOnly] public NativeArray<Quaternion> PreviousHipsRotation;
133
134 public void ProcessAnimation(AnimationStream stream)
135 {
136 if (!stream.isHumanStream)
137 {
138 return;
139 }
140
141 var humanStream = stream.AsHuman();
142
143 // 姿勢をTポーズにして...
144 humanStream.ResetToStancePose();
145
146 // マッスル値を設定する
147 // さしあたり、Tポーズのマッスル値に対して
148 // マッスル値の変化量を加算するスタイルにした
149 // つまり、Tポーズを基準に体を動かすことになる
150 for (var i = 0; i < this.Muscles.Length; i++)
151 {
152 humanStream.SetMuscle(this.Muscles[i], humanStream.GetMuscle(this.Muscles[i]) + this.AdditiveMuscleValues[i]);
153 }
154
155 // IKゴールのワールド座標はこうなるが...
156 var leftHandPosition = this.LeftHandGoal.GetPosition(stream);
157 var rightHandPosition = this.RightHandGoal.GetPosition(stream);
158
159 // 後でHips復元、および寝転がすことを見越して、位置を逆回転させる
160 var rootRotation = stream.rootMotionRotation;
161 var hipsRotation = this.PreviousHipsRotation[0] * this.InverseInitialHipsRotation;
162 var inverseRotation = rootRotation * Quaternion.Inverse(this.AdditiveRootRotation[0]) * hipsRotation * Quaternion.Inverse(rootRotation);
163
164 // IKゴールとウェイトを設定する
165 humanStream.SetGoalPosition(AvatarIKGoal.LeftHand, inverseRotation * leftHandPosition);
166 humanStream.SetGoalWeightPosition(AvatarIKGoal.LeftHand, this.LeftHandWeight[0]);
167 humanStream.SetGoalPosition(AvatarIKGoal.RightHand, inverseRotation * rightHandPosition);
168 humanStream.SetGoalWeightPosition(AvatarIKGoal.RightHand, this.RightHandWeight[0]);
169
170 // IK計算を行う
171 humanStream.SolveIK();
172 }
173
174 public void ProcessRootMotion(AnimationStream stream)
175 {
176 }
177 }
178
179 // OnAnimatorIKタイミングでHips回転復元を行い...
180 private void OnAnimatorIK(int layerIndex)
181 {
182 this.previousHipsRotation[0] = this.hips.rotation * Quaternion.Inverse(this.root.rotation);
183 this.animator.SetBoneLocalRotation(HumanBodyBones.Hips, this.hipsInitialLocalRotation);
184 }
185
186 // LateUpdateタイミングでHips位置復元と寝転がしを行う
187 private void LateUpdate()
188 {
189 this.hips.position = this.root.TransformPoint(this.hipsInitialLocalPosition);
190 this.root.localRotation = this.additiveRootRotation[0] * this.rootInitialLocalRotation;
191 }
192 }
AnimatorのUpdate ModeはNormalにしました。また、OnAnimatorIK
を動作させるためAnimatorのControllerにセットされている「AC」のBase LayerのIK Passはオンにしています。
すみませんがこの機能も今回のご質問を受けて初めて使ってみたもので、こんな使い方でいいのか自信がありません。HumanPoseHandler
だと動いていたはずのUpperChest Front-Backがなぜか無反応だったり(マッスル値は設定されているように見えたのですが、何か見落としているのか、それともAnimationHumanStream
の場合はこうなってしまう仕様なのか...)、IKゴールへの手の追従が何だかずれているような気がしたり、他にも不具合を見逃している可能性があります。legacy
切り替え方式でうまくいきそうでしたら、ひとまずはそちらの方法でいいかもしれません。
とはいえこれはこれで別な場面で使い道がありそうですし、私としては収穫があったと感じます。Unityはいつの間にか新機能がどんどん増えていって、存在を知らないまま放置してしまう機能も多そうですね。今回のご質問は、私にとってもちょうどいい学習の機会になってよかったと思います。