Files
ngzz-mc/internal/ui/inventory.go
NianGao Dev 5da460c9a2 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 行列表)
- 动物攻击测试、原版掉落测试(草→泥土/动物掉肉)
2026-08-16 20:35:21 +08:00

119 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//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})
}
}