Managing Sprites and Frames

Sprite Class

We'll finally be sorting out our sprites with a proper Sprite class. About time, I know. This will be a short lesson and is simple to do, and will also make it simpler to add various graphical features in our future lessons.

The tileset we'll use is the same as the previous lesson, and the visible example doesn't really change, but for a refresher here's how our game features currently look together:

Sprites class example

Our Sprite class will take one argument, which will be an array of frames that compose the sprite. As in previous code, our frames are objects with the x, y position of the sprite on the tileset, the w, h of the sprite, and (optionally) d, the duration in milliseconds to show the frame for (if there are multiple frame).

Each frame can also now optionally contain the values loop, a boolean that states whether this sprite should loop or simply rest on the last frame once it's cycled through the frames once (if multiple frames have this value set, only the last is used), and offset, an array in the form [x,y], which will modify the drawn position of this frame relative to the sprites position on the map.

When a new Sprite instance is created, we'll begin by setting the properties animated, which is true if the number of frames is greater than 1, frameCount which is simply the number of frames, and duration and loop which will begin as 0 and true respectively:

function Sprite(data)
{
	this.animated	= data.length > 1;
	this.frameCount	= data.length;
	this.duration	= 0;
	this.loop		= true;

If there's more than one frame, we'll then begin looping through the frame data the sprite will use:

	if(data.length > 1)
	{
		for(var i in data)
		{

For each frame, we need to check it has a duration, d, property set. If it does not, we'll set an arbitrary value of 100 milliseconds for this value. After, we'll add the frames duration to the Sprite duration property.

			if(typeof data[i].d=='undefined')
			{
				data[i].d = 100;
			}
			this.duration+= data[i].d;

We'll then also check if the frame data has the loop property. If so, we'll set the Sprite loop property to true or false depending on this value.

			if(typeof data[i].loop!='undefined')
			{
				this.loop = data[i].loop ? true : false;
			}

We'll then close this loop and if block, and set the Sprite frames property to the data argument before closing the constructor:

	this.frames		= data;
}

Drawing the Sprite

Our Sprite class will have a draw method which will take 3 arguments: t, the currently elapsed game time (we're passing this as an argument here as we may wish to use a different time value for drawing sprites, such as the time since the associated object was created, interacted with, etc.), and x, y, the calculated position to draw the sprite on the Canvas.

We'll need to calculte the frame to draw for the sprite, so we'll begin by assuming it's the first frame by setting frameIdx to 0:

Sprite.prototype.draw = function(t, x, y)
{
	var frameIdx = 0;

If the sprite is animated but does not loop, and the t value is greater than or equal to the duration of the sprite, we'll simply select the last frame:

	if(!this.loop && this.animated && t>=this.duration)
	{
		frameIdx = (this.frames.length - 1);
	}

Otherwise, if the sprite is animated and does loop, we'll calculate the current frame to show. We'll use the modulus of the t value % Sprite duration so that t is not a higher value than the sprite duration; we'll also set a temporary variable totalD to 0, which we'll use to store the time at which each frame ends:

	else if(this.animated)
	{
		t = t % this.duration;
		var totalD = 0;

We'll then loop through the frames, beginning by adding the duration (d) of the current frame to the totalD variable and setting the frameIdx value to the current frame index (i). If the value of t is less than or equal to totalD, this is the frame we'll be showing so we break out of the loop.

		for(var i in this.frames)
		{
			totalD+= this.frames[i].d;
			frameIdx = i;
			
			if(t<=totalD)
			{
				break;
			}
		}

We'll now also close the frame searching loop:

	}

We'll look and see if the current frame has an offset value to modify its calculated drawing position. If so, we'll assign it to a temporary offset variable. Otherwise, the value of this variable will be [0,0].

	var offset = (typeof this.frames[frameIdx].offset=='undefined' ?
		[0,0] : this.frames[frameIdx].offset);

After, we can draw our sprite to the Canvas. The tilset x, y, w, h properties are all taken from the current frame data, and the position at which the sprite will be drawn is the x, y arguments modified by the offset. After drawing the method is complete and we'll close it:

	ctx.drawImage(tileset,
		this.frames[frameIdx].x, this.frames[frameIdx].y,
		this.frames[frameIdx].w, this.frames[frameIdx].h,
		x + offset[0], y + offset[1],
		this.frames[frameIdx].w, this.frames[frameIdx].h);
};

Modifying our sprites

To modify our existing code to make use of this, we'll replace every instance of sprite : [...] with sprite : new Sprite([...]). We'll also remove the section of code from the window.onload event handler:

	for(x in tileTypes)
	{
		...
	}

We'll also completely remove our now defunct getFrame method.

In our drawGame method we'll also replace every ctx.drawImage(...) call with simply calling the draw method for the sprite in question with the arguments gameTime, and the x, y values from the 6th and 7th arguments we were using for the drawImage method.

As there are multiple instances and direct code replacement, please refer to the Modified Code on the next page to see where this has been done.

Page loaded in 0.014 second(s).