• RuntimesUnity
  • rootmotionの正しい使い方を知りたい / How to make rootmotion work as expected

koyu2 先にRigidbody2Dについてだけ回答させていただきますが、動画内でも警告が表示されている通り、Skeleton Animation コンポーネントの Advanced - Animation Update Inspector パラメーターを、In Update ではなく In Fixed Update に設定することが推奨されています:


Unity内で表示される警告は全て英文なので分かりづらいかもしれませんが、設定が正しくない場合にはこのように ⚠️ マーク付きのメッセージが表示されるようになっていますので何かがうまくいかないときはこのような警告がないかを確認してみてください。

その他のご質問についてはHaraldからの回答をお待ちいただけますと幸いです。

    Related Discussions
    ...

    @koyu2 Spinebot must have made up a skeletonRootMotion.Initialize() method, there is only SkeletonAnimation.Initialize(true).

    What is the setup of your Rogidibody2D component?
    You're assigning the Rigidbody2D to the property in the middle of the game. Does it work as expected if you set it before entering play-mode?


      Misaki
      Harald
      PlayModeに入る前にRigidbody2Dを適用しUpdateのタイミングをFixedUpdateにしましたが、残念ながら期待した動作はしませんでした。
      なお、移動モーションにはrootmotionに割り当てたボーンは動かさずRigidbody2DのVelocityだけで動かしています。

      Spinebot must have made up a skeletonRootMotion.Initialize() method, there is only SkeletonAnimation.Initialize(true)

      Spinebotの情報は間違いだったのでしょうか?
      SkeletonDataAssetの再設定しつつ、rootmotionを再度有効にする方法はありますか?

        koyu2 Spinebot must have made up a skeletonRootMotion.Initialize() method, there is only SkeletonAnimation.Initialize(true)

        Spinebotの情報は間違いだったのでしょうか?

        Sorry to hear your problem persists! Yes, Spinebot was incorrect, there is no skeletonRootMotion.Initialize(true); method.

        SkeletonDataAssetの再設定しつつ、rootmotionを再度有効にする方法はありますか?

        It might have been translated incorrectly, but why reconfiguring SkeletonDataAsset?

        In general the is nothing that needs to be done (like skeletonRootMotion.Initialize) after assigning an object at SkeletonRootMotion.rigidBody2D. If it does not work as desired even with setting update mode to FixedUpdate something else is going wrong. Could you please send us a minimal Unity project which still shows this issue? You can send it as a zip file to contact@esotericsoftware.com, briefly mentioning this forum thread URL so that we know the context. Then we can have a look at what's going wrong.

        お返事ありがとうございます!
        contact@esotericsoftware.com 宛にプロジェクトをお送りしました。
        ご確認いただけますと幸いです。

        but why reconfiguring SkeletonDataAsset?

        ゲームではキャラクターを1体ずつInstanceとDestroyを繰り返すのではなく、
        オブジェクトプールのようにSetActiveのtrueとfalseを切り替え、キャラクターオブジェクトを使い回そうと考えました。
        剣を持った男性とゴブリンは同一スケルトンですが、別種のスケルトンにも切り替えられるよう、
        キャラクターがスポーンする度にSkeletonDataAssetの読み込みをしていました(女性は別タイプのスケルトンです)

        ひとまずRootMotionを使う際は別スケルトンへの切り替えを行わないよう対応したい思います。

        ご対応に感謝します!

        @koyu2 Thanks for sending the reproduction project. Unfortunately I could not reproduce any issue with it: when I started the included scene, I can see the female character in the red dress in an idle pose, but she is not playing any moving animation. WASD or the arrow keys don't start playing any animation either, the character just changes the facing direction. Also I found no text in either the email nor in the Unity project explaining what reproduction steps would be.

        Please always create a minimal reproduction scene where we don't have to do anything, hitting play should already display the problem. If normal setup is not sufficient, you should write scripts which trigger the problematic things to happen.

        Also please just include the necessary things in the zip file. The Library directory shall never be included as it contains only generated files and increases the file size from around 20 MB to 870MB. There are also other unnecessary directories included which are either empty or are not relevant.

          Harald
          お返事ありがとうございます。
          再現手順の説明が不足しており申し訳ありません。

          WASDキーで向きを変えてもキャラクターが動かないのは既に問題が再現しているためです。
          ヒエラルキーのFrontTypeSkeletonオブジェクトのRootMotionコンポーネントには、既にFrontTypeSkeletonオブジェクト自身のRigidbody2Dが設定されております。
          RootMotionコンポーネントに設定されているRigidbody2DをNoneにしてみてください。WASDキーで左右に動けるようになるはずです。RootMotionコンポーネントにRigidbody2Dを設定すると再び動かなくなります。

          アニメーションが変わらないのは他のコンポーネントやコードを削除して最小限のプロジェクトにしたためです。
          Libraryディレクトリは今後含めないように気をつけます。

          もう一度ご確認頂けないでしょうか?何卒よろしくお願いいたします

          @koyu2 Oh, thanks for the clarification! I just noticed that the walk animation is not moving any bones at all and thus looked as if no animation at all was playing. I will investigade the cause of the problem.

          @koyu Unfortunately the code is not minimal, the Character class which calls AnimationState.SetAnimation includes far too much functionality and seems to also call Rigidbody2D.AddForce if some conditions are met. Also there is an unnecessary SkinChanger component which seems to have nothing to do with the issue. Please note that we can't debug your character controller class.

          Please create a minimal project where there is only a single script which sets your root-motion-walk animation without any input interaction. If the issue only arises when you add additional rigidbody interaction from code, add this code as well, but in as few lines as possible and without user input interaction. Please remove anything which can be removed.

          @koyu2 Oh, I think now I understand your issue. Do you mean that you apply forces to your Rigidbody2D, but when you have the SkeletonRootMotion component set to target the Rigidbody2D, the forces you apply manually to the rigidbody no longer have any effect?

          If so, likely your component is executed too early in the update cycle. FixedUpdate of your component shall be called after SkeletonRootMotion. You can set the script exectution order of your component higher e.g. by adding the line [DefaultExecutionOrder(2)] above your class.

          An alternative would be to use skeletonRootMotion.AdditionalRigidbody2DMovement which is provided for such a reason:

          /// <summary>Additional translation to add to <c>Rigidbody2D.MovePosition</c>
          /// called in FixedUpdate. This can be necessary when multiple scripts call
          /// <c>MovePosition</c>, where the last call overwrites the effect of preceding ones.

          こんにちはHaraldさん。
          お返事ありがとうございます。

          私は原因を探しているうちに、SkeletonRootMotionを使用した際、Rigidbodyの奇妙な挙動を発見しました。以下の動画をご覧ください。

          SkeletonRootMotionにRigidbody2Dを適用した途端、velocity_Yの数値がマイナス方向へ増え続けました。ジャンプするとvelocityに数値を代入しているので上空に飛びますが地面に着地後もvelocity_Yは減り続けています。

          Characterのコードはこれしか記述していません。
          私にはvelocity_Yの速度が下がり続けているため、地面に押さえつけられてキャラクターが動かなくなっているように見えます。
          SkeletonRootMotionのRigidbody2Dを取り除くと、この不可解なY軸速度の減少は停止します
          これはSkeletonRootMotionのバグではないでしょうか?

          Do you mean that you apply forces to your Rigidbody2D, but when you have the SkeletonRootMotion component set to target the Rigidbody2D, the forces you apply manually to the rigidbody no longer have any effect?

          その通りです!
          私のプロジェクトでは、左右キーの入力を受け取るとvelocityに数値を代入してキャラクターを動かしています。しかし、SkeletonRootMotionコンポーネントにRigidbody2Dをアタッチするとキャラクターは動けなくなってしまいます。

          [DefaultExecutionOrder(2)]

          この方法を試してみましたが残念ながら問題は解決しませんでした。

          この問題が再現するプロジェクトをメールアドレスにお送りします。
          UnityのVerは2022.3.13f1で、使用しているランタイムは最新です。

          ご覧いただけますと幸いです。

            koyu2 [DefaultExecutionOrder(2)]

            [DefaultExecutionOrder(2)] didn't have the desired effect of setting the script execution order to after SkeletonRootMotion because under Project Settings - Script Execution Order you have the script set to execution order -4. This obviously overrides the [DefaultExecutionOrder(2)] then.

            koyu2 SkeletonRootMotionにRigidbody2Dを適用した途端、velocity_Yの数値がマイナス方向へ増え続けました。ジャンプするとvelocityに数値を代入しているので上空に飛びますが地面に着地後もvelocity_Yは減り続けています。

            Unfortunately Rigidbody2D provides no grounded flag itself. As we can't determine safely whether your Rigidbody2D is grounded, you need to perform the test yourself, and then set applyRigidbody2DGravity accordingly.

            You could write the test e.g. as follows:

            protected bool isGrounded2D = false;
            
            void OnCollisionStay2D (Collision2D collision) {
            	foreach (ContactPoint2D contact in collision.contacts) {
            		if (contact.normal.y > 0.5f) { // additionally you could check if the layer of the collision object whether it counts as a ground object, accessing contact.collider.gameObject.layer.
            			isGrounded2D = true;
            			return;
            		}
            	}
            	isGrounded2D = false;
            }
            
            void OnCollisionExit2D (Collision2D collision) {
            	isGrounded2D = false;
            }

            you have the script set to execution order -4

            すみません。私のプロジェクトでscript set to execution orderを変えていたことを忘れていました。
            私は下記のように[DefaultExecutionOrder(2)]をProjectSettingとスクリプト両方で設定しました。

            しかし、依然として期待した動作は得られません。
            私はSkeletonRootMotionにRigidbody2Dを割り当てた場合の挙動が理解できません。
            分かっていることは以下の通りです。

            ■分かったこと
            SkeletonRootMotionにRigidbody2Dを割り当てた場合、Characterは
            ・ApplyGravity = true … velocityで全く移動できなくなる。
            ・ApplyGravity = false … Characterが地面コライダーに接地してもy軸の速度が下がり続けるので移動できなくなる

            ■知りたいこと
            ・SkeletonRootMotionにRigidbody2Dを設定した上で、Characterをvelocityで移動させる方法。

            ●疑問点
            SkeletonRootMotionにRigidbody2Dを割り当てた場合、
            ・Unityの物理移動ではなく、SkeletonRootMotion独自の物理移動になる
            ・RootMotionの物理挙動は、地面コライダーと設置していても、ApplyGravityされていればY軸速度は下がり続ける
            ・そのため接地を自分のコードでチェックしてApplyGravity=falseにする必要がある?
            そういうことなのでしょうか?
            しかし、上述の通り、ApplyGravity = trueでは、velocityで全く移動できなくなります。

            SkeletonRootMotionにRigidbody2Dを設定した上で、Characterをvelocityで移動させるにはどうすればよいのでしょうか?

            SkeletonRootMotionにRigidbody2Dを割り当てた場合、Characterは
            ・ApplyGravity = true … velocityで全く移動できなくなる。
            ・ApplyGravity = false … Characterが地面コライダーに接地してもy軸の速度が下がり続けるので移動できなくなる

            しかし、上述の通り、ApplyGravity = trueでは、velocityで全く移動できなくなります。

            すみません、ApplyGravity == true と ApplyGravity == false の挙動の記述が逆でした。
            どちらにせよvelocityで移動させる方法が分かりません。
            お手数おかけしてすみませんが何卒お願いいたします

            @koyu2 Sorry for the inconvenience! I just noticed that even when your Character script is executed after the SkeletonRootMotion component, adding a velocity vector to rigidbody2D.velocity does not have the desired effect. RigidBody2D.MovePosition() seems to not only set Rigidbody2D.velocity accordingly, but also zero any future modifications of Rigidbody2D.velocity after execution in the same frame.

            There is a parameter skeletonRootMotionBase.AdditionalRigidbody2DMovement available, which can be used to set additional movement. Unfortunately it seems to be quite complicated to set persistent velocities. Nevertheless, the following code would be an adjusted equivalent of your script code using AdditionalRigidbody2DMovement:

            public class Character : MonoBehaviour
            {
                public Rigidbody2D chrRigid;
                protected SkeletonRootMotionBase rootMotion;
            
                protected void Awake()
                {
                    chrRigid = GetComponent<Rigidbody2D>();
                    rootMotion = GetComponent<SkeletonRootMotionBase>();
                }
                protected void FixedUpdate()
                {
            		Vector2 additionalMovement = Vector2.zero;
            
            		float input_X = Input.GetAxis("Horizontal");
            		if (input_X != 0.0f) {
            			additionalMovement.x = 10 * input_X * Time.deltaTime;
            		}
            		if (Input.GetKey(KeyCode.Space)) {
            			additionalMovement.y = 30 * Time.deltaTime;
            		}
            		rootMotion.AdditionalRigidbody2DMovement = additionalMovement;
            	}
            }

            The following component would then check for grounding, set rootMotion.applyRigidbody2DGravity = !isGrounded2D; and zero velocity upon grounding as well:

            using UnityEngine;
            
            public class ApplyGravityWhenGrounded : MonoBehaviour
            {
            	public bool isGrounded2D = false;
            	protected SkeletonRootMotionBase rootMotion;
            	protected new Rigidbody2D rigidbody2D;
            
            	void Start () {
            		rootMotion = this.GetComponent<SkeletonRootMotionBase>();
            		rigidbody2D = this.GetComponent<Rigidbody2D>();
            	}
            
            	void OnCollisionStay2D (Collision2D collision) {
            		foreach (ContactPoint2D contact in collision.contacts) {
            			if (contact.normal.y > 0.5f) {
            				isGrounded2D = true;
            				rootMotion.applyRigidbody2DGravity = !isGrounded2D;
            				rigidbody2D.velocity = new Vector2(rigidbody2D.velocity.x, 0f);
            				return;
            			}
            		}
            		isGrounded2D = false;
            		rootMotion.applyRigidbody2DGravity = !isGrounded2D;
            	}
            
            	void OnCollisionExit2D (Collision2D collision) {
            		isGrounded2D = false;
            		rootMotion.applyRigidbody2DGravity = !isGrounded2D;
            	}
            }

            We will investigate whether we can provide a better solution out of the box which makes adding forces and velocities easier.

              @koyu2 FYI: While it is not a fix for more easily setting persistent velocities, we just fixed an issue with SkeletonRootMotion not updating it's references after changing SkeletonDataAsset and calling skeletonAnimation.Initialize(true). See this posting for details:
              https://esotericsoftware.com/forum/d/26488-游戏运行中是否可以切换skeletondataasset/3

              We will let you know once we have an improvement to offer for setting velocities.

              Harald
              こんにちはハラルドさん。お返事が遅くなってすみません。

              コードのご提示ありがとうございます!
              キャラクターが着地した際、velocity.y = 0rootMotion.applyRigidbody2DGravity=falseにして、
              AdditionalRigidbody2DMovementで動かすことでCharacterを動かすことができました。
              残念ながらAddforceとVelocityは未対応とのことなので、今後の実装を期待したいと思います。

              we just fixed an issue with SkeletonRootMotion not updating it's references after changing SkeletonDataAsset and calling skeletonAnimation.Initialize(true).

              こちらのSkeletonRootMotionのアップデートもありがとうございます!
              現在のプロジェクトでskeletonDataAssetの読み込み直しができるか試してみますね。

              また、もう一つすみません。

              koyu2 ---■ 親ボーンのスケールの影響を受けない
              コンストレイントをスキンに組み込み、体型の異なるキャラを実装しています。
              rootmotionに指定したボーンは親のスケール縮小によって移動距離が調整されます

              基準となっている男性は問題なく前進しながら攻撃します。
              しかしスケールが小さくなったゴブリンはモーションが完了する度に前に瞬間移動してしまいます。
              親ボーンのスケール縮小は考慮されないのでしょうか?

              最初の投稿の2番目の疑問なのですが、
              rootmotionの移動量をボーンのローカル座標ではなく、ワールド座標を参照する方法はないものでしょうか?
              もし現在その方法がなければ、将来的にワールド座標でrootmotionの移動ができるようになってほしいと思っています。
              理由としては、異なる体型の移動量の違いを表現できず、下記の動画のように別の体型のキャラクターが瞬間移動してしまうからです

              この異なる体型をスキンを用いて実装する手法はSpine公式のやり方を参考にしています
              https://ja.esotericsoftware.com/blog/Skin-constraints-for-different-proportions

              RootMotionの移動量をSpineエディタ上のワールド座標で取得することができれば、この瞬間移動の問題は解消され、異なる体型毎にアニメを用意する必要がなくなります。
              ご検討いただけると嬉しいです。

                koyu2 最初の投稿の2番目の疑問なのですが、
                rootmotionの移動量をボーンのローカル座標ではなく、ワールド座標を参照する方法はないものでしょうか?
                もし現在その方法がなければ、将来的にワールド座標でrootmotionの移動ができるようになってほしいと思っています。

                Sorry, the RootMotion components sample the animation timelines and support only local-space rootmotion. In general it should not be necessary to support skeleton-space root motion (and it would potentially create problems due to feedback effects). Do you have a setup where the parent of the root-motion bone is moving or scaling? If so, why?

                koyu2 理由としては、異なる体型の移動量の違いを表現できず、下記の動画のように別の体型のキャラクターが瞬間移動してしまうからです
                The reason is that it is not possible to express the difference in the amount of movement between different body types, and characters with different body types will move instantaneously, as shown in the video below.

                Unfortunately I don't understand the problem from your video. What does your bone hierarchy setup look like that that's a problem? Please always share some screenshots of the setup of your skeleton or components if things go wrong, not just a video of the undesired result. Note that we have no idea how your two characters are setup, what is common and where they differ.

                @koyu2 Unfortunately me tests have concluded the rigidbody2D.MovePosition can't reliably be replaced by setting (or adding) rigidbody2D.velocity.

                A longer explanation:
                MovePosition seems to be the only precise and reliable way to set movement delta, for both 2D and 3D rigidbodies. Setting velocity like "rigidBody2D.velocity = movement/deltaTime" works perfectly in mid-air
                without gravity and ground collision, unfortunately when on the ground, friction causes severe slowdown. Using a zero-friction PhysicsMaterial leads to sliding endlessly along the ground as soon as forces are applied. Additionally, there is no rigidBody2D.isGrounded, requiring our own checks.

                So the best workaround to apply your own forces and velocities is to write your own scripts where you can add forces, set velocities, apply your own drag, etc. Then each frame you can calculate the resulting movement for this frame and assign the value at rootMotion.AdditionalRigidbody2DMovement = movementThisFrame.