Jump to content

Seamlessly swap between sprite animations on a single sprite


forleafe
 Share

Recommended Posts

I feel like this is something that gets asked a lot, but after searching a bunch I haven't found anything definitive.

I've loaded my animations into pixi via a spritesheet on the Pixi loader and I can place an animated sprite in the game easy enough. But say I have my main character perform a running animation, but then I need her to do a punching animation. Is there an easy way to achieve this on the same sprite seamlessly? The only way I can currently think of how to achieve this is to manually remove the animated sprite from the container, then manually add a new animated sprite in its place, which just seems cumbersome and inefficient at best.

Link to comment
Share on other sites

43 minutes ago, botmaster said:

It's not cumbersome and inefficient, why would it be? Swapping your spritesheet might actually be a lot more inefficient.

I mean, is It? I'm not a master programmer by any means. But it just felt a little like a cumbersome work around. I figured since there was a really good system in place to create animated sprites, there would be a system in place to swap them easily. If this is the best way then thats fine i guess.

Link to comment
Share on other sites

1 hour ago, ivan.popelyshev said:

Pixi AnimatedSprite is a very basic thing. You have to make your own animation class, or extend this one. just "animatedSprite.textures = newTextrures" should work, but i have no guarantees.

Thanks. My only question is, how would I access the new textures if I used this method? "AnimatedSprite.textures = new Textures" would create a new array of animation frames under the "AnimatedSprite", right? So how would I differentiate between the different sets of frames?

I hope this question is clear and that I'm not misunderstanding too badly. Thanks for your help.

Link to comment
Share on other sites

you need build you own class to swap textures sheets.

Example this it's released only with spritesheetsAnimation from pixijs, but now i prefer use the pixi-spine engine
In this context, i preCache the sheet, and make a master Class that contole all animation , and not animed sheets are only .renderable = false;

 

If you need to take a look how to do this , my hold source  code available here
https://forums.rpgmakerweb.com/index.php?threads/pixi-spritesheets-animations-core-v1-0-texturepacker.83083/

 

 

There are more and less for each approach.
Pixi-Spine

  • Easy work-flow.
  • Complexe Animations
  • Easy export import
  • easy animation for games


pixiSpriteSheetAnimation

  • Very fast performance (*3~~*4) (example 1000 animations object run very more easy vs 400 spine obj)
  • Easy to add native motionBlur or FX from AE (spine dont have motionBlur)
  • Easy to understand under the hood
  • But a lot jobs to make a great animation system for game

 

I remember that I had to modify the original source code of pixi a bit to get a satisfying result. also take a look at this

 /* replace and add some feature to the PIXI_extras_AnimatedSprite for allow update when motions not loop
        □ [initialize] □ [initialize_before] □ [initialize_after] □ [prototyping_aniObj] □ [checkJSON_Integrity]
    */
     
    PIXI.extras.AnimatedSprite.prototype.updateTexture = function updateTexture() {
        this._texture = this._textures[this.currentFrame];
        this._textureID = -1;
        if (this.renderable && this.onFrameChange) { //+ this.renderable fix overleay callback memory fast call
            var ran = Math.random() * 99;
            var len = this._motionContexts.length; // how many context to pass chek
            this.onFrameChange(this.currentFrame, ran, len); //+ pass frame context + ran
        }
    };
    PIXI.extras.AnimatedSprite.prototype.update = function update(deltaTime) {
        var elapsed = this.animationSpeed * deltaTime;
        var previousFrame = this.currentFrame;
        if (this._durations !== null) {
            var lag = this._currentTime % 1 * this._durations[this.currentFrame];
            lag += elapsed / 60 * 1000;
            while (lag < 0) {
                this._currentTime--;
                lag += this._durations[this.currentFrame];
            };
            var sign = Math.sign(this.animationSpeed * deltaTime);
            this._currentTime = Math.floor(this._currentTime);
            while (lag >= this._durations[this.currentFrame]) {
                lag -= this._durations[this.currentFrame] * sign;
                this._currentTime += sign;
            };
            this._currentTime += lag / this._durations[this.currentFrame];
        } else {
            this._currentTime += elapsed;
        };
        if (this._currentTime < 0 && !this.loop) {
            this.gotoAndStop(0);
            if (this.onComplete) {
                this.onComplete();
            };
        } else if (this._currentTime >= this._textures.length && !this.loop) {
            if (this.loopContext) { // if perma loopContext, continu update only but keep texture
                var ran = Math.random() * 99;
                var len = this._motionContexts.length; // how many context to pass chek
                return this.onFrameChange(this._textures.length - 1, ran, len);
            };
            this.gotoAndStop(this._textures.length - 1);
     
            if (this.onComplete) {
                this.onComplete(this);
            }; // ++ pass arg current AnimatedSprite
        } else if (previousFrame !== this.currentFrame) {
            if (this.loop && this.onLoop) {
                if (this.animationSpeed > 0 && this.currentFrame < previousFrame) {
                    this.onLoop();
                } else if (this.animationSpeed < 0 && this.currentFrame > previousFrame) {
                    this.onLoop();
                };
            };
            this.updateTexture();
        };
    };

 

Link to comment
Share on other sites

I'm not sure if this is improper, but I've always just skipped the whole animatedSprite thing and just made animations manually. I also just put every single sprite together in one spritesheet and name them stuff like goblin_left_run0, globin_up_attack6, etc. I put the whole game in a requestAnimationFrame loop, and invoke update on every entity, and then its up to that entity's own animation code to see if its graphics should change.

Here's some pseudo code for the core loop that changes the frames, as well as idle, run, and attack.  This would hypothetically be inside of a class, but I've written it just plain.

// defaulting to the 4 frame idle animation
let animationName = 'idle'
let frameNumber = 0
let acc = 0 // a variable that stores time
let maxFrame = 4
let frameDelay = 0.5

update(delta) {
    // accumulate time
    acc += delta 
    // is it time for next frame?
    if (acc > frameDelay) {
        // next frame
        frameNumber++
        if (frameNumber > maxFrame) {
           // loop to start of the animation
           frameNumber = 0
        }
        // change the graphics
        sprite.texture = PIXI.Texture.fromFrame(animationName + '_' + frameNumber + '.png')
    }    
}

// change to the run animation
run() {
  animationName = 'run'
  acc = 0
  frameNumber = 0
  maxFrame = 8
  frameDelay = 0.250
}

// change to the attack animation
attack() {
  animationName = 'attack'
  acc = 0
  frameNumber = 0
  maxFrame = 12
  frameDelay = 0.180
}
//etc

Maybe in the end its not too different than the animatedSprite... except for very explicit control over the timings/loops, and no specific arrays of frames (though they're implied by the names of the frames in the spritesheet). Performance will be fine as in the end all any of this does is display a small subsection of a texture that is already loaded -- one can easily have hundreds on a low end machine, and sometimes many thousands.

Link to comment
Share on other sites

5 hours ago, timetocode said:

I'm not sure if this is improper, but I've always just skipped the whole animatedSprite thing and just made animations manually. I also just put every single sprite together in one spritesheet and name them stuff like goblin_left_run0, globin_up_attack6, etc. I put the whole game in a requestAnimationFrame loop, and invoke update on every entity, and then its up to that entity's own animation code to see if its graphics should change.

Here's some pseudo code for the core loop that changes the frames, as well as idle, run, and attack.  This would hypothetically be inside of a class, but I've written it just plain.


// defaulting to the 4 frame idle animation
let animationName = 'idle'
let frameNumber = 0
let acc = 0 // a variable that stores time
let maxFrame = 4
let frameDelay = 0.5

update(delta) {
    // accumulate time
    acc += delta 
    // is it time for next frame?
    if (acc > frameDelay) {
        // next frame
        frameNumber++
        if (frameNumber > maxFrame) {
           // loop to start of the animation
           frameNumber = 0
        }
        // change the graphics
        sprite.texture = PIXI.Texture.fromFrame(animationName + '_' + frameNumber + '.png')
    }    
}

// change to the run animation
run() {
  animationName = 'run'
  acc = 0
  frameNumber = 0
  maxFrame = 8
  frameDelay = 0.250
}

// change to the attack animation
attack() {
  animationName = 'attack'
  acc = 0
  frameNumber = 0
  maxFrame = 12
  frameDelay = 0.180
}
//etc

Maybe in the end its not too different than the animatedSprite... except for very explicit control over the timings/loops, and no specific arrays of frames (though they're implied by the names of the frames in the spritesheet). Performance will be fine as in the end all any of this does is display a small subsection of a texture that is already loaded -- one can easily have hundreds on a low end machine, and sometimes many thousands. 

Honestly, out of every answer posted here, this is literally the only one that has made any sense to me. As far as the sprite sheet goes, yeah I'm doing the exact same thing as you. Putting everything on a single sprite sheet just seemed like common sense. I don't really understand why a person would want to use multiple sprite sheets when one will work fine and be more efficient. Anywho, I'm going to study how Pixi's animation tools work and see if there is a way to implement what you have here into the tools Pixi has already built.

But a moment a brutal honesty here, the devs for Pixi need to take a serious look at implementing some form of animation swapping on a single sprite,  and include it within pixi's code. I think this falls well within the scope of common features that Pixi should be able to do right out of the box. I shouldn't have to take apart Pixi's code nor reinvent a feature that pixi already has included, just so I can achieve something so basic and so common that a great number of users need. Google is littered with people asking how to achieve this, and every answer is a convoluted amalgamation of custom code and head scratching "Do it yourself" confusion.  

Anyways, thanks for the help. And sorry if this is coming of confrontational. It's not meant to be.

Edit:: I had a thought. Is there a way to set the loop bounds? If there was I could, theoretically, load in every frame of animation when the sprite is first loaded, and only play a specified section of the loop. "goToAndPlay()" could handle that pretty well. The only part missing that I can't find is a way to restrict the loop boundaries.

Link to comment
Share on other sites

40 minutes ago, forleafe said:


But a moment a brutal honesty here, the devs for Pixi need to take a serious look at implementing some form of animation swapping on a single sprite,  and include it within pixi's code. I think this falls well within the scope of common features that Pixi should be able to do right out of the box. I shouldn't have to take apart Pixi's code nor reinvent a feature that pixi already has included, just so I can achieve something so basic and so common that a great number of users need. Google is littered with people asking how to achieve this, and every answer is a convoluted amalgamation of custom code and head scratching "Do it yourself" confusion.  

Anyways, thanks for the help. And sorry if this is coming of confrontational. It's not meant to be.
 

There's https://github.com/pixijs/pixi.js/issues/3469 , and i hope to make it in v5.

For now, there are many things DIY in pixi, my approach is to cover them by plugins. But in this case, yeah, there's a straight improvement for animation.

Link to comment
Share on other sites

1 hour ago, ivan.popelyshev said:

There's https://github.com/pixijs/pixi.js/issues/3469 , and i hope to make it in v5.

For now, there are many things DIY in pixi, my approach is to cover them by plugins. But in this case, yeah, there's a straight improvement for animation.

Thanks for that. I'll take a look. Either way I appreciate all the work you do for this wonderful tool.

Link to comment
Share on other sites

To bundle sprites I use the free version of texture packer.  The primary feature for me there is that I can just put all of the sprites, as individual frames, into a folder and point texture packer to it. Texture packer can then automagically produce two files: a spritesheet.png where all the images exist together, and a spritesheet.json that basically describes the locations (x,y,width,height) of each image.

Pixi can load this and then exposes them via the function `fromFrame('/goblin_north_attack0.png')` etc. So PIXI.Sprite.fromFrame('filename') and PIXI.Texture.fromFrame('filename') are two ways to use it. On a technical level these are no longer the files that they're named after. These are just rectangular selections from the generated spritesheet. The folders become part of the sprite names, so one way to organize instead of goblin_north_attack0, is to make /goblin/north/attack/0.png and then that whole string ` /goblin/north/attack/0.png` is the name of the frame. I'm not super keen on naming something 0.png, but this can be nice if the source art work has a lot of sprites... and really it can be named anything, all we truly need from it is that it has a number somewhere that we plan on incrementing for moving through the animation. 

Multiple spritesheets are still an option though, even doing the above. One just has to be sure not to name a sprite the same thing in both sheets (easily done via the folder trick, as it ends up being a prefix to everything in it). I usually end up with a spritesheet for all the characters, items, creatures etc, and then another spritesheet for the game world if I'm using TiledMapEditor which has its own format. I've written a barebones exporter from TiledMapEditor to pixi if anyone wants it.

I'd say that this solves many problems, and it does, but it isn't without some tedious work b/c usually whatever drawing program I use or whatever art I purchase has its own ideas about the frame format. For example most purchased sprites are sold as images containing frames in rows or grids. Most of the drawing programs I've used export animations as a row of sprites all right next to each other, or as a folder of individual frames that have been named automatically. They need to be cut up into individual frames and renamed to be put into a single sheet.

If the volume of the work for cutting the images out and naming them is going to exceed an hour, I usually go fire up some imagemagick tutorials. Imagemagick is a command line script that can do things like split a grid-aligned spritesheet into multiple images. I end up doing this pretty much any time I purchase artwork... usually after a fair amount of opening the spritesheet in drawing programs and displaying grids to figure out the frame width and height, and sometimes some manual removal of areas that I don't plan on feeding into the scripts.

I'd also like to note that the code I pasted above is very pseudo. Just skimming it again I can see that the checks for varying things are off by 1. There's also usually some section about facing left or facing right, and then taking the set of animations (e.g. goblin_right_attack) and flipping them by setting the sprite's scale.x to be -1 (only applies if left/right are just going to be mirrors of each other).

Good luck!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...