有没有更好的方法可以在同一位置重复显示多张图像?

时间:2019-09-23 15:48:39

标签: javascript html html5-canvas

我正在使用Canvas HTML5创建一个Minigame,并试图在另一张图像的顶部显示LED,目前它的工作方式如下:Image displayed

当前,Image确实会按预期显示和更改,但是该函数并未立即初始化(我猜这是由于Javascript运行在一个核上以及setInterval函数的工作原理),代码似乎也很笨拙且很长wind。

是否有更好的方法来实现这些图像的循环以形成动画?

由于迷你游戏为“空闲”,我打算添加更多动画,理想情况下,应该容易破坏控制图像循环的功能。


var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');

function drawSafeBuster(imageSources, callback) {
    var images = {};
    var loadedImages = 0;
    var numImages = 0;
    // get number of images
    for (var src in imageSources) {
        numImages++;
    }
    for (var src in imageSources) {
        images[src] = new Image();
        images[src].onload = function () {
            if (++loadedImages >= numImages) {
                callback(images);
            }
        };
        images[src].src = imageSources[src];
    }
}

//Image path variables.
var imageSources = {
    ledPath: './graphics/leds_safe_dial_minigame.png'
};

drawSafeBuster(imageSources, function (images) {

    //Draw initial LED images.
    context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100);
    context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100);

    //LED Animation Loop
    var ledRepeat = setInterval(function () {
        context.fillStyle = '#999999';

        var ledRepeat1 = setInterval(function () {
            context.fillRect(850, 300, 120, 45);
            context.fillRect(1015, 300, 120, 45);
            context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100);
            context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100);
        }, 500);

        var ledRepeat2 = setInterval(function () {
            context.fillRect(850, 300, 120, 45);
            context.fillRect(1015, 300, 120, 45);
            context.drawImage(images.ledPath, 120, 0, 115, 100, 850, 300, 120, 100);
            context.drawImage(images.ledPath, 120, 0, 115, 100, 1015, 300, 120, 100);
        }, 1500);

        var ledRepeat3 = setInterval(function () {
            context.fillRect(850, 300, 120, 45);
            context.fillRect(1015, 300, 120, 45);
            context.drawImage(images.ledPath, 238, 0, 115, 100, 850, 300, 120, 100);
            context.drawImage(images.ledPath, 238, 0, 115, 100, 1015, 300, 120, 100);
        }, 2500);

        var clearInterval = setInterval(function () {

            clearInterval(ledRepeat1);
            clearInterval(ledRepeat2);
            clearInterval(ledRepeat3);
        }, 3500);

    }, 4500);

});
}

2 个答案:

答案 0 :(得分:0)

我建议使每个LED成为具有其自身状态(打开/关闭)的对象。创建一个游戏对象以跟踪游戏状态和当前时间/滴答声。 (以下是有关game loops的一些读物)

不确定您的确切要求,但是这里有一个示例说明我将如何处理类似的问题。在理想情况下,每个requestAnimationFrame()都是每秒的1/60秒〜每秒60帧...有关为什么可能不是这种情况以及如何进行纠正的信息,请参见上面的链接。

我没有将图像用于LED,但是可以将其添加到LED对象中并用于绘图功能。

let canvas, c, w, h,
  TWOPI = Math.PI * 2;

canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
w = canvas.width = 600;
h = canvas.height = 400;

let game = {
  state: "RUNNING",
  tick: 0,
  actors: []
};

//LED object.
let LED = function(x, y, hue, radius, on, toggleRate) {
  this.position = {
    x: x,
    y: y
  };
  this.hue = hue;
  this.radius = radius;
  this.on = on;
  this.toggleRate = toggleRate;
  this.update = function(tick) {
    if (tick % this.toggleRate === 0) {
      this.on = !this.on;
    }
  };
  this.draw = function(ctx) {
    ctx.beginPath();
    ctx.arc(this.position.x, this.position.y, this.radius, 0, TWOPI, false);
    ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 70 : 30}%)`;
    ctx.fill();
    ctx.beginPath();
    ctx.arc(this.position.x + this.radius / 5, this.position.y - this.radius / 5, this.radius / 3, 0, TWOPI, false);
    ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 90 : 50}%)`;
    ctx.fill();
  };
}

//create LEDs
for (let i = 0; i < 10; i++) {
  game.actors.push(
    new LED(
      100 + i * 25,
      100,
      i * 360 / 10,
      8,
      Math.random() * 1 > 0.5 ? true : false,
      Math.floor(Math.random() * 240) + 60
    )
  );
}

function update() {
  if (game.state === "RUNNING") {
    //increase game counter
    game.tick++;

    //update actors
    for (let a = 0; a < game.actors.length; a++) {
      game.actors[a].update(game.tick);
    }
  } else {
    //noop.
  }
}

function clear() {
  c.clearRect(0, 0, w, h);
}

function draw() {
  //draw all actors
  for (let a = 0; a < game.actors.length; a++) {
    game.actors[a].draw(c);
  }
}

function loop() {
  update();
  clear();
  draw();
  requestAnimationFrame(loop);
}

canvas.addEventListener('click', function() {
  if (game.state === "RUNNING") {
    game.state = "PAUSED";
  } else {
    game.state = "RUNNING";
  }
  console.log(game.state);
});

requestAnimationFrame(loop);
body {
  background: #222;
}
<!doctype html>
<html>

<head>
  <meta charset="utf-8">
</head>

<body>
  <canvas id="canvas"></canvas>
</body>

</html>

答案 1 :(得分:0)

为获得最佳质量,请始终通过requestAnimationFrame的回调将其渲染到画布上。使用计时器进行渲染会导致动画闪烁和/或剪切。

当页面不清晰或不可见时,

setTimeoutsetInterval会被大多数浏览器限制。回调也不总是准时

如果计时对1/60秒很重要,请使用performance.now并将time参数传递给requestAnimationFrame回调。您将永远不会准时到达,因为动画仅每1/60秒(16.66666 ... ms)才显示一次,因此请在所需时间后尽快绘制动画的下一帧(请参见示例)

根据您在问题中提供的信息,我无法锻炼您的动画效果,因此我举了一个例子。

示例

  • 加载媒体时使用promise而不是回调
  • 已添加图像属性,该属性表示子图像(子画面)的位置。函数drawSprite使用imageNamespriteIdxlocationName在画布上的某个位置绘制子图像。
  • 主要的渲染循环是函数renderLoop,它等待直到媒体加载完毕,然后使用timing进行动画处理。
  • 数组timing包含每个动画事件的对象。该对象具有事件的时间偏移,在该事件上要调用的函数以及传递给该函数的参数。
  • 此示例中的最后一个计时对象只是重置开始时间以重复动画。

请注意,此示例不检查浏览器是否停止了动画,并且将循环浏览所有动画以赶上。

requestAnimationFrame(renderLoop);
canvas.width = 64;
canvas.height = 16;
const ctx = canvas.getContext("2d");
var mediaLoaded = false;
const images = {};
var currentStage = 0;
var startTime;   // do not assign a value to this here or the animation may not start
loadMedia({ 
        leds: {
            src: "https://i.stack.imgur.com/g4Iev.png",
            sprites: [
                {x: 0,  y: 0,  w: 16, h: 16}, // idx 0 red off
                {x: 16, y: 0,  w: 16, h: 16}, // idx 1 red on
                {x: 0,  y: 16, w: 16, h: 16}, // idx 2 orange off
                {x: 16, y: 16, w: 16, h: 16}, // idx 3 orange on
                {x: 0,  y: 32, w: 16, h: 16}, // idx 4 green off
                {x: 16, y: 32, w: 16, h: 16}, // idx 5 green on
                {x: 0,  y: 48, w: 16, h: 16}, // idx 6 cyan off
                {x: 16, y: 48, w: 16, h: 16}, // idx 7 cyan on
            ]
        },
    }, images)
    .then(() => mediaLoaded = true);

const renderLocations = {
    a: {x: 0,  y: 0, w: 16, h: 16},
    b: {x: 16, y: 0, w: 16, h: 16},
    c: {x: 32, y: 0, w: 16, h: 16},
    d: {x: 48, y: 0, w: 16, h: 16},
};


function loadMedia(imageSources, images = {}) {
    return new Promise(allLoaded => {
        var count = 0;
        for (const [name, desc] of Object.entries(imageSources)) {
            const image = new Image;
            image.src = desc.src;
            image.addEventListener("load",() => {
                images[name] = image;
                if (desc.sprites) { image.sprites = desc.sprites }
                count --;
                if (!count) { allLoaded(images) }
            });
            count ++;
        }
    });
}

function drawSprite(imageName, spriteIdx, locName) {
    const loc = renderLocations[locName];
    const spr = images[imageName].sprites[spriteIdx];
    ctx.drawImage(images[imageName], spr.x, spr.y, spr.w, spr.h, loc.x, loc.y, loc.w, loc.h);
}
function drawLeds(sprites) {
    for(const spr of sprites) { drawSprite(...spr) }
}
function resetAnimation() {
    currentStage = 0;
    startTime += 4500;
}


const timing = [
    {time: 0,    func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 4, "c"], ["leds", 6, "d"]]]},
    {time: 500,  func: drawLeds, args: [[["leds", 1, "a"]]]},
    {time: 1500, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 3, "b"]]]},
    {time: 2000, func: drawLeds, args: [[["leds", 1, "a"]]]},
    {time: 3000, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 5, "c"], ["leds", 7, "d"]]]},
    {time: 3250, func: drawLeds, args: [[["leds", 1, "d"]]]},
    {time: 3500, func: drawLeds, args: [[["leds", 3, "d"]]]},
    {time: 3750, func: drawLeds, args: [[["leds", 5, "d"]]]},
    {time: 4000, func: drawLeds, args: [[["leds", 7, "d"]]]},
    {time: 4250, func: drawLeds, args: [[["leds", 1, "d"]]]},
    {time: 4500 - 17, func: resetAnimation, args: []},
];
   
function renderLoop(time) {
    if (mediaLoaded) {
        if (startTime === undefined) { startTime = time }
        var offsetTime = time - startTime;
        const stage = timing[currentStage];
        if(offsetTime > stage.time) {
            currentStage++;
            stage.func(...stage.args);
        }
    }
    requestAnimationFrame(renderLoop);
}
canvas {
  border:1px solid black;
}
<canvas id="canvas"></canvas>

示例中使用的图片

Color LEDs sprites On and Off