Skip to content

命中判定 ​

子弹、道具这类数量多、只需要知道"碰没碰到"的对象,用 HitTester 判定,不走物理引擎:没有物理反应,也不占刚体预算。

ts
import { circle, HitTester, rectangle, Scene, Sprite2D, v } from 'sapling2d'

// 碰撞形状在模块里建一次,所有子弹共用(不要每颗子弹 new 一个)
const BULLET_SHAPE = circle(8)
const ENEMY_SHAPE = rectangle(120, 60) // 以节点位置为中心,轴对齐

class Bullet extends Sprite2D {
  readonly hitShape = BULLET_SHAPE
  override process(dt: number) {
    this.y -= 1200 * dt // 用 y -= …,不分配 Vector2
  }
}

class Enemy extends Sprite2D {
  readonly hitShape = ENEMY_SHAPE
  hp = 3
}

export class Battle extends Scene {
  readonly bullets: Bullet[] = []
  readonly enemies: Enemy[] = []
  private readonly _hits = new HitTester()

  override ready() {
    // 互相比较的对象挂在同一个父节点下:HitTester 比较的是局部坐标 x / y
    this.enemies.push(this.add(new Enemy({ position: v(375, 300) })))
    for (let i = 0; i < 5; i++) this.bullets.push(this.add(new Bullet({ position: v(375, 1000 + i * 100) })))
  }

  override process() {
    // 子弹 × 敌机:返回 true 表示这颗子弹用掉了,不再和别的敌机比较
    this._hits.forEachHit(this.bullets, this.enemies, (bullet, enemy) => {
      bullet.queueFree()
      if (--enemy.hp <= 0) enemy.queueFree() // 已 queueFree 的对象自动不再参与判定
      return true
    })
    HitTester.compact(this.bullets) // 原地去掉已失效的对象
    HitTester.compact(this.enemies)
  }
}

参与判定的对象 ​

  • 对象有 x、y 和 hitShape 就能参与;hitShape 是 circle(r) 或 rectangle(w, h)。形状在模块里建一次,所有对象共用。
  • 形状以节点位置为中心,不随 rotation / scale 变化(矩形总是轴对齐)。
  • x / y 是局部坐标,所以互相比较的对象要挂在同一个父节点下(或者父节点之间没有相对变换)。
  • dead 为 true、已 queueFree() 或已销毁的对象自动跳过。只看对象自己:父节点 queueFree() 时,子节点要到帧末销毁后才跳过。

API ​

  • forEachHit(as, bs, hit):对每一对相交的 a、b 调用 hit(a, b);返回 true 表示 a 用掉了,不再和别的 b 比较。
  • as 和 bs 可以传同一个数组,做组内判定;对象不和自己比较。
  • HitTester.compact(list):原地去掉已失效的对象。

什么时候用 ​

  • 弹幕、子弹、拾取物:用 HitTester。
  • 需要反弹、堆叠、推挤等物理反应:用 物理。
  • 在微信小游戏的 iOS 端,同时活跃的刚体建议控制在 60–80 个以内,大量对象更应该用 HitTester。

MIT 许可证