Canvas迷宫游戏流畅动画
Canvas maze game smooth animation
我正在根据 this 教程构建一个迷宫游戏。只要您按住箭头键,我就成功地让播放器矩形继续移动。当你第一次开始游戏时,动画真的很好而且很快,但似乎玩游戏几秒钟后动画变得越来越慢。谁能帮我弄清楚为什么会这样?
我已经创建了一个代码片段,但不幸的是,由于我使用的迷宫图像导致的跨源错误,它无法正常工作。
var canvas;
var ctx;
var dx = 2;
var dy = 2;
var WIDTH = 482;
var HEIGHT = 482;
//movement
var x = 200,
y = 5,
staticX = 200,//this should be the same as x (used for resetting the game)
staticY = 5,//this should be the same as y (used for resetting the game)
keys = [];
var img = new Image();
var collision = 0;
var showingWinScreen = false;
var playerSize = 15;
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
FPS = 1000/60; // ideal frame rate
function rect(x,y,w,h) {
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.closePath();
ctx.fill();
}
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
function drawMaze() {
ctx.drawImage(img, 0, 0);
}
function drawPlayer() {
doKeyDown();
ctx.fillStyle = "purple";
rect(x, y, 15,15);
}
function draw() {
clear();
if(showingWinScreen) {
isRunning = false;
drawRect(0,0,canvas.width, canvas.height,"black");
ctx.fillStyle = "white";
ctx.font = "20px Arial";
ctx.fillText("You Won! Click to play again", 70,canvas.height/2);
ctx.fillText("Time Elapsed: " + endTime, 70, canvas.height*0.6);
return;
}
isRunning = true;
drawMaze();
drawPlayer();
requestAnimationFrame(loop);
}
function drawRect(leftX, topY, width, height, drawColor) {
ctx.fillStyle = drawColor;
ctx.fillRect(leftX,topY,width, height);
}
//timer
function loop(timeStamp) {
if (!startTime) {
startTime = timeStamp;
}
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
var totalTime = timeElapsed*0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
drawRect(WIDTH,10,70, 30,"black");
ctx.fillStyle = "white";
ctx.font = "14px Arial";
ctx.fillText(minutes + ":" + (seconds).toFixed(2), WIDTH*1.04, 30);
endTime = minutes + ":" + (seconds).toFixed(0);
if (isRunning) requestAnimationFrame(loop);
}
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
img.src = "https://html5.litten.com/images/maze.gif";
var framesPerSecond = 60;
return setInterval( function() {
draw();
}, 1000/framesPerSecond);
}
function checkcollision() {
var imgd = ctx.getImageData(x, y, playerSize, playerSize);
var pix = imgd.data;
for (var i = 0; n = pix.length, i < n; i += 4) {
if (pix[i] == 0) {
collision = 1;
}
}
}
function checkWin() {
var imageData = ctx.getImageData(x, y, playerSize, playerSize);
var r, g, b, a;
for (var i = 0; i+3 < imageData.data.length; i += 4) {
r = imageData.data[i];
g = imageData.data[i+1];
b = imageData.data[i+2];
a = imageData.data[i+3];
//if red
if ( r === 255 && b === 0 ) {
console.log(' R: ' + r + '<br>G: ' + g + '<br>B: ' + b);
isRunning = false;
showingWinScreen = true;
}
}
}
function handleMouseClick(event) {
if (showingWinScreen) {
showingWinScreen = false;
x = staticX;
y = staticY;
draw();
}
}
//arrow keys
function doKeyDown(){
//left
if (keys[37]) {
if (x - dx > 0){
x -= dx;
checkcollision();
checkWin();
if (collision == 1){
x += dx;
collision = 0;
}
}
}
//right
if (keys[39]) {
if (x + dx < WIDTH){
x += dx;
checkcollision();
checkWin();
if (collision == 1){
x -= dx;
collision = 0;
}
}
}
//down
if (keys[40]) {
if (y + dy < HEIGHT ){
y += dy;
checkcollision();
checkWin();
if (collision == 1){
y -= dy;
collision = 0;
}
}
}
//up
if (keys[38]) {
if (y - dy > 0){
y -= dy;
checkcollision();
checkWin();
if (collision == 1){
y += dy;
collision = 0;
}
}
}
}
init();
window.addEventListener("keydown", function (e) {
keys[e.keyCode] = true;
});
window.addEventListener("keyup", function (e) {
keys[e.keyCode] = false;
});
canvas.addEventListener("mousedown", handleMouseClick);
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})()
<canvas id="canvas" width="582" height="582">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
这里最大的问题是什么?
你的计时器。
让我们删除与 canvas 相关的所有内容,然后尝试记录每帧 绘制这个小时间计数器的次数 :
// If everything was ok, frame_count should never be higher than 1
var frame_count = 0;
var total_count = 0;
function frame_loop() {
frame_log.textContent = frame_count;
total_log.textContent = total_count;
// reset our frame counter
frame_count = 0;
// do it again next loop
requestAnimationFrame(frame_loop);
}
frame_loop();
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
FPS = 1000/60; // ideal frame rate
function draw() {
isRunning = true;
requestAnimationFrame(loop);
}
//timer
function loop(timeStamp) {
frame_count ++;
total_count ++;
if (!startTime) {
startTime = timeStamp;
}
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
var totalTime = timeElapsed*0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
endTime = minutes + ":" + (seconds).toFixed(0);
if (isRunning) requestAnimationFrame(loop);
}
function init() {
var framesPerSecond = 60;
return setInterval( function() {
draw();
}, 1000/framesPerSecond);
}
init();
<p>number of times loop() has been called <b>during last frame</b>: <span id="frame_log"></span></p>
<p>number of times loop() has been called <b>in total</b>: <span id="total_log"></span></p>
你看到问题了吗?
在几秒钟内,您将此文本绘制数千次每帧。
每帧只需要绘制一次。
那是因为你混合了 setInterval
和 requestAnimationFrame
。
根据经验,永远不要这样做。
requestAnimationFrame
只能从其回调或初始化函数中调用。
setInterval
不应该用来调用任何应该高频绘制的东西。那是 requestAnimationFrame 的工作。
所以摆脱这个 setInterval
,并使用单个 requestAnimationFrame
循环:
// If everything was ok, frame_count should never be higher than 1
var frame_count = 0;
var total_count = 0;
function frame_loop() {
frame_log.textContent = frame_count;
total_log.textContent = total_count;
// reset our frame counter
frame_count = 0;
// do it again next loop
requestAnimationFrame(frame_loop);
}
frame_loop();
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
FPS = 1000/60; // ideal frame rate
function draw() {
isRunning = true;
// remove this one, loop will call itself
// requestAnimationFrame(loop);
}
//timer
function loop(timeStamp) {
frame_count ++;
total_count ++;
if (!startTime) {
startTime = timeStamp;
}
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
var totalTime = timeElapsed*0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
endTime = minutes + ":" + (seconds).toFixed(0);
if (isRunning) requestAnimationFrame(loop);
}
function init() {
isRunning = true;
// from init only we can start the loop
loop();
}
init();
<p>number of times loop() has been called <b>during last frame</b>: <span id="frame_log"></span></p>
<p>number of times loop() has been called <b>in total</b>: <span id="total_log"></span></p>
现在我们已经解决了这个大问题,我们可以多检查一下您正在做的其他事情...
不要将游戏逻辑与渲染逻辑混在一起。
基本设置是一个主循环,它将调用所有的子函数,以及它自己。
这些子功能基本上是
- 1 更新场景
- 2 渲染场景
在更新部分,您将更新玩家位置、检查碰撞等。
然后渲染将只需要使用更新后的值。
(function(imgurl) {
function mainLoop(t) {
update(t);
render();
if (isRunning) {
requestAnimationFrame(mainLoop);
}
}
function update(t) {
updateTimer(t);
updatePlayer(t);
}
function render() {
clear();
if (showingWinScreen) {
showWinScreen();
return;
}
renderTimer();
renderMaze();
renderPlayer();
}
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
img.onload = mainLoop;
img.src = imgurl;
canvas.addEventListener("mousedown", handleMouseClick);
isRunning = true;
}
function showWinScreen() {
isRunning = false;
drawRect(0, 0, canvas.width, canvas.height, "black");
ctx.fillStyle = "white";
ctx.font = "20px Arial";
ctx.fillText("You Won! Click to play again", 70, canvas.height / 2);
ctx.fillText("Time Elapsed: " + endTime, 70, canvas.height * 0.6);
}
var canvas;
var ctx;
var dx = 2;
var dy = 2;
var WIDTH = 482;
var HEIGHT = 482;
//movement
var x = 200,
y = 5,
staticX = 200,
staticY = 5,
keys = [];
var img = new Image();
var collision = 0;
var showingWinScreen = false;
var playerSize = 15;
//timer
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
timer = '';
function updateTimer(timeStamp) {
if (!startTime) {
startTime = timeStamp;
}
var timeElapsed = timeStamp - startTime;
lastTime = timeStamp;
var totalTime = timeElapsed * 0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
timer = minutes + ":" + (seconds).toFixed(2);
endTime = minutes + ":" + (seconds).toFixed(0);
}
function renderTimer() {
drawRect(WIDTH, 10, 70, 30, "black");
ctx.fillStyle = "white";
ctx.font = "14px Arial";
ctx.fillText(timer, WIDTH * 1.04, 30);
}
// merge both collision and win in a single check
function checkPosition() {
collision = 0;
showingWinScreen = false;
var imgd = ctx.getImageData(x, y, playerSize, playerSize);
var pix = imgd.data;
var r, g, b, a;
for (var i = 0, n = pix.length; i < n; i += 4) {
r = pix[i];
g = pix[i + 1];
b = pix[i + 2];
a = pix[i + 3];
if (r == 0) {
collision = 1;
}
//if red
if (r === 255 && b === 0) {
console.log(' R: ' + r + '<br>G: ' + g + '<br>B: ' + b);
isRunning = false;
showingWinScreen = true;
}
}
}
function updatePlayer() {
var direction_x = 0;
var direction_y = 0;
// get the directions
if (keys[37]) { //left
direction_x = -dx;
}
if (keys[39]) { //right
direction_x += dx;
}
if (keys[40]) { //bottom
direction_y += dy;
}
if (keys[38]) { //top
direction_y -= dy;
}
var updated = false;
// update the position
if (x + direction_x > 0 && x + direction_x < WIDTH) {
x += direction_x;
updated = true;
}
if (y + direction_y > 0 && y + direction_y < HEIGHT) {
y += direction_y;
updated = true;
}
// check for collision/win
if (updated) {
checkPosition();
}
// undo if needed
if (collision === 1) {
x -= direction_x;
y -= direction_y;
}
}
function renderPlayer() {
drawRect(x, y, playerSize, playerSize, "purple");
}
function renderMaze() {
ctx.drawImage(img, 0, 0);
}
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
function drawRect(leftX, topY, width, height, drawColor) {
ctx.fillStyle = drawColor;
ctx.fillRect(leftX, topY, width, height);
}
function handleMouseClick(event) {
if (showingWinScreen) {
showingWinScreen = false;
x = staticX;
y = staticY;
mainLoop(); // restart
}
}
window.addEventListener("keydown", function(e) {
e.preventDefault();
keys[e.keyCode] = true;
});
window.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
init();
})('data:image/gif;base64,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');
<canvas id="canvas" width="582" height="582"></canvas>
那里还有很多地方需要改进,例如,您最好将迷宫设置为 JSON 格式,并且只查看您的 x
和 y
collision 和 win 的值而不是检查绘制的像素,但这对于这个小答案来说有点太多了。
我正在根据 this 教程构建一个迷宫游戏。只要您按住箭头键,我就成功地让播放器矩形继续移动。当你第一次开始游戏时,动画真的很好而且很快,但似乎玩游戏几秒钟后动画变得越来越慢。谁能帮我弄清楚为什么会这样?
我已经创建了一个代码片段,但不幸的是,由于我使用的迷宫图像导致的跨源错误,它无法正常工作。
var canvas;
var ctx;
var dx = 2;
var dy = 2;
var WIDTH = 482;
var HEIGHT = 482;
//movement
var x = 200,
y = 5,
staticX = 200,//this should be the same as x (used for resetting the game)
staticY = 5,//this should be the same as y (used for resetting the game)
keys = [];
var img = new Image();
var collision = 0;
var showingWinScreen = false;
var playerSize = 15;
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
FPS = 1000/60; // ideal frame rate
function rect(x,y,w,h) {
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.closePath();
ctx.fill();
}
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
function drawMaze() {
ctx.drawImage(img, 0, 0);
}
function drawPlayer() {
doKeyDown();
ctx.fillStyle = "purple";
rect(x, y, 15,15);
}
function draw() {
clear();
if(showingWinScreen) {
isRunning = false;
drawRect(0,0,canvas.width, canvas.height,"black");
ctx.fillStyle = "white";
ctx.font = "20px Arial";
ctx.fillText("You Won! Click to play again", 70,canvas.height/2);
ctx.fillText("Time Elapsed: " + endTime, 70, canvas.height*0.6);
return;
}
isRunning = true;
drawMaze();
drawPlayer();
requestAnimationFrame(loop);
}
function drawRect(leftX, topY, width, height, drawColor) {
ctx.fillStyle = drawColor;
ctx.fillRect(leftX,topY,width, height);
}
//timer
function loop(timeStamp) {
if (!startTime) {
startTime = timeStamp;
}
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
var totalTime = timeElapsed*0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
drawRect(WIDTH,10,70, 30,"black");
ctx.fillStyle = "white";
ctx.font = "14px Arial";
ctx.fillText(minutes + ":" + (seconds).toFixed(2), WIDTH*1.04, 30);
endTime = minutes + ":" + (seconds).toFixed(0);
if (isRunning) requestAnimationFrame(loop);
}
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
img.src = "https://html5.litten.com/images/maze.gif";
var framesPerSecond = 60;
return setInterval( function() {
draw();
}, 1000/framesPerSecond);
}
function checkcollision() {
var imgd = ctx.getImageData(x, y, playerSize, playerSize);
var pix = imgd.data;
for (var i = 0; n = pix.length, i < n; i += 4) {
if (pix[i] == 0) {
collision = 1;
}
}
}
function checkWin() {
var imageData = ctx.getImageData(x, y, playerSize, playerSize);
var r, g, b, a;
for (var i = 0; i+3 < imageData.data.length; i += 4) {
r = imageData.data[i];
g = imageData.data[i+1];
b = imageData.data[i+2];
a = imageData.data[i+3];
//if red
if ( r === 255 && b === 0 ) {
console.log(' R: ' + r + '<br>G: ' + g + '<br>B: ' + b);
isRunning = false;
showingWinScreen = true;
}
}
}
function handleMouseClick(event) {
if (showingWinScreen) {
showingWinScreen = false;
x = staticX;
y = staticY;
draw();
}
}
//arrow keys
function doKeyDown(){
//left
if (keys[37]) {
if (x - dx > 0){
x -= dx;
checkcollision();
checkWin();
if (collision == 1){
x += dx;
collision = 0;
}
}
}
//right
if (keys[39]) {
if (x + dx < WIDTH){
x += dx;
checkcollision();
checkWin();
if (collision == 1){
x -= dx;
collision = 0;
}
}
}
//down
if (keys[40]) {
if (y + dy < HEIGHT ){
y += dy;
checkcollision();
checkWin();
if (collision == 1){
y -= dy;
collision = 0;
}
}
}
//up
if (keys[38]) {
if (y - dy > 0){
y -= dy;
checkcollision();
checkWin();
if (collision == 1){
y += dy;
collision = 0;
}
}
}
}
init();
window.addEventListener("keydown", function (e) {
keys[e.keyCode] = true;
});
window.addEventListener("keyup", function (e) {
keys[e.keyCode] = false;
});
canvas.addEventListener("mousedown", handleMouseClick);
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})()
<canvas id="canvas" width="582" height="582">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
这里最大的问题是什么?
你的计时器。
让我们删除与 canvas 相关的所有内容,然后尝试记录每帧 绘制这个小时间计数器的次数 :
// If everything was ok, frame_count should never be higher than 1
var frame_count = 0;
var total_count = 0;
function frame_loop() {
frame_log.textContent = frame_count;
total_log.textContent = total_count;
// reset our frame counter
frame_count = 0;
// do it again next loop
requestAnimationFrame(frame_loop);
}
frame_loop();
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
FPS = 1000/60; // ideal frame rate
function draw() {
isRunning = true;
requestAnimationFrame(loop);
}
//timer
function loop(timeStamp) {
frame_count ++;
total_count ++;
if (!startTime) {
startTime = timeStamp;
}
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
var totalTime = timeElapsed*0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
endTime = minutes + ":" + (seconds).toFixed(0);
if (isRunning) requestAnimationFrame(loop);
}
function init() {
var framesPerSecond = 60;
return setInterval( function() {
draw();
}, 1000/framesPerSecond);
}
init();
<p>number of times loop() has been called <b>during last frame</b>: <span id="frame_log"></span></p>
<p>number of times loop() has been called <b>in total</b>: <span id="total_log"></span></p>
你看到问题了吗?
在几秒钟内,您将此文本绘制数千次每帧。
每帧只需要绘制一次。
那是因为你混合了 setInterval
和 requestAnimationFrame
。
根据经验,永远不要这样做。
requestAnimationFrame
只能从其回调或初始化函数中调用。
setInterval
不应该用来调用任何应该高频绘制的东西。那是 requestAnimationFrame 的工作。
所以摆脱这个 setInterval
,并使用单个 requestAnimationFrame
循环:
// If everything was ok, frame_count should never be higher than 1
var frame_count = 0;
var total_count = 0;
function frame_loop() {
frame_log.textContent = frame_count;
total_log.textContent = total_count;
// reset our frame counter
frame_count = 0;
// do it again next loop
requestAnimationFrame(frame_loop);
}
frame_loop();
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
FPS = 1000/60; // ideal frame rate
function draw() {
isRunning = true;
// remove this one, loop will call itself
// requestAnimationFrame(loop);
}
//timer
function loop(timeStamp) {
frame_count ++;
total_count ++;
if (!startTime) {
startTime = timeStamp;
}
var timeDiff = lastTime ? timeStamp - lastTime : FPS,
timeElapsed = timeStamp - startTime,
timeScale = timeDiff / FPS; // adjust variations in frame rates
lastTime = timeStamp;
var totalTime = timeElapsed*0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
endTime = minutes + ":" + (seconds).toFixed(0);
if (isRunning) requestAnimationFrame(loop);
}
function init() {
isRunning = true;
// from init only we can start the loop
loop();
}
init();
<p>number of times loop() has been called <b>during last frame</b>: <span id="frame_log"></span></p>
<p>number of times loop() has been called <b>in total</b>: <span id="total_log"></span></p>
现在我们已经解决了这个大问题,我们可以多检查一下您正在做的其他事情...
不要将游戏逻辑与渲染逻辑混在一起。
基本设置是一个主循环,它将调用所有的子函数,以及它自己。
这些子功能基本上是
- 1 更新场景
- 2 渲染场景
在更新部分,您将更新玩家位置、检查碰撞等。
然后渲染将只需要使用更新后的值。
(function(imgurl) {
function mainLoop(t) {
update(t);
render();
if (isRunning) {
requestAnimationFrame(mainLoop);
}
}
function update(t) {
updateTimer(t);
updatePlayer(t);
}
function render() {
clear();
if (showingWinScreen) {
showWinScreen();
return;
}
renderTimer();
renderMaze();
renderPlayer();
}
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
img.onload = mainLoop;
img.src = imgurl;
canvas.addEventListener("mousedown", handleMouseClick);
isRunning = true;
}
function showWinScreen() {
isRunning = false;
drawRect(0, 0, canvas.width, canvas.height, "black");
ctx.fillStyle = "white";
ctx.font = "20px Arial";
ctx.fillText("You Won! Click to play again", 70, canvas.height / 2);
ctx.fillText("Time Elapsed: " + endTime, 70, canvas.height * 0.6);
}
var canvas;
var ctx;
var dx = 2;
var dy = 2;
var WIDTH = 482;
var HEIGHT = 482;
//movement
var x = 200,
y = 5,
staticX = 200,
staticY = 5,
keys = [];
var img = new Image();
var collision = 0;
var showingWinScreen = false;
var playerSize = 15;
//timer
var startTime = null,
lastTime = null,
endTime, // for scale
isRunning = false,
timer = '';
function updateTimer(timeStamp) {
if (!startTime) {
startTime = timeStamp;
}
var timeElapsed = timeStamp - startTime;
lastTime = timeStamp;
var totalTime = timeElapsed * 0.001;
var minutes = Math.floor(totalTime / 60);
var seconds = totalTime % 60;
timer = minutes + ":" + (seconds).toFixed(2);
endTime = minutes + ":" + (seconds).toFixed(0);
}
function renderTimer() {
drawRect(WIDTH, 10, 70, 30, "black");
ctx.fillStyle = "white";
ctx.font = "14px Arial";
ctx.fillText(timer, WIDTH * 1.04, 30);
}
// merge both collision and win in a single check
function checkPosition() {
collision = 0;
showingWinScreen = false;
var imgd = ctx.getImageData(x, y, playerSize, playerSize);
var pix = imgd.data;
var r, g, b, a;
for (var i = 0, n = pix.length; i < n; i += 4) {
r = pix[i];
g = pix[i + 1];
b = pix[i + 2];
a = pix[i + 3];
if (r == 0) {
collision = 1;
}
//if red
if (r === 255 && b === 0) {
console.log(' R: ' + r + '<br>G: ' + g + '<br>B: ' + b);
isRunning = false;
showingWinScreen = true;
}
}
}
function updatePlayer() {
var direction_x = 0;
var direction_y = 0;
// get the directions
if (keys[37]) { //left
direction_x = -dx;
}
if (keys[39]) { //right
direction_x += dx;
}
if (keys[40]) { //bottom
direction_y += dy;
}
if (keys[38]) { //top
direction_y -= dy;
}
var updated = false;
// update the position
if (x + direction_x > 0 && x + direction_x < WIDTH) {
x += direction_x;
updated = true;
}
if (y + direction_y > 0 && y + direction_y < HEIGHT) {
y += direction_y;
updated = true;
}
// check for collision/win
if (updated) {
checkPosition();
}
// undo if needed
if (collision === 1) {
x -= direction_x;
y -= direction_y;
}
}
function renderPlayer() {
drawRect(x, y, playerSize, playerSize, "purple");
}
function renderMaze() {
ctx.drawImage(img, 0, 0);
}
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
function drawRect(leftX, topY, width, height, drawColor) {
ctx.fillStyle = drawColor;
ctx.fillRect(leftX, topY, width, height);
}
function handleMouseClick(event) {
if (showingWinScreen) {
showingWinScreen = false;
x = staticX;
y = staticY;
mainLoop(); // restart
}
}
window.addEventListener("keydown", function(e) {
e.preventDefault();
keys[e.keyCode] = true;
});
window.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
init();
})('data:image/gif;base64,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');
<canvas id="canvas" width="582" height="582"></canvas>
那里还有很多地方需要改进,例如,您最好将迷宫设置为 JSON 格式,并且只查看您的 x
和 y
collision 和 win 的值而不是检查绘制的像素,但这对于这个小答案来说有点太多了。