Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setAnimation doesn't work immediately after adding an already-removed child back on the stage #159

Open
jmlee2k opened this issue Feb 27, 2017 · 10 comments
Labels

Comments

@jmlee2k
Copy link
Contributor

jmlee2k commented Feb 27, 2017

First, thanks for all the great work here!

It seems that performing the following sequence of events will cause a spine animation not to play the second time:

  1. add a spine object to the stage
  2. play an animation on the spine object (this works as expected)
  3. wait some length of time, via setTimeout, a complete listener on the spine object, or any other method.
  4. remove the child from the stage
  5. wait another length of time
  6. add the child to the stage, and play the same animation as in step 2 (nothing appears to happen).

In addition, the same thing happens when setting visible to true and false instead of removing and adding the spine object to/from the stage.

I've managed to get it to work by either waiting around 20ms between showing the animation the second time and playing it, or by using requestAnimationFrame. However, both of these methods leave the animation at the last frame for a split second before playing it again.

My guess is that this is some sort of optimization to prevent animations from playing on objects which aren't on the stage or hidden, and it doesn't know that the object is visible / back on the stage until the next JS frame.

I know this is a bit complicated, so I've created a test case which displays the issue at https://jmlee2k.github.io/pixi-spine-anim-test/. You can also view the source at https://github.com/jmlee2k/pixi-spine-anim-test.

Each button will:

  1. add the spine object and play the animation (which works as expected)
  2. wait 2000ms
  3. hide the spine object (depending on the button pressed, this will either remove the spine object from the stage or set it's visibility to false)
  4. wait 1000ms
  5. show the spine object (depending on the button pressed, this will either add the spine object on the stage, or set it's visibility to true)
  6. play the animation again (which will only work if selecting "timeout" or "RAF" from the dropdown.

I've tried to keep the test case as simple as possible, so please excuse the somewhat brutal code :).

Please let me know if you need any other information.

Thanks in advance!

@cursedcoder cursedcoder added the bug label Mar 1, 2017
@albymack
Copy link

albymack commented Aug 15, 2017

The reason this breaks is because pixi spine is doing calling update () in the autoUpdateTransform function. Pixi.js is looping through it's children during its updateTransform function, so if you add/remove children during that loop, things break. In this case, the autoUpdateTransform calling update() causes events to fire, and if you remove/add children it will break pixi.js loop.

So there's 2 solutions.

  1. set the spine anim autoUpdate = false, and then call update manually yourself somewhere in your game loop.

  2. Fix pixi spine so it uses PIXI.js shared ticker instead of calling update () during the autoUpdateTransform function.

something like this.

    Object.defineProperty(Spine.prototype, "autoUpdate", {
        get: function () {
            return (this._setTicker);
        },
        set: function (value) {
            this.updateTransform = PIXI.Container.prototype.updateTransform;
            if (value)
            {
                if (!this._setTicker)
                    PIXI.ticker.shared.add (this.updateTick, this);
            }
            else
            {
                PIXI.ticker.shared.remove (this.updateTick, this);
            }
            this._setTicker = value;
        },
        enumerable: true,
        configurable: true
    });
    Spine.prototype.updateTick = function (tickerDeltaTime)
    {
        this.update (tickerDeltaTime / PIXI.ticker.shared.speed / PIXI.settings.TARGET_FPMS * 0.001);
    }

autoUpdateTranform function isn't used at all in this example modification.

@albymack
Copy link

You'll probably also want to listen for 'added" and 'removed' events from pixi and add/remove the shared ticker handler as appropriate so it isn't updating spine animations that isn't added to the scene.

@jmlee2k
Copy link
Contributor Author

jmlee2k commented Aug 15, 2017

Thanks, If you make a PR, I'll be happy to try it out to see if it works.

@ivanpopelyshev
Copy link
Collaborator

ivanpopelyshev commented Aug 15, 2017

The problem is that pixi has no "added to stage" or "removed from stage" events.

Vote for it: https://github.com/pixijs/pixi.js/issues

Until i fixed, you have to do it on your own. autoUpdate works only in simple cases :(

@AndreyGalaktionov
Copy link

Any updates ?

@jeremygillespiecloutier

Does anyone have a complete working fix for this? I need to make my spine visible and start an animation in the same frame, but this does not seem possible. @albymack Do you have the complete code including the "added" and "removed" events that I could use as a workaround for this bug? I've been stuck on this for quite a while and haven't really made any progress. Thanks!

@ivanpopelyshev
Copy link
Collaborator

ivanpopelyshev commented May 29, 2018

Which version do you use ? try update to latest (from bin folder of this repo)

but yeah, nobody solved it, and there's no animation system in pixi, its user job to implement it for their app.

@ivanpopelyshev
Copy link
Collaborator

good idea :)

@jeremygillespiecloutier

I changed updateTick to the following and it seems to work, now it only updates when the spine is visible:

        Spine.prototype.updateTick = function (tickerDeltaTime){
            if(this.worldVisible){
                this.update (tickerDeltaTime / PIXI.ticker.shared.speed / PIXI.settings.TARGET_FPMS * 0.001);
            }
        };

@jeremygillespiecloutier

I had some issues with the fix above that caused flickering on screen, but I think I found another way to solve the problem. I only tried it on a test case so far and it seems to work but I will try it on my main project tomorrow.

var oldRender=PIXI.Application.prototype.render;
var defferedAnimations=[];

PIXI.Application.prototype.render=function(){
    oldRender.apply(this, arguments);
    for(var i=0;i<defferedAnimations.length;i++){
        var anim=defferedAnimations[i];
        anim.func.apply(anim.scope, anim.args);
    }
    defferedAnimations=[];
};

var statePrototype=pixi_spine.core.AnimationState.prototype;
var oldAnim=statePrototype.setAnimation;

statePrototype.setAnimation=function(){
    defferedAnimations.push({func:oldAnim, scope:this, args:arguments});
};

Essentially I defer calls to the setAnimation function so that it only gets executed at the end of PIXI's rendering phase (after it has finished calling updateTransform on everything).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants