起初,Cocoa 游戏中的键盘事件处理似乎很容易:我在自定义游戏地图视图中添加了 -acceptsFirstResponder 和 -becomeFirstResponder 覆盖,然后覆盖了 -moveUp:、-moveDown:、-moveLeft: 和 -moveRight: 来处理箭头键。
但与大多数其他游戏有一个很大的区别:它一次只接受一次按键。因此,如果我按住向上箭头键让我的角色向前奔跑,然后快速按向右箭头键避开和障碍物,我的角色将停止前进,就像我释放了向上箭头键一样。
这对于文本输入是有意义的,在文本输入中,当另一个手指按下下一个手指时,一个人可能会意外地按住一个角色,但对于游戏来说,这很烦人。
如何将任意组合键和弦在一起?
解决方案是自己跟踪哪个键关闭。覆盖-keyDown
和-keyUp
以跟踪按住的键。我为此使用了C++ unordered_set
,但 Objective-C NSIndexSet
也可以正常工作:
@interface ICGMapView : NSView
{
std::unordered_set pressedKeys;
}
@end
在实施中:
-(void) keyDown:(NSEvent *)theEvent
{
NSString * pressedKeyString = theEvent.charactersIgnoringModifiers;
unichar pressedKey = (pressedKeyString.length > 0) ? [pressedKeyString characterAtIndex: 0] : 0;
if( pressedKey )
pressedKeys.insert( pressedKey );
}
-(void) keyUp:(NSEvent *)theEvent
{
NSString * pressedKeyString = theEvent.charactersIgnoringModifiers;
unichar pressedKey = (pressedKeyString.length > 0) ? [pressedKeyString characterAtIndex: 0] : 0;
if( pressedKey )
{
auto foundKey = pressedKeys.find( pressedKey );
if( foundKey != pressedKeys.end() )
pressedKeys.erase(foundKey);
}
}
然后向类添加一个NSTimer
,该定期检查是否按下了任何键,如果是,则对它们做出反应:
-(void) dispatchPressedKeys: (NSTimer*)sender
{
BOOL shiftKeyDown = pressedKeys.find(ICGShiftFunctionKey) != pressedKeys.end();
for( unichar pressedKey : pressedKeys )
{
switch( pressedKey )
{
case 'w':
[self moveUp: self fast: shiftKeyDown];
break;
...
}
}
}
由于您的计时器在这里以一定的间隔轮询,并且您不能使该间隔太快,因为它是发送按键重复的速率,因此理论上您可能会丢失持续时间短于计时器间隔的按键。为了避免这种情况,您可以将结构存储在数组中,而不仅仅是将按键存储在集合中。此结构将记住最初按下键的时间,以及发送最后一个键事件的时间。
这样,当用户开始按住某个键时,您将立即触发一次此键的处理,并记下何时发生。从那时起,您的-dispatchPressedKeys:
方法将检查自上次处理该特定密钥以来它是否足够长,并将为每个到期密钥发送密钥重复。作为奖励,当释放密钥时,您也可以通知自己。