Files
ngzz-mc/cmd/client/input.go
NianGao Dev 768a9b8096 feat(crafting): 工作台 3×3 合成界面——右键开台、槽位交换、输出格合成、关闭退回
- 会话层(工作台与合成表.md §5):TryOpenCrafting 右键命中工作台开台;
  CraftingSwap 网格槽与手持栈交换/合并;CraftingTakeResult 匹配配方
  (有序优先)消耗材料取产物;CloseCrafting 材料退回背包(放不下掉落)
- 客户端屏幕状态 3:gui/container/crafting_table.png 176×166 窗口、
  3×3 网格 (30,17) + 输出格 (124,35) + 热键栏行 (7,141)(UI还原.md §3.4);
  ESC 关闭并回收手持栈;界面打开时释放光标、关闭恢复捕获并重置鼠标基准
- 物品图标双图集:block/* 图标回退 blocks 图集(原版 items 图集不收录
  block 纹理,此前方块物品图标全部缺失)
- 单测:shaped 2×2 木板→工作台、shapeless 原木→木板、木镐多符号、
  关闭退回材料;dev 参数 -crafttest 全链路截图验证
2026-08-16 10:23:01 +08:00

217 lines
5.1 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 §6
//
// 设计:回调只更新状态,游戏逻辑在 tick 里消费状态(主循环解耦)。
package main
import (
"math"
"mc/internal/game"
"mc/internal/physics"
"github.com/go-gl/glfw/v3.3/glfw"
)
// inputState 键鼠输入状态。
type inputState struct {
win *glfw.Window
// 按键状态
forward, back, left, right, jump, sneak, sprint bool
// 鼠标
lastX, lastY float64
firstMouse bool
dyaw, dpitch float64 // 累计角度增量弧度tick 消费)
// 按键事件tick 消费)
breakDown bool
breakHeld bool
placeDown bool
selectSlot int // -1 无
// 鼠标与 UI 事件UI与输入.md §6 屏幕交互)
cursorX, cursorY float64
clickX, clickY float32
clicked bool
escPressed bool
}
// newInputState 创建输入状态并注册回调。
func newInputState(win *glfw.Window) *inputState {
in := &inputState{win: win, firstMouse: true, selectSlot: -1}
win.SetKeyCallback(in.keyCallback)
win.SetMouseButtonCallback(in.mouseButtonCallback)
win.SetCursorPosCallback(in.cursorCallback)
win.SetScrollCallback(in.scrollCallback)
return in
}
// keyCallback 键盘回调动作映射WASD/空格/Shift/Ctrl/Esc
func (in *inputState) keyCallback(_ *glfw.Window, key glfw.Key, _ int, action glfw.Action, _ glfw.ModifierKey) {
down := action != glfw.Release
switch key {
case glfw.KeyW:
in.forward = down
case glfw.KeyS:
in.back = down
case glfw.KeyA:
in.left = down
case glfw.KeyD:
in.right = down
case glfw.KeySpace:
in.jump = down
case glfw.KeyLeftShift:
in.sneak = down
case glfw.KeyLeftControl:
in.sprint = down
case glfw.KeyEscape:
if action == glfw.Press {
in.escPressed = true // 暂停/继续由屏幕状态机处理
}
case glfw.Key1, glfw.Key2, glfw.Key3, glfw.Key4, glfw.Key5, glfw.Key6, glfw.Key7, glfw.Key8, glfw.Key9:
if action == glfw.Press {
in.selectSlot = int(key) - int(glfw.Key1)
}
}
}
// mouseButtonCallback 鼠标按键左键破坏按住累积、右键放置UI与输入.md §6 长按)。
func (in *inputState) mouseButtonCallback(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, _ glfw.ModifierKey) {
switch button {
case glfw.MouseButtonLeft:
if action == glfw.Press {
in.breakDown = true
in.clicked = true
in.clickX = float32(in.cursorX)
in.clickY = float32(in.cursorY)
}
in.breakHeld = action != glfw.Release
case glfw.MouseButtonRight:
if action == glfw.Press {
in.placeDown = true
}
}
}
// cursorCallback 鼠标移动yaw/pitch 累积(渲染.md §2 主线程消费)。
func (in *inputState) cursorCallback(_ *glfw.Window, x, y float64) {
in.cursorX, in.cursorY = x, y
if in.firstMouse {
in.lastX, in.lastY = x, y
in.firstMouse = false
return
}
dx, dy := x-in.lastX, y-in.lastY
in.lastX, in.lastY = x, y
const sens = 0.0025
in.dyaw += dx * sens
in.dpitch -= dy * sens
}
// scrollCallback 滚轮切热键栏UI与输入.md §6
func (in *inputState) scrollCallback(_ *glfw.Window, _, yoff float64) {
if yoff > 0 {
in.selectSlot = -2 // 上滚
} else if yoff < 0 {
in.selectSlot = -3 // 下滚
}
}
// apply 每 tick 把输入状态转成玩家动作game.Session 驱动)。
func (in *inputState) apply(s *game.Session) {
cam := s.Cam
p := s.Player
// 视角(俯仰钳制在相机上,而非增量上——旧实现钳增量,抬头可翻转超过 ±90°
if in.dyaw != 0 || in.dpitch != 0 {
cam.Yaw += in.dyaw
cam.Pitch += in.dpitch
in.dyaw, in.dpitch = 0, 0
const maxPitch = math.Pi/2 - 0.01
if cam.Pitch > maxPitch {
cam.Pitch = maxPitch
}
if cam.Pitch < -maxPitch {
cam.Pitch = -maxPitch
}
}
// 游戏中左键=破坏,不是 UI 点击:清除遗留点击状态,防止暂停菜单收到过期点击
in.clicked = false
// 水平移动(物理与碰撞.md §3走/冲刺/潜行)
f := cam.Forward()
r := cam.Right()
wish := [2]float64{}
if in.forward {
wish[0] += f[0]
wish[1] += f[2]
}
if in.back {
wish[0] -= f[0]
wish[1] -= f[2]
}
if in.right {
wish[0] += r[0]
wish[1] += r[2]
}
if in.left {
wish[0] -= r[0]
wish[1] -= r[2]
}
speed := 4.3
if in.sprint {
speed *= 1.3
}
if in.sneak {
speed *= 0.3
}
l := math.Sqrt(wish[0]*wish[0] + wish[1]*wish[1])
if l > 0 {
p.Vel[0] = wish[0] / l * speed
p.Vel[2] = wish[1] / l * speed
} else {
p.Vel[0] = 0
p.Vel[2] = 0
}
// 跳跃
if in.jump && p.OnGround {
p.Jump()
}
// 热键栏切换
switch in.selectSlot {
case -2:
s.Inv.Selected = (s.Inv.Selected + 1) % 9
case -3:
s.Inv.Selected = (s.Inv.Selected + 8) % 9
default:
if in.selectSlot >= 0 && in.selectSlot < 9 {
s.Inv.Selected = in.selectSlot
}
}
in.selectSlot = -1
// 破坏(按住累积,方块交互与动画.md §3
if in.breakDown {
s.BeginBreak()
in.breakDown = false
}
if !in.breakHeld && s.BreakProgress() != 0 {
s.EndBreak()
}
// 放置(右键;命中工作台则打开合成界面,工作台与合成表.md §5
if in.placeDown {
if !s.TryOpenCrafting() {
s.PlaceSelected()
}
in.placeDown = false
}
}
var _ = physics.Player{} // 保持引用(后续输入扩展用)