Есть ли лучший способ повторить показ нескольких изображений в одном месте? - PullRequest
0 голосов
/ 23 сентября 2019

Я создаю мини-игру с использованием Canvas HTML5 и пытаюсь отобразить светодиоды поверх другого изображения, в настоящее время оно работает следующим образом: Отображаемое изображение

В настоящее время изображение отображаетсяи измените по назначению, однако функция не инициализируется сразу (я предполагаю, что это происходит из-за того, что Javascript работает на одном ядре и как работает функция setInterval), код также кажется очень неуклюжим и многословным.

Есть ли лучший способ добиться зацикливания этих изображений для формирования анимации?

Я намереваюсь добавить больше анимаций, поскольку мини-игра «простаивает», и в идеале функция, контролирующая зацикливание изображений, должна быть легко нарушена.


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 голосов
/ 24 сентября 2019

Для наилучшего качества всегда рендеринг на холст с помощью обратного вызова requestAnimationFrame.Рендеринг с использованием таймеров может привести к мерцанию и / или сдвигу анимации.

setTimeout или setInterval подавляются большинством браузеров, когда страница не находится в фокусе или не видна.Также обратные вызовы не всегда вовремя

Если время важно для 1/60 секунды, используйте performance.now и аргумент времени передается обратному вызову requestAnimationFrame.Вы никогда не будете точно вовремя, так как анимация отображается только каждые 1/60 секунды (16.66666 ... мс), поэтому нарисуйте следующий кадр анимации как можно скорее после требуемого времени (см. Пример)

Из информации, которую вы дали в этом вопросе, я не смог понять, как выглядит ваша анимация, поэтому я придумал что-то в качестве примера.

Пример

  • При загрузке используется обещание, а не обратный вызовmedia
  • К изображению добавлено свойство, представляющее положение подизображений (спрайтов).Функция drawSprite использует imageName, spriteIdx и locationName для отрисовки вспомогательного изображения в месте на холсте.
  • Основным циклом рендеринга является функция 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

0 голосов
/ 24 сентября 2019

Я бы предложил сделать каждый светодиод объектом со своим состоянием (вкл / выкл).Создайте игровой объект, чтобы отслеживать состояние игры и текущее время / отметку.(Вот небольшое чтение о игровых циклах )

Не уверен насчет ваших точных требований, но вот пример того, как я бы подошел к чему-то похожему.В идеальном мире каждый requestAnimationFrame () равен 1/60 секунды ~ 60 кадров в секунду ... Смотрите приведенную выше ссылку, чтобы узнать, почему это не так, и как это исправить.

не используются изображения для светодиодов, но их можно добавить к объекту светодиодов и использовать в функции рисования.

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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...