基于Pokemon的2D网页游戏角色未碰边界却无法移动问题
2D俯视游戏角色移动与碰撞修复方案
核心问题分析
你的代码存在几个关键错误,导致角色完全无法移动:
- 移动逻辑未放入动画循环,仅执行一次
moving变量未正确重置与使用- 引用了未定义的
player.sprites和checkForCharacterCollision lastKey变量声明位置错误
修复步骤
将移动逻辑移入
animate函数
所有按键判断和移动代码必须放在animate函数内,才能每帧响应按键状态。修正
moving变量逻辑
每次移动前重置moving为true,若检测到碰撞则设为false,阻止移动。替换未定义的属性与函数
用直接的图片变量(如playerUpImage)替代不存在的player.sprites.up,移除未实现的checkForCharacterCollision调用。调整
lastKey声明位置
将lastKey移到全局作用域顶部,确保初始值可用。修正碰撞检测的位置偏移
根据移动方向调整边界的位置偏移,确保碰撞预判正确。
修复后的完整代码
const canvas = document.querySelector("canvas"); const c = canvas.getContext("2d"); let moving = true; let lastKey = ""; // 移到全局顶部 canvas.width = 1024; canvas.height = 576; const collisionsMap = []; for (let i = 0; i < collisions.length; i += 70) { collisionsMap.push(collisions.slice(i, 70 + i)); } class Boundary { static width = 48; static height = 48; constructor({ position }) { this.position = position; this.width = 48; this.height = 48; } draw() { c.fillStyle = "red"; c.fillRect(this.position.x, this.position.y, this.width, this.height); } } const boundaries = []; const offset = { x: -1175, y: -590, }; collisionsMap.forEach((row, i) => { row.forEach((symbol, j) => { if (symbol === 1027 || symbol === 1028) boundaries.push( new Boundary({ position: { x: j * Boundary.width + offset.x, y: i * Boundary.height + offset.y, }, }) ); }); }); const image = new Image(); image.src = "./img/Lonely Island.png"; const playerDownImage = new Image(); playerDownImage.src = "./img/playerDown.png"; const playerUpImage = new Image(); playerUpImage.src = "./img/playerUp.png"; const playerLeftImage = new Image(); playerLeftImage.src = "./img/playerLeft.png"; const playerRightImage = new Image(); playerRightImage.src = "./img/playerRight.png"; class Sprite { constructor({ position, image, frames = { max: 1 } }) { this.position = position; this.image = image; this.frames = frames; this.width = 0; this.height = 0; this.image.onload = () => { this.width = this.image.width / this.frames.max; this.height = this.image.height; }; } draw() { if (!this.width || !this.height) return; // 图片加载完成前不绘制 c.drawImage( this.image, 0, 0, this.image.width / this.frames.max, this.image.height, this.position.x, this.position.y, this.image.width / this.frames.max, this.image.height ); } } const player = new Sprite({ position: { x: canvas.width / 2 - 192 / 4 / 2, y: canvas.height / 2 - 30 / 2, }, image: playerDownImage, frames: { max: 4, }, }); const background = new Sprite({ position: { x: offset.x, y: offset.y, }, image: image, }); const keys = { w: { pressed: false, }, a: { pressed: false, }, s: { pressed: false, }, d: { pressed: false, }, }; const movables = [background, ...boundaries]; function rectangularCollision({ rectangle1, rectangle2 }) { return ( rectangle1.position.x + rectangle1.width >= rectangle2.position.x && rectangle1.position.x <= rectangle2.position.x + rectangle2.width && rectangle1.position.y <= rectangle2.position.y + rectangle2.height && rectangle1.position.y + rectangle1.height >= rectangle2.position.y ); } function animate() { window.requestAnimationFrame(animate); background.draw(); boundaries.forEach((boundary) => { boundary.draw(); }); // 移动逻辑移入动画循环 moving = true; // 每次循环重置移动状态 if (keys.w.pressed && lastKey === "w") { player.image = playerUpImage; // 预判向上移动后的碰撞 for (let i = 0; i < boundaries.length; i++) { const boundary = boundaries[i]; if ( rectangularCollision({ rectangle1: player, rectangle2: { ...boundary, position: { x: boundary.position.x, y: boundary.position.y + 3, // 背景向下移,相当于角色向上,边界位置加3 }, }, }) ) { moving = false; break; } } if (moving) { movables.forEach((movable) => { movable.position.y += 3; }); } } else if (keys.a.pressed && lastKey === "a") { player.image = playerLeftImage; for (let i = 0; i < boundaries.length; i++) { const boundary = boundaries[i]; if ( rectangularCollision({ rectangle1: player, rectangle2: { ...boundary, position: { x: boundary.position.x + 3, // 背景向右移,角色向左,边界位置加3 y: boundary.position.y, }, }, }) ) { moving = false; break; } } if (moving) { movables.forEach((movable) => { movable.position.x += 3; }); } } else if (keys.s.pressed && lastKey === "s") { player.image = playerDownImage; for (let i = 0; i < boundaries.length; i++) { const boundary = boundaries[i]; if ( rectangularCollision({ rectangle1: player, rectangle2: { ...boundary, position: { x: boundary.position.x, y: boundary.position.y - 3, // 背景向上移,角色向下,边界位置减3 }, }, }) ) { moving = false; break; } } if (moving) { movables.forEach((movable) => { movable.position.y -= 3; }); } } else if (keys.d.pressed && lastKey === "d") { player.image = playerRightImage; for (let i = 0; i < boundaries.length; i++) { const boundary = boundaries[i]; if ( rectangularCollision({ rectangle1: player, rectangle2: { ...boundary, position: { x: boundary.position.x - 3, // 背景向左移,角色向右,边界位置减3 y: boundary.position.y, }, }, }) ) { moving = false; break; } } if (moving) { movables.forEach((movable) => { movable.position.x -= 3; }); } } player.draw(); } animate(); window.addEventListener("keydown", (e) => { switch (e.key) { case "w": keys.w.pressed = true; lastKey = "w"; break; case "a": keys.a.pressed = true; lastKey = "a"; break; case "s": keys.s.pressed = true; lastKey = "s"; break; case "d": keys.d.pressed = true; lastKey = "d"; break; } }); window.addEventListener("keyup", (e) => { switch (e.key) { case "w": keys.w.pressed = false; break; case "a": keys.a.pressed = false; break; case "s": keys.s.pressed = false; break; case "d": keys.d.pressed = false; break; } });
额外说明
- 移除了重复的按键监听和未实现的函数调用
- 增加了图片加载完成前的绘制判断,避免因图片未加载导致的宽高错误
- 碰撞预判的偏移方向已根据实际移动逻辑修正,确保角色碰到红色边界时停止移动
内容的提问来源于stack exchange,提问作者ChadThunderCock
相关产品推荐
相关产品推荐

