craftworkgames / MonoGame.Extended

Extensions to make MonoGame more awesome
http://www.monogameextended.net/
Other
1.42k stars 322 forks source link

AnimatedSprite displays first region of texture atlas before animation plays #929

Open aconstas opened 1 month ago

aconstas commented 1 month ago

Link to docs: https://www.monogameextended.net/docs/features/2d-animations/animatedsprite/

I was following the documentation but I'm seeing this issue where the first region of the atlas displays before the desired animation. In my code I've defined a sleep animation that does play, but before it plays the first region of the texture atlas (atlas) displays before.

@AristurtleDev: "The problem is how the AnimatedSprite(SpriteSheet, String) constructor is done when you call it by doing _dog = new AnimatedSprite(_spriteSheet, "sleep").

That constructor takes the _spriteSheet and calls the other constructor to set that up. Since AnimatedSprite is derived from Sprite, then it has to call the base constructor and pass in a texture to use, which just defaults to the first index in the texture atlas.

For right now, you should be able to fix this by instead doing the following workaround until a fix is pushed"

_dog = new AnimatedSprite(_spriteSheet);
_dog.SetAnimation("sleep");
dev-ISO commented 3 weeks ago

I ran into the same issue where the first region of the Texture2DAtlas was displaying before the desired animation and even with your workaround I still was running into this issue. I ended up having to fork and modify the source code to implement a working fix.

What I did to temporarily fix this was adding a custom constructor to AnimatedSprite to overwrite the broken one:

AnimatedSprite Broken Constructor:

public AnimatedSprite(SpriteSheet spriteSheet)
    : base(spriteSheet.TextureAtlas[0])
{
    ArgumentNullException.ThrowIfNull(spriteSheet);
    _spriteSheet = spriteSheet;
}

Custom AnimatedSprite Constructor Workaround

public AnimatedSprite(SpriteSheet spriteSheet, int textureAtlasRegionIndex)
    : base(spriteSheet.TextureAtlas[textureAtlasRegionIndex])
{
    ArgumentNullException.ThrowIfNull(spriteSheet);
    _spriteSheet = spriteSheet;
}

and adding two custom methods to SpriteSheet that I use to try and get the Index of the First Texture2DRegion for my targeted Animation Frame:

public bool TryGetAnimation(string name, out SpriteSheetAnimation animation)
{
    return _animations.TryGetValue(name, out animation);
}

public void GetAnimationFirstRegion(string name, out Texture2DRegion firstTextureRegion)
{
    TryGetAnimation(name, out SpriteSheetAnimation animation);
    var firstFrame = animation.Frames[0];
    int frameIndex = firstFrame.FrameIndex;
    firstTextureRegion = TextureAtlas.GetRegion(frameIndex);
}

So now when I create an AnimatedSprite I call these two methods with the name of the defined Animation which in your case was "sleep" and in my case its stored in the animationKey variable to get the first frame's index in the Texture Atlas and pass that to the new Animated Sprite like this :

mySpriteSheet.GetAnimationFirstRegion(animationKey, out Texture2DRegion firstTextureRegion);
int textureRegionIndex = mySpriteSheet.TextureAtlas.GetIndexOfRegion(firstTextureRegion.Name);

AnimatedSprite myAnimatedSprite = new AnimatedSprite(mySpriteSheet, textureRegionIndex);