//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 消费) sens float64 // 灵敏度(设置界面可调,client.yaml mouse_sensitivity) // 按键事件(tick 消费) breakDown bool breakHeld bool placeDown bool selectSlot int // -1 无 toggleInv bool // E 打开/关闭物品栏 dropItem bool // Q 丢弃选中槽 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, sens: 0.0025} win.SetKeyCallback(in.keyCallback) win.SetMouseButtonCallback(in.mouseButtonCallback) win.SetCursorPosCallback(in.cursorCallback) win.SetScrollCallback(in.scrollCallback) return in } // SetSensitivity 设置鼠标灵敏度(弧度/像素,设置界面)。 func (in *inputState) SetSensitivity(s float64) { if s > 0 { in.sens = s } } // Sensitivity 当前灵敏度。 func (in *inputState) Sensitivity() float64 { return in.sens } // 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) } case glfw.KeyE: if action == glfw.Press { in.toggleInv = true // 物品栏开关(UI还原.md §3.3) } case glfw.KeyQ: if action == glfw.Press { in.dropItem = true // 丢弃物品(UI与输入.md §5) } } } // 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 主线程消费)。 // 约定:鼠标右移 dx>0 应右转(yaw 减小——yaw=0 朝向 -Z,右转朝 +X); // 鼠标上移 dy<0 应抬头(pitch 增大)。 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 in.dyaw -= dx * in.sens in.dpitch -= dy * in.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 §5),未命中再破坏方块(按住累积) if in.breakDown { if !s.Attack() { 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 } // 丢弃选中槽 1 个物品(UI与输入.md §5 Q 键) if in.dropItem { s.DropSelected() in.dropItem = false } } var _ = physics.Player{} // 保持引用(后续输入扩展用)