我正在使用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);
});
}
答案 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
的回调将其渲染到画布上。使用计时器进行渲染会导致动画闪烁和/或剪切。
setTimeout
或setInterval
会被大多数浏览器限制。回调也不总是准时
如果计时对1/60秒很重要,请使用performance.now
并将time参数传递给requestAnimationFrame
回调。您将永远不会准时到达,因为动画仅每1/60秒(16.66666 ... ms)才显示一次,因此请在所需时间后尽快绘制动画的下一帧(请参见示例)
根据您在问题中提供的信息,我无法锻炼您的动画效果,因此我举了一个例子。
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>