115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
//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})
|
||
}
|
||
}
|