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

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

but why reconfiguring SkeletonDataAsset?

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

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

ご対応に感謝します!

Related Discussions
...

@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.

            Harald

            Do you have a setup where the parent of the root-motion bone is moving or scaling? If so, why?

            はい。私のroot-motion boneは親のスケールの影響を受けています。
            なぜそうするのか? 異なる体型に合わせて、rootmotionの移動量も適切な位置に(Spineエディタ上では)自動的に調整できたからです。

            このように、rootmotion に必要な「.root_move-」ボーンは「bodysize_all」ボーンの子に配置されています。「bodysize_all」ボーンはスキンに割り当てられたコンストレイントの影響を受けスケールが縮小します
            .

            ゴブリンのスキンを有効にすると、このようにbodysizeボーンがスケール縮小し、体型が変わります。
            このスケルトンでRootmotionを用いて作った前進攻撃のアニメが以下の動画です
            .

            root_move ボーンの位置を見てください。これは同一のアニメですが、スケール縮小の影響を受けるため、rootmoveボーンがどちらも適切な股下の位置に移動しています。
            root_moveボーンをRootmotionに適用すれば、一見問題なく異なる移動量を実現できそうです。

            しかし、残念ながらUnityランタイムのRootMotionはローカルの移動量しか計算しません。
            エディタ上で適切に見えるrootmotionの移動量は実際には下記の画像の位置で計算されています。
            .

            スケール縮小が考慮されなくなった分、実際の移動量より前にroot_moveボーンが移動しています。
            それはRootMotionの移動量に対してSpineグラフィック上の移動量が足りないことを意味します。
            そのためアニメが遷移した際、前の投稿で示した動画のような瞬間移動が起こります。

            Spineは公式ブログで異なる体型の実装テクニックを紹介しています。
            しかし、現在Rootmotionはエディタ上のワールド位置ではなく、ローカル移動量しか計算しないため、Rootmotionと上記のテクニックを併用することができません。
            そのためrootmotionの移動量を調整したアニメを体型の数だけ用意しなければならないケースに直面します。異なる体型が5つあった場合、rootmotionを使用したアニメの数が5倍になります。

            また、Rootmotionがワールド位置を参照できた場合、Rootmotionボーンとhipボーンをコンストレイントしてボーン移動を自動化できるかもしれません。

            以上がRootmotionがワールド位置を参照してくれるようになってほしい理由です
            ここまで読んでくださりありがとうございます。

            Harald

            Unfortunately me tests have concluded the rigidbody2D.MovePosition can't reliably be replaced by setting (or adding) rigidbody2D.velocity.
            Then each frame you can calculate the resulting movement for this frame and assign the value at rootMotion.AdditionalRigidbody2DMovement = movementThisFrame.

            了解です!velocityとAddforceでの移動が難しい旨、承知しました。
            教えていただいたrootMotion.AdditionalRigidbody2DMovementを使用して物理挙動の再現を試してみたいと思います

            あなたのご尽力に感謝します!

            @koyu2 Thanks for the detailed writeup. Sorry, my previous answer was actually not entirely correct regarding skeleton space vs local space root motion: Parent bone scale is supported, as it looks like you're using in your skeleton setup. What is not supported is rotation in the parent bones combined with scale and the like. If a simply scaled parent bone breaks root-motion, there might be a bug in the spine-unity runtime.

            I just noticed that your description already contains the likely source of the problem:

            The "bodysize_all" bone is scaled down by the constraint assigned to the skin

            Currently only transform constraints are respected and applied if they are targeting the root bone, but not constraints scaling parents of the root-motion bone.
            This is a valid scenario, so we would like to fix this issue. Could you please send us the problematic skeleton assets which has a scaled parent above the root motion bone? Alterantively you could also send us a minimal Unity project again with this skeleton asset.

              Harald
              こんにちはハラルドさん。
              ご対応ありがとうございます!
              先ほどSpineのプロジェクトをメールアドレスに送信しました!
              最小限のUnityプロジェクトも同梱されています。

              ご査収頂けますと幸いです。