No, your approach to keeping things "pixel perfect" is actually a very appropriate thing to do. Windows Presentation Foundation, Microsoft's (relatively) new GUI framework, has a feature called "layout rounding" that does this exact same thing for you, so you don't need to worry about it. As far as I know, XNA does not have this feature, so you'd need to do it yourself. What you've got is basically exactly what I'd do too.
As far as changing the frame rate of your animation, you've got a couple of choices. There's the easy way, and the better way. I'll start with the easy way, which builds off of the texture atlas tutorial.
Right now, we're assuming that every update means advancing to the next frame. We're also assuming that each of the updates happen at regular intervals (30 times per second, if I'm remembering correctly). Unless you go mess around with things, this will be the case, unless things are running so slowly that the computer can't keep up, in which case we'd see our animation drag a bit, along with the rest of the game.
We can easily change our code so that we only advance to the next frame every other update, or every five updates, or whatever we choose. This will slow down the animation by that same factor. (Advancing every other frame will make the animation happen at half the speed, etc.)
To do this, we'd add in a couple more instance variables like this:
private int currentUpdate;
private int updatesPerFrame = 3;
And then we'd change our logic in the Update method to account for this:
public void Update()
{
currentUpdate++;
if(currentUpdate == updatesPerFrame)
{
currentUpdate = 0;
currentFrame++;
if (currentFrame == totalFrames)
currentFrame = 0;
}
}
So basically, the current frame only gets updated when the current update gets to the right number (updatesPerFrame), at which point the current frame gets incremented, and the current update gets reset.
The completed code might look like this (I haven't actually ran it yet to test it, but it should work in theory):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace TextureAtlas
{
public class AnimatedSprite
{
public Texture2D Texture { get; set; }
public int Rows { get; set; }
public int Columns { get; set; }
private int currentFrame;
private int totalFrames;
public AnimatedSprite(Texture2D texture, int rows, int columns)
{
Texture = texture;
Rows = rows;
Columns = columns;
currentFrame = 0;
totalFrames = Rows * Columns;
}
public void Update()
{
currentUpdate++;
if(currentUpdate == updatesPerFrame)
{
currentUpdate = 0;
currentFrame++;
if (currentFrame == totalFrames)
currentFrame = 0;
}
}
public void Draw(SpriteBatch spriteBatch, Vector2 location)
{
int width = Texture.Width / Rows;
int height = Texture.Height / Columns;
int row = (int)((float)currentFrame / (float)Columns);
int column = currentFrame % Columns;
Rectangle sourceRectangle = new Rectangle(width * column, height * row, width, height);
Rectangle destinationRectangle = new Rectangle((int)location.X, (int)location.Y, width, height);
spriteBatch.Begin();
spriteBatch.Draw(Texture, destinationRectangle, sourceRectangle, Color.White);
spriteBatch.End();
}
}
}
The other way is, as you mentioned, to update based on the GameTime. This would involve changing the AnimatedSprite class a bit. In particular, the Update method would require passing in a GameTime object. By the way, your main Game class will have a GameTime object that you can just pass along to this class when you update it, so that shouldn't be a problem.
Then you could add in code that determines what frame you should be on, based on the total time the game has been running for:
double timePerFrame = 0.2;
int totalFrames = 3;
currentFrame = (int)(gameTime.TotalGameTime.TotalSeconds / timePerFrame);
currentFrame = currentFrame % totalFrames;
timePerFrame and totalFrames would actually probably be best as instance variables, up at the top of your class. But that should about do it.
Of course, one of the problems that you have with this is that the animation is based on when the game started, not when the animation started. In many cases, that's no big deal. People just won't notice. But if you want to worry about that, you would have the AnimatedSprite class keep track of a TimeSpan object (gameTime.TotalGameTime is a TimeSpan object) and subtract it from the current total game time. Then your current animation frame will be based on when the animation started, not when the game started.
So a complete Update method might look something like this:
public void Update(GameTime gameTime)
{
double timePerFrame = 0.2;
currentFrame = (int)(gameTime.TotalGameTime.TotalSeconds / timePerFrame);
currentFrame = currentFrame % totalFrames;
}
Though, again, I'd recommend making timePerFrame an instance variable. (When I went back and checked, it looks like totalFrames is already an instance variable.
Hmm… I said that was the harder way, but looking at it, it seems as easy as the first way.
Anyway, if you're using the GameTime object, and someone's computer is running slow, that won't slow the animation down.
Anyway, there you have it. Hope it helps!