SpriteBatch Draw Animation

I’m using monogame for the first time, and am getting familiar with spriteBatch.Draw. I found this site, which helped me out a great deal.
I am trying to understand the part about animation, since it’s really interesting, and looks quite useful.

I tested it out on my 1024x1024 image, and got an animation, but I was not sure it was using every portion of the image, so I made an image with 64x64 blocks with a few numbered, and I realize, it was only using a few portions.
I slowed the animation, and changed the modulus. 16 seems to be the most I get. Only 17 blocks from right to left, are used.

imgFrame = (int)(gameTime.TotalGameTime.TotalSeconds * 1) % 16;

My draw function looks like this:

_spriteBatch.Draw(
heightmap,
destinationRectangle: new Rectangle(64, 64, 64, 64),
sourceRectangle: new Rectangle(
imgFrame * 64, 64, 64, 64),
Color.White,
rotation: imgRot,
origin: imgPivot,
//imgScale,
0,
0.0f);

When I increase the modulus, the frames seem to freeze (or something) because I dont see an animation, just gray.
Does anyone know how to get all the portions of the image framed, and is this the best method, or is there another way?
Also, are there any examples, or tutorials, that go into spriteBatch, that can help a beginner become a pro at using spriteBatch.Draw?

Can you post the image you’re using?

The reason you can only get at most 16 frames of animation is because, according to what you wrote, your image is 1024 pixels wide and your sprites are 64 pixels wide. You can have less if you want to, but using this calculation to get the frame index means you’re constrained to the size of the image.

Past that, you might need to post more details as to what the problem is. Maybe post some code snippets, along with your image so people can get a better idea of what’s going on. From the code you have posted everything looks fine to me… I would expect it to draw each image index, 0 to 15, from left to right starting at the second row of your source image, since you’re specifying y=64 in your sourceRectangle (instead of y=0).

I can do better. Here is one I “cooked up” for better visual.

I did it with different colors, so as to differentiate, but the file size is too large for upload. So I had to grayscale it.

The reason you can only get at most 16 frames of animation is because, according to what you wrote, your image is 1024 pixels wide and your sprites are 64 pixels wide. You can have less if you want to, but using this calculation to get the frame index means you’re constrained to the size of the image.

Sorry. I don’t understand. Can you please explain? Can I get all 256 images framed?

Past that, you might need to post more details as to what the problem is. Maybe post some code snippets, along with your image so people can get a better idea of what’s going on. From the code you have posted everything looks fine to me… I would expect it to draw each image index, 0 to 15, from left to right starting at the second row of your source image, since you’re specifying y=64 in your sourceRectangle (instead of y=0).

I thought I posted the code. It looks like I did. There is not much else, other than loading the image.
The frame was going right to left, since I had them numbered 1, 2, … 17, … 24. It went from 24 through to 17, then through to 1, and repeated.
However, I changed y to 0, and it started from the left. Thanks.

What I am looking to accomplish is getting all 256 images frame by frame.

Yea, so your image is 1024x1024 with 64x64 sprites (you called them blocks). This means you’ve got 16 sprites going left to right and 16 sprites top to bottom making, as you said, 256 total sprites. I think that’s all good so far; however, you’re drawing them using a source rectangle that is indexed on the x-axis based on the modulus of the game time’s seconds and the number of sprites on the x-axis of your sprite sheet.

Your calculation for imgFrame will always generate a number between 0 and 15. This is fine if all the sprites you want to animate are in a single row (or column if you switch everything to go vertically), but if you want to access all the sprites in your sprite sheet you’ll need to do something different.

Looking at your sprite sheet, what I’m seeing is that you’ve got 3 frames of walking for 4 different orientations (up, down, left, right). I assume one of those walking frames is probably reusable for idle but lets not worry about that for now. I’m not sure what the column and row at index 15 represents though… is there maybe more character data that didn’t make it in here? Anyway…

You’ve got a few options…

  1. Reformat your sprite sheet to put all your animations on a single row (or column) so you can just play them as needed. In this case, you could arrange your sprite sheet so that all the walking animations go left to right, and all the orientations are top to bottom. Then you can use your existing code to animate between the three frames (use modulus 3, not 16) and use a row selector based on the orientation the character is in to get the right y coordinate.
  2. Leave the spritesheet as is and calculate the index on the fly based on a starting point and some offsets. For example, the mage character with the nifty hat looks like it’s at a starting index of 3,1. If you multiply that start index by 64 on both x and y, that will get you the coordinate of the first frame of the front-facing orientation. Then you can animate between indexes 0, 1, and 2 to play the animation. You can also add 0, 1, 2, or 3 to the y coordinate of the starting index to get your orientation (ie, 0 = facing down, 1 = facing left, etc…).
  3. As a variation on the above, just build a table of Points (or Rectangles) representing the frames of your animation and animate through that. For example…
Point[] mageLeftWalk = { new Point(9 * 64, 4 * 64), new Point(10 * 64, 4 * 64), new Point(11 * 64, 4 * 64) };
int index = (int)gameTime.TotalGameTime.TotalSeconds * 1) % 16;
Rectangle sourceRect = new Rectangle(mageLeftWalk[index], new Point(64, 64));
_spriteBatch.Draw(..., sourceRect, ...);

There’s probably a lot of other things you could do, but hopefully you’re getting the idea :slight_smile:

With a simple sprite sheet like that, you can do this:

public class TextureData
{
    Texture2D texture;
    int framesPerRow;
    int framesPerColumn;
    public TextureData(Texture2D texture, int framesPerRow, int framesPerColumn)
    {
        this.texture = texture;
        this.framesPerRow = framesPerRow;
        this.framesPerColumn = framesPerColumn;
    }
    public int Column(int currentFrame)
    {
        return currentFrame % framesPerRow;
    }
    public int Row(int currentFrame)
    {
        return currentFrame / framesPerRow;
    }
    public int Width()
    { return texture.Width / framesPerColumn; }
    public int Height()
    { return texture.Height / framesPerRow; }
    public Rectangle SourceRectangle(int currentFrame)
    {
        int frameWidth = Width();
        int frameHeight = Height();
        int column = Column(currentFrame) * frameWidth;
        int row = Row(currentFrame) * frameHeight;
        return new Rectangle(column, row, frameWidth, frameHeight);
    }

So initialize a class TextureData with your texture, 16 frames per row (from top to bottom), and 16 frames per column (from left to right). Then in your draw code, put textureData.SourceRectangle(int frameThatYouWantToShow). If you are trying to do an animation you could do something like:

public struct AnimationTimer
{
    public int firstFrameOfAnimation;
    public int lastFrameOfAnimation;
    public float max;
    
    public int currentFrame { private set; get; }
    float count;
    public void Update(GameTime gameTime)
    {
        float deltaTime = (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond;
        count += deltaTime;
        if (count >= max)
        {
            count = 0;
            currentFrame++;
            if (currentFrame > lastFrameOfAnimation)
            {
                currentFrame = firstFrameOfAnimation;
            }
        }
     }
 }

With this you would just set your max (1.0f is 1 second), and firstFrame and lastFrame of the animation within the sprite sheet you want to iterate through. Then call Update(GameTime) to update your animation. Then use that currentFrame when getting your SourceRectangle.

1 Like

Thanks for your effort Trinith, but I think you got a bit carried away with the image. :smiley:
The image was just an example. I may want to use every image in a sprite, just to display different images, and not animate a character or such.

I am now learning, and Ias I mentioned, in the original post…
Does anyone know how to get all the portions of the image framed, and is this the best method, or is there another way?
Also, are there any examples, or tutorials, that go into spriteBatch, that can help a beginner become a pro at using spriteBatch.Draw?

I am really interested in understanding how to frame every section in the image. Once I know how to do that, I think I can then try working from there.

Thanks though, for trying to help. It was a bit steep for a beginner like me :wink:, but I think you did help a bit, as I realize from what you said, I needed a code for vertical as well.
Everything else after #1, just flew over my head.

Thanks again for trying. :+1:

Thanks. I’ll take a look at your code later. I’ve got some stuff to take care of. :smile:

I haven’t had time to study your code indepth, but I “formatted” it to fit in with my brain cells :joy: so I can better understand and relate to it.

    private Vector2 imgPos, imgPivot;
    private float 
        imgScale = 0.25f, imgRot = 0.0f, 
        count = 0, max = 256;
    private int 
        imgFrame = 0, framesPerRow = 16, framesPerColumn = 16, 
        firstFrameOfAnimation = 1, lastFrameOfAnimation = 256, 
        imgWidth, imgHeight, column, row;
    Rectangle srcRect;

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);

        heightmap = Content.Load<Texture2D>("aniTest");
        imgPivot = new Vector2(
            (heightmap.Width / 16) / 2, 
            (heightmap.Height / 16) / 2);
        imgWidth = heightmap.Width / framesPerColumn;
        imgHeight = heightmap.Height / framesPerRow;
    }

    protected override void Update(GameTime gameTime)
    {

        float deltaTime =
                (float)gameTime.ElapsedGameTime.Ticks /
                TimeSpan.TicksPerSecond;
        count += deltaTime;

        column = currentFrame % framesPerRow;
        row = currentFrame / framesPerRow;
        imgWidth = heightmap.Width / framesPerColumn;
        imgHeight = heightmap.Height / framesPerRow;
        srcRect = sourceRectangle(currentFrame);

        if (count >= max)
        {
            count = 0;
            currentFrame++;
            if (currentFrame > lastFrameOfAnimation)
            {
                currentFrame = firstFrameOfAnimation;
            }
        }

        base.Update(gameTime);
    }

    private Rectangle sourceRectangle(int currentFrame)
    {
        int frameWidth = imgWidth;
        int frameHeight = imgHeight;
        int _column = column * frameWidth;
        int _row = row * frameHeight;
        return new Rectangle(_column, _row, frameWidth, frameHeight);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        _spriteBatch.Begin();
        #region Draw content to screen
        _spriteBatch.Draw(
            heightmap,
            destinationRectangle: new Rectangle(64, 64, 64, 64),
            srcRect,
            //sourceRectangle: new Rectangle(
            //    currentFrame, 0, 64, 64),
            Color.White,
            rotation: imgRot, 
            origin: imgPivot, 
            //imgScale, 
            0,
            0.0f);
        #endregion
        _spriteBatch.End();

        base.Draw(gameTime);
    }

Is that okay so far? I’m not getting any animation.

Your value max is 256 seconds. The count keeps track of time since the last frame. If max is 256, it will take 256 seconds until the next frame. Reduce max down to 1 for 1 second, or whatever time between frames you’d like.

Oh. maxSecs.
Great! It does exactly what i wanted. Thanks a lot. You were a big help.
Just one thing I noticed you didn’t point out, I had wrong. firstFrameOfAnimation should start at 0, and lastFrameOfAnimation should end at 255.

@Trinith thanks to you also. I might well look over your code a little later, and see how that works.

:smile:

2 Likes