// Package physics 玩家 AABB 碰撞与逐轴移动(场景/物理与碰撞.md §2)。 // // 核心:分轴解算(X → Z → Y),每轴移动后检测 AABB 与固体方块重叠,重叠则回退该轴。 // 不用物理引擎:体素世界自己写最快、最可控。 package physics import "math" // World 物理层所需的世界接口(与渲染/交互共用同一张 solid 判定表)。 type World interface { // Solid 是否碰撞(台阶/栅栏等多 AABB 形状后置,MVP 按整方块判定)。 Solid(x, y, z int32) bool // Fluid 是否液体(游泳/浮力判定)。 Fluid(x, y, z int32) bool } // Player 玩家实体(设计.md §7.1:宽 0.6、高 1.8、眼高 1.62)。 type Player struct { Pos [3]float64 // 脚底坐标 Vel [3]float64 // 速度(方块/秒) Width float64 Height float64 EyeHeight float64 OnGround bool // 落地 Submerged bool // 在液体中 Flying bool // 飞行模式 // 累积的位移余量(亚步进精度,防边界抖动) rem [3]float64 } // NewPlayer 创建默认玩家(站立尺寸)。 func NewPlayer(x, y, z float64) *Player { return &Player{ Pos: [3]float64{x, y, z}, Width: 0.6, Height: 1.8, EyeHeight: 1.62, } } // Move 逐轴移动(物理与碰撞.md §2.2):X → Z → Y,每轴独立碰撞回退。 func (p *Player) Move(w World, dx, dy, dz float64) { p.OnGround = false deltas := [3]float64{dx, dy, dz} for _, axis := range [3]int{0, 2, 1} { p.Pos[axis] += deltas[axis] if p.Collides(w) { p.Pos[axis] -= deltas[axis] p.Vel[axis] = 0 if axis == 1 && deltas[axis] < 0 { p.OnGround = true // 垂直下落被阻挡 = 落地 } } } // 液体状态更新(头部/脚部任一在水里) headY := p.Pos[1] + p.Height*0.6 p.Submerged = w.Fluid(int32(math.Floor(p.Pos[0])), int32(math.Floor(p.Pos[1]+0.3)), int32(math.Floor(p.Pos[2]))) || w.Fluid(int32(math.Floor(p.Pos[0])), int32(math.Floor(headY)), int32(math.Floor(p.Pos[2]))) } // Collides 当前位置 AABB 是否与固体方块重叠(含边界 epsilon 防抖动)。 func (p *Player) Collides(w World) bool { const eps = 1e-6 minX := int32(math.Floor(p.Pos[0] - p.Width/2 + eps)) maxX := int32(math.Floor(p.Pos[0] + p.Width/2 - eps)) minY := int32(math.Floor(p.Pos[1] + eps)) maxY := int32(math.Floor(p.Pos[1] + p.Height - eps)) minZ := int32(math.Floor(p.Pos[2] - p.Width/2 + eps)) maxZ := int32(math.Floor(p.Pos[2] + p.Width/2 - eps)) for x := minX; x <= maxX; x++ { for y := minY; y <= maxY; y++ { for z := minZ; z <= maxZ; z++ { if w.Solid(x, y, z) { return true } } } } return false } // Eye 返回眼睛位置(相机位置)。 func (p *Player) Eye() [3]float64 { return [3]float64{p.Pos[0], p.Pos[1] + p.EyeHeight, p.Pos[2]} } // Tick 重力与液体阻力积分(dt 秒;调用方随后调用 Move 应用位移)。 // 飞行/游泳模式由调用方在 Move 前覆写速度。 func (p *Player) Tick(w World, dt float64) { const gravity = 32.0 // 方块/秒² if !p.Flying { if p.Submerged { p.Vel[1] -= gravity * 0.3 * dt // 水中浮力近似 p.Vel[1] *= 1 - 2.0*dt // 阻力 } else { p.Vel[1] -= gravity * dt } if p.Vel[1] < -64 { p.Vel[1] = -64 // 终端速度 } } p.Move(w, p.Vel[0]*dt, p.Vel[1]*dt, p.Vel[2]*dt) } // Jump 起跳(仅落地时生效;约 1.25 方块高度)。 func (p *Player) Jump() { if p.OnGround { p.Vel[1] = 9.0 p.OnGround = false } }