• RuntimesGodotBugs
  • [Godot] Additive Mix Blending Bug After Track Entries Completed

TrackEntry.set_mix_blend(SpineConstant.MixBlend_Add) is causing recursive additive even when both track entries are completed.

Using the "02-animation-state-listeners" Spine-Godot example, I replaced the "func _ready():" contents in GDScript.
https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-godot/example/examples/02-animation-state-listeners/animation-state-listeners.gd#L25
Replaced code:

func _ready():
	var spineboy = $Spineboy
	spineboy.connect("animation_started", self, "_animation_started")
	spineboy.connect("animation_interrupted", self, "_animation_interrupted")
	spineboy.connect("animation_ended", self, "_animation_ended")
	spineboy.connect("animation_completed", self, "_animation_completed")
	spineboy.connect("animation_disposed", self, "_animation_disposed")
	spineboy.connect("animation_event", self, "_animation_event")
	
	var animation_state = spineboy.get_animation_state()
	animation_state.set_animation("walk", false, 0)
	animation_state.set_animation("run", false, 1).set_mix_blend(SpineConstant.MixBlend_Add)
		
	pass

One of the arms is drifting away when both animation track entries completed their timeline cycle.

I hope that is explanatory enough to replicate.

Godot 3.5.1
Spine 4.1

Related Discussions
...

Additively mixing animations that were not designed to be additively mixed will result in such behaviour. I do not believe this is actually a bug. @Nate thoughts?

    Yay, I can link my Discord attachment. Both tracks have stopped, I don't expect the arm to drift away like that.

    This also happens when using the oficial Owl spine asset, trying to replicate the Additive Blending example with Godot.
    With MixBlend_Add it seems like setting the state alpha is constatly adding it on top every frame, finally breaking the animations and bones.
    With the other MixBlend is working fine.

    It is totally a bug that should be reported at github?

    Yes, please report it on GitHub, I'll take a look.

    Mario After consulting with the masterful Erika, I think I understand the mix blend add caveat.
    "Properties set by additive animations must be set manually or by another animation before applying the additive animations, else the property values will increase each time the additive animations are applied."
    https://en.esotericsoftware.com/spine-api-reference#MixBlend
    I feel like it should be explained more clearly.

    Sorry I'm late. Additively mixing animations adds them to the current pose. If you don't reset the values somehow, you'll just keep adding to the pose. This results in your arms drifting off into outer space and other fun.

    That text is pretty explicit. How would you change it? API documentation is intended to be brief, it isn't the place to provide a long description of functionality. We should explain it in more details on the applying animations page.

      Nate How about explain it in more details on the applying animations page and link the API documentation to that applying animation page. 🤔

      um mês depois

      Can someone provide an example of how it should be done to reset the values before adding to the mix blend? Some samples on this working on Godot could be nice.
      Currently im checking the webgl samples, and the additive blending sample is constantly adding to the alpha without reseting any values:
      function calculateBlend(x, y, isPageCoords) {
      var canvasBounds = canvas.getBoundingClientRect();
      var centerX = canvasBounds.x + canvasBounds.width / 2;
      var centerY = canvasBounds.y + canvasBounds.height / 2;
      right.alpha = x < centerX ? 1 - x / centerX : 0;
      left.alpha = x > centerX ? (x - centerX) / (window.innerWidth - centerX) : 0;
      up.alpha = y < centerY ? 1 - y / centerY : 0;
      down.alpha = y > centerY ? (y - centerY) / (window.innerHeight - centerY) : 0;
      }

      I have been using SpineSprint.get_skeleton().set_to_setup_pose() in GDscript.

      You can set everything to the setup pose, as SilverStraw showed, or use an animation. An animation is more efficient since it only needs to reset the values that are changed additively. That might matter if the rest of the skeleton is complex.

      16 dias depois

      I've also been having a problem implementing mixblending into my code, it seems like a pretty simple task as I can get it to function properly in the preview window in the spine editor. I've tried a lot of different methods but nothing seems to work, either it isn't additive and it just ends up overwriting a previous track, or it has the same problem of scaling into infinity, or when I reset/try to mix out with empty tracks afterward nothing ends up being applied at all. For something so simply done in the editor I would think it would be pretty easy to communicate to new users how to integrate something like this in Godot.

      I'm not sure if I have to have empty tracks first, to use a trackentry instance and then set mixblending there (which doesn't seem to do anything), use .SetMixBlend on the track entry, on an emptyanimation track, or the animations themselves, whether to use the timeline api or continue using animationStateData. I understand that the general runtimes API can only be so specific/detailed when it's supposed to pertain to every unique game engine but I can't deny feeling kinda frustrated spending hours trying to figure out even the most basic functions.

      You need to set TrackEntry mixBlend to MixBlend.add. After that you need to ensure that properties keyed in an animation that is applied additively are reset each frame.

      If you are seeing keyed properties increasing (or decreasing) each frame, then it sounds like you are using MixBlend.add but are not resetting the keyed properties each frame. The easiest way to do that is to reset the skeleton to the setup pose with Skeleton setToSetupPose.

      You could also apply an animation on a lower track that resets the properties. To create that animation, in Spine: space to deselect so all timelines are visible, ctrl+shift+L in the dopesheet for the animation applied additively to key all visible, ctrl+C to copy those keys, ctrl+Z to remove them. Create a new animation, advance the timeline to > frame 0 (say frame 10), ctrl+V to paste, move the timeline to frame 0, ctrl+shift+L to key all visible at the setup pose, then delete all the keys on frame 10. Now you have an animation that keys everything to the setup pose that the additively applied animation keys. Apply the "reset" animation on a lower track than the additively applied animation.

      While the Preview is close to how things work at runtime, it's not identical. We have an issue to improve that:
      EsotericSoftware/spine-editor547

      Idk if I'm just dumb but what am I doing wrong here? None of these solutions seem to fix the infinite scaling and I still have no clue how I'm supposed to integrate this, should I set tracks 3 and 4 to my setupPose/reset animation (even though it's identical to the setup pose so I don't see how that's preferable to SetToSetupPose()), should I not be doing this in an if statement under PhysicsProcess (idk why that would matter since the code is only being executed in a single frame), setting the mixblend back to setup seems to do literally nothing, should I also be using emptyAnimation tracks? Should I have a blanket reset code happening in Process/PhysicsProcess outside the if statement in case the if statement is ignoring the reset to setup?



      • Responderam a isso Nate.

        Alyria even though it's identical to the setup pose so I don't see how that's preferable to SetToSetupPose()

        setToSetupPose resets everything to the setup pose. This can be a lot more than is necessary, as it's rare (and discouraged) to key everything.

        Alyria setting the mixblend back to setup seems to do literally nothing

        It needs to be set to add to get additive blending. You won't get additive blending when you set it to setup.

        setToSetupPose doesn't use TrackEntry at all. AnimationState uses TrackEntry to keep track of which animations are playing on a track, the times, etc. Changing MixBlend around a call to setToSetupPose will not affect setToSetupPose.

        should I also be using emptyAnimation tracks

        Empty animations are used to mix "in" from the setup pose to an animation, or "out" from an animation to the setup pose. It has nothing to do with additive animations.

        Should I have a blanket reset code happening in Process/PhysicsProcess outside the if statement in case the if statement is ignoring the reset to setup?

        I'm not sure what "blanket reset code" would be, but setToSetupPose is not being ignored (and in general it doesn't make sense to think that code will be ignored). I'll explain below.

        idk why that would matter since the code is only being executed in a single frame

        There's lots of other code getting executed every frame. When the mouse button is pressed you set some animations. Your code for that looks fine (in the first image). Don't set MixBlend to setup and remove setToSetupPose. Next your app continues to run. Every frame AnimationState is applied to pose your skeleton with the animations, then Skeleton updateWorldTransform calculates world bone transforms, then the skeleton's region and mesh attachments are rendered. When the animations are applied, since you have set MixBlend add, the animation values are added to the skeleton's pose values instead of overwriting the current values. That causes the values to increase every frame.

        To fix it, you can call setToSetupPose, but you need to do this every frame, not just when the mouse button is pressed. Also, you need to do it before animations are applied, otherwise you will lose the pose from the animations.

        Alternatively, add your "reset" animation on eg track 3 (assuming you use tracks 0, 1 and 2 for other, non-additive animations), then put your additive animations on higher tracks, like tracks 4 and 5.

        I get that it's being applied every frame but after the mixing from the (AddAnimation 0.5seconds) I thought I could just reset it in the same if statement instead of having a "blanket" setToSetupPose outside of the if statement (meaning it will occur every frame in code since it's in PhysicsProcess and can account for any weird additive usage elsewhere). I put setToSetupPose outside but still have issues, if I use track 4 and 5 instead, should I mix in my setup pose animation on track 3 and then mix out my two recoil animations on 4 and 5 with emptyanimation tracks? All I want to do is play two very quick recoil animations with additive blending activated (I have them split so I can use different configurations of vertical and horizontal recoil for different weapons) and then immediately stop playing them like in the spine preview window.

        • Responderam a isso Nate.
          • Editado
          while (true) { // Each frame:
             if (mouseClicked) {
                animState.addAnimation(...); // Set new animations.
                x = setup.value; // reset
             }
             x += additiveAnimation.value;
          }

          In this psuedo code x represents a skeleton property and will increase every frame. Resetting x needs to be moved outside the if statement, so it happens every frame:

             x = setup.value; // reset
             x += additiveAnimation.value;

          The interesting bit for additive animations is when other animations animate the values, then the additive animation is added to that:

             x = setup.value; // reset
             x = otherAnimation.value;
             x += additiveAnimation.value;

          This way your recoil can be on top of any animation. In this case you don't need to reset the value because the other animation is setting it. However, you may have many other animations and in all those you'd need to remember to key all the values used in your additive animations. When you forget, your additive animations will increase the value every frame. Resetting with setToSetupPose or an animation ensures that won't happen.

          Alyria should I mix in my setup pose animation on track 3 and then mix out my two recoil animations on 4 and 5 with emptyanimation tracks?

          You don't need to mix in the reset animation. It probably sets the values to the setup pose. If your "main" animations (run, jump, etc on lower tracks) don't key the values keyed in the additive animations, they will be set to the setup pose by the reset animation without mixing in with an empty animation. If your lower track animations do key those values and you apply the reset animation on top, the values would snap to the setup pose + the recoil animations. It may be better to set your tracks like this:

          0: the "reset" animation
          1: main animations: idle run, jump, etc
          2: recoil x (additive)
          3: recoil y (additive)

          This way if the main animations key the values changed additively, you get those keys + recoil. If the main animations don't key those values, you get the setup pose (from the reset animation) + recoil. You never get the values increasing every frame. 🙂

          So wait you never turn off the additive you just have a setup animation that keeps resetting stuff on a previous track, and additive sounds like it's universal for all tracks not just a select few tracks like it's represented in spine? Why does this work so differently to how it functions in the editor, I feel it's setting up a completely different expectation for a lot of users. I get runtime is very different from what an editor can show you but this seems needlessly complicated for what I'm trying to do. I tried putting my setup/reset animation on track 0 (though it's a little more complicated with my setup since I have two setup poses for the front and back view of my character) with everything else on top but I still had the same scaling issue and it wasn't showing any sign of the actual recoil animations even with the additive infinity scaling. Maybe I can key more things on my setup/reset animations but even then it seems like the recoil animations aren't even going through at all. Do you mind if I send my project files over or should I just scrap having recoil animations through spine since this seems like a really unreliable process, especially if my rig/animation setup gets even more complicated later on.

          • Responderam a isso Nate.