// Package entity 实体系统:生命周期、AI 状态机、掉落物(场景/实体系统.md)。 // // 设计要点: // - 实体与方块分离,随激活区块 tick(卸载冻结); // - AI 决策限频(0.25–0.5s 一次),寻路简化为直线移动 + 卡住重试(实体系统.md §3); // - 掉落物合并 + 上限(实体系统.md §5)。 package entity import ( "math" "sync" "mc/internal/block" ) // ID 实体 ID(服务器分配,多人同步.md §3)。 type ID uint64 // Kind 实体类型。 type Kind uint8 // 实体类型(僵尸与敌对生物.md、动物体系.md、村民系统.md)。 const ( KindAnimal Kind = iota // 被动动物 KindZombie // 敌对生物样板 KindVillager // 村民 KindItemDrop // 掉落物 ) // WorldView 实体 AI 所需的世界只读视图。 type WorldView interface { Solid(x, y, z int32) bool Block(x, y, z int32) block.State SkyLight(x, y, z int32) uint8 BlockLight(x, y, z int32) uint8 } // Entity 实体。 type Entity struct { ID ID Kind Kind Pos [3]float64 // 脚底坐标 Vel [3]float64 Yaw float64 Health float64 Dead bool // AI 状态(实体系统.md §3 状态机) state uint8 decisionIn float64 // 下次决策倒计时(限频) attackIn float64 // 攻击冷却(僵尸与敌对生物.md §3) wanderDir [2]float64 // Data 类型附加数据(掉落物 = *Drop,村民 = *Villager) Data any } // 僵尸 AI 状态。 const ( zIdle uint8 = iota zChase zAttack ) // 动物 AI 状态。 const ( aWander uint8 = iota aFlee ) // Manager 实体管理器(主线程所有权)。 type Manager struct { mu sync.Mutex next ID entities map[ID]*Entity drops map[string]*Entity // 掉落物合并键(物品名)→ 实体 } // NewManager 创建实体管理器。 func NewManager() *Manager { return &Manager{entities: make(map[ID]*Entity), drops: make(map[string]*Entity)} } // Spawn 生成实体。 func (m *Manager) Spawn(kind Kind, x, y, z float64) *Entity { m.mu.Lock() defer m.mu.Unlock() m.next++ e := &Entity{ID: m.next, Kind: kind, Pos: [3]float64{x, y, z}, Health: 20, decisionIn: 0.5} m.entities[e.ID] = e return e } // Remove 移除实体。 func (m *Manager) Remove(id ID) { m.mu.Lock() delete(m.entities, id) m.mu.Unlock() } // List 导出实体列表(副本)。 func (m *Manager) List() []*Entity { m.mu.Lock() defer m.mu.Unlock() out := make([]*Entity, 0, len(m.entities)) for _, e := range m.entities { out = append(out, e) } return out } // Tick 每 tick 更新全部实体(激活区块内;冻结区由调用方过滤)。 func (m *Manager) Tick(dt float64, w WorldView, playerPos [3]float64) { for _, e := range m.List() { if e.Dead { continue } switch e.Kind { case KindZombie: e.tickZombie(dt, w, playerPos) case KindAnimal: e.tickAnimal(dt, w) case KindVillager: e.tickVillager(dt, w) } if e.Kind != KindItemDrop { e.move(dt, w) } } } // move 简化移动:水平意愿速度 + 重力 + AABB 碰撞(复用物理思路,实体系统.md §3)。 func (e *Entity) move(dt float64, w WorldView) { const g = 32.0 e.Vel[1] -= g * dt if e.Vel[1] < -64 { e.Vel[1] = -64 } // 逐轴(X → Z → Y) for _, axis := range [3]int{0, 2, 1} { e.Pos[axis] += e.Vel[axis] * dt if e.collides(w) { e.Pos[axis] -= e.Vel[axis] * dt e.Vel[axis] = 0 } } } // collides AABB 碰撞判定(0.6×1.8)。 func (e *Entity) collides(w WorldView) bool { const hw, hh = 0.3, 1.8 minX := int32(math.Floor(e.Pos[0] - hw + 1e-6)) maxX := int32(math.Floor(e.Pos[0] + hw - 1e-6)) minY := int32(math.Floor(e.Pos[1] + 1e-6)) maxY := int32(math.Floor(e.Pos[1] + hh - 1e-6)) minZ := int32(math.Floor(e.Pos[2] - hw + 1e-6)) maxZ := int32(math.Floor(e.Pos[2] + hw - 1e-6)) 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 } // tickZombie 僵尸 AI(僵尸与敌对生物.md §2–§4)。 func (e *Entity) tickZombie(dt float64, w WorldView, playerPos [3]float64) { // 白天燃烧(暴露天空光 ≥15 且无头盔 → 每秒伤害;逐 tick 而非秒杀) if w.SkyLight(int32(e.Pos[0]), int32(e.Pos[1]+1.5), int32(e.Pos[2])) >= 15 { e.Health -= 2.0 * dt if e.Health <= 0 { e.Dead = true return } } e.decisionIn -= dt e.attackIn -= dt dx, dz := playerPos[0]-e.Pos[0], playerPos[2]-e.Pos[2] dist := math.Sqrt(dx*dx + dz*dz) switch e.state { case zIdle: if dist < 16 && w.BlockLight(int32(e.Pos[0]), int32(e.Pos[1]), int32(e.Pos[2])) <= 7 { e.state = zChase } case zChase: if dist < 2.0 { e.state = zAttack e.Vel[0], e.Vel[2] = 0, 0 break } if e.decisionIn <= 0 { e.decisionIn = 0.5 // 限频寻路(实体系统.md §3) speed := 3.0 e.Vel[0] = dx / dist * speed e.Vel[2] = dz / dist * speed e.Yaw = math.Atan2(-dx, -dz) } if dist > 24 { e.state = zIdle } case zAttack: if dist > 2.5 { e.state = zChase } else if e.attackIn <= 0 { e.attackIn = 1.0 // 攻击间隔 1s(僵尸与敌对生物.md §3) e.Vel[0], e.Vel[2] = 0, 0 } } } // tickAnimal 动物 AI:游荡 + 受击逃跑(动物体系.md §2 状态机子集)。 func (e *Entity) tickAnimal(dt float64, w WorldView) { e.decisionIn -= dt if e.Health < 20 { e.state = aFlee // 受击逃跑(简化:血不满即逃) } if e.decisionIn <= 0 { e.decisionIn = 2.0 switch e.state { case aFlee: ang := math.Atan2(e.wanderDir[1], e.wanderDir[0]) + 0.5 e.wanderDir = [2]float64{math.Cos(ang), math.Sin(ang)} e.state = aWander default: // 随机游荡方向(确定性:用实体 ID 派生) ang := float64(e.ID%628) / 100.0 e.wanderDir = [2]float64{math.Cos(ang), math.Sin(ang)} if e.ID%3 == 0 { e.wanderDir = [2]float64{0, 0} // 原地休息 } } } e.Vel[0] = e.wanderDir[0] * 1.2 e.Vel[2] = e.wanderDir[1] * 1.2 _ = w } // tickVillager 村民 AI:白天游荡工作(建造队列由村民系统接入),夜晚回屋/逃跑。 func (e *Entity) tickVillager(dt float64, w WorldView) { e.decisionIn -= dt isNight := w.SkyLight(int32(e.Pos[0]), int32(e.Pos[1]+1.5), int32(e.Pos[2])) < 5 if e.decisionIn <= 0 { e.decisionIn = 1.0 if isNight { e.wanderDir = [2]float64{0, 0} // 夜晚停下(回屋由村庄数据驱动,MVP) } else { ang := float64(e.ID%628) / 100.0 e.wanderDir = [2]float64{math.Cos(ang) * 0.5, math.Sin(ang) * 0.5} } } e.Vel[0] = e.wanderDir[0] e.Vel[2] = e.wanderDir[1] }