Files
ngzz-mc/cmd/client/input.go
NianGao Dev f25ae15077 feat(game): 音频接入、原版掉落、动物攻击、村庄村民、种子/灵敏度配置与设置界面
- 音频(音频.md §2):Session.Sound 事件回调,破坏/放置/攻击/村民环境音接入,
  客户端 audio.Manager 3D 衰减 + 监听器跟随 + 音量可调
- 原版掉落:草方块→泥土、石头→圆石、树叶无掉落(no_drop);掉落物实体
  用物品图标小广告牌渲染(物品纹理并入实体图集)
- 动物攻击(动物体系.md §5):左键优先射线命中实体(半径 0.6 球体判定),
  伤害/击退/死亡移除+肉类掉落;僵尸掉腐肉
- 村庄村民(村民系统.md §2):Generator.VillageAt 定位村庄中心,会话扫描
  已加载区块在村庄生成 3 名村民
- 种子与灵敏度配置:client.yaml world.seed/name、mouse_sensitivity;
  存档与种子联动;鼠标左右反转修复(右移应右转)
- 设置界面(UI与输入.md §6):屏幕状态 4,灵敏度/音量滑条、世界种子显示、
  按键教程多行列表(cjkfont 支持 \\n)、完成按钮;主菜单/暂停菜单入口
- 准星指向方块名:左下角 CJK 显示(lang 本地化,tick 光栅化渲染线程上传)
- 截图统一存项目根目录 jietu/(paths.RootDir;dev 构建识别 bin/ 布局)
2026-08-16 20:18:52 +08:00

231 lines
5.7 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 消费)
sens float64 // 灵敏度(设置界面可调,client.yaml mouse_sensitivity)
// 按键事件(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, 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)
}
}
}
// 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
}
}
var _ = physics.Player{} // 保持引用(后续输入扩展用)