Make Your Game Input Robust
仅供个人学习使用,请勿转载,勿用于任何商业用途。
如果你读过 之前 那篇关于InputManager的文章,那么非常抱歉,最近发现那样的设计是有缺陷的。我又一次被隐藏的复杂性打败了-_-#。
问题一,以什么频率进行输入更新。对于XNA framework来说,每次程序主循环调用Update时,都会刷新KeyboardState和MouseState,通过这2个对象,可以查询某个按键是否处于pressed或者released状态。那么是否每次update时同时更新IM的状态呢? 显然不行。通常来说,人的输入速度极限是相对固定的,比如每秒击键n次,并且只可能在n附近的范围内小幅波动。而Update则不一样,可能每秒更新30次,60次或者更多。之前的文章讨论过合适的更新频率,不再重复。这里要提醒的是过高和过低的更新频率都将会让输入变的不精确。过低的更新频率导致的输入失真是显而易见的。但为什么过高也不行呢?当然,这里IM更新的最高频率只能小于等于主循环update的频率。设想用户正在通过敲击键盘输入文字,假设用户的极限击键速度是0.05秒,也就是至少有0.05秒按键将处于pressed的状态。好了,如果你0.01秒更新一次IM状态,那么在0.05秒内将触发5次KeyPressed事件,结果是用户敲击一个按键,就出现5个相同的字母。当然,可以通过取巧的方法来解决这个问题:只通过KeyDown事件完成输入,如果要重复输入同一文字,则必须反复击键而不能通过一直按下按键来完成——虽然不完美,但也算是可以接受的。
问题二:IM是否该提供IsKeyDown,IsKeyUp,IsMouseDown,IsMouseUp这四个函数。对问题1的结论稍加推测,你就可以发现答案是否定的。这里明确一下Down和Up的定义,它们描述的都是按键状态改变*瞬间*的状态。也就是说t1时刻a处于released状态,t2时刻a被按下,此时a处于down状态,同时也处于pressed状态,t2之后的任何时刻,即使a仍处于pressed状态,但都不满足down状态。当然,由于采样可能存在的误差,不可能精确的恰好在t2时检测到keydown状态,通常是在t2之后某个很短的时间内才检测到,不过这并不会有太大影响。 有了这个定义,就可以发现,只有在IM的更新频率和主循环的更新频率相同时,这4个函数才有可能正确工作。假设2者更新频率不同,t1时刻,Update和IM同时进行更新,IM发现键a刚好被按下,于是标记为keydown和pressed状态。Update中的逻辑代码通过IsKeyDown查询按键a,获得了keydown和pressed信息;t2时刻,Update再一次被调用,但IM的更新间隔还没有到,这时逻辑代码再次检测a键的状态,将会再次获得keydown信息,而这次的消息显然是错误的。当然,如果你的主循环和IM都以30fps的速率更新,则不会有这样的问题,不过,为了满足更通用的情况,应该完全删除这4个函数,down和up消息都只能通过事件获得,而无法查询。
问题三:IM是否该记录按键被按下的时间。从实践上来看,需要查询按键被按下时间长度的情景是非常少的。不过这并不能成为是否需要这个储存这个值的主要因素。如果继续对问题二的讨论,你就会发现IM所记录的按键时间同样是带有误导性的,只有在IM的更新频率和主循环的更新频率相同时正确。所以同样同样应该删除这个变量,让逻辑代码自己实现是否记录按键所按下的时间。
问题四:IM是否应该实现click和doubleclick事件。这是我常常看到人问的问题。在IM中添加这2个事件是非常容易的,问题在于当click事件发生以后,谁应该获得这个消息呢?我是这样定义click事件的:当鼠标在某个对象的屏幕范围内,很短时间里相继触发了同一按键的Down和Up事件,则称为一次click。从定义可以看出,问题的关键在于click依赖于某一个IM之外的对象,我们不能仅凭IM在很短时间内探测到的连续Down,Up事件,就触发一次click。很有可能是在a物体的屏幕范围内发生的down,然后在b物体的范围内发生了up,这时无论a还是b都不应该获得click事件。因此,click应该属于更高层的类中,不应该出现在IM里。