Animating sprites using TypeScript

I've been tackling the development of a small Mario game lately.

However, I'm facing some difficulty when it comes to animating sprites. For instance, I have a mario.gif file featuring running Mario (although he's not actually running in the gif).

Check out the Mario image here.

The image size is 60 x 20 pixels. Here's my current code snippet:

class Character {

    public y_: number;
    public x_: number;
    public nFrames: number = 30;

    constructor(public _x: number, public _y: number) {
        this._x = _x;
        this._y = _y;
    };

 sprite: HTMLImageElement;

    setSpriteUrl(input: string) : void {
        this.sprite = new Image();
        this.sprite.src = input;
    }

drawSprite(): void {
        ctx.save();
        ctx.beginPath();
        ctx.drawImage(this.sprite, 0, 0, 15, 20, this._x, this._y, 20, 20);
            ctx.restore;
      }
}

Then follows this part:

var mario = new Character(40, 50);
mario.setSpriteUrl("graphics/mario/small/Running-mario.gif");

The picture width is 60 pixels with 4 running images of Mario. The height remains 20 pixels.
Hence, 60/4 = 15.

ctx.drawImage(this.sprite, 0, 0, 15, 20, this._x, this._y, 20, 20);

This makes me believe that I could advance from 15 to 30 to select the next stage of Mario running. However, instead of that, it displays 2 running Marios from the same image.
How does this functionality work? How can every running phase of Mario be selected?


Once that's resolved, should the sprite be animated using a for loop and timer? It doesn't seem like the most optimal approach to me, especially considering there are more sprites than just Mario's running animation.

Answer №1

The definition of drawImage function is as follows:

ctx.drawImage(image, dx, dy)
ctx.drawImage(image, dx, dy, dWidth, dHeight)
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy)
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

In the above context, (sx, sy) represent the starting position of the slice within the source image to begin copying from. The dimensions of this slice are given by (sWidth x sHeight).

Given that the sprite frames are arranged horizontally, the value of sx needs to be incremented in order to draw subsequent frames.

class Character {

    frameWidth: number = 15;
    frameHeight: number = 20;

    constructor(
        public x: number,
        public y: number) { }

    sprite: HTMLImageElement;

    setSpriteUrl(input: string) : void {
        this.sprite = new Image();
        this.sprite.src = input;
    }

    drawSprite(frameIndex: number): void {
        ctx.save();
        ctx.beginPath();
        ctx.drawImage(this.sprite,
            frameIndex * this.frameWidth, 0,   // Beginning of slice
            this.frameWidth, this.frameHeight, // Dimensions of slice
            this.x, this.y);                   // Destination coordinates
        ctx.restore();
    }
}

Answer №2

    class Character {

    frameWidth: number;
    frameHeight: number; 
    tickCount: number;
    ticksPerFrame: number = 1;
    frameIndex: number;
    jump: boolean;

    constructor(public position: Vector, public numberOfFrames : number) {}

    sprite: HTMLImageElement;


    setSpriteUrl(input: string) : void {
        this.sprite = new Image();
        this.sprite.src = input;
    }

    addGravity(): void {

        this.position.y += downForce;
        if (this.position.y >= 415)
            this.position.y = 415;
    }

    drawSprite(): void {

        this.tickCount = this.ticksPerFrame;

        if (this.tickCount >= this.ticksPerFrame) {
            this.tickCount = 0;
            if (this.frameIndex < this.numberOfFrames - 1) {
                this.frameIndex += 1;
            } else {
                this.frameIndex = 0;
            }
        }

        this.frameHeight = this.sprite.height;
        this.frameWidth = this.sprite.width / this.numberOfFrames;

        this.position.setWidth(this.frameWidth);
        this.position.getHeight(this.frameHeight);
        ctx.drawImage(this.sprite,
            this.frameIndex * this.frameWidth, 0,   // Start of slice
            this.frameWidth, this.frameHeight, // Size of slice
            this.position.x, this.position.y, 15, 20);
    }

}

Utilizing the sprite's this.sprite.height and this.sprite.width allows for dynamic sizing. This flexibility enables the loading of various sprites.

Example with Mario standing:

var mario = new Character(new Vector(40,50), 4);
mario.setSpriteUrl("graphics/mario/small/Standing-mario.gif");
mario.numberOfFrames = 1;

In this instance, numberOfFrames is set to 1 since the standing Mario gif consists of only one image.

However, when Mario is in motion:

function keyboardInput(event: KeyboardEvent) {


switch (event.keyCode) {
    case 65: case 37: //a
        mario.setSpriteUrl("graphics/mario/small/Running-mario-left.gif");
        mario.numberOfFrames = 4;
        mario.position.x -= 10;
        break;

    case 38: case 87: //w
        mario.numberOfFrames = 1;
        mario.setSpriteUrl("graphics/mario/small/Jumping-mario.gif");
        if(mario.position.y < 415) {
            return false;
        }
        mario.position.y -= 30;
        break;
    case 39: case 68: //d
        mario.setSpriteUrl("graphics/mario/small/Running-mario.gif");
        mario.numberOfFrames = 4;
        mario.position.x += 10;
        break;
    case 40: case 83: //s
        mario.position.y += 20;
        break;
    case 32: //space
        break;
    default:
        mario.setSpriteUrl("graphics/mario/small/Standing-mario.gif");
        mario.numberOfFrames = 1;
        break;      
}

}

For running Mario, a different number of frames are utilized to accommodate the four images within the running Mario gif.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips on how to transform an <input type="text"> element into a <tr> element using jquery

I am currently working on updating a row using jQuery Ajax. Whenever the Edit button is clicked, it switches the <tr> element to <input type='text'>. I have utilized the .Change method which triggers successfully, but my goal is to r ...

Is it possible to specify broad keys of a defined object in TypeScript using TypeScript's typing system?

const obj: {[key: string]: string} = {foo: 'x', bar: 'y'}; type ObjType = keyof typeof obj; Is there a way to restrict ObjType to only accept values "foo" or "bar" without changing the type of obj? ...

Executing the Npm audit fix --force command will automatically implement its own suggestions

Despite coming across countless similar questions, none of them have been helpful in addressing my issue. I am working on resolving critical vulnerabilities. I have executed npm update, npm audit fix, and npm audit fix --force multiple times, but the prob ...

Using JSON data in an ArrayBuffer with TypeScript

I am currently struggling with converting the received ArrayBuffer data from a server via Websocket into another format. Below is the WebSocket code snippet: let ws = new WebSocket('wss://api.example.com/websocket'); ws.binaryType = 'arrayb ...

Understanding how to implement action logic in React Redux to control visibility of specific categories

Seeking guidance on how to implement action logic for displaying and hiding elements based on user interaction. Currently, all categories and subcategories are shown at once, but I would like them to be displayed only when a user clicks on them. When a use ...

Error: EPERM -4048 - the system is unable to perform the operation specified

Every time I attempt to install a package using npm install, I encounter an error message (usually referring to a different path). I have attempted running npm cache clean, restarting my computer, and checking for any processes that may be interfering with ...

obtain a jQuery element from a function

Can a function return a $ object, or does it need to be converted to a string before being returned? function returnObj() { obj = $("<img id='' src='' />"); return obj; } var obj = returnObj(); alert(obj); //returns [obj ...

Executing a single Function within the UseEffect Hook

Can anyone assist me with solving this code puzzle? I have a carousel element that includes icons for moving to the previous and next slides. Whenever these icons are clicked, a specific function needs to be triggered within the useEffect() hook. The spec ...

Encountering a ReferrenceError when utilizing jQuery with TypeScript

After transitioning from using JavaScript to TypeScript, I found myself reluctant to abandon jQuery. In my search for guidance on how to integrate the two, I came across several informative websites. Working with Visual Studio 2012, here is my initial atte ...

What techniques can be used to optimize Angular template integration and minimize http requests?

Situation: Our team is currently in the process of migrating an ASP.NET application from traditional WebForms to Web API + Angular. The application is primarily used in regions with limited internet connectivity, where latency issues overshadow bandwidth c ...

Utilizing pushState following a seamless ajax retrieval operation

I'm facing some challenges while trying to implement the pushState method. Despite triggering the history.pushState line, I'm unable to achieve the desired change in the browser window URL. Strangely, there are no error messages displayed in the ...

How can I dynamically load a 3D model (in JSON format) at the current location of the mouse using Three.JS?

I'm currently working on a feature that involves loading a 3D model based on the mouse's position. Utilizing jQuery drag and drop functionality for this purpose has helped me load the model onto the canvas successfully, but I'm facing issues ...

"Enhance your software with a customizable interface or develop new functionalities to generate analogous

Having API data with a similar structure, I am looking to streamline my code by filtering it through a function. However, as someone new to TypeScript, I am struggling to implement this correctly using a function and an interface. Essentially, I aim to ach ...

Trigger a Vue method using jQuery when a click event occurs

I'm attempting to attach a click event to an existing DOM element. <div class="logMe" data-log-id="{{ data.log }}"></div> ... <div id="events"></div> Struggling to access external Vue methods within my jQuery click handler. A ...

Is there a maximum size limit for the Fabric.js Path Array?

Has anyone tried plotting a line graph using Fabric.js and encountered issues with the fabric.Path? I've noticed that it stops drawing after 8 segments even though I have attempted different methods like loops and individually coding each segment. co ...

Webpack attempting to load build.js from a nested folder

I am in the process of setting up a Vue app with Vuetify and Vue Router. Everything works fine when I load the base URL "/", and I can easily navigate to /manage/products. However, if I try to directly access /manage/products by typing it into the address ...

Error in Internet Explorer 8 - the object does not support this property or method

My ExtJS custom "treecombo" works perfectly in Firefox and Chrome, but encounters issues in Internet Explorer. Specifically, I am facing problems with IE8 when trying to run the following code: Ext.define('MyApp.plugins.TreeCombo', { extend: & ...

Inject a heavy dose of Vue into your project

**I'm trying to implement the provide/inject logic in Vue. In my 'App.vue' component, I have defined the 'firstName' input as a string "John", and I want to display this value when the child component 'Step1' is created. ...

Obtaining the jqXHR object from a script request loaded within the <head> tag using the <script> tag

Is it possible to retrieve the jqXHR object when a script loaded from a script tag within the head tag? function loadScript(url){ const script = document.createElement("script"); script.src = url; // This line will load the script in the ...

Add the word "String" in front of the moment timestamp

In my project, I have used React along with Material UI to show the message Running check... when the clicked state is set to true. If it's not true, then I am displaying a timestamp by utilizing the react-moment library. <Typography gutterBottom ...