Canvas恐龙跳跃异常:按下跳跃键后立即落回地面的JS问题排查
问题分析与修复方案
你的角色跳起来立刻落地,核心问题出在碰撞检测逻辑错误和重力参数设置不当,还有几个细节遗漏,下面逐个解决:
1. 修正碰撞检测逻辑
当前colisionDetection函数的条件完全写反了:你判断「角色底部还没到地面」就强制拉回地面,导致角色刚起跳就被重置。正确逻辑应该是当角色底部碰到/超过地面时,才触发落地处理:
function colisionDetection(){ // 角色底部接触或超出地面时,触发落地逻辑 if(dino.y + dino.h >= board.height){ gravity = 0; velocityY = 0; dino.onGround = true; // 固定角色在地面位置,避免超出画布 dino.y = board.height - dino.h; } else { // 角色在空中时,恢复重力 gravity = 1; } }
2. 调整重力与跳跃参数
- 原
gravity = -1方向错误:画布y轴向下递增,重力应该让角色y值增加(向下),所以设为正的小数值(比如1),避免下落过快。 - 原
jumpY = 100数值过大:会导致角色瞬间位移,改成15左右,跳跃更自然。
3. 初始化frames变量
frames未初始化会导致动画逻辑出错,在代码开头补充:
let frames = 0;
4. 补充缺失的函数
代码里调用了drawBase、drawPipes和placePipes但未定义,会触发控制台报错,补充基础实现(可根据需求调整样式):
function drawBase(){ context.fillStyle = '#888'; context.fillRect(base.x, base.y, base.w, base.h); // 地面滚动逻辑(可选) if(state.current == state.game){ base.x -= base.dx; base.x = base.x <= -base.w/2 ? 0 : base.x; } } function drawPipes(){ context.fillStyle = '#2ecc71'; pipeArray.forEach(pipe => { context.fillRect(pipe.x, pipe.y, pipe.width, pipe.height); if(state.current == state.game) pipe.x += velocityX; }); } function placePipes(){ if(state.current != state.game) return; pipeArray.push({ x: boardWidth, y: boardHeight - pipeHeight - 30, // 和地面保持间距 width: pipeWidth, height: pipeHeight }); // 移除超出画布的管道,避免内存占用 if(pipeArray.length > 5) pipeArray.shift(); }
5. 简化跳跃逻辑
原跳跃逻辑里直接修改dino.y是多余的,update函数会自动更新位置,只需要设置velocityY即可:
document.addEventListener('keydown', (evt) => { let keyPressed = evt.keyCode; if(keyPressed == 32 && state.current == state.game && dino.onGround == true){ console.log('space key pressed'); velocityY = -jumpY; // 负数值实现向上跳 dino.onGround = false; gravity = 1; // 起跳时立刻恢复重力 } });
完整修正后的代码
let board; let context; let boardWidth = 600; let boardHeight = 300; let frames = 0; // 初始化frames变量 let dinoX = 30; let dinoY = boardHeight - 60; let dinoWidth = 50; let dinoHeight = 50; let pipeX = 900; let pipeY = boardHeight - 50; let pipeWidth = 40; let pipeHeight = 30; let pipeArray = []; let velocityX = -1; let jumpY = 15; // 调整跳跃力度 let pressedSpace = 0; let gravity = 1; // 修正重力方向 let velocityY = 0; let intervalId; let base = { x : 0, y : boardHeight - 30, w : 1400, h : 20, dx : 1 } let dino = { animation : [ // 临时用纯色矩形替代图片,可替换为实际图片资源 {draw: (ctx, x, y, w, h) => {ctx.fillStyle='#3498db'; ctx.fillRect(x,y,w,h)}}, {draw: (ctx, x, y, w, h) => {ctx.fillStyle='#e74c3c'; ctx.fillRect(x,y,w,h)}}, {draw: (ctx, x, y, w, h) => {ctx.fillStyle='#f1c40f'; ctx.fillRect(x,y,w,h)}}, {draw: (ctx, x, y, w, h) => {ctx.fillStyle='#3498db'; ctx.fillRect(x,y,w,h)}} ], x : dinoX, y : dinoY, w : dinoWidth, h : dinoHeight, onGround : true, frame : 0 } let state = { current : 0, getReady : 0, game : 1, over : 2 } window.onload = function() { board = document.querySelector('#canvas'); boardWidth = board.width; boardHeight = board.height; context = board.getContext('2d'); setInterval(placePipes, 1500); requestAnimationFrame(update); }; document.addEventListener('click', (evt) => { switch(state.current){ case state.getReady: state.current = state.game; break; case state.game: break; case state.over: state.current = state.getReady; break; } }) // update function function update(){ requestAnimationFrame(update); context.clearRect(0, 0, board.width, board.height); drawBase(); drawDino(); drawPipes(); frames++; if(state.current == state.game){ velocityY += gravity; dino.y = Math.max(dino.y + velocityY, 0); colisionDetection(); } }; // jump button document.addEventListener('keydown', (evt) => { let keyPressed = evt.keyCode; if(keyPressed == 32 && state.current == state.game && dino.onGround == true){ console.log('space key pressed'); velocityY = -jumpY; dino.onGround = false; gravity = 1; } }); // colision detector function colisionDetection(){ if(dino.y + dino.h >= board.height){ gravity = 0; velocityY = 0; dino.onGround = true; dino.y = board.height - dino.h; } else { gravity = 1; } }; function drawDino(){ let dinoA = dino.animation[dino.frame]; dinoA.draw(context, dino.x, dino.y, dino.w, dino.h); if(state.current == state.game){ dino.period = 10; // 调整动画帧率,避免切换过快 dino.frame += frames % dino.period == 0 ? 1 : 0; dino.frame = dino.frame % dino.animation.length; } }; // 补充缺失的drawBase函数 function drawBase(){ context.fillStyle = '#888'; context.fillRect(base.x, base.y, base.w, base.h); if(state.current == state.game){ base.x -= base.dx; base.x = base.x <= -base.w/2 ? 0 : base.x; } } // 补充缺失的drawPipes函数 function drawPipes(){ context.fillStyle = '#2ecc71'; pipeArray.forEach(pipe => { context.fillRect(pipe.x, pipe.y, pipe.width, pipe.height); if(state.current == state.game) pipe.x += velocityX; }); } // 补充缺失的placePipes函数 function placePipes(){ if(state.current != state.game) return; pipeArray.push({ x: boardWidth, y: boardHeight - pipeHeight - 30, width: pipeWidth, height: pipeHeight }); if(pipeArray.length > 5) pipeArray.shift(); }
注:如果你的startRunImg等图片变量未定义,需要先加载图片资源,或者用代码里的矩形占位方式临时替代。
内容的提问来源于stack exchange,提问作者Jask
相关产品推荐
相关产品推荐

