// Package physics 玩家 AABB 碰撞与逐轴移动(场景/物理与碰撞.md §2)。 // // 核心:分轴解算(X → Z → Y),每轴移动后检测 AABB 与固体方块重叠,重叠则回退该轴。 // 不用物理引擎:体素世界自己写最快、最可控。 package physics import "math" // World 物理层所需的世界接口(与渲染/交互共用同一张 solid 判定表)。 type World interface { Solid(x, y, z int32) bool Fluid(x, y, z int32) bool } const ( AirMax = 15.0 // 氧气秒数(300 tick) standHeight = 1.8 standEye = 1.62 sneakHeight = 1.5 sneakEye = 1.27 swimHeight = 0.6 swimEye = 0.4 ) // 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 InWater bool // 脚或躯干在液体 EyeInWater bool // 眼睛在液体(氧气 / 水下雾) Swimming bool Flying bool Air float64 // 剩余氧气秒数 FallSpeed float64 // 落地前一帧垂直速度(摔落判定) rem [3]float64 } // NewPlayer 创建默认玩家(站立尺寸)。 func NewPlayer(x, y, z float64) *Player { return &Player{ Pos: [3]float64{x, y, z}, Width: 0.6, Height: standHeight, EyeHeight: standEye, Air: AirMax, } } // 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 } } } p.updateFluid(w) } func (p *Player) updateFluid(w World) { fx := int32(math.Floor(p.Pos[0])) fz := int32(math.Floor(p.Pos[2])) foot := w.Fluid(fx, int32(math.Floor(p.Pos[1]+0.3)), fz) mid := w.Fluid(fx, int32(math.Floor(p.Pos[1]+p.Height*0.6)), fz) p.InWater = foot || mid eye := p.Eye() p.EyeInWater = w.Fluid(int32(math.Floor(eye[0])), int32(math.Floor(eye[1])), int32(math.Floor(eye[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]} } // SetPose 切换潜行/游泳碰撞箱;站立会卡住则保持当前矮箱。 func (p *Player) SetPose(w World, sneak, swim bool) { oldH, oldE := p.Height, p.EyeHeight switch { case swim && p.InWater: p.Height, p.EyeHeight, p.Swimming = swimHeight, swimEye, true case sneak: p.Height, p.EyeHeight, p.Swimming = sneakHeight, sneakEye, false default: p.Height, p.EyeHeight, p.Swimming = standHeight, standEye, false } if p.Collides(w) { p.Height, p.EyeHeight = oldH, oldE if oldH <= swimHeight+0.01 { p.Swimming = true } } } // TickAir 氧气:眼睛在水递减,出水回满(物理与碰撞.md §4)。 func (p *Player) TickAir(dt float64) { if p.EyeInWater { p.Air -= dt if p.Air < 0 { p.Air = 0 } } else { p.Air = AirMax } } // Tick 重力与液体阻力积分(dt 秒)。 func (p *Player) Tick(w World, dt float64) { const gravity = 32.0 p.FallSpeed = p.Vel[1] if !p.Flying { if p.InWater { p.Vel[1] -= gravity * 0.15 * dt p.Vel[1] *= 1 - 3.0*dt if p.Vel[1] < -4 { p.Vel[1] = -4 } } 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) p.TickAir(dt) } // Jump 起跳(仅落地时生效;约 1.25 方块高度)。 func (p *Player) Jump() { if p.OnGround { p.Vel[1] = 9.0 p.OnGround = false } } // SwimUp 水中上浮(不要求落地)。 func (p *Player) SwimUp() { if p.InWater { p.Vel[1] = 4.2 } } // SwimDown 水中下潜。 func (p *Player) SwimDown() { if p.InWater { p.Vel[1] = -3.2 } }