feat(ui): 小地图(左上角地表色图+玩家标记)与物品栏界面(E 键/Q 丢弃)
- 小地图(UI还原.md §3.5):96×96 地表色图(方块注册表→图集中心色, 植被染色后采样),每 1.5s 重建,玩家标记烘焙进地图图(白点+黑描边); 覆盖层 quad 批处理末尾颜色异常,标记改走烘焙规避 - 物品栏(UI还原.md §3.3):E 键开关屏幕状态 5,inventory.png 176×166 窗口 + 27 主物品区 (7,83) + 热键栏行 (7,141),点击与手持栈交换/合并, Esc 关闭手持栈自动回收 - Q 丢弃选中槽 1 个物品(掉落在视线前方,UI与输入.md §5) - cjkfont 多行渲染(设置界面按键教程 4 行列表) - 动物攻击测试、原版掉落测试(草→泥土/动物掉肉)
This commit is contained in:
@@ -37,6 +37,9 @@ type HUD struct {
|
||||
targetW float32
|
||||
targetH float32
|
||||
|
||||
// 小地图(左上角,UI还原.md §3.5)
|
||||
minimapTex uint32
|
||||
|
||||
// 逻辑分辨率与缩放(UI还原.md §3.1:427×240 基准 × 整数缩放)
|
||||
baseW, baseH float32
|
||||
scale float32
|
||||
@@ -76,6 +79,9 @@ func (h *HUD) SetTarget(tex uint32, w, hgt float32) {
|
||||
h.targetTex, h.targetW, h.targetH = tex, w, hgt
|
||||
}
|
||||
|
||||
// SetMinimap 设置小地图纹理(渲染线程注入;tex=0 清除)。
|
||||
func (h *HUD) SetMinimap(tex uint32) { h.minimapTex = tex }
|
||||
|
||||
// Draw 每帧绘制 HUD(调用方已 Begin)。
|
||||
func (h *HUD) Draw(r *Renderer) {
|
||||
w, hgt := float32(r.w), float32(r.h)
|
||||
@@ -174,6 +180,16 @@ func (h *HUD) Draw(r *Renderer) {
|
||||
if h.targetTex != 0 {
|
||||
h.blit(r, h.targetTex, 2*s, hgt-96*s-h.targetH, h.targetW, h.targetH)
|
||||
}
|
||||
// 小地图(左上角 96×96 逻辑像素 + 边框;玩家标记已烘焙进地图图——
|
||||
// 覆盖层 quad 在批处理末尾存在颜色异常,见 updateMinimap drawMarker)
|
||||
if h.minimapTex != 0 {
|
||||
const m = 96.0
|
||||
h.blit(r, h.minimapTex, 2*s, 2*s, m*s, m*s)
|
||||
r.DrawRect(1.5*s, 1.5*s, (m+1)*s, 1*s, [4]float32{0, 0, 0, 0.7})
|
||||
r.DrawRect(1.5*s, (m+1.5)*s, (m+1)*s, 1*s, [4]float32{0, 0, 0, 0.7})
|
||||
r.DrawRect(1.5*s, 1.5*s, 1*s, (m+1)*s, [4]float32{0, 0, 0, 0.7})
|
||||
r.DrawRect((m+0.5)*s, 1.5*s, 1*s, (m+1)*s, [4]float32{0, 0, 0, 0.7})
|
||||
}
|
||||
}
|
||||
|
||||
// blit 以纹理原比例绘制一个四边形(UI 精灵)。
|
||||
|
||||
118
internal/ui/inventory.go
Normal file
118
internal/ui/inventory.go
Normal file
@@ -0,0 +1,118 @@
|
||||
//go:build gl
|
||||
|
||||
// 物品栏界面(UI还原.md §3.3、物品与背包.md §4):
|
||||
// inventory.png 176×166 窗口 + 27 主物品区 (7,83) + 热键栏行 (7,141);
|
||||
// 点击槽位与手持栈交换/合并;E 键打开、Esc 关闭。
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"mc/internal/item"
|
||||
)
|
||||
|
||||
// InventoryScreen 物品栏界面。
|
||||
type InventoryScreen struct {
|
||||
bgTex uint32 // inventory.png(256×256,内容 176×166)
|
||||
items *item.Registry
|
||||
itemTex uint32
|
||||
itemUV map[string][4]float32
|
||||
// 方块物品图标回退来源(原版 block/* 图标在 blocks 图集)
|
||||
blockTex uint32
|
||||
blockUV map[string][4]float32
|
||||
|
||||
winW, winH float32 // 176×166
|
||||
}
|
||||
|
||||
// NewInventoryScreen 创建物品栏界面。
|
||||
func NewInventoryScreen(bgTex uint32, items *item.Registry) *InventoryScreen {
|
||||
return &InventoryScreen{bgTex: bgTex, items: items,
|
||||
itemUV: map[string][4]float32{}, blockUV: map[string][4]float32{}, winW: 176, winH: 166}
|
||||
}
|
||||
|
||||
// SetItemAtlas 设置 items 图集。
|
||||
func (s *InventoryScreen) SetItemAtlas(tex uint32, uv map[string][4]float32) {
|
||||
s.itemTex = tex
|
||||
s.itemUV = uv
|
||||
}
|
||||
|
||||
// SetBlockAtlas 设置 blocks 图集(方块物品图标回退)。
|
||||
func (s *InventoryScreen) SetBlockAtlas(tex uint32, uv map[string][4]float32) {
|
||||
s.blockTex = tex
|
||||
s.blockUV = uv
|
||||
}
|
||||
|
||||
// HitTest 命中检测:返回 "main0".."main26" / "hot0".."hot8" / ""。
|
||||
// 主物品区 3×9 自 (7,83),热键栏 1×9 自 (7,141)(UI还原.md §3.3)。
|
||||
func (s *InventoryScreen) HitTest(mx, my float32, screenW, screenH, scale float32) string {
|
||||
bx, by := screenW/2-s.winW/2*scale, screenH/2-s.winH/2*scale
|
||||
hit := func(px, py float32) bool {
|
||||
return mx >= bx+px*scale && mx < bx+(px+18)*scale &&
|
||||
my >= by+py*scale && my < by+(py+18)*scale
|
||||
}
|
||||
for i := 0; i < 27; i++ {
|
||||
px := 7 + float32(i%9)*18
|
||||
py := 83 + float32(i/9)*18
|
||||
if hit(px, py) {
|
||||
return "main" + fmt.Sprintf("%d", i)
|
||||
}
|
||||
}
|
||||
for i := 0; i < 9; i++ {
|
||||
if hit(7+float32(i)*18, 141) {
|
||||
return "hot" + fmt.Sprintf("%d", i)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Draw 绘制物品栏界面(调用方已 Begin)。
|
||||
func (s *InventoryScreen) Draw(r *Renderer, slots []item.Stack, held item.Stack, mx, my, scale float32) {
|
||||
w, h := float32(r.w), float32(r.h)
|
||||
r.DrawRect(0, 0, w, h, [4]float32{0, 0, 0, 0.35})
|
||||
bx, by := w/2-s.winW/2*scale, h/2-s.winH/2*scale
|
||||
if s.bgTex != 0 {
|
||||
r.pushQuad(quad{x: bx, y: by, w: s.winW * scale, h: s.winH * scale,
|
||||
u0: 0, v0: 0, u1: 176.0 / 256.0, v1: 166.0 / 256.0,
|
||||
r: 1, g: 1, b: 1, a: 1, tex: s.bgTex})
|
||||
}
|
||||
// 27 主物品区
|
||||
for i := 0; i < 27 && i < len(slots); i++ {
|
||||
px := 7 + float32(i%9)*18
|
||||
py := 83 + float32(i/9)*18
|
||||
s.drawSlot(r, slots[i], bx+(px+1)*scale, by+(py+1)*scale, scale)
|
||||
}
|
||||
// 热键栏行
|
||||
for i := 0; i < 9 && 9+i < len(slots); i++ {
|
||||
px := 7 + float32(i)*18
|
||||
s.drawSlot(r, slots[9+i], bx+(px+1)*scale, by+(141+1)*scale, scale)
|
||||
}
|
||||
// 手持栈跟随鼠标
|
||||
if !held.Empty() {
|
||||
s.drawSlot(r, held, mx-8*scale, my-8*scale, scale)
|
||||
}
|
||||
}
|
||||
|
||||
// drawSlot 绘制物品图标 + 数量(16×16 逻辑像素)。
|
||||
func (s *InventoryScreen) drawSlot(r *Renderer, st item.Stack, x, y, scale float32) {
|
||||
if st.Empty() || s.items == nil {
|
||||
return
|
||||
}
|
||||
def, ok := s.items.Get(st.Name)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tex := s.itemTex
|
||||
uv, found := s.itemUV[def.Texture]
|
||||
if !found {
|
||||
uv, found = s.blockUV[def.Texture]
|
||||
tex = s.blockTex
|
||||
}
|
||||
if found && tex != 0 {
|
||||
r.pushQuad(quad{x: x, y: y, w: 16 * scale, h: 16 * scale,
|
||||
u0: uv[0], v0: uv[1], u1: uv[2], v1: uv[3],
|
||||
r: 1, g: 1, b: 1, a: 1, tex: tex})
|
||||
}
|
||||
if st.Count > 1 {
|
||||
r.DrawText(x+10*scale, y+8*scale, fmt.Sprintf("%d", st.Count), scale, [4]float32{1, 1, 1, 1})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user