简体   繁体   中英

How do I run an animation when moving to the left or right in JavaScript?

How do I run a sprite animation when pressing the left or right arrow keys in JavaScript? Here's my code:

var avatarX = 0;
var avatarY = 240;
var avatarImage;
var counter = 1;
var XWIDTH = 0;
var WIDTH = 400;
var dx = 5;
var tt;
var gameCanvas;
var context;
var moving;
var animationCounter = 1;

window.addEventListener('keydown', KeyDown);

function setUpGame() { //This is the function that is called from the html document.

    gameCanvas = document.getElementById("gameCanvas"); //Declare a new variable & assigns it the id of the CANVAS from the html document.
    context=gameCanvas.getContext("2d");
    context.font = "18px Iceland";
    context.textBaseline = "top";

    avatarImage = new Image(); //Declaring a new variable. This is so that we can store the image at a later date.
    avatarImage.onload=function(){

    // avatarImage is now fully loaded and ready to drawImage
        context.drawImage(avatarImage, Math.random() * 100, avatarY);

    // start the timer
        tt = setInterval(function(){counTer()},1000);
        setInterval(handleTick, 25);


    }
    avatarImage.addEventListener('load', startLoop, false);
    avatarImage.src = "img/ships.png"; //Ditto from above.

}

function startLoop() {
    console.log("Detecting whether moving to the right is: " + moving);
    if(moving == 0) {
        gameLoop();
    }
}

function gameLoop() {

    setTimeout(gameLoop, 100);
    handleTick();

}

function KeyDown(evt) {
  switch (evt.keyCode) {
  case 39: /*Arrow to the right*/
      if(avatarX + dx <WIDTH && avatarX + dx >XWIDTH) {
         avatarX += dx;
         moving = 0;
      }
    break;
   case 37: /*Arrow to the left*/
      if(avatarX - dx >XWIDTH) {
         avatarX -= dx;
         moving = 1;
      } 
    break;
  }
}

function counTer() {
  if(counter == 60) {
    clearInterval(tt);
  } else {
    counter++;
  }
}

function handleTick() {
    context.clearRect(0,0,gameCanvas.width,gameCanvas.height);
    context.drawImage(avatarImage, 32*animationCounter, 0, 32,32, avatarX, avatarY, 64, 64);
    context.fillText("Seconds: " + counter, 5, 5);
    context.fillText("1 is Right, 2 is Left, 0 is idle: " + moving, 20, 20);


    animationCounter++
    if(animationCounter >1) {
        animationCounter = 0;

    }
}

There are many ways to implement animation. The one i use is pretty easy and looks something like this:

var player = {
    x: 0,
    y: 0,
    width: 50,
    height: 100,
    sprite: {
        column: 0,
        row: 0,
        height: 50,
        width: 100
    },
    image: PlayerImage,
    animation: {
        active: true, //Determines if the animation is running or not.
        counter: 0,
        progress: 0,
        sequence: []
    }
}

//This functions fires when the left arrow is pressed.
var onLeft = function(){
    player.animation.sequence = [{column: 0, row: 0, t: 12}, {column: 1, row: 0, t: 12}, {column: 2, row: 0, t: 12}];
    player.animation.active = true;
}

//This functions fires when the right arrow is pressed.
var onRight = function(){
    player.animation.sequence = [{column: 0, row: 1, t: 12}, {column: 1, row: 1, t: 12}, {column: 2, row: 1, t: 12}];
    player.animation.active = true;
}

//This function fires when no arrow are pressed. 
var standingStill = function(){
    player.animation.active = false;
}

var handleTick = function(){

    //Clear the canvas.
    context.canvas.width = context.canvas.width;

    //If the animation is active.
    if(player.animation.active){

        //If the counter >= tick in the sequence. If not, just keep on increasing the counter value.
        if(player.animation.counter >= player.animation.sequence[player.animation.progress]){
            player.animation.counter = 1;

            if(player.animation.progress >= player.animation.sequence.length - 1){
                player.animation.progress = 0;
            }else{
                player.animation.progress++;
            }

            var currentFrame = player.animation.sequence[player.animation.progress];

            //Change player.sprite column and row.(new frame)
            player.sprite.column = currentFrame.column;
            player.sprite.row = currentFrame.row;
        }else{
            player.animation.counter++;
        }

    }

    context.drawImage(player.image, player.sprite.column * player.sprite.width, player.sprite.row * player.sprite.height, player.sprite.width, player.sprite.height, player.x, player.y, player.width, player.height)

}

The sequence is an array that contains objects that look like that: {column: 0, row: 0, t: 12} Every object is a frame. The column value is the current x of the sprite, the row is the y of the sprite. The script automatically creates the x and the y value, so all you need to add is value like 0, 1, 3, 5, and so on.(Just which frame it is on either x or y axis.) So for example, if the column is 0 and the row is 0, this is the frame that is in the top-left corner(the first frame). The t value stands for tick, this determines how many ticks there has to happend before the animation goes to the next frame.

Player.sprite also has properties width and height , that's the width and the height of the frame, it is often the same value as the width and height of the player object.

You have to make your own player.animation.sequence in onLeft and onRight function so it animates how you want it to animate.

I hope you understood what i meant. This might seem complicated, but it really isn't and if you don't get it now, don't worry, you'll get it eventually. If you really have difficulties with understanding, just ask.


Edit: First of all i highly recommend using objects to store information about entities. It makes game making much easier and cleaner. Just look at the player object, it's much easier to get the x simply by writing player.x than PlayerX . It looks more professional too. :) And when you have to give a lot of information about your entity you don't have to pass many arguments, you pass the whole object. Example:

//Entity properties stored in separate variable.
function animate(EntityX, EntityY, EntitySpriteX, EntitySpriteY, ...){
    var x = EntityX;
    //And so on...  
}

//Entity stored in an object.
function animate(Entity){
    var x = Entity.x;
    //And so on...
}

Anyway, back to your question. First, we have to add variables that store information about our sprite.

var avatarSpriteColumn = 0; //Sprite frame on the x axis.
var avatarSpriteRow = 0; //Sprite frame on the y axis.
var avatarSpriteWidth = 50; //The width of a frame.
var avatarSpriteHeight = 100; //The height of a frame.

We also have to add variables that store the information about the animation.

var animationActive = false; //A variable that controls if the animation is 'running'.
var animationCounter = 0; //How many frames(ticks) have passed since the last frame(animation frame) has changed. (I'm not good at describing variables. :P)
var animationProgress = 0; //Current animation frame.
var animationSequence = []; //Array that stores the sequence of animation, as i explained.

Then, in your handleTick function you have to add a code that will animate the sprite. You have to add this code before you draw your entity.

//If the animation is active.
if(animationActive){

    //If the counter >= tick in the sequence. If not, just keep on increasing the counter value.
    if(animationCounter >= animationSequence[animationProgress]){
        animationCounter = 1;

        //Reset the progress, so that next time another animation frame shows up.
        if(animationProgress >= animationSequence.length - 1){
            animationProgress = 0;
        }else{
            animationProgress++;
        }

        //Select information about the current animation frame and store it in a variable so it is easier to access.
        var currentFrame = animationSequence[animationProgress];

        //Change player.sprite column and row.(new frame);
        avatarSpriteColumn = currentFrame.column;
        avatarSpriteRow = currentFrame.row;
    }else{
        animationCounter.counter++;
    }

}

Ok, now you have a code that animates the sprite, but how do we run it? Well, i see you have a variable called moving . It tells us in what direction the player is moving and if it is moving at all. It looks like you implemented it a little bit wrong. Right now your function that operates the keys looks like this:

function KeyDown(evt) {
  switch (evt.keyCode) {
  case 39: /*Arrow to the right*/
      if(avatarX + dx <WIDTH && avatarX + dx >XWIDTH) {
         avatarX += dx;
         moving = 0;
      }
    break;
   case 37: /*Arrow to the left*/
      if(avatarX - dx >XWIDTH) {
         avatarX -= dx;
         moving = 1;
      } 
    break;
  }
}

The variable is supposed to return 1 if the entity is going to the right, 2 if it is going to the left and 0 if the entity is standing still, right? Right now it shows 0 when the entity is moving to the right and 1 when it is moving to the left. It also doesn't show us if the entity is idle. We have to fix it. Change it to something like this:

function KeyDown(evt) {
    switch (evt.keyCode) {
        case 39: /*Arrow to the right*/
            if(avatarX + dx <WIDTH && avatarX + dx >XWIDTH) {
                avatarX += dx;
                moving = 1;
            }
        break;
        case 37: /*Arrow to the left*/
            if(avatarX - dx >XWIDTH) {
                avatarX -= dx;
                moving = 2;
            } 
        break;
        default:
            moving = 0;
    }
}

Ok, now we have to add this code to the handleTick function. This code starts the animation and changes the sequence.:

if(moving == 1){ //Moving in the right direction.
    animationSequence = []; //Animation of moving in the right direction. Change the sequence to your own.
    animationActive = true; //Run the animation.
}else if(moving == 2){ //Moving to the left.
    animationSequence = []; //Animation of moving to the left. Change the sequence to your own.
    animationActive = true; //Run the animation.
}else{
    animationActive = false; //Stops the animation, but the last frame stays.

    /*

    Alternatively, if you want a separate frame or animation that is animating when the entity is standing, you run this code.

    animationSequence = []; // Your sequence. If you want a single frame, with no animation just add one frame to the sequence.
    animationActive = true;

    */
}

Now, the last thing we have to do is to draw the entity. In your case, this will look something like this:

context.drawImage(avatarImage, avatarSpriteColumn * avatarSpriteWidth, avatarSpriteRow * avatarSpriteHeight, avatarWidth, avatarHeight, avatarX, avatarY, 64, 64);

In the end your whole code will look something like this:

var avatarX = 0;
var avatarY = 240;
var avatarImage;
var counter = 1;
var XWIDTH = 0;
var WIDTH = 400;
var dx = 5;
var tt;
var gameCanvas;
var context;
var moving;
var animationCounter = 1;

var avatarSpriteColumn = 0; //Sprite frame on the x axis.
var avatarSpriteRow = 0; //Sprite frame on the y axis.
var avatarSpriteWidth = 50; //The width of a frame.
var avatarSpriteHeight = 100; //The height of a frame.

var animationActive = false; //A variable that controls if the animation is 'running'.
var animationCounter = 0; //How many frames(ticks) have passed since the last frame(animation frame) has changed. (I'm not good at describing variables. :P)
var animationProgress = 0; //Current animation frame.
var animationSequence = []; //Array that stores the sequence of animation, as i explained.

window.addEventListener('keydown', KeyDown);

function setUpGame() { //This is the function that is called from the html document.

    gameCanvas = document.getElementById("gameCanvas"); //Declare a new variable & assigns it the id of the CANVAS from the html document.
    context=gameCanvas.getContext("2d");
    context.font = "18px Iceland";
    context.textBaseline = "top";

    avatarImage = new Image(); //Declaring a new variable. This is so that we can store the image at a later date.
    avatarImage.onload=function(){

    // avatarImage is now fully loaded and ready to drawImage
        context.drawImage(avatarImage, Math.random() * 100, avatarY);

    // start the timer
        tt = setInterval(function(){counTer()},1000);
        setInterval(handleTick, 25);


    }
    avatarImage.addEventListener('load', startLoop, false);
    avatarImage.src = "img/ships.png"; //Ditto from above.

}

function startLoop() {
    console.log("Detecting whether moving to the right is: " + moving);
    if(moving == 0) {
        gameLoop();
    }
}

function gameLoop() {

    setTimeout(gameLoop, 100);
    handleTick();

}

function KeyDown(evt) {
    switch (evt.keyCode) {
        case 39: /*Arrow to the right*/
            if(avatarX + dx <WIDTH && avatarX + dx >XWIDTH) {
                avatarX += dx;
                moving = 1;
            }
        break;
        case 37: /*Arrow to the left*/
            if(avatarX - dx >XWIDTH) {
                avatarX -= dx;
                moving = 2;
            } 
        break;
        default:
            moving = 0;
    }
}

function counTer() {
  if(counter == 60) {
    clearInterval(tt);
  } else {
    counter++;
  }
}

function handleTick() {
    context.clearRect(0,0,gameCanvas.width,gameCanvas.height);

    if(moving == 1){ //Moving in the right direction.
        animationSequence = []; //Animation of moving in the right direction. Change the sequence to your own.
        animationActive = true; //Run the animation.
    }else if(moving == 2){ //Moving to the left.
        animationSequence = []; //Animation of moving to the left. Change the sequence to your own.
        animationActive = true; //Run the animation.
    }else{
        animationActive = false; //Stops the animation, but the last frame stays.

        /*

        Alternatively, if you want a separate frame or animation that is animating when the entity is standing, you run this code.

        animationSequence = []; // Your sequence. If you want a single frame, with no animation just add one frame to the sequence.
        animationActive = true;

        */
    }

    //If the animation is active.
    if(animationActive){

        //If the counter >= tick in the sequence. If not, just keep on increasing the counter value.
        if(animationCounter >= animationSequence[animationProgress]){
            animationCounter = 1;

            //Reset the progress, so that next time another animation frame shows up.
            if(animationProgress >= animationSequence.length - 1){
                animationProgress = 0;
            }else{
                animationProgress++;
            }

            //Select information about the current animation frame and store it in a variable so it is easier to access.
            var currentFrame = animationSequence[animationProgress];

            //Change player.sprite column and row.(new frame);
            avatarSpriteColumn = currentFrame.column;
            avatarSpriteRow = currentFrame.row;
        }else{
            animationCounter.counter++;
        }

    }

    context.drawImage(avatarImage, avatarSpriteColumn * avatarSpriteWidth, avatarSpriteRow * avatarSpriteHeight, avatarWidth, avatarHeight, avatarX, avatarY, 64, 64);

    context.fillText("Seconds: " + counter, 5, 5);
    context.fillText("1 is Right, 2 is Left, 0 is idle: " + moving, 20, 20);

}

The only thing you have to do right now is to make your own animationSequences and check if it works, let me know if you have any problems with that.

Of course, the code i am using is more complicated and has more "abilities" and is easier to use(the code behind is more complicated), but hopefully this will help you.

I must also apoligize for making this thing seem so complicated, when it's not. I am bad at explaining.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM