碎碎念
这次还是在自己写玩具的时候碰到了一点难题然后花了大量时间所以想着发一篇博文记录一下
这个东西我写了两天才写出来
想要解决的问题
实现组合按键的检测,也就是实现当数个按键在连续的一段时间内按顺序按下并按住时才触发即返回true
如果闲我话多可以直接翻到最后看代码
我写了注释&&注释没删
问题分析
解决方案的选择
因为涉及到时间尺度的变化,所以显然不可能用流式存储的方式存储一系列按键状态来解决“一定时间中”这个持续变量的问题。
同时由于按键监测的要求是在一段时间内 按顺序 按住 组合键中声明的按键,所以可以通过一个可以变动的下标来表示在当前时刻“合法”的按键簇(雾)
类的初始化
总之就是灵光乍现什么的然后我们在最开始有了这样一个类的声明
class FnKeys:
__i = 0 # 数组下标,标记当前键 # 私有化因为害羞⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
def __init__(self, mod: str, keys: [int]):
# 初始化,以列表格式记录组合键包含哪些键,
# 比如[pygame.K_a, pygame.K_s, pygame.K_d]表示一个包含a,s,d三个键的组合键
if type(keys) == int:
keys = [keys]
更多的分析
有了下标,那相应的就应该让下标能有办法够增长。为了能够实现同时按下多个按键能够被检测到,我想到用下面这一段代码|来实现
while self.__i < len(self.keys):
if keyMap[self.keys[self.__i]]:
self.__i += 1
else:
break
# 记住这个坑。就是因为这个部分,害的我把check函数重新写了好多遍才找出问题
对输入的分析
因为要检测的是组合键,所以检测用的函数需要传入用来表示按键映射的列表或数组。于是我们有了抽象函数|·+·|
def check(self, keyMap: [int]):
对输入的时间轴分析
先声明一下,这个类的check方法是基于主循环写的,所以check函数所做的其实必须是帧检查(我把循环跑一遍当做一帧),不能写成抢占式检测最大的难点。
由此我产生了对输入组合键可能性的思考…折寿中…
经过一段时间的思考后,我想出来以下四种情况
不同状态 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
上一帧 | 0 - ★ | ★ 0 - | -★0 | - - - |
这一帧 | ★★★ | ★★★ | - - - | - - - |
期望结果 | False | False | True | True |
图形解释:
- 为释放状态,按键松开;
0 为按住状态,按键被按住;
★ 为任意状态,此时按键处于薛定谔态(即不管这个键的状态)
一串图形代表组合键中声明的多个按键在同一时刻的状态
当时我的思路是依据下标所在的键(当前键)做分割,分别处理声明组合键列表self.keys
中当前键之前的部分(前键,前键下标小于当前键)和之后的部分(后键,后键下标大于当前键),然后分类讨论。
对“前键”的处理
对前键的判断直接关系到状态1、3、4的结果。
如果组合键生效,那么显然前键必须全部都被按住。同时如果前键中有任意键被松开,则组合键失效。对应以下代码
# 前键检测
j = self.__i - (self.__i == len(self.keys))
while j >= 0:
if keyMap[self.keys[j]]: # 如果前键(含当前键)有键被释放则j!=-1同时前键上锁
j -= 1
else:
break
if j != -1:
self.__lock_a = False # 上锁,因为前键中有键被释放
else:
self.__lock_a = self.__lock_b # 上锁,因为前键全部都被按住,如果后键已锁,则不允许跳变。如果跳变与后键锁无关则状态1,2会出现非预期结果
对“后键”的处理
对后键的判断直接影响状态1,2,3三种结果
如果组合键生效,则显然后键不存在,此时不考虑后键即可。
但是在按键未全部按下(比如组合键ctrl+v键按住了ctrl键但是v键没按下或者v键按下了但ctrl键没有按下)时,后键中必须全部都没有按下才允许当前键向后推进,否则容易出现预料之外的状况(比如只按下v键但结果产生了ctrl+v的效果)。
后键代码如下
if not self.__lock_a: # 如果前键锁锁住了,则显然产生了回滚,此时一旦后键中有键被按下,则上后键锁
# 前键已锁->发生回滚->当前键已被按下且下一个键一定未被按下
j = self.__i + 1
while j < len(self.keys):
if not keyMap[self.keys[j]]:
j += 1
else:
break
self.__lock_b = j == len(self.keys)
else: # 如果前键未锁,则向后推进,因为输入随时间变动,所以需要向后推进再判断后键锁
# 前键未锁->没有发生回滚->当前键已被按下->可以+1来减少对当前键的判断
# if self.__i == len(self.keys): # 如果当前键是最后的键,则可以跳过后键检测
j = self.__i + 1
if self.__lock_b: # 只有当后键锁未锁时向后推进是有效的,如果后键被锁,则只有后键全部被释放时才解锁
# 如果后键锁住时推进会导致全键按住时后键锁无效
while j < len(self.keys):
if keyMap[self.keys[j]]: # 向后找到首个未被按住的键
j += 1
else:
j += 1 # +1 因为此时j键为被按住顺序中最后的键后面的键,+1来跳过未被按住的一个键
break
while j < len(self.keys):
if not keyMap[self.keys[j]]: # 向后找到首个被按住的键
j += 1
else:
self.__lock_b = False # 上锁,因为当前键的非邻后键中有键被按住
break
else:
self.__lock_b = True # 解锁,因为所有后键都被释放
期间还有一些东西我忘记了,但反正是很重要的细节,不过最后的代码成品是对的,如果和先前的分析有什么不一样的地方,以成品代码为准。
放在最后的成品类
"""
Time: 2022/6/25
Author: Zhai H***g(ZII)
Version: alpha-0.0.1
File: 抽象-事件-栈.py
Describe: 组合键按键的帧检测
"""
class FnKeys: # 这样一个组合键就是一个对象
__i = 0 # 配套check,数组下标
__lock_a = True # 配套check,用来标记前键状态是否合法。True为合法。如果前键不合法则一直保持False直到所有键被松开
__lock_b = True # 配套check,用来标记后键状态是否合法。True为合法。如果后键不合法则一直保持False直到所有非邻后键被松开
def __init__(self, mod: str, keys: [int]):
# 初始化,以列表格式传入组合键包含哪些键,
# 比如[pygame.K_a, pygame.K_s, pygame.K_d]表示一个包含a,s,d三个键的组合键
if type(keys) == int:
keys = [keys]
def check(self, keyMap) -> bool: # 传入一个按键的列表,比如pygame.key.get_gressed()
"""装饰:有序"""
def rollBack(): # i值回滚
i = 0
while i < len(self.keys):
if keyMap[self.keys[i]]:
i += 1
else:
break
return i
# 前键检测
j = self.__i - (self.__i == len(self.keys))
while j >= 0:
if keyMap[self.keys[j]]: # 如果前键(含当前键)有键被释放则j!=-1同时前键上锁,i回滚
j -= 1
else:
break
if j != -1:
self.__lock_a = False # 上锁,因为前键中有键被释放
self.__i = rollBack()
else:
self.__lock_a = self.__lock_b # 上锁,因为前键全部都被按住,如果后键已锁,则不允许跳变
# 后键检测
if not self.__lock_a: # 如果前键锁锁住了,则显然产生了回滚,此时一旦后键中有键被按下,则上后键锁
# 前键已锁->发生回滚->当前键已被按下且下一个键一定未被按下
j = self.__i + 1
while j < len(self.keys):
if not keyMap[self.keys[j]]:
j += 1
else:
break
self.__lock_b = j == len(self.keys)
else: # 如果前键未锁,则向后推进,因为输入随时间变动,所以需要向后推进再判断后键锁
# 前键未锁->没有发生回滚->当前键已被按下->可以+1来减少对当前键的判断
# if self.__i == len(self.keys): # 如果当前键是最后的键,则可以跳过后键检测
j = self.__i + 1
if self.__lock_b: # 只有当后键锁未锁时向后推进是有效的,如果后键被锁,则只有后键全部被释放时才解锁
# 如果后键锁住时推进会导致全键按住时后键锁无效
while j < len(self.keys):
if keyMap[self.keys[j]]: # 向后找到首个未被按住的键
j += 1
else:
j += 1 # +1 因为此时j键为被按住顺序中最后的键后面的键,+1来跳过未被按住的一个键
break
while j < len(self.keys):
if not keyMap[self.keys[j]]: # 向后找到首个被按住的键
j += 1
else:
self.__lock_b = False # 上锁,因为当前键的非邻后键中有键被按住
break
else:
self.__lock_b = True # 解锁,因为所有后键都被释放
# i的推进 当任何锁被锁住时禁止推进
while self.__i < len(self.keys) and self.__lock_a and self.__lock_b:
if keyMap[self.keys[self.__i]]:
self.__i += 1
else:
break
# i的回缩 # 回缩同样与推进同标准,即只有当无锁时才允许回滚
if self.__i < len(self.keys) and self.__lock_a and self.__lock_b:
self.__i = rollBack()
# 返回结果
# print('a ', self.__lock_a, ' b ', self.__lock_b, 'i', self.__i, end='') # 检测窗口
if self.__i == len(self.keys) and self.__lock_a and self.__lock_b:
return True
else:
return False
允许个人及商业使用,使用请注明作者及出处(╹◡╹)
转载及使用请注明:
出处CSDN
作者:一只正在学编程的仓鼠