命中判定
子弹、道具这类数量多、只需要知道"碰没碰到"的对象,用 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。