Author: Zibin Zheng
http://www.cse.cuhk.edu.hk/~zbzheng
canvas中的keyEvent具有局限性,它对大小写字符不敏感,对连续按键的无法处理,使得它无法处理字符的输入,所以对于需要字符输入功能要用 textfield或是textbox才行。因此如果想在canvas中自己编码来实现文本输入的功能是不现实的,还是使用高级界面类textbox等来 进行文本输入的实现比较方便。
ITU-T standard telephone keypad 定义了12个通用的手机按钮,为了让写出来的游戏尽可能的容易移植和通用,游戏设计时要尽量的用这些键:
KEY_NUM0, KEY_NUM1, KEY_NUM2, KEY_NUM3, KEY_NUM4, KEY_NUM5, KEY_NUM6, KEY_NUM7, KEY_NUM8, KEY_NUM9, KEY_STAR, and KEY_POUND
MIDP defines the following game actions: UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B, GAME_C, and GAME_D.
因为上面提到的这些按键是标准,每个手机厂家都需要将自己手机的功能键与它们进行映射,这个工作是有函数getGameAction()来完成的,这个函 数由不同的手机厂商根据自己手机的不同情况写好,放于手机中。提供给系统调用。不同的手机的功能键并不一样,因此每个厂家给出的 getGameAction()是不一样的,在编写相关程序的时候要小心处理可能会出现的情况。
手机分为有方向键和没有方向键的手机,对于有方向键的手机,要注意下面的情况:
有些手机将数字键也进行了映射,比如:
getGameAction(Canvas.KEY_NUM8); //当按下8键时,它返回的是Canvas.DOWN
getGameAction(-2); //当按下功能键” 下”,它返回的也是Canvas.DOWN
对于这种情况,那么反过来:
用getKeycode(Canvas.DOWN), 系统会在-2和KEY_NUM8中选择一个返回,返回结果就具有不确定性了,这里容易出现问题。 因此为了程序的通用性,getKeycode这个函数应该尽量避免使用。
从编程的角度,一个游戏需要对方向键和2,4,6,8键作为方向键这两种情况进行相同的处理,可以按下面的做法:
将上下左右映射到2,4,6,8;然后程序中只对2,4,6,8键进行处理,
Public keyPressed(int keyCode){
Int key = mergeKey(getGameAction(keyCode), keyCode);
switch(key){
case KEY_NUM2:
…………….
Case KEY_NUM4:
………………..
Case KEY_NUM6:
……………….
Case KEY_NUM8:
………………
}
特别注意mergeKey函数的用处是将两种按键映射到2,4,6,8键。比如:
MergeKey(UP, -1) à KEY_NUM2;
MergeKey(0, KEY_NUM2) à KEY_NUM2;
mergeKey中需要注意一种特殊情况,就是当用户按下:KEY_NUM2,而getGameAction(KEY_NUM2)得到的是UP,那么就出现了 mergeKey(UP, KEY_NUM2)的现象。函数中要处理这种特殊的情况
上面keyPress函数的写法只是为了方便说明,它不好的地方在于将按钮从处理工作switch写在了keyPressed的函数中,因为 keyPressed()的处理时间应该尽可能短,不然用户连续的按键的时候就会出现按键丢失的现象,因此可以用一个全局的变量gkey来暂时存放经过 merge的key,然后由canvas的画面线程由run函数中的while中处理。这个全局变量gkey需要注意一个“用完即0”的概念,避免出现按 键丢失的问题。这个思路同时也防止了相关操作多次调用.比如:
Process(gkey);
Gkey = 0;
这种写法当process运行的时间太长的话,那么会出现短时间内用户再按键,他的按键会因为=0 那一句而发生丢失。所以要用下面方法写会好一点:
Gkey_temp = gkey;
Gkey = 0;
Process(gkey_temp);
关于连续按键的实现是通过在keyPressed函数中用一个变量keyPressing 来保存gkey并记录下按下的时间, 在keyReleased函数中将keyPressing清0。在按键处理的地方对按键按下的时间进行判断,如果时间大于500毫秒的话那么就用 switch(keyPressing)的做法, 因为keyPressing在用户松开按键后才会清0,而run又是一直调用按键处理, 所以会形成多次调用,从而形成连续按键的效果.
连续按键的速度调整问题,上面的500毫秒只是决定了其开始的快慢而已. 而一秒调用多少次按键处理是由run的一秒循环多少次决定的. ZhanGuoMap设置为50毫秒,就是一秒run20次.以一次移动一个方格计算,按住后等500毫秒启动,一秒会移动20个格。
这里要注意可能出现的一个问题:
原因:(wtk2.2/doc中关于canvas的介绍)
The key-related, pointer-related, and paint() methods will only be called while the Canvas is actually visible on the output device. These methods will therefore only be called on this Canvas object only after a call to showNotify() and before a call to hideNotify().
上面的第2步已经将当前的屏幕的显示设置为form,canvas不再是当前显示类,它的keyReleased函数自然不会被调用,造成keypressing不会被清0。
解决方法:
在display.setcurrent()调用之前先了sleep一段时间,让系统有时间先调用keyReleased函数,然后再换屏幕显示。在模拟器上测试过sleep100毫秒可以解决问题,50毫秒就不够,但是到手机上100毫秒是否足够?没有试过。
【结论】不要认为keyPressing变量在按键松开一定会被清0,在屏幕切换的特殊情况下它是有可能不会被清0的!!!
keyPressed()是在线程sleep的时候引发的,也就是说当Canvas这个线程在空闲状态时,KVM才有机会向激活的Display传递消息 说有人按了某键, keyPressed()是在Canvas线程sleep的时候被引发的。这也是按键不响应或延时的根本原因:Canvas线程过于繁忙,没有sleep 或很少sleep。解决办法:
(具体见参考文章1)
战国中组合键(*3)的做法, 在keyPressed函数中用一个变量gAltKey存放* 或是# 下一次按需要判断是否上一次按了拿两个键,如果是对两次的按键进行移位和或操作,形成一个新的组合键赋值给gkey, 在keyHandler那里还是只要对gkey进行判断处理就可以了。 注意gAltKey在keyPressed函数中一赋值给gkey就马上清0, 而在run中需要对gAltKey存在时间进行判断,多于1秒那么就没有效了,清0.
参考文章: http://www.bestsoft.net.cn/Article/other/2006-5/31130.html (J2ME键盘响应详解)
TODO: 1. 关于复杂组合键有什么比较好的实现方法?