【问题标题】:How do I run an animation when moving to the left or right in JavaScript?在 JavaScript 中向左或向右移动时如何运行动画?
【发布时间】:2014-01-12 20:08:39
【问题描述】:

如何在 JavaScript 中按向左或向右箭头键时运行精灵动画?这是我的代码:

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;

    }
}

【问题讨论】:

  • 不是我的想法。只想使用 JavaScript 而不是任何插件。
  • 那么,您遇到了什么问题?有一个问题和一堆代码,但问题似乎与代码无关。
  • 如果你能看看 Doozerman 的回答,你就会明白我想要做什么。

标签: javascript html animation sprite


【解决方案1】:

有很多方法可以实现动画。我用的很简单,看起来像这样:

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)

}

序列是一个数组,其中包含如下所示的对象:{column: 0, row: 0, t: 12} 每个对象都是一个框架。 column 值是精灵的当前xrow 是精灵的y。该脚本会自动创建 xy 值,因此您只需添加 0、1、3、5 等值。(只是它在 x 或 y 轴上的哪个帧。)因此,例如,如果列为 0,行为 0,则这是位于左上角的帧(第一帧)。 t 值代表tick,这决定了动画进入下一帧之前必须发生多少tick。

Player.sprite也有属性widthheight,即帧的宽度和高度,通常与播放器对象的宽度和高度相同。

您必须在 onLeftonRight 函数中创建自己的 player.animation.sequence,以便它按照您希望的方式制作动画。

我希望你明白我的意思。这可能看起来很复杂,但实际上并非如此,如果你现在不明白,别担心,你最终会明白的。如果你真的有理解上的困难,就问吧。


编辑:首先我强烈建议使用对象来存储有关实体的信息。它使游戏制作更容易和更清洁。看看player 对象,简单地写player.xPlayerX 更容易得到x。它看起来也更专业。 :) 当您必须提供有关您的实体的大量信息时,您不必传递很多参数,而是传递整个对象。 示例:

//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...
}

不管怎样,回到你的问题。首先,我们必须添加存储精灵信息的变量。

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.

然后,在您的handleTick 函数中,您必须添加一个代码来为精灵设置动画。您必须在绘制实体之前添加此代码。

//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++;
    }

}

好的,现在你有一个动画精灵的代码,但是我们如何运行它呢?好吧,我看到你有一个名为moving 的变量。它告诉我们玩家正在向哪个方向移动以及它是否正在移动。看起来你实现它有点错误。现在你操作按键的函数如下所示:

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;
  }
}

如果实体向右移动,变量应该返回 1,如果向左移动,则返回 2,如果实体静止不动,则返回 0,对吗?现在它在实体向右移动时显示 0,在向左移动时显示 1。如果实体处于空闲状态,它也不会向我们显示。我们必须修复它。把它改成这样:

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;
    }
}

好的,现在我们必须将这段代码添加到 handleTick 函数中。此代码启动动画并更改序列。:

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;

    */
}

现在,我们要做的最后一件事是绘制实体。在你的情况下,这看起来像这样:

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

最后你的整个代码看起来像这样:

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);

}

您现在唯一要做的就是创建自己的animationSequences 并检查它是否有效,如果您有任何问题,请告诉我。

当然,我使用的代码更复杂,有更多的“能力”,更容易使用(后面的代码更复杂),但希望对你有所帮助。

我还必须为让这件事看起来如此复杂而道歉,而事实并非如此。我不擅长解释。

【讨论】:

  • 很好的答案,但是您将如何使用我在上述问题中的代码来做到这一点?谢谢你的帮助。没想到我会在假期里得到答案。祝你圣诞快乐,新年快乐。
  • @Formula1 也祝你圣诞快乐,新年快乐,看看我的回答。我对其进行了编辑,并添加了更多解释和代码。也许这会对你有所帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-14
  • 2015-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-01
相关资源
最近更新 更多