HarmonyOS开发:游戏物理引擎——碰撞检测与物理模拟

举报
Jack20 发表于 2026/06/28 20:59:26 2026/06/28
【摘要】 HarmonyOS开发:游戏物理引擎——碰撞检测与物理模拟📌 核心要点:物理引擎让游戏从"看起来对"变成"感觉上对",碰撞检测解决"撞没撞",物理模拟解决"撞了之后怎么动",性能和精度的平衡是核心矛盾。 背景与动机你玩过一个叫"愤怒的小鸟"的游戏吗?小鸟飞出去的弧线、碰到木块后的散落、猪被砸中的翻滚——这些全都是物理引擎算出来的。没有物理引擎的游戏,角色穿墙而过、子弹穿透敌人、箱子飘在空...

HarmonyOS开发:游戏物理引擎——碰撞检测与物理模拟

📌 核心要点:物理引擎让游戏从"看起来对"变成"感觉上对",碰撞检测解决"撞没撞",物理模拟解决"撞了之后怎么动",性能和精度的平衡是核心矛盾。

背景与动机

你玩过一个叫"愤怒的小鸟"的游戏吗?小鸟飞出去的弧线、碰到木块后的散落、猪被砸中的翻滚——这些全都是物理引擎算出来的。

没有物理引擎的游戏,角色穿墙而过、子弹穿透敌人、箱子飘在空中。看起来就不对劲,玩起来更不对劲。

但物理引擎这东西,自己写吧,数学公式一堆;用现成的吧,鸿蒙上能用的物理库又不多。怎么办?

答案是:简单场景自己写,复杂场景用库。2D平台跳跃、弹幕射击这种,AABB碰撞+简单运动学就够了。3D射击、赛车、物理益智这种,得上刚体动力学。

这篇就帮你把碰撞检测和物理模拟的原理和实现搞清楚,让你知道什么时候该用什么方案。

核心原理

碰撞检测的两阶段策略

碰撞检测不是"两个对象碰没碰"这么简单。当场景里有几百个对象时,两两检查的复杂度是O(n²),性能根本扛不住。

所以碰撞检测分两个阶段:

graph LR
    A[所有游戏对象] --> B[宽阶段 Broad Phase]
    B -->|可能碰撞的对| C[窄阶段 Narrow Phase]
    C -->|确认碰撞的对| D[碰撞响应]
    
    subgraph 宽阶段算法
        B1[空间网格]
        B2[四叉树/八叉树]
        B3[排序扫描]
    end
    
    subgraph 窄阶段算法
        C1[AABB精确检测]
        C2[圆形/球体检测]
        C3[分离轴SAT]
        C4[GJK算法]
    end
    
    B -.-> B1
    B -.-> B2
    B -.-> B3
    C -.-> C1
    C -.-> C2
    C -.-> C3
    C -.-> C4
    
    classDef phaseStyle fill:#E74C3C,stroke:#C0392B,color:#fff,font-weight:bold
    classDef broadStyle fill:#F39C12,stroke:#E67E22,color:#fff
    classDef narrowStyle fill:#27AE60,stroke:#229954,color:#fff
    classDef resultStyle fill:#3498DB,stroke:#2980B9,color:#fff
    
    class A,B,C,D phaseStyle
    class B1,B2,B3 broadStyle
    class C1,C2,C3,C4 narrowStyle

宽阶段负责快速筛选"可能碰撞"的对象对,窄阶段负责精确判断"到底碰没碰"。两阶段配合,性能提升一个数量级。

刚体物理模拟

碰撞检测只告诉你"撞了",物理模拟告诉你"撞了之后怎么动"。

刚体动力学核心就三个量:位置、速度、加速度。加上牛顿第二定律F=ma,再加上碰撞后的动量守恒,就能模拟出真实的物理效果。

物理量 2D 3D 说明
位置 x, y x, y, z 物体在哪
速度 vx, vy vx, vy, vz 物体往哪走
加速度 ax, ay ax, ay, az 速度怎么变
旋转 θ qx, qy, qz, qw 物体转了多少
质量 m m 物体有多重
Fx, Fy Fx, Fy, Fz 外力作用

2D物理相对简单,旋转用角度就行。3D物理必须用四元数,不然万向节锁(Gimbal Lock)会让你抓狂。

性能与精度的平衡

物理模拟有个经典矛盾:算得越精确,性能消耗越大

  • 固定时间步长(Fixed Timestep):精度稳定,但帧率低时物理会变慢
  • 可变时间步长(Variable Timestep):帧率自适应,但大deltaTime会导致物理不稳定
  • 半隐式欧拉(Semi-implicit Euler):精度和性能的折中方案
  • 韦尔莱积分(Verlet Integration):约束求解更稳定,适合布料和绳索

游戏开发最常用的是固定时间步长+半隐式欧拉。物理用固定步长算,渲染用可变步长画,两者解耦。

代码实战

基础用法:2D碰撞检测系统

先搞定2D碰撞检测——游戏开发中最常用的部分。

// Collision2D.ets - 2D碰撞检测

// 2D向量
class Vector2 {
  x: number = 0
  y: number = 0
  constructor(x: number = 0, y: number = 0) {
    this.x = x
    this.y = y
  }
  add(v: Vector2): Vector2 { return new Vector2(this.x + v.x, this.y + v.y) }
  sub(v: Vector2): Vector2 { return new Vector2(this.x - v.x, this.y - v.y) }
  scale(s: number): Vector2 { return new Vector2(this.x * s, this.y * s) }
  dot(v: Vector2): number { return this.x * v.x + this.y * v.y }
  length(): number { return Math.sqrt(this.x * this.x + this.y * this.y) }
  normalize(): Vector2 {
    const len = this.length()
    return len > 0 ? new Vector2(this.x / len, this.y / len) : new Vector2()
  }
}

// AABB包围盒
class AABB {
  minX: number = 0
  minY: number = 0
  maxX: number = 0
  maxY: number = 0

  constructor(minX: number, minY: number, maxX: number, maxY: number) {
    this.minX = minX
    this.minY = minY
    this.maxX = maxX
    this.maxY = maxY
  }

  // 中心点
  get center(): Vector2 {
    return new Vector2(
      (this.minX + this.maxX) / 2,
      (this.minY + this.maxY) / 2
    )
  }

  // 半宽半高
  get halfWidth(): number { return (this.maxX - this.minX) / 2 }
  get halfHeight(): number { return (this.maxY - this.minY) / 2 }

  // 是否包含点
  contains(point: Vector2): boolean {
    return point.x >= this.minX && point.x <= this.maxX &&
           point.y >= this.minY && point.y <= this.maxY
  }

  // 是否与另一个AABB相交
  intersects(other: AABB): boolean {
    return this.minX < other.maxX && this.maxX > other.minX &&
           this.minY < other.maxY && this.maxY > other.minY
  }
}

// 圆形碰撞体
class Circle {
  center: Vector2 = new Vector2()
  radius: number = 0

  constructor(cx: number, cy: number, r: number) {
    this.center = new Vector2(cx, cy)
    this.radius = r
  }
}

// 碰撞检测结果
interface CollisionResult {
  collided: boolean
  normal: Vector2    // 碰撞法线
  depth: number      // 穿透深度
  point: Vector2     // 碰撞点
}

// 2D碰撞检测器
class CollisionDetector2D {
  // AABB vs AABB
  static aabbVsAabb(a: AABB, b: AABB): CollisionResult {
    if (!a.intersects(b)) {
      return { collided: false, normal: new Vector2(), depth: 0, point: new Vector2() }
    }

    // 计算穿透深度和法线
    const dx = b.center.x - a.center.x
    const dy = b.center.y - a.center.y
    const overlapX = a.halfWidth + b.halfWidth - Math.abs(dx)
    const overlapY = a.halfHeight + b.halfHeight - Math.abs(dy)

    // 取最小穿透轴
    if (overlapX < overlapY) {
      const normal = new Vector2(dx > 0 ? 1 : -1, 0)
      return { collided: true, normal, depth: overlapX, point: a.center.add(normal.scale(a.halfWidth)) }
    } else {
      const normal = new Vector2(0, dy > 0 ? 1 : -1)
      return { collided: true, normal, depth: overlapY, point: a.center.add(normal.scale(a.halfHeight)) }
    }
  }

  // 圆 vs 圆
  static circleVsCircle(a: Circle, b: Circle): CollisionResult {
    const diff = b.center.sub(a.center)
    const dist = diff.length()
    const sumRadius = a.radius + b.radius

    if (dist >= sumRadius) {
      return { collided: false, normal: new Vector2(), depth: 0, point: new Vector2() }
    }

    const normal = dist > 0 ? diff.normalize() : new Vector2(1, 0)
    const depth = sumRadius - dist
    const point = a.center.add(normal.scale(a.radius))

    return { collided: true, normal, depth, point }
  }

  // AABB vs 圆
  static aabbVsCircle(box: AABB, circle: Circle): CollisionResult {
    // 找到AABB上离圆心最近的点
    const closestX = Math.max(box.minX, Math.min(circle.center.x, box.maxX))
    const closestY = Math.max(box.minY, Math.min(circle.center.y, box.maxY))
    const closest = new Vector2(closestX, closestY)

    const diff = circle.center.sub(closest)
    const dist = diff.length()

    if (dist >= circle.radius) {
      return { collided: false, normal: new Vector2(), depth: 0, point: new Vector2() }
    }

    const normal = dist > 0 ? diff.normalize() : new Vector2(0, -1)
    const depth = circle.radius - dist

    return { collided: true, normal, depth, point: closest }
  }
}

进阶用法:刚体物理模拟

碰撞检测完了,接下来处理碰撞后的物理响应。

// RigidBody2D.ets - 2D刚体物理

// 刚体类型
enum BodyType {
  STATIC,    // 静态物体(墙壁、地面)
  DYNAMIC,   // 动态物体(角色、子弹)
  KINEMATIC  // 运动学物体(平台、电梯)
}

// 2D刚体
class RigidBody2D {
  // 基本属性
  position: Vector2 = new Vector2()
  velocity: Vector2 = new Vector2()
  acceleration: Vector2 = new Vector2()
  force: Vector2 = new Vector2()      // 累积力
  impulse: Vector2 = new Vector2()    // 冲量

  // 物理属性
  mass: number = 1.0
  invMass: number = 1.0               // 质量的倒数,静态物体为0
  restitution: number = 0.5           // 弹性系数(0=完全非弹性, 1=完全弹性)
  friction: number = 0.3              // 摩擦系数
  drag: number = 0.01                 // 空气阻力

  // 旋转
  angle: number = 0
  angularVelocity: number = 0

  // 碰撞体
  type: BodyType = BodyType.DYNAMIC
  aabb: AABB = new AABB(0, 0, 0, 0)
  circle?: Circle

  // 碰撞回调
  onCollision?: (other: RigidBody2D, result: CollisionResult) => void

  constructor(type: BodyType = BodyType.DYNAMIC) {
    this.type = type
    if (type === BodyType.STATIC) {
      this.invMass = 0
    }
  }

  // 设置质量
  setMass(m: number): void {
    if (m <= 0 && this.type !== BodyType.STATIC) {
      console.warn('质量必须大于0')
      return
    }
    this.mass = m
    this.invMass = this.type === BodyType.STATIC ? 0 : 1 / m
  }

  // 施加力
  applyForce(f: Vector2): void {
    this.force = this.force.add(f)
  }

  // 施加冲量
  applyImpulse(imp: Vector2): void {
    this.impulse = this.impulse.add(imp)
  }

  // 更新物理状态(半隐式欧拉积分)
  integrate(dt: number): void {
    if (this.type === BodyType.STATIC) return

    // 加速度 = 力 / 质量
    this.acceleration = new Vector2(
      this.force.x * this.invMass,
      this.force.y * this.invMass
    )

    // 速度 += 加速度 * dt + 冲量/质量
    this.velocity = new Vector2(
      this.velocity.x + this.acceleration.x * dt + this.impulse.x * this.invMass,
      this.velocity.y + this.acceleration.y * dt + this.impulse.y * this.invMass
    )

    // 空气阻力
    this.velocity = new Vector2(
      this.velocity.x * (1 - this.drag),
      this.velocity.y * (1 - this.drag)
    )

    // 位置 += 速度 * dt
    this.position = new Vector2(
      this.position.x + this.velocity.x * dt,
      this.position.y + this.velocity.y * dt
    )

    // 旋转
    this.angle += this.angularVelocity * dt

    // 清除力和冲量
    this.force = new Vector2()
    this.impulse = new Vector2()
  }

  // 更新AABB
  updateAABB(): void {
    if (this.circle) {
      this.aabb = new AABB(
        this.position.x - this.circle.radius,
        this.position.y - this.circle.radius,
        this.position.x + this.circle.radius,
        this.position.y + this.circle.radius
      )
    }
  }
}

// 物理世界
class PhysicsWorld2D {
  private bodies: RigidBody2D[] = []
  private gravity: Vector2 = new Vector2(0, 400) // 重力加速度
  private fixedDeltaTime: number = 1 / 60        // 固定物理步长
  private accumulator: number = 0                 // 时间累积器

  // 添加刚体
  addBody(body: RigidBody2D): void {
    body.updateAABB()
    this.bodies.push(body)
  }

  // 移除刚体
  removeBody(body: RigidBody2D): void {
    const idx = this.bodies.indexOf(body)
    if (idx > -1) this.bodies.splice(idx, 1)
  }

  // 更新物理世界
  step(dt: number): void {
    // 固定时间步长累积
    this.accumulator += dt

    // 防止螺旋死亡(spiral of death)
    if (this.accumulator > 0.1) {
      this.accumulator = 0.1
    }

    // 用固定步长更新物理
    while (this.accumulator >= this.fixedDeltaTime) {
      this.fixedStep(this.fixedDeltaTime)
      this.accumulator -= this.fixedDeltaTime
    }
  }

  // 固定步长更新
  private fixedStep(dt: number): void {
    // 施加重力
    for (const body of this.bodies) {
      if (body.type === BodyType.DYNAMIC) {
        body.applyForce(this.gravity.scale(body.mass))
      }
    }

    // 积分更新
    for (const body of this.bodies) {
      body.integrate(dt)
      body.updateAABB()
    }

    // 碰撞检测与响应
    this.resolveCollisions()
  }

  // 碰撞检测与响应
  private resolveCollisions(): void {
    for (let i = 0; i < this.bodies.length; i++) {
      for (let j = i + 1; j < this.bodies.length; j++) {
        const a = this.bodies[i]
        const b = this.bodies[j]

        // 静态物体之间不检测
        if (a.type === BodyType.STATIC && b.type === BodyType.STATIC) continue

        // 宽阶段:AABB粗筛
        if (!a.aabb.intersects(b.aabb)) continue

        // 窄阶段:精确检测
        const result = CollisionDetector2D.aabbVsAabb(a.aabb, b.aabb)
        if (!result.collided) continue

        // 碰撞响应
        this.resolveCollision(a, b, result)

        // 触发回调
        if (a.onCollision) a.onCollision(b, result)
        if (b.onCollision) b.onCollision(a, {
          ...result,
          normal: result.normal.scale(-1) // 反转法线
        })
      }
    }
  }

  // 碰撞响应
  private resolveCollision(a: RigidBody2D, b: RigidBody2D, result: CollisionResult): void {
    const { normal, depth } = result

    // 位置修正:把重叠的物体推开
    const totalInvMass = a.invMass + b.invMass
    if (totalInvMass === 0) return

    const correction = normal.scale(depth / totalInvMass * 0.8) // 80%修正
    a.position = a.position.sub(correction.scale(a.invMass))
    b.position = b.position.add(correction.scale(b.invMass))

    // 速度响应:基于弹性系数
    const relVel = b.velocity.sub(a.velocity)
    const velAlongNormal = relVel.dot(normal)

    // 物体正在分离,不处理
    if (velAlongNormal > 0) return

    // 计算弹性系数(取较小值)
    const e = Math.min(a.restitution, b.restitution)

    // 计算冲量标量
    let j = -(1 + e) * velAlongNormal / totalInvMass

    // 施加冲量
    const impulse = normal.scale(j)
    a.velocity = a.velocity.sub(impulse.scale(a.invMass))
    b.velocity = b.velocity.add(impulse.scale(b.invMass))

    // 摩擦力
    const tangent = relVel.sub(normal.scale(velAlongNormal)).normalize()
    const velAlongTangent = relVel.dot(tangent)
    let jt = -velAlongTangent / totalInvMass

    // 库仑摩擦定律
    const mu = Math.sqrt(a.friction * b.friction)
    if (Math.abs(jt) < j * mu) {
      // 静摩擦
    } else {
      // 动摩擦
      jt = -j * mu * Math.sign(jt)
    }

    const frictionImpulse = tangent.scale(jt)
    a.velocity = a.velocity.sub(frictionImpulse.scale(a.invMass))
    b.velocity = b.velocity.add(frictionImpulse.scale(b.invMass))
  }

  getBodies(): RigidBody2D[] {
    return this.bodies
  }
}

完整示例:物理弹球游戏

把碰撞检测和物理模拟串起来,做一个弹球游戏:

// PhysicsBallGame.ets - 物理弹球游戏
@Entry
@Component
struct PhysicsBallGame {
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private world: PhysicsWorld2D = new PhysicsWorld2D()
  private balls: RigidBody2D[] = []
  private walls: RigidBody2D[] = []
  private canvasW: number = 360
  private canvasH: number = 720
  private running: boolean = false
  private lastTime: number = 0
  private timer: number = -1
  private score: number = 0

  aboutToAppear(): void {
    this.setupWalls()
    this.running = true
  }

  aboutToDisappear(): void {
    this.running = false
    if (this.timer !== -1) clearTimeout(this.timer)
  }

  // 设置边界墙壁
  private setupWalls(): void {
    const wallThickness = 20

    // 左墙
    const left = new RigidBody2D(BodyType.STATIC)
    left.position = new Vector2(-wallThickness / 2, this.canvasH / 2)
    left.aabb = new AABB(-wallThickness, 0, 0, this.canvasH)
    this.world.addBody(left)
    this.walls.push(left)

    // 右墙
    const right = new RigidBody2D(BodyType.STATIC)
    right.position = new Vector2(this.canvasW + wallThickness / 2, this.canvasH / 2)
    right.aabb = new AABB(this.canvasW, 0, this.canvasW + wallThickness, this.canvasH)
    this.world.addBody(right)
    this.walls.push(right)

    // 地面
    const ground = new RigidBody2D(BodyType.STATIC)
    ground.position = new Vector2(this.canvasW / 2, this.canvasH + wallThickness / 2)
    ground.aabb = new AABB(0, this.canvasH, this.canvasW, this.canvasH + wallThickness)
    this.world.addBody(ground)
    this.walls.push(ground)

    // 天花板
    const ceiling = new RigidBody2D(BodyType.STATIC)
    ceiling.position = new Vector2(this.canvasW / 2, -wallThickness / 2)
    ceiling.aabb = new AABB(0, -wallThickness, this.canvasW, 0)
    this.world.addBody(ceiling)
    this.walls.push(ceiling)

    // 添加一些平台
    this.addPlatform(80, 400, 120, 15)
    this.addPlatform(200, 300, 120, 15)
    this.addPlatform(140, 200, 100, 15)
  }

  // 添加平台
  private addPlatform(x: number, y: number, w: number, h: number): void {
    const platform = new RigidBody2D(BodyType.STATIC)
    platform.position = new Vector2(x + w / 2, y + h / 2)
    platform.aabb = new AABB(x, y, x + w, y + h)
    this.world.addBody(platform)
    this.walls.push(platform)
  }

  // 生成弹球
  private spawnBall(x: number, y: number): void {
    const ball = new RigidBody2D(BodyType.DYNAMIC)
    ball.position = new Vector2(x, y)
    ball.restitution = 0.7 + Math.random() * 0.3
    ball.friction = 0.2
    ball.setMass(1)
    ball.circle = new Circle(x, y, 12 + Math.random() * 8)
    ball.aabb = new AABB(
      x - ball.circle.radius, y - ball.circle.radius,
      x + ball.circle.radius, y + ball.circle.radius
    )

    // 随机初始速度
    ball.velocity = new Vector2(
      (Math.random() - 0.5) * 200,
      -100 - Math.random() * 200
    )

    // 碰撞回调
    ball.onCollision = (_other, _result) => {
      this.score += 1
    }

    this.world.addBody(ball)
    this.balls.push(ball)
  }

  build() {
    Column() {
      Canvas(this.ctx)
        .width('100%')
        .height('100%')
        .onReady(() => {
          this.lastTime = Date.now()
          this.gameLoop()
        })
        .onTouch((event: TouchEvent) => {
          if (event.type === TouchType.Down) {
            const touch = event.touches[0]
            this.spawnBall(touch.x, touch.y)
          }
        })
    }
    .width('100%')
    .height('100%')
  }

  private gameLoop(): void {
    if (!this.running) return

    const now = Date.now()
    const dt = Math.min((now - this.lastTime) / 1000, 0.05)
    this.lastTime = now

    // 更新物理
    this.world.step(dt)

    // 更新球体AABB
    for (const ball of this.balls) {
      if (ball.circle) {
        ball.circle.center = ball.position
        ball.updateAABB()
      }
    }

    // 清理掉出屏幕的球
    this.balls = this.balls.filter(b => b.position.y < this.canvasH + 100)

    this.render()

    this.timer = setTimeout(() => this.gameLoop(), 16)
  }

  private render(): void {
    const ctx = this.ctx
    ctx.clearRect(0, 0, this.canvasW, this.canvasH)

    // 背景
    ctx.fillStyle = '#1a1a2e'
    ctx.fillRect(0, 0, this.canvasW, this.canvasH)

    // 绘制平台
    ctx.fillStyle = '#16213e'
    for (const wall of this.walls) {
      const aabb = wall.aabb
      ctx.fillRect(aabb.minX, aabb.minY, aabb.maxX - aabb.minX, aabb.maxY - aabb.minY)
    }

    // 绘制弹球
    const colors = ['#e94560', '#0f3460', '#533483', '#e94560', '#00b4d8']
    for (let i = 0; i < this.balls.length; i++) {
      const ball = this.balls[i]
      if (!ball.circle) continue

      ctx.beginPath()
      ctx.arc(ball.position.x, ball.position.y, ball.circle.radius, 0, Math.PI * 2)
      ctx.fillStyle = colors[i % colors.length]
      ctx.fill()

      // 高光效果
      ctx.beginPath()
      ctx.arc(
        ball.position.x - ball.circle.radius * 0.3,
        ball.position.y - ball.circle.radius * 0.3,
        ball.circle.radius * 0.25,
        0, Math.PI * 2
      )
      ctx.fillStyle = 'rgba(255,255,255,0.4)'
      ctx.fill()
    }

    // UI
    ctx.fillStyle = '#ffffff'
    ctx.font = '18px sans-serif'
    ctx.fillText(`点击屏幕生成弹球`, 10, 30)
    ctx.fillText(`碰撞次数: ${this.score}`, 10, 55)
    ctx.fillText(`弹球数量: ${this.balls.length}`, 10, 80)
  }
}

跑起来,点击屏幕就能生成弹球,弹球受重力影响下落,碰到平台和墙壁会弹开,碰到其他弹球也会弹开。这就是物理引擎的效果——不需要手动计算每个球的运动轨迹,物理引擎自动帮你算。

踩坑与注意事项

坑1:穿透问题(Tunneling)

物体速度太快时,一帧就穿过了薄墙。这叫穿透问题(Tunneling)。解决方案有两种:

  • 连续碰撞检测(CCD):在两帧之间做射线检测,找到碰撞的精确时刻。计算量大,但精确。
  • 增大墙壁厚度/降低速度:简单粗暴,但有效。大部分2D游戏用这招就够了。

坑2:物理步长与渲染步长不同步

物理用1/60秒的固定步长,渲染用可变步长。如果渲染帧率是30FPS,一个渲染帧里会跑两个物理步长。这没问题。但如果渲染帧率是120FPS,一个渲染帧里只跑半个物理步长——物理状态在两个渲染帧之间是插值出来的。你需要做状态插值,不然画面会抖。

坑3:弹性系数设置不当

两个弹性系数都是1.0的物体碰撞,能量不会损失,会一直弹。看起来不真实。大部分游戏里,弹性系数设0.3-0.6比较自然。

坑4:碰撞法线方向

碰撞法线方向决定了物体弹开的方向。法线算反了,物体会"吸"到一起而不是弹开。记住:法线总是从A指向B。

坑5:螺旋死亡

物理步长太长时,物体穿透更深,需要更多步长来修正,导致更多穿透……形成恶性循环。这就是"螺旋死亡"。解决方案:限制累积器的最大值,超过就截断。

HarmonyOS 6适配说明

HarmonyOS 6在物理引擎方面没有新增专门的物理API,但有几个间接利好:

  1. 计算性能提升:ArkTS引擎的数值计算性能提升约20%,物理模拟的帧时间更短,可以支持更复杂的物理场景。

  2. Worker多线程:HarmonyOS 6增强了Worker的能力,支持SharedArrayBuffer。你可以把物理计算放到Worker线程,渲染留在主线程,两者并行执行。

// HarmonyOS 6 Worker物理计算示例
import worker from '@ohos.worker'

// 主线程
const physicsWorker = new worker.ThreadWorker('workers/PhysicsWorker.ets')

// 发送物理更新请求
physicsWorker.postMessage({
  type: 'step',
  deltaTime: dt,
  bodies: this.serializeBodies()
})

// 接收物理计算结果
physicsWorker.onmessage = (e) => {
  const result = e.data
  this.deserializeBodies(result.bodies)
}
  1. SIMD支持:HarmonyOS 6的ArkTS引擎开始支持SIMD指令,向量运算性能大幅提升。碰撞检测中的大量向量运算可以直接受益。

总结

物理引擎让游戏从"看起来对"变成"感觉上对"。碰撞检测解决"撞没撞"的问题,物理模拟解决"撞了之后怎么动"的问题。

2D游戏用AABB+简单运动学就够;3D游戏需要球体/凸包碰撞+刚体动力学。碰撞检测用两阶段策略(宽阶段粗筛+窄阶段精检)保证性能。物理模拟用固定时间步长+半隐式欧拉保证稳定性。

性能和精度永远在博弈。你的任务不是追求物理上100%精确,而是让玩家"感觉"物理是对的。

评估维度 学习难度 使用频率 重要程度
AABB碰撞检测 ★★☆☆☆ ★★★★★ ★★★★★
圆形碰撞检测 ★★☆☆☆ ★★★★☆ ★★★★☆
碰撞响应 ★★★★☆ ★★★★★ ★★★★★
刚体物理模拟 ★★★★★ ★★★★☆ ★★★★☆
固定时间步长 ★★★☆☆ ★★★★★ ★★★★★
空间分区优化 ★★★★☆ ★★★☆☆ ★★★★☆
穿透问题处理 ★★★☆☆ ★★★☆☆ ★★★★☆

下一篇讲游戏音频——背景音乐和音效管理。画面再好看,没有声音的游戏就像默片,少了灵魂。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。