Setting up Spritesheet

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="/teacher/images/dogSprites.png">
        </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, which will allow the computer to know the code is no longer HTML

<script>
    window.addEventListener('load', function () {
        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 within the canvas object. It allows us more functionality with the sprite image.

        // constant variables used for sprite and canvas
        const SPRITE_WIDTH = 160;
        const SPRITE_HEIGHT = 144;
        const SCALE_FACTOR = 2;
        const FRAME_LIMIT = 48;
        const FRAME_RATE = 15;

        // sets canvas properties
        canvas.width = SPRITE_WIDTH * SCALE_FACTOR;
        canvas.height = SPRITE_HEIGHT * SCALE_FACTOR;

        //more code will be placed here later
    });

</script>

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 = SPRITE_WIDTH;
                this.spriteHeight = SPRITE_HEIGHT;
                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 = FRAME_LIMIT;  // 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
                );
            }

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 = SPRITE_WIDTH;
            this.spriteHeight = SPRITE_HEIGHT;
            this.width = this.spriteWidth;
            this.height = this.spriteHeight;
            this.x = 0;
            this.y = 0;
            this.scale = 1;
            this.minFrame = 0;
            this.maxFrame = FRAME_LIMIT;
            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() {
            // increases the horizontal position within sprite
            if (this.frameX < this.maxFrame) {
                this.frameX++;
            } else {
                this.frameX = 0;  // resets the origin point to the beginning of the row after the 24th frame
            }
        }
}

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 after the Dog class

const dog = new Dog();  // This makes the game object, Dog

// Add event listener to the parent container for event delegation
const controls = document.getElementById('controls');
controls.addEventListener('click', function (event) {
    if (event.target.tagName === 'INPUT') {
        const selectedAnimation = event.target.id;
        switch (selectedAnimation) {
            case 'idle':
                dog.frameY = 0;
                break;
            case 'barking':
                dog.frameY = 1;
                break;
            case 'walking':
                dog.frameY = 2;
                break;
            default:
                break;
        }
    }
});

Calling Class Methods

All the functions have been written; all thats left is to put them together by calling them when the page loads.

Place the following code outside after previous code, but still within the onLoad function:

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);  // clears canvas from previous run 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 of frames per second
}

animate();

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

Hacks

  • 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="/teacher/images/dogSprites.png">
        </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 () {
        const canvas = document.getElementById('spriteContainer');
        const ctx = canvas.getContext('2d');
        const SPRITE_WIDTH = 160;
        const SPRITE_HEIGHT = 144;
        const SCALE_FACTOR = 2;
        const FRAME_LIMIT = 48;
        const FRAME_RATE = 15;

        canvas.width = SPRITE_WIDTH * SCALE_FACTOR;
        canvas.height = SPRITE_HEIGHT * SCALE_FACTOR;

        class Dog {
            constructor() {
                this.image = document.getElementById("dogSprite");
                this.spriteWidth = SPRITE_WIDTH;
                this.spriteHeight = SPRITE_HEIGHT;
                this.width = this.spriteWidth;
                this.height = this.spriteHeight;
                this.x = 0;
                this.y = 0;
                this.scale = 1;
                this.minFrame = 0;
                this.maxFrame = FRAME_LIMIT;
                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 < this.maxFrame) {
                    this.frameX++;
                } else {
                    this.frameX = 0;
                }
            }
        }

        const dog = new Dog();

        // Add event listener to the parent container for event delegation
        const controls = document.getElementById('controls');
        controls.addEventListener('click', function (event) {
            if (event.target.tagName === 'INPUT') {
                const selectedAnimation = event.target.id;
                switch (selectedAnimation) {
                    case 'idle':
                        dog.frameY = 0;
                        break;
                    case 'barking':
                        dog.frameY = 1;
                        break;
                    case 'walking':
                        dog.frameY = 2;
                        break;
                    default:
                        break;
                }
            }
        });

        function animate() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            dog.draw(ctx);
            dog.update();
            requestAnimationFrame(animate);
        }

        animate();
    });
</script>