18. 输入,判断,连招
3.14,周六,宅。
a327ex/boipushy 的输入模块
lua
local input_path = (...):match('(.-)[^%.]+$') .. '.'
local Input = {}
Input.__index = Input
-- 把键盘、鼠标、手柄所有可能的按键都列出来,方便后续统一处理
Input.all_keys = {
" ", "return", "escape", "backspace", "tab", -- ... 省略一大堆按键名 ...
}
function Input.new()
local self = {}
-- 【核心数据结构】
self.prev_state = {} -- 上一帧的按键状态
self.state = {} -- 当前的按键状态(把键盘、鼠标、手柄的状态全混在一起存)
self.binds = {} -- 动作绑定表
self.functions = {} -- 直接绑定到按键的函数
self.repeat_state = {} -- 记录按住连发的状态
self.sequences = {} -- 记录“搓招/连招”的状态
-- 获取手柄(作者偷懒了,目前只支持玩家1的手柄)
self.joysticks = love.joystick.getJoysticks()
-- 【黑科技:自动劫持(Hook)LÖVE 的底层事件】
-- 这里把 LÖVE 引擎原生的按键回调函数全部拦截下来,
-- 在执行你写的原生代码之前,先执行 Input 自己的记录逻辑。
local callbacks = { 'keypressed', 'keyreleased', 'mousepressed', 'mousereleased', 'gamepadpressed', 'gamepadreleased',
'gamepadaxis', 'wheelmoved', 'update' }
local old_functions = {}
local empty_function = function() end
for _, f in ipairs(callbacks) do
old_functions[f] = love[f] or empty_function
love[f] = function(...)
old_functions[f](...) -- 先执行引擎原有的功能
self[f](self, ...) -- 再执行本库自己对应的函数(比如往 self.state 里记录按键)
end
end
return setmetatable(self, Input)
endlua
-- 绑定功能:不仅能把键绑定给“字符串动作”,还能直接绑定给一个“函数”
function Input:bind(key, action)
if type(action) == 'function' then
self.functions[key] = action; return
end
if not self.binds[action] then self.binds[action] = {} end
table.insert(self.binds[action], key)
end
-- 判断动作是否“刚刚按下”
function Input:pressed(action)
if action then
-- 遍历这个动作对应的所有按键,有一个刚按下就算 true
for _, key in ipairs(self.binds[action]) do
if self.state[key] and not self.prev_state[key] then return true end
end
else
-- 如果没传 action,就检查是否触发了直接绑定的函数
for _, key in ipairs(Input.all_keys) do
if self.state[key] and not self.prev_state[key] then
if self.functions[key] then self.functions[key]() end
end
end
end
end
-- 判断动作是否“刚刚松开”
function Input:released(action)
for _, key in ipairs(self.binds[action]) do
if self.prev_state[key] and not self.state[key] then return true end
end
end
-- 【特色功能:按住并支持“连发” (Auto-repeat)】
-- delay: 按下多久后开始连发
-- interval: 连发的时间间隔
function Input:down(action, interval, delay)
if action and delay and interval then
-- 带有初始延迟的连发(类似长按删除键,先删一个,等一小会儿,然后开始狂删)
-- ... 逻辑省略 ...
elseif action and interval and not delay then
-- 没有初始延迟,直接开始按频率连发
-- ... 逻辑省略 ...
elseif action and not interval and not delay then
-- 最普通的“按住”检测(回退到轮询 LÖVE 原生的 isDown 方法)
-- ... 逻辑省略 ...
end
end
-- 解除绑定
function Input:unbind(key)
-- ...
endlua
-- 【特色功能:搓招/连招系统 (Sequence)】
-- 用法类似:input:sequence('up', 0.5, 'down', 'combo')
-- 意思是:按下上,在0.5秒内按下下,触发'combo'动作
function Input:sequence(...)
local sequence = { ... }
-- ... 参数合法性检查省略 ...
-- 把传入的参数拼成一个字符串作为唯一标识符
local sequence_key = ''
for _, seq in ipairs(sequence) do sequence_key = sequence_key .. tostring(seq) end
-- 状态机:检查玩家按下的顺序和时间间隔是否符合要求
if not self.sequences[sequence_key] then
self.sequences[sequence_key] = { sequence = sequence, current_index = 1 }
else
-- ... 具体的连招判定逻辑,通过 love.timer.getTime() 计算按键时间差 ...
end
end
-- ... 各种设备按键名称映射表省略 ...lua
-- 每帧更新(由于前面劫持了 love.update,这个函数也会自动被调用)
function Input:update()
self:pressed()
self.prev_state = copy(self.state) -- 备份上一帧状态
self.state['wheelup'] = false -- 滚轮状态用完即清
self.state['wheeldown'] = false
-- 处理按住连发的时间计时器
-- ...
end
-- 以下是一系列被 LÖVE 底层触发的回调函数,作用都是把按键状态写入 self.state
function Input:keypressed(key) self.state[key] = true end
function Input:keyreleased(key) self.state[key] = false; self.repeat_state[key] = false end
function Input:mousepressed(x, y, button) self.state[button_to_key[button]] = true end
-- ... 鼠标松开、滚轮、手柄按下/松开/摇杆的记录逻辑省略 ...
return setmetatable({}, { __call = function(_, ...) return Input.new(...) end })SNKRX 的输入模块
lua
Input = Object:extend()
-- 初始化函数:当你创建一个 Input 对象时,首先会运行这里的代码
function Input:init(joystick_index)
-- 定义鼠标的按键名字(左键、右键、中键、侧键、滚轮上下)
self.mouse_buttons = {"m1", "m2", "m3", "m4", "m5", "wheel_up", "wheel_down"}
-- 定义手柄的按键名字(上下左右、十字键、开始、返回等)
self.gamepad_buttons = {"fdown", "fup", "fleft", "fright", "dpdown", "dpup", "dpleft", "dpright", "start", "back", "guide", "leftstick", "rightstick", "rb", "lb"}
-- 手柄按键和摇杆的映射表(为了兼容不同手柄的叫法)
self.index_to_gamepad_button = {["a"] = "fdown", ["b"] = "fright", ["x"] = "fleft", ["y"] = "fup", ["back"] = "back", ["start"] = "start", ["guide"] = "guide", ["leftstick"] = "leftstick",
["rightstick"] = "rightstick", ["leftshoulder"] = "lb", ["rightshoulder"] = "rb", ["dpdown"] = "dpdown", ["dpup"] = "dpup", ["dpleft"] = "dpleft", ["dpright"] = "dpright",
}
self.index_to_gamepad_axis = {["leftx"] = "leftx", ["rightx"] = "rightx", ["lefty"] = "lefty", ["righty"] = "righty", ["triggerleft"] = "lt", ["triggerright"] = "rt"}
self.gamepad_axis = {} -- 记录手柄摇杆的推动程度
-- 获取连接的第几个手柄,默认是第1个
self.joystick_index = joystick_index or 1
self.joystick = love.joystick.getJoysticks()[self.joystick_index]
-- 【核心概念:状态追踪】
-- 这里分别记录键盘、鼠标、手柄“当前帧”和“上一帧”的按键状态
self.keyboard_state = {}
self.previous_keyboard_state = {}
self.mouse_state = {}
self.previous_mouse_state = {}
self.gamepad_state = {}
self.previous_gamepad_state = {}
self.actions = {} -- 用来存放你绑定的动作(比如 "jump" 对应 空格键)
self.textinput_buffer = '' -- 用来存放玩家打字的缓存
end
-- 更新函数:游戏每运行一帧(大概每秒60次),就会执行一次这个函数
function Input:update(dt)
-- 第一步:先把你绑定的所有动作的状态清零
for _, action in ipairs(self.actions) do
self[action].pressed = false -- 刚刚按下
self[action].down = false -- 一直按着
self[action].released = false -- 刚刚松开
end
-- 第二步:检查每个动作对应的按键状态
for _, action in ipairs(self.actions) do
for _, key in ipairs(self[action].keys) do
-- 如果这个键是鼠标按键
if table.contains(self.mouse_buttons, key) then
-- 核心逻辑:如果当前帧按下了,但上一帧没按下,说明是“刚刚按下” (pressed)
self[action].pressed = self[action].pressed or (self.mouse_state[key] and not self.previous_mouse_state[key])
-- 如果当前帧按着,说明是“一直按着” (down)
self[action].down = self[action].down or self.mouse_state[key]
-- 如果当前帧没按,但上一帧按了,说明是“刚刚松开” (released)
self[action].released = self[action].released or (not self.mouse_state[key] and self.previous_mouse_state[key])
-- 如果这个键是手柄按键,逻辑同上
elseif table.contains(self.gamepad_buttons, key) then
self[action].pressed = self[action].pressed or (self.gamepad_state[key] and not self.previous_gamepad_state[key])
self[action].down = self[action].down or self.gamepad_state[key]
self[action].released = self[action].released or (not self.gamepad_state[key] and self.previous_gamepad_state[key])
-- 否则就是键盘按键,逻辑同上
else
self[action].pressed = self[action].pressed or (self.keyboard_state[key] and not self.previous_keyboard_state[key])
self[action].down = self[action].down or self.keyboard_state[key]
self[action].released = self[action].released or (not self.keyboard_state[key] and self.previous_keyboard_state[key])
end
end
end
-- 第三步:把当前帧的状态保存起来,作为下一帧的“上一帧状态”
self.previous_mouse_state = table.copy(self.mouse_state)
self.previous_gamepad_state = table.copy(self.gamepad_state)
self.previous_keyboard_state = table.copy(self.keyboard_state)
-- 鼠标滚轮的状态只保留一帧,用完即清空
self.mouse_state.wheel_up = false
self.mouse_state.wheel_down = false
end
-- 锁定鼠标(让鼠标出不去游戏窗口)
function Input:set_mouse_grabbed(v)
love.mouse.setGrabbed(v)
end
-- 设置鼠标是否可见
function Input:set_mouse_visible(v)
love.mouse.setVisible(v)
end
-- 【重要】绑定函数:把物理按键(如 "space")绑定到逻辑动作(如 "jump")
function Input:bind(action, keys)
if not self[action] then self[action] = {} end
if type(keys) == "string" then self[action].keys = {keys}
elseif type(keys) == "table" then self[action].keys = keys end
table.insert(self.actions, action)
end
-- 取消绑定
function Input:unbind(action)
self[action] = nil
end
-- 获取手柄摇杆的值
function Input:axis(key)
return self.gamepad_axis[key]
end
-- 接收玩家打字的文本
function Input:textinput(text)
self.textinput_buffer = self.textinput_buffer .. text
return self.textinput_buffer
end
-- 获取并清空打字缓存(比如按了回车键发送消息后清空)
function Input:get_and_clear_textinput_buffer()
local buffer = self.textinput_buffer
self.textinput_buffer = ""
return buffer
end
-- 暴力绑定:给键盘上所有的键都绑定一个和自己名字一样的动作
-- 通常用于制作输入框、或者不需要自定义改键的内部工具
function Input:bind_all()
local keyboard_binds = {['a'] = {'a'}, ['b'] = {'b'}, ... } -- 省略了长长的列表
for k, v in pairs(keyboard_binds) do self:bind(k, v) end
self:bind('m1', {'m1'})
-- ... 绑定鼠标等
end