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>

你看到问题了吗?
在几秒钟内,您将此文本绘制数千次每帧
每帧只需要绘制一次。

那是因为你混合了 setIntervalrequestAnimationFrame

根据经验,永远不要这样做。
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,R0lGODdh4gHiAYAAAP///wAAACwAAAAA4gHiAQAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImKi4yNjomBQQKTlJWWl5iZmpucnZ6fkJGmqJIVpqeoqaqrrK2orJ4horO0uLSlqLm6u7y5sKG+AS+Rgh/FZMeJySrLI8vNCsBv0nTUJdYu0MgF22rdf98Q0S3jgOVl53rpG+sY7YvvUOF18xb1E/eG+Vv7YP0U8MLFuDf1IInjHIAOHAgAKfMWyjkEzEBBMVVLxzUUnG/i8bNxrwOAekEZHwHqIASVKeSX4rAXVseSKlMZhoZGJ5GYymQJtBeOrTKQIlUGc+fxSlgrPF0TRLeTSNkvRXw4RDJVbNE3XFUzNbc3R1kpXZVUdfb5RlElbZWHJrzbVF91ZdXHZz3dUtaSithHW+LvT6K+vWLcB97REuTG+uXoCDTTU+DFmUYL+RHVOuLJnyZLGbe95VCXoIX8WfE3cGcnZJahqrD4w+HZM0kdZIaMew/VqzVtmiS7uxDQM3b8NSdQsBXgR5ztnDTe+G7UN579DHm1NY7M/380LSi1eHzng78enctf9kDj67d+ffy1P3nP4Bdvnm1eatjxR/7PgO/ub3129Cd/DNhJ5x7NlnIGoAHrQgFMLxt9B61zU4goAKvndhghP4FyF5yFAIFogh5DYeZxpGJ6IYFiLYXokHnmSdUSmGsSKMBbo4oYQbzuhBjT34GCCPHZD44n4n/iikF0BekyRdEFKl415NynUfhjI+6VCU4bWIz5TJeYmlRTEG+dhfYVKEWZqdlKlmLWyCciYCRNp4GWFxutZmnpe8qacrfHpy50djMrmcUoM61eScLA6opXpHVpNopIHeJumjhHooXpFSNnopp//dmGmOhoJqIo47elphpaaiWOiqRnJZqqZbLhprradiauumo+JK662uQtpqqL4yKqyuqOIg/tSkHAalqqyIBivqsWY1G62zqULbq7HE5jort8/u+it90opD7bAZjosmtqyCa+2nxb76rqOwZtstvd+iK+eh17Lrrbi80hkusP/CW23BBNvbabtelattvzoka6m7Du+LL56kIixxvDtAHHCHEzOrbr3rVixoyPdqnLHBZPKLscfbtgwlyyOj7LLKCX88or4PMyzywTPjLOakQmfAscLI8iwvzUeb7G/H5DItsNPTQl0zzFPL3HPSACud89BUgxwxxS9vbbPWPp+9stQ2FF32zVdibXbDaDed2GHK2hJ2zGqnm3eWdfY5y90ka0Oa3X2LeYrgh/O9d75/Aq6K4kAz/j521nRP7vjimTduMYGad4456JXHbXnURvvNecmfK6lz1WmfvnbrenMd9OqESw6R7KhbXfu8cv8+d8rmDg886TXp3jvvlJ8L+/LKb9489G0LHw3yzr8+/cK4h6566rfbvqTp2c9ONvFIbv+86OPvvn7ybChavNjt1wB/6fKbb/zl+OtfPfrY778z/wXPdfYjHwAJeDwBiu+ASwMf0vhXQPYxUIL9c+DgHmRBuEEwf9SLoPtYosD7xS+AGbyg9aQ3wQ9W0HvLouD5Ski763lQhhxEIINCCDbvBeeE6ktf96LXwxHakCs8/GH5hNhAFj6wgzU0IBJdmEAYHnGGrCni/vdMiMMhatEqWXya7XbYxa5JMYU03OANx/i/J8YujF5UIhvLSMQ3cqB+J5sfCqfYRCiakYl75IYVW6jCF7pRg3ws5Bb1aEguotFtUjLcIJWYuEXmMZDZgVzgJNnHNtbNTphMpEMi+UggGnFHlgxMJw/ZIytS6ZROpOKQ/qjKONAxk+CIpRyD6MpR4pGWlOQlRmCJRUKiEo7DvOMAWzlJYnoDmDE0ZhpzeUVRRpN70ySjM/swS0+m0musRGQxcfnMZF5Tm7+85Sp1SDRmhpKa2fwmHdqJTBFC85zShKc3c1hPW3punce8pxj5qUZl/jOf3PyNOn1YzV2Sc5zuTGg//nu5UHBi86AKbSg97SjRiOryoQKFqEVlSVGOMhSfGN2oODMqz5Oa1Jd2sKdHNVlSJ3Xzpf6EqTVRGk8+uLSjrywoQWfK020CdaRYCWk4WdrToeK0prU0p0P9sFOiChWdTo3qUm0aUKkGFS5O7Wo6q6pPgEIzfNpT6kVvOkejjnWJDbXqO9W6wKwmVawqfSoj1+rT98E1pUhNK1jzila7alSwe3ArYbE6z6+a9bBMnepPqbrPxyIUMTEdDSjzednKFq6UbfprM/GU2cBa1jKQJK1kJ8TZzi4WkPQL62dXOljDkpWkonWtXGVgWDCudq9x3O3gdAvZYNK1r4xVEW8//hrc1xa3tZ4dbm+di1xpAle6L5DtcXPqx+YidLox5W5ttatX8Ip0Brmt7nUbe9Wtsk68RyVhctl5XprKl0bxVa9ifZfYr6EXtm21bRWsC9i6eve2lGIvXiOr2QATd8D5FSZ2H7zf5WrVLQaOqyCpq9/5Tji9G+avIqELYccyr7vmrTBxZzvQBH9xxRm2L4PrCmAWZyHG722wcEd34vp2uAs0xrCPbzzi7/oWwUKusYBLLGMgF/m0IBwyNV+8YCSDOMLl1UKPSWzk8Xr4alPWMIe/vF4nb1fKP1bulZccXjHDKLWVeBwn3LwJE8eWza0Aa2iDROdJwFkTe86EnPub/ufI7fbOd61jYM9a0RC7+L5XYNuYs9ze6EI6xwo+8sUSnWItFzjJZVbxpAc7ErbSNtJJ7PShGe1p5Ya6xSLWNJRBjerJ+pfAq3ZwpknNZVPTOtYEdnR+WD3XR9vay34dtn19PQVk47rVPzt1pSnNaSwPbNlNVTWZpf3p/j4b1pd2Na8LvbFZ25jJu952WX/7bQsbutzRRrOz291sTCNW3eE2t6KZfWB0d5va+Ka3e3U97lRjG+Dr9ra9Fw3sLc8b3HNO+BGUzfBbX3jgFHc3u7Nd6ienm6//rvjFyR3wd+OX38GWN3PhHfJei9vS0474qF2ea4+nPN8Ej/K+Yb7w/pcXnOUYp/LKbd5yf3Nc4jsHes+JLWEwy/Qmoib60KlCaBhvthR9foUDHblJwNwN6zKfc9ShbZiv/0fsgOZk2M2OWbR3XdtqtvjMeY5jod+bvD+fu8KPrWOlQ9xBTUf6qyXtdqn/2e5Lb3TfEW7sHQ/e54vP+RP2Dnj4KlnljUf80U8++YmLPPBgB7ngEw+Jwyv+2punfNtNf/nR31zuEf474Su/4zN/fO0dl/XqI09zgXO+4aCvjeiV7vrWnz73u8f97N+W+aJz2/Od1z3qrX17nRvdzEKSPfGfT3vja97kqu990hF9/fB/PvXA/5LDMUh+64+f+csnP25/f/cq/h489nmPv97rrn3lP134569+/b8PgOqHc6oBfwAYfH43fOvnfG83fUHGfeXXf8nXfMVHeJAXIhEYdPnHdl2Gd141f5hHfdHHf+y3gSRYgQVYaxJYe8engBTIeAlIH4FGWaRkWqildi7YbxOIfTjYgX8jg1Tng6FQdbHggSb4giOng9s3FQIofbznfoW3gPo3DEzodE5Yc6yHdOg3FfT3gfbnhQEIJjC4gkv4f7AHgd5XZd5nF0UYhUkohUcYd0RRhmKogVjYg0iYDVToeCfYhSXHg/K3hXd4hXDYhmPYgvFGhmz4h/cnglkYhk8oB3pYbRzYfSpYh/tHFnNIiYyY/oF82ImPIIk5aIXZ14Ql6ICJuFhm+IUHKIin2BCh6Ic7yIKalob65h6pSIevR3qyqISvqIlG6IiN2IrIF4hfWIuLaIyPCIyWB323uIngd4gMaIdc+ImZqIi8GI1vGIzVyBbXKH459oNbJ4ND+IN+kouTuIz5Eo5XN45BWI67oIob1365U4yiiInDOIujGAgoZlwY+IzyeIkfVo/QOIDUmH74J5AD2YfJ6I3ZOFEKCYW2h4v/GInKKAgWSIil9406ZZH7WICwWGyQiBcQCZCl6IkHuZA81pEu8ZG/WIiFtZLT0JIN6YYPSZIpCZJn+JLLdJM4+X+sVYn02JNPmJOr/jiRRDaUcLeTGUlyRmklSdmAyAiGlsiTUKmR2FiTJjmCBmWVV7mRpkiLMVkQYmmTVMmUBamTQtmVSimVx4iVHLmW0giWb1mFZfeOd4mXedlm57iNkueOegmYgclm8eiWX6mLDleRT2mYZzmNnIiHMKmYDqmPTTmV3NhSZGmIWTmXaMmQlslVSLmYfSmRIpmSXMCP6LiUovmAnfmYVcmVfMmMdHmYaghSkamZJzmag6iVeHCa9oibqRmLoQmCF4mZ55aOjpmbpFiXrqmWx8mayemVlJmYoCmZm9mYz+mKZdmcwGmQ0JmPnBkSxZlxbYmQ+CiXu1lO1HmbjHmP3Zmd/nCpnpMpnCHpnJv2IbYpn9W5h+yZmfD5mv9YmPq5lc34n9tJnn1YlPbZJfhpnevpm/xpnMTJoL8plYSJN6UlmHzGjhnqZ7AZlLLpc2R3ghyqoRhKoqPgoWkJoghYmrvYi/WGhj/pfzTJloiojcPpnfOpojrqlCh3o4DYmssJoRRKmQF6njFHjO/ZnsjJnRG5oqj5X+I5oBpnoQjqkrqJpDYKo2apmtHZoPOonGvkme/nj/UJlEwqbEEqpmoKpU/qnmHpk1YmpSxKm1Xqo18akFOapHuqpddZmRSZovR5HmOqoH4JoDN6lKRZp4tqi4dKo3g6m4raqARKqU2KnV5K/qRHSqdsCqRxiJ5v6qZOyqOiOpbC2KmGaqaICqgtipgu+qPBKaBdWqTluamemqUv6qcJeqaXupoPN6exmaap+qiZWqOaiqN9GqE5GqvAuqwfiqnJiqzj2au6qqqpeqKmdHZAaKK9YKerKqx/ea0XmnWZsa280K31ea4SGqOmiqah2qOW+qcLuq6ESq1WGo/TyaXHOqoJmq4eWaZKSqwOyqwCC6oy+a98uq9X6q7xqq75eqoHmqgQK6mGd7DROqQXW6sLa6RR5LB016/fGbD5qZ2oCrAYa57FKrIpC5nzyqntmrAdy6sGy7K2KqsvG4IRm2YwW6gSi66BaoC1ObMl/luzzbqjRBuzUDWT9Cqj9uqz+HqzLXu0BFu0Uhu1/vm0NHuyVPuzjnqnz3W1Qpu1KhupPBum9FWxuAqpJuusRsuw/hq0CMu2/Oqzvemxb2uxQ6u1u0o4IhqiN9hI4bqX5QqPc0ur98e3jsh1NAi4erahZkK4TAu1W4uzURqnj9u1ejqtSVugZVuwcVu47/q1ynpGRLm0k4uy4ImRGUu5lxuyaYu3n1q3pMuqSnu2ngu5WLu2o2qan+uytsu6AzurtTu6WPq6rhu2QrqmxHu8Cfmsreu8akuqW6q8nTu8nNu7ecu7Wmi9VZtdv1u8z/u9bYq2Ywuvu1u5XOut2xu9/mBaoSMpu5aLkrRrt0zBu9wLvssrvq8KuuXrvtN7vWILvQTZn3kavv3ovfhbwAgMq9KrvhsLFfXbthqbvb9KvblKwbHrv/YbwBEMnqyYu2xrvgdcwcY7wvuZvA0MwSmYwRzsu/EbuakLvA98vt+avoqbZx8LEItLCeKorTTcs9l6w/A7rjrMw+Tqw/w7sSRLtsFKpfJbwyALrTb7vpGrwvCqt1W8xFBMvkwMt9g7qWBbwhsswFFcqk0rwhhsxRCsvauLvlx8twocxiasr2Xcxr2KxRI8w2Acevd6xmQqxE38xV0MwKp7ugN8v3GcvwzMxkdscE7MyHbsqjJsxkks/rqDHMPsC8nsqsGEHJVvjMifvMCG3AT1WqmCTML/u6R+zHR8TMmZy8p9vL6PV7qAjLuofMihTMaSXMeN/MKzXMqZ7MhIvMIebMtiXJK3SseP3MHm98duzMu1vMmXjLnju8EOjMnPrMfR/MEEPMdeDMtr/GuTnHaBhsOeq8OYQY6B67+jRc7NLMHnXBnpvMNnbM2pzMLe3Mr0q8nHzMnB687Gepn7PKz2TMpP7LZUbLpw+s+FvLIIbdCuvNCdLLMO/cPz28IVfZ+EOtCwq83FnMCMAM6oe8EdTdL3fM1IK9AJ7c+7vLBrqNEqLdLlLNEo/dIPrdAsrbuHENIWvM8F/o3R8krRwqyzPq2+TpvN/CzNHG3Sp1zSLh3U7Su8lgzK+3vQ0IzU28zTEX3SI2vKcozVBO3LQg3UVr3RyAvHHn3WirDTYN3TYQ3VzkjWMJ3VOA3COj3SkpvGiYvHjQvP7QzE3ErPsKTXUjzOfe3XQ2yugT27Q53Ckcx3edy8ae2rr/zURw3DX/3ReyzOld3V01zXNq2/S73FnqzZU7zOjR3VTD3ZM6bGE7zYodvSzLzK3+zasEzMJU23VE3aup3ToK3Iez3bwb3C9ZzZvkfZcd3Wqe3VUxvacpvSyW3RbM3ax33UdwzQaC2okX3CWZxsdz3Go93bP23ZCtvZaNHa/rdb1I6t2rmN11Z92cyt2tZ90YMq3CiM3trdzVIN36Js3pCtxZIt3/hMsdNN2/f939sN3ANe3/g91Ve937ms37wd2gG+1RHu4BL+24RN3wo+3LX914trZ36bsIeLJiROfyb+4Ijb1yGudYNWgzYshHwNJ4rNqLB93aos3gye4mad3cDM2HJa1v0808ts2uP92kp84Ent2UTe4dCdzyvd5DVOy0mO2TyO3aWd41S+48v93VaO42Jt5AUu5c5c4dz8oMW92r5d5uAtnWLey0deyWa+5UrO5Rcey9Ia3m/u5r+MzTfe3kju57ct2l5+58hsz12O4Hnu3uc95j6u4xg+/tfDrN633ON97uhaDunS/eQxvedTTuZynulKTdyGbuGInsj8jctKLeijnuif3enlneqEvuqoHd1rvuS2Xuf+Tc1oTuHxbeem/rBtDucJvuFg/uh//ulsPuSRnt7KfeugjuyX7udrXeqyneXTftd6a10ojtuGLdgiXknt6MLW+uLvvOIyPrgfnloGruFvvbnHDuxYbr1E7e4GiumF3t80Ttdaa9Tw/uvWPu9uHdsT+uyqDvA6Tu8DH5/QHu/Gre/K7Orvfu8Nn+anLdO4bra2/e9OHueUPui8Seubzuw3DfHL3r0iz/CTXvCSnfATLenDTvIQve8YT2EaD/NQLvMl/k/zHBHyL1/rhN7yDM28E7/xwRzz3C30Y8Dqpy7L7D7f9Z6zPo/yol6taRzkGT/1K9/qJo/mQc/VRH/znA7oTw/2BJ71Qo7qVd7gXm+1zW7zP1/nbJ/yQP72Z8/0yu7x1Ort4B6DfD924o7Yn/DjLr73gK3uuiDP6Ayua2LjEx6XLu/pEc/nj/+ZgSz5kU/5lT/5vt6qmd/Qm+/xgu75bZ/sxtz5ow/yp2/s0o76/c76/K7yrQ/5pc/rsS/7X2/psK/6t+/6uV/tu8/7BJ/rcn3owf9WwK/omG/8KwT6pt/oy8/8ys/5zw/9HNv8tY/81a/P1D/9lq/91Uv7AG77mt8fntmv+9xP/gbs/b+P/ulf8+u/EIUv//NP/4p/kPWP//mv/5dk9+7v//9PAPAxdbn9YZSTVntx1pt3/8FQHMnSPNFUXdnWfeFYnunavvFc3/ne/4FB4ZBYNB6RSeWS2XQ+oVHplFq1XrFZ7Zbb9X7BYfGYXDaf0Wn1mt12v+Fx+Zxet9/xef2e3/f/AQMFBwkLDQ8RExUVCgAAOw==');
<canvas id="canvas" width="582" height="582"></canvas>

那里还有很多地方需要改进,例如,您最好将迷宫设置为 JSON 格式,并且只查看您的 xy collision 和 win 的值而不是检查绘制的像素,但这对于这个小答案来说有点太多了。