You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas JS游戏实现角色触地跳跃 禁止空中无限连跳

Canvas角色无限连跳问题修复方案

问题原因

代码里虽然提前声明了isGrounded落地状态变量,但全程没有给这个变量赋过值,W键的按下逻辑也没加任何拦截,只要按键触发就直接给角色设置向上的初速度,自然会出现空中无限连跳的问题。

修复逻辑

  • 在所有落地判定节点(触碰画布底部、触碰平台顶部)正确更新isGrounded状态为true,上升、下落的非落地状态统一设为false
  • 跳跃触发(按W键)前增加校验:仅当isGrounded为true时才允许执行跳跃逻辑,跳跃触发后立刻将isGrounded设为false,直到下次落地才重置状态

具体修改点

  1. 修改Player类的update方法,碰到底部地面时同步更新落地状态:
update() {
  this.draw();
  this.position.x += this.velocity.x;
  this.position.y += this.velocity.y;

  if (this.position.y + this.height + this.velocity.y >= canvas.height) {
    this.velocity.y = 0
    // 站在画布底部时标记为落地
    isGrounded = true
  } else {
    this.velocity.y += gravity;
    // 离开底部时先标记为非落地,后续结合平台碰撞结果更新
    isGrounded = false
  }
}
  1. 修改平台碰撞检测逻辑,站在平台上时标记为落地:
// 平台碰撞检测
platforms.forEach(platform => {
  if (player.position.y + player.height <= platform.position.y &&
    player.position.y + player.height + player.velocity.y >= platform.position.y &&
    player.position.x + player.width >= platform.position.x &&
    player.position.x <= platform.position.x + platform.width) {
    player.velocity.y = 0
    // 站在平台上时标记为落地
    isGrounded = true
  }
})
  1. 修改W键按下的事件逻辑,增加落地校验:
case 87:
  keys.up.pressed = true;
  // 仅落地状态允许触发跳跃
  if (isGrounded) {
    player.velocity.y = -10
    // 跳跃后立刻标记为非落地,防止同一次停留期间重复触发
    isGrounded = false
  }
  break;

修复后完整可运行代码

<canvas></canvas>
<script>
const canvas = document.querySelector("canvas");
const c = canvas.getContext('2d')

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const gravity = 0.5;
let isGrounded;

class Player {
  constructor() {
    this.position = {
      x: 100,
      y: 100
    }
    this.width = 100
    this.height = 100
    this.velocity = {
      x: 0,
      y: 1
    }
  }
  draw() {
    c.fillStyle = 'black'
    c.fillRect(this.position.x, this.position.y,
      this.width, this.height)
  }

  update() {
    this.draw();
    this.position.x += this.velocity.x;
    this.position.y += this.velocity.y;

    if (this.position.y + this.height + this.velocity.y >= canvas.height) {
      this.velocity.y = 0
      isGrounded = true
    } else {
      this.velocity.y += gravity;
      isGrounded = false
    }
  }
}

class Platform {
  constructor({x, y}) {
    this.position = {x, y}
    this.width = 400
    this.height = 20
  }

  draw() {
    c.fillStyle = 'black'
    c.fillRect(this.position.x, this.position.y,
      this.width, this.height)
  }
}

const player = new Player()
const platforms = [
  new Platform({x: 150, y: 800}),
  new Platform({x: 700, y: 900}),
];

const keys = {
  right: {pressed: false},
  left: {pressed: false},
  up: {pressed: false}
}

let scrollOffset = 0

function animate() {
  requestAnimationFrame(animate)
  c.clearRect(0, 0, canvas.width, canvas.height)
  player.update();
  platforms.forEach(platform => platform.draw())

  if (keys.right.pressed && player.position.x < 400) {
    player.velocity.x = 5
  } else if (keys.left.pressed && player.position.x > 100) {
    player.velocity.x = -5
  } else {
    player.velocity.x = 0

    if (keys.right.pressed) {
      scrollOffset += 5;
      platforms.forEach(platform => platform.position.x -= 5);
    } else if (keys.left.pressed) {
      scrollOffset -= 5
      platforms.forEach(platform => platform.position.x += 5);
    }
  }
  // 平台碰撞检测
  platforms.forEach(platform => {
    if (player.position.y + player.height <= platform.position.y &&
      player.position.y + player.height + player.velocity.y >= platform.position.y &&
      player.position.x + player.width >= platform.position.x &&
      player.position.x <= platform.position.x + platform.width) {
      player.velocity.y = 0
      isGrounded = true
    }
  })
  if (scrollOffset > 300) {
    console.log('u win')
  }
}

animate()

window.addEventListener('keydown', ({keyCode}) => {
  switch (keyCode) {
    case 65:
      keys.left.pressed = true;
      break;
    case 83:
      break;
    case 68:
      keys.right.pressed = true;
      break;
    case 87:
      keys.up.pressed = true;
      if (isGrounded) {
        player.velocity.y = -10
        isGrounded = false
      }
      break;
  }
})

window.addEventListener("keyup", ({keyCode}) => {
  switch (keyCode) {
    case 65:
      keys.left.pressed = false;
      break;
    case 83:
      break;
    case 68:
      keys.right.pressed = false;
      break;
    case 87:
      keys.up.pressed = false;
      break;
  }
});
</script>

后续如果要扩展二段跳、土狼时间等跳跃机制,只需要基于isGrounded状态增加跳跃计数、落地时间窗口逻辑即可,不需要重构现有碰撞判断代码。

内容的提问来源于stack exchange,提问作者GeorgeAnghel114

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:03:21