Animated characters, objects, or backgrounds have been an essential part of videogames. Many iconic games, such as Super Mario Bros and Sonic the Hedgehog, have used sprite animation to bring their characters to life, and make their products more engaging and visually appealing. This blog is a brief exploration of how javascript can be used to create your own simple animations using sprites. We’ll also talk about how your animations can be made more dynamic, and adapt to user inputs
Spritesheet
Animation is achieved by quickly sequencing through a series of still images, which when put together, allow the images to appear as if they are moving
In order to create the animation, you need to find an image which contains each individual frame side by side (like shown below) also known as a spritesheet. The code will then sequence through each frame to create the animation.
Below is an example spritesheet, showing 4 different kinds of animation
Deer by Calciumtrice, usable under Creative Commons Attribution 3.0 license.
A good place to find sprite sheets is OpenGameArt, which provides free open source assets which can be used by anyone. Be sure to always credit the sources of images and other creative property (or: make your own, which eliminates the need to credit!)
This blog will go through the steps by using this spritesheet as a demonstration:
Download the image into your files. Then, create an image folder in the _notebooks directory and save this image in that folder.
Base HTML
Create the webpage layout of your animation, including the div in which each image is generated. Below is HTML for a very basic structure which can be used to test your sprites:
<body>
<div>
<canvas id="spriteContainer"> <!-- Within the base div is a canvas. An HTML canvas is used only for graphics. It allows the user to access some basic functions related to the image created on the canvas (including animation) -->
<img id ="dogSprite" src="images/carSprites.png"> ## link the image with the src tag, and the directory of the sprite sheet in the quotation marks
</canvas>
<div id="controls"> <!--basic radio buttons which can be used to check whether each individual animaiton works -->
<input type="radio" name="animation" id="idle" checked>
<label for="idle">Idle</label><br>
<input type="radio" name="animation" id="barking">
<label for="barking"> Barking</label><br>
<input type="radio" name="animation" id="walking">
<label for="walking"> Walking</label><br>
</div>
</div>
</body>
Setting up Javascript
Set up the main function, which will contain the object class for the animation, including the parameters of the object, and the different methods of what the object can do.
For most cases, it is simplest to have the function called when the page is loaded, which would ensure the code works every time someone opens the game. We will also set a few variables, which are further explained in the code comments.
Since we are switching over to Javascript, be sure to put all code within the <”script”> tag (w/out quotations), which will allow the computer to know the code is no longer HTML
window.addEventListener('load', function(){
sheet_row = 0 // this variable sets which animation is being played from the sprite sheet; we'll explore this later
const canvas = document.getElementById('spriteContainer'); // sets the canvas as a variable by calling the canvas element from the HTML code, using the id we set
const ctx = canvas.getContext('2d'); // the getContext function is a given function with the canvas element. It allows us more functionality with the sprite image
canvas.width = 160 * 2; // see below
canvas.height = 144 * 2;
//more code will be placed here later
});
NOTE: the particular sprite sheet given for this animation has sprites that are 160 by 144 pixels. Setting the width and height as a variable allows us to change the scale easily. When using other sprite sheets, be sure to find out what the dimensions of the sprite are
Using Object Oriented Programming
Object Oriented Programming is a model in programming which creates ‘objects’. Specific data or parameters can then be assigned to the object in classes. The classes are essentially ‘blueprints’, which can be used to create more instances of the same object. This makes it easier to reuse and keep track of data.
We are going to create a class for our sprite. Each class can have different methods, which are functions assigned to each class. Our sprite object will have 3 methods; constructor method, draw method, and update method
The constructor method sets the values which will be assigned to this class. The values assigned are explained in the code’s comments
We are also creating a draw method, in order to ‘draw’ the image on the canvas created earlier. The parameters given to the method are the same we set in the constructor method
place this class structure within the onLoad function we created earlier
class Dog {
constructor(){ // constructor method for outlining data points about the sprite
this.image = document.getElementById("dogSprite")
this.spriteWidth = 160; // place the sprite width and height here
this.spriteHeight = 144;
this.width = this.spriteWidth; //takes sprite dimensions and accounts for it in image generation
this.height = this.spriteHeight;
this.x = 0; //starts image generation in coordinate (0,0) px on sprite sheet
this.y = 0;
this.scale = 1; //scale of image
this.minFrame = 0;
this.maxFrame = 24; // how many sprites there are in a row
this.frameX = 0; // sets position of sprite that is being generated. There are the two parameters we will be changing in order to crete the animation.
this.frameY = 0;
}
draw(context){
context.drawImage(this.image, this.frameX * this.spriteWidth, this.frameY * this.spriteHeight, this.spriteWidth, this.spriteHeight, this.x, this.y, this.width * this.scale, this.height * this.scale);
}
// more code will placed here later
};
How the animation works
As mentioned above, the sprite sheet contains sprites placed side by side at even intervals. The code we write will generate an image with the dimensions 160 by 144 pixels, since that is the size of an individual sprite. The first image generated starts at the origin point (0,0) px. The update method (not written yet) will then shift the origin point over to the right by 160 pixels, so that the next image generated is the 2nd frame.
Writing the Update Method
class Dog {
constructor(){
this.image = document.getElementById("dogSprite")
this.spriteWidth = 160;
this.spriteHeight = 144;
this.width = this.spriteWidth;
this.height = this.spriteHeight;
this.x = 0;
this.y = 0;
this.scale = 1;
this.minFrame = 0;
this.maxFrame = 24;
this.frameX = 0;
this.frameY = 0;
}
draw(context){
context.drawImage(this.image, this.frameX * this.spriteWidth, this.frameY * this.spriteHeight, this.spriteWidth, this.spriteHeight, this.x, this.y, this.width * this.scale, this.height * this.scale);
}
update(){
if (this.frameX < 24) this.frameX++ // increases the horizontal position of the origin point by sprite width
else this.frameX = 0; // resets the origin point to the beginning of the row after the 24th frame, creates a loop
//more code will be placed here later
}
};
NOTE: This code only plays one of the three animations. In other words only one row of the sprite sheet is being played. In order to have the animation change, we will need to code user inputs into the update method.
Making Your Animation User Responsive
In order to make our animations adaptive to user inputs, we can use eventListeners to detect when a user clicks on a different button. While we are using radio buttons in this example, eventListeners can be set to detect other user inputs, such as a mouse movement or keystroke, or even an in-game event like losing the game.
These eventListeners will then trigger a certain function which changes the animation on the screen.
The parameter this.frameY is what controls which animation is being played, but we can change its value through eventListeners. ‘Idle’ would set this.frameY equal to 0, which would play the first row of animation on the spreadsheet (remember: python starts it’s index from 0). The “bark” button would play the second row by setting this.frameY equal to 1, and so on.
Place the following code in the update method
const idle = document.getElementById('idle');
idle.addEventListener('click', function(){
dog.frameY = 0;
});
const barking = document.getElementById('barking');
barking.addEventListener('click', function(){
dog.frameY = 1;
});
const walking = document.getElementById('walking');
walking.addEventListener('click', function(){
dog.frameY = 2;
});
Calling Class Methods
All the functions have been written; all thats left is to call them when the page loads.
Place the following code outside of the class framework, but still within the onLoad function:
const dog = new Dog(); // creates new instance of object with class Dog
function animate(){
ctx.clearRect(0, 0, canvas.width, canvas.height); // clears canvas of previous frame before generating new frame
dog.draw(ctx); // draw method from Dog class: generates image
dog.update(); // update method from Dog class: moves origin point for the next frame drawn
requestAnimationFrame(animate); // built in function; lets the browser know you want an animation to be displayed; sets the rate at the usual animation rate of 24 frames per second
}
animate(); // calling animate function
Put It all Together
Create a new HTML file and place all the code together! The animation should run similar to the example show below
As an extra challenge:
-
Try and see if you can get the animation to respond to different user inputs (other then the radio buttons)
-
Or; get the dog sprite to actually move across the screen during the walk animation using translations
your code should end up looking like the example below
%%html
<body>
<div >
<canvas id="spriteContainer"> <!-- Within the base div is a canvas. An HTML canvas is used only for graphics. It allows the user to access some basic functions related to the image created on the canvas (including animation) -->
<img id ="dogSprite" src="">
</canvas>
<div id="controls"> <!--basic radio buttons which can be used to check whether each individual animaiton works -->
<input type="radio" name="animation" id="idle" checked>
<label for="idle">Idle</label><br>
<input type="radio" name="animation" id="barking">
<label for="barking"> Barking</label><br>
<input type="radio" name="animation" id="walking">
<label for="walking"> Walking</label><br>
</div>
</div>
</body>
<script>
window.addEventListener('load', function(){
sheet_row = 0
const canvas = document.getElementById('spriteContainer');
const ctx = canvas.getContext('2d');
canvas.width = 160 * 2;
canvas.height = 144 * 2;
class Dog {
constructor(){
this.image = document.getElementById("dogSprite")
this.spriteWidth = 160;
this.spriteHeight = 144;
this.width = this.spriteWidth;
this.height = this.spriteHeight;
this.x = 0;
this.y = 0;
this.scale = 1;
this.minFrame = 0;
this.maxFrame = 48;
this.frameX = 0;
this.frameY = 0;
}
draw(context){
context.drawImage(this.image, this.frameX * this.spriteWidth, this.frameY * this.spriteHeight, this.spriteWidth, this.spriteHeight, this.x, this.y, this.width * this.scale, this.height * this.scale);
}
update(){
if (this.frameX < 47) this.frameX++
else this.frameX = 0;
const idle = document.getElementById('idle');
idle.addEventListener('click', function(){
dog.frameY = 0;
});
const barking = document.getElementById('barking');
barking.addEventListener('click', function(){
dog.frameY = 1;
console.log('hello');
});
const walking = document.getElementById('walking');
walking.addEventListener('click', function(){
dog.frameY = 2;
});
};
};
const dog = new Dog(); // creates new instance of object with class Dog
function animate(){
ctx.clearRect(0, 0, canvas.width, canvas.height); // clears canvas of previous frame before generating new frame
dog.draw(ctx); // draw method from Dog class: generates image
dog.update(); // update method from Dog class: moves origin point for the next frame drawn
requestAnimationFrame(animate); // built in function; lets the browser know you want an animation to be displayed; sets the rate at the usual animation rate of 24 frames per second
}
animate();
});
</script>