質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2683閲覧

スクリプトでのAnimationの制御に関しまして

user10

総合スコア37

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

1グッド

0クリップ

投稿2021/11/12 08:32

編集2021/11/13 05:35

経緯が複雑で説明不足、言葉足らずなどあるかもしれません。すいません。

ざっくり説明します
1.アニメーターコントローラーがアタッチされたアバターはHumanPoseHandlerの
制御を受けませんでした。
アニメーションコントローラーにつきまして

↓LateUpdate関数で解決

2.HumanPoseHundlerの操作では前屈などのポーズをすると現実的な体勢(軸)ではなくなってしまう
HumanPoseHandlerでの姿勢制御に関しまして

↓Hipボーンを戻すことによって解決

ここで現在の問題に至ります。
HumanPoseHandlerでの姿勢制御機能にプラスして新しくIKでの姿勢制御機能を加える工程になったのですが、
1.で解決したLateUpdateの兼ね合いでこの仕様ではIKでの制御は難しいと判断しました。
1.の問題に一度戻り考え、アバターにアタッチされているアニメーションコントローラーの中のアニメーションクリップがHumanposeHundlerのポーズを上書きしている(アバターが地面に埋まりバイクポーズになる)原因であるとはわかったので、Animator Override Controllerを使用して、現在のアニメーションクリップにHumanposeHundlerの値を設定しを上書きをすることで解決しました。(Animator.各ポーズにキーと値を設定します)
そして2の問題と同様に前屈ポーズを解決したいと思ったのですが、
Hipsがスクリプトでの制御を受け付けません。(Missingエラーは出ていません)

clip.SetCurve("Hipsまでのパス", typeof(Transform), "localPosition.x", カーブ);

手動でアバター名_HipsのインスペクターからTransform動かすこともできないようです。
こちらを解決したいです。

よろしくお願いします。

追記1:
Inspector>Animator>Update Mode>Animate Physicsに設定
AnimationClipプロパティ名
hips.localPosition→RootT
hips.localRotation→RootQ
ことで同じことができるのですが、Rootとある通りHipsより上の階層を制御してる?
感じがするのと挙動が変なため、自己解決とするか少し悩んでいます。
各プロパティが何の制御にあたるのかも少し調べる必要があるのですが、なかなか出てきません

Bongo👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Bongo

2021/11/14 22:33

まだ調査不十分なのですが、もしかするとOnAnimatorIK内でSetBoneLocalRotation(https://docs.unity3d.com/ja/current/ScriptReference/Animator.SetBoneLocalRotation.html )を使ってHipsの回転をコントロールできるかもしれません。 あいにくご質問者さんのおっしゃるAnimator Override Controllerを併用した方法がどのようなものであるか理解しかねていまして、腰の屈曲との組み合わせはまだ実現できていませんが、IKとHips回転の両立は可能かもしれません(https://teratail-v2.storage.googleapis.com/uploads/contributed_images/0703bdd91bc67edb14ee186ac7bacbf3.gif )。 もしこれが何らかの手がかりになればいいのですが、行き詰まってしまうようでしたらAnimator Override Controllerを使った腰屈曲についてもっと詳しく手順をご説明いただければ(あるいはプロジェクトをどこかにアップロードしていただければ)、可能であればもっと詳しく検討してみようと思います。
Bongo

2021/11/15 21:29

プロジェクトご提示ありがとうございます。恥ずかしながらAnimator Override Controllerの使用経験がなくて、あんな使い方があるのかと感服いたしました。まだ試行錯誤中なのですが、ちょっとやっかいそうな問題が出てきてしまいました... ポーズ修正用のアニメーションクリップは、マッスル値によるコントロールやIKを組み合わせることを見越すとMecanim対応でないとまずいはずです。ですがAnimationClip.SetCurveのリファレンス(https://docs.unity3d.com/ja/current/ScriptReference/AnimationClip.SetCurve.html )によると... Note: SetCurve will only work at runtime for legacy animation clips. For non-legacy AnimationClips it is an editor-only function. とのことで、実行時のアニメーションカーブ書き換えはレガシーアニメーションでないと動かないらしく、現状ではビルドして実行した時には動作しなくなってしまうかもしれません。Unityエディタ上で動きさえすればいいのでしたら問題ないのですが、最終的に作品をビルドするおつもりでしたら何か別の手を模索する必要がありそうですね...
user10

2021/11/16 02:07 編集

とても参考になります。ありがとうございます AnimatorControllerをスクリプトでどうにかするにはだいたいUnityEditorでってことですが それだとビルドすると当然動かないと思い、回りくどいですけどこの手に至った経緯です。 私が思うがまま作ってたわけで、使う前にリファレンスを確認を怠っていました。勉強になります。 別の手を考えてみます。 進展がありましたら報告させていただきます。
user10

2021/11/16 06:28

自分でも厳密に説明ができないほどアバウトな感じですが、 ビルドしても動くように一応はできましたので報告させていただきます。(テスト用のプロジェクトでは動きました。自分のプロジェクトではまだです。) ClipPair等はObsoleteみたいな警告がでたので刷新しました。 一時的な処理としてclip.SetCurveを行う前にclip.legacy=trueを入れsetCurve後にclip.legacy=falseをしています。 clip.legacy=falseでレガシーでなくなるかは正直わかりません。 PCのビルドでは動いているようです。今はそれ問題がないか、そもそもクリップに更新が行われているか 調べています。 以下はプロジェクトです。中にビルドというフォルダがありビルド済みのものがございます。 https://drive.google.com/file/d/1O80n8f89MYj8ELoqcbA4FYsY2WVADvjB/view?usp=sharing
guest

回答1

0

ベストアンサー

legacy切り替え案を拝見いたしました。よもやあのような単純な手で制限を回避できるとは信じがたかったのですが、確かにちゃんとビルド後でもゲーム上で設定したマッスル値がポーズに反映されているようですね。となると、なぜレガシーでないとカーブ設定を許さないようにしてあるのか謎ですね...

私の方では制限回避を狙う方針はのっけからあきらめてしまいまして、AnimationHumanStreamとかいう機能が使えるかもしれない...と思って試してみました。
スクリプトは下記のようにして、

lang

1using Unity.Collections; 2using UnityEngine; 3using UnityEngine.Animations; 4using UnityEngine.Playables; 5 6public 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はいつの間にか新機能がどんどん増えていって、存在を知らないまま放置してしまう機能も多そうですね。今回のご質問は、私にとってもちょうどいい学習の機会になってよかったと思います。

投稿2021/11/16 14:19

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

user10

2021/11/17 01:59

ありがとうございます。凄いですねこれ UpperChestをなんとかできないか試してみます。 HumanStreamははじめて知りました。日本語のドキュメントはほとんどなさそうですね。 でもこちらのほうがIKと親和性が高いような気がしますね。 legacyの件調べました。英語全然ダメなんですが翻訳かけると 状況は把握されてるけど、放置されてる?ような感じでしょうか。 https://forum.unity.com/threads/unable-to-build-animationclip-at-runtime.382799/ 重ねて、こちらの情報不足でお手間をおかけしてしまいすいません。 素晴らしいスクリプトありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問