//go:build gl // HUD 抬头显示:准星、热键栏、生命/饥饿、破坏进度(UI还原.md §2、§3.4 规格)。 package ui import ( "fmt" "mc/internal/game" "mc/internal/item" ) // HUD 游戏内 HUD。 type HUD struct { sess *game.Session items *item.Registry // 物品图标:items 图集纹理与 UV itemTex uint32 itemUV map[string][4]float32 // 逻辑分辨率与缩放(UI还原.md §3.1:427×240 基准 × 整数缩放) baseW, baseH float32 scale float32 // OnlineCount 在线玩家数(联机时由调用方注入;nil 表示单机不显示) OnlineCount func() int } // NewHUD 创建 HUD。 func NewHUD(sess *game.Session, items *item.Registry, baseW, baseH, scale float32) *HUD { return &HUD{sess: sess, items: items, itemUV: map[string][4]float32{}, baseW: baseW, baseH: baseH, scale: scale} } // SetItemAtlas 设置物品图标纹理与 UV 表(items 图集)。 func (h *HUD) SetItemAtlas(tex uint32, uv map[string][4]float32) { h.itemTex = tex h.itemUV = uv } // Draw 每帧绘制 HUD(调用方已 Begin)。 func (h *HUD) Draw(r *Renderer) { w, hgt := float32(r.w), float32(r.h) s := h.scale white := [4]float32{1, 1, 1, 1} // 准星(UI还原.md §3.4:屏幕正中 15×15 十字) cx, cy := w/2, hgt/2 r.DrawRect(cx-1*s, cy-8*s, 2*s, 16*s, white) r.DrawRect(cx-8*s, cy-1*s, 16*s, 2*s, white) // 热键栏(UI还原.md §3.4:182×22,底部居中) barW, barH := 182*s, 22*s bx, by := cx-barW/2, hgt-barH r.DrawRect(bx, by, barW, barH, [4]float32{0, 0, 0, 0.5}) for i := 0; i < 9; i++ { sx := bx + float32(i)*20*s + 1*s sy := by + 3*s if i == h.sess.Inv.Selected { r.DrawRect(sx-2*s, sy-2*s, 20*s, 20*s, [4]float32{1, 1, 1, 0.9}) // 选中高亮框 } h.drawItemIcon(r, h.sess.Inv.Slots[i], sx, sy) } // 生命:10 心(每心 9×9,屏幕左下,UI还原.md §3.4) hearts := int(20 / 2) hp := h.sess.PlayerHealth() hx, hy := w/2-91*s, hgt-30*s for i := 0; i < hearts; i++ { color := [4]float32{0.2, 0.2, 0.2, 0.6} // 空 if i < hp/2 { color = [4]float32{1, 0.1, 0.1, 1} // 满 } r.DrawRect(hx+float32(i%10)*8*s, hy+float32(i/10)*9*s, 7*s, 7*s, color) } // 饥饿:10 鸡腿(右侧偏移) for i := 0; i < 10; i++ { r.DrawRect(w/2-91*s+100*s+float32(i%10)*8*s, hy+float32(i/10)*9*s, 7*s, 7*s, [4]float32{0.6, 0.4, 0.15, 1}) } // 破坏进度(方块交互与动画.md §4:HUD 中央进度) if prog := h.sess.BreakProgress(); prog > 0 { r.DrawRect(cx-40*s, cy+20*s, 80*s, 4*s, [4]float32{0, 0, 0, 0.7}) r.DrawRect(cx-40*s, cy+20*s, 80*s*float32(prog), 4*s, [4]float32{1, 1, 1, 0.9}) } // 坐标调试(后置移除) r.DrawText(w/2, hgt-40*s, fmt.Sprintf("XYZ %.1f / %.1f / %.1f", h.sess.Player.Pos[0], h.sess.Player.Pos[1], h.sess.Player.Pos[2]), 1*s, white) // 联机在线人数(多人同步.md §3) if h.OnlineCount != nil { r.DrawText(w/2, hgt-56*s, fmt.Sprintf("在线玩家: %d", h.OnlineCount()), 1*s, white) } } // drawItemIcon 在槽位绘制物品图标 + 数量。 func (h *HUD) drawItemIcon(r *Renderer, stack item.Stack, x, y float32) { s := h.scale if stack.Empty() { return } def, ok := h.items.Get(stack.Name) if !ok { return } if uv, found := h.itemUV[def.Texture]; found && h.itemTex != 0 { r.pushQuad(quad{x: x + 2*s, y: y + 2*s, w: 16 * s, h: 16 * s, u0: uv[0], v0: uv[1], u1: uv[2], v1: uv[3], r: 1, g: 1, b: 1, a: 1, tex: h.itemTex}) } if stack.Count > 1 { r.DrawText(x+10*s, y+8*s, fmt.Sprintf("%d", stack.Count), 1*s, [4]float32{1, 1, 1, 1}) } }