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

2D Sonic平台游戏碰撞检测故障:角色穿透平台

2D Sonic平台游戏碰撞问题:Sonic穿透平台

我正在开发一款2D Sonic平台游戏,目前在实现Sonic实体与平台的碰撞功能时遇到困难——Sonic会直接穿透平台,就像collisionCheck()方法完全未生效一样。我已调试过collisionCheck()方法,且确认Sonic和平台的Bounding Box绘制正确。以下是相关代码:

sonic.js

class Sonic {

  constructor(game) {
      this.game = game;
      this.game.sonic = this; // special entity
      this.position = {
        x: 0,
        y: 0
      }
      this.velocity = {
        x: 0, // 向右为正
        y: 0 // 向下为正
      }
      this.speed = 1300;
      this.jumpSpeed = 200;
      this.spinSpeed = 1400;
      this.spritesheet = ASSET_MANAGER.getAsset("./sprites/realSonicSheet.png");
      this.updateBB();

      this.animations = [];
      this.state = 0;
      this.direction = 0;
      this.loadAnimations();
      this.ground = 550;
      this.onGround = false;
      this.BB = null;
      this.lastBB = null;
  }

  loadAnimations() {
    for (var i = 0; i < 4; i++) { // 四种状态: idle, running, jumping, spinning
        this.animations.push([]);
        for (var k = 0; k < 2; k++) { // 两个方向:左右
            this.animations[i].push([]);
        }
     }

    // idle 状态(state=0)
    // 向右(direction=0)
    this.animations[0][0] = new Animator(this.spritesheet, 5, 723, 45, 45, 1, 0.33, 0, false, true);

    // 向左(direction=1)
    this.animations[0][1] = new Animator(this.spritesheet, 741, 38, -41, 47, 1, 0.33, 0, true, true); // 有问题的话改成true true

    // 跑步动画
    // 向右
    this.animations[1][0]=  new Animator(this.spritesheet, 2, 916, 49, 45, 14, 0.08, 0, false, true);

    // 向左
    this.animations[1][1] = new Animator(this.spritesheet, 745, 230, -49, 47, 14, 0.08, 0, false, true);

    // 跳跃动画
    // 向右
    this.animations[2][0] = new Animator(this.spritesheet, 340, 1160, 47, 56, 8, 0.08, 0, false, true);

    // 向左
    this.animations[2][1] = new Animator(this.spritesheet, 410, 480, -47, 56, 8, 0.08, 0, false, true);

    // 旋转动画
    // 向右
    this.animations[3][0] =  new Animator(this.spritesheet, 1, 1113, 47, 40, 10, 0.08, 0, false, true);
    // 向左
    
    this.animations[3][1] = new Animator(this.spritesheet, 746, 427, -47, 40, 10, 0.08, 0, false, true);
  }
  updateBB() {
    this.BB = new BoundingBox(this.position.x, this.position.y, 155, 130, "red");
  
    console.log("This is this.BB", this.BB);
  }
  
update() {
  const GRAVITY = 0.5;
  // 先保存上一帧的碰撞盒,用于碰撞方向判断
  this.lastBB = new BoundingBox(this.BB.x, this.BB.y, this.BB.width, this.BB.height, this.BB.color);
  let standingOnPlatform = false;
  let canvasHeight = 768;

  // 左移
  if (this.game.left) {
    this.position.x -= this.speed * this.game.clockTick;
    this.direction = 1;
    this.state = 1;
  }
  // 旋转左移
  if (this.game.spin && this.game.left) {
    this.position.x -= this.spinSpeed * this.game.clockTick;
    this.state = 3;
  } 
  // 右移
  if (this.game.right) {
    this.position.x += this.speed * this.game.clockTick;
    this.direction = 0;
    this.state = 1;
  }
  // 跳跃:仅在地面时生效
  if (this.game.jump && this.onGround) {
    this.velocity.y = -this.jumpSpeed;
    this.onGround = false;
    this.state = 2;
  }

  // 旋转动作
  if (this.game.spin) {
    this.position.x += this.spinSpeed * this.game.clockTick;
    this.position.y += 10;
    this.state = 3;
  }

  // 无操作时回到idle状态
  if (!this.game.left && !this.game.right && !this.game.jump && !this.game.spin) {
    this.state = 0;
  }

  // 应用重力
  this.velocity.y += GRAVITY;
  this.position.y += this.velocity.y;

  // // 检测是否掉出屏幕
  // if (this.position.y > canvasHeight) {
  //   window.location.reload();
  //   this.position.y = 300;
  // }

  // 更新当前位置的碰撞盒
  this.updateBB();
  // 执行碰撞检测
  this.collisionCheck();
  // 更新地面状态
  this.onGround = standingOnPlatform;
}

collisionCheck() {
  this.game.entities.forEach(entity => {
    if (this !== entity && entity.BB && this.BB.collide(entity.BB)) {
      if (entity instanceof Platform) {
        // 从上方落到平台
        if (this.velocity.y > 0 && this.lastBB.bottom <= entity.BB.top) {
          this.position.y = entity.BB.top - this.BB.height;
          this.velocity.y = 0;
          this.onGround = true;
          return;
        }
        // 从下方撞到平台
        if (this.velocity.y < 0 && this.lastBB.top >= entity.BB.bottom) {
          this.position.y = entity.BB.bottom;
          this.velocity.y = 0;
          return;
        }
        // 从右侧撞到平台
        if (this.velocity.x > 0 && this.lastBB.right <= entity.BB.left) {
          this.position.x = entity.BB.left - this.BB.width;
          this.velocity.x = 0;
          return;
        }
        // 从左侧撞到平台
        if (this.velocity.x < 0 && this.lastBB.left >= entity.BB.right) {
          this.position.x = entity.BB.right;
          this.velocity.x = 0;
          return;
        }
      }
    }
  });
}

  draw(ctx) {
    if(this.state < 0 || this.state > 3) this.state = 0;
    let done = this.animations[this.state][this.direction].drawFrame(this.game.clockTick, ctx, this.position.x - this.game.camera.x , this.position.y);
    if (done) {
      this.animations[this.state][this.direction].elapsedTime = 0;
      this.state = 0;
    }
    if (PARAMS.DEBUG) {
      this.game.ctx.strokeStyle = "red";
      this.game.ctx.strokeRect(this.BB.x - this.game.camera.x, this.BB.y, this.BB.width, this.BB.height);
    }
  }
}

platform.js

class Platform {
  constructor(game, x, y, width, height) {
    this.game = game;
    this.position = {
      x: x,
      y: y
    };
    this.width = width;
    this.height = height;
    // 统一碰撞盒属性名为BB,和Sonic保持一致
    this.BB = {
      x: this.position.x,
      y: this.position.y,
      width: this.width,
      height: this.height
    };
    this.spritesheet = ASSET_MANAGER.getAsset("./sprites/floor.png");
  }

  updateBB() {
    this.BB.x = this.position.x;
    this.BB.y = this.position.y;
  }

  update() {
    this.updateBB();
  }

  draw(ctx) {
    ctx.drawImage(this.spritesheet, this.position.x - this.game.camera.x, this.position.y, this.width, this.height);

    if (PARAMS.DEBUG) {
      ctx.strokeStyle = "lime";
      ctx.strokeRect(
        this.BB.x - this.game.camera.x,
        this.BB.y,
        this.BB.width,
        this.BB.height
      );
    }
  }
}

核心问题与修复说明

  1. 碰撞盒属性名不匹配:原Platform类使用boundingBox作为碰撞盒属性,但Sonic的碰撞检测代码判断的是entity.BB,导致碰撞检测无法识别平台。已将Platform的碰撞盒属性名改为BB,统一命名规则。

  2. lastBB更新时机错误:原代码每次调用updateBB()都会覆盖lastBB,导致碰撞判断时的“上一帧位置”与当前帧位置一致,无法正确识别碰撞方向。现在调整为在移动操作前保存当前碰撞盒到lastBB。

  3. 重复应用重力:原代码中两次执行重力叠加和位置更新,导致Sonic下落速度翻倍,即使碰撞生效也可能快速穿透平台。已移除重复的重力代码,仅保留一次。

  4. 跳跃逻辑不合理:原代码直接修改position.y实现跳跃,且未判断是否在地面,导致无限跳。改为通过修改velocity.y实现跳跃,且仅在onGround为true时触发。

  5. 地面状态未正确同步:原代码中standingOnPlatform变量始终为false,导致onGround状态无法正确更新。现在在碰撞检测中检测到站在平台时,直接设置onGround为true。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 22:55:19