返回列表 发帖

[转]J2ME键盘响应详解及处理方法

J2ME上的键盘响应估计是继画图后出现Bug最多的地方了,尤其像手机游戏这种键盘操作较多的J2ME程序。在工作的过程中CoCoMo曾不止一次的被问及有关键盘响应的问题,pigham前两天还在为他的游戏在7210上按键不能及时响应而发愁,就在刚才我还在努力的解决着S700上不支持getGameAction()的问题。虽然CoCoMo在这个行业已经工作了一年多了,但是键盘响应的bug仍然时常蹦出来刺激我的神经,所以千万不要小瞧了这个不起眼的keyPressed() keyPressed()响应的位置: 弄清楚keyPressed()响应的位置对最终解决按键响应不及时很有帮助。理论上keyPressed()是由KVM负责的,当Canvas的子类被Display.setCurrent()之后,只要按下任何按钮就会引发keyPressed()。但这只是某些人一厢情愿的美好愿望,仅限于理论研究的范畴,理论和实际往往相差甚远不是吗。实际上keyPressed()的响应是有位置的,CoCoMo可以用如下程序做一个实验: while(b_running) { updateState(); repaint(); serviceRepaints(); long d = System.currentTimeMillis() - s_curFrameTime; if (d < 80) Thread.sleep(80 - d); s_curFrameTime = System.currentTimeMillis(); } 这是一个传统意义上的游戏循环,CoCoMo分别在updateState(),paint()和keyPressed()中加入调试语句如下: void updateState() { System.out.println("UpdateState"); } protected void paint(Graphics g) { System.out.println("Paint"); } protected void keyPressed(int keyCode) { System.out.println("keyPressed"); } protected void keyReleased(int keyCode) { System.out.println("keyReleased"); } 然后启动MIDlet,一阵狂按之后,得出如下结果: UpdateState Paint keyReleased UpdateState Paint keyPressed UpdateState Paint keyReleased keyPressed 你会惊奇的发现keyPressed()和keyReleased()总是在paint()之后,UpdateState()之前。实际上keyPressed()是在线程sleep的时候引发的,也就是说当Canvas这个线程在空闲状态时,KVM才有机会向激活的Display传递消息说有人按了某键,或者KVM总能向Display传递消息,但Display需要在空闲状态时才能调用keyPressed(),至于是哪种方式那是底层实现的事情,我们并不得而知,但有一点是可以肯定的:keyPressed()是在Canvas线程sleep的时候被引发的。这也是按键不响应或延时的根本原因:Canvas线程过于繁忙,没有sleep或很少sleep。解决办法: 一:优化程序,减轻Canvas线程负担,使sleep时间增加。 二:在优化后情况仍然存在,可以在需要按键响应的地方强行使线程sleep(20),从而引发keyPressed()。在我的一个项目中需要频繁创建图片,致使线程过度繁忙,即便在S700这样的机型上也出现了按键响应不及时的问题,我就是这么处理的,对帧速率影响并不大。 J2ME按键处理方法: 一:按键逻辑直接写在keyPressed()里 优点:测试时经常使用,对于短小程序编写速度快。 缺点:需要在keyPressed()里再写个switch(gameState)状态机,这样的缺点估计地球人都知道了。而且将逻辑运算写进keyPressed()里影响keyPressed()的响应。 二:将keyPressed()里的键值提取,在需要的地方做判断。 基本上现在手机游戏编写都使用这种方法了,实现方式也千变万化,最简单的就是定义一个curKey变量,在keyPressed()里将其赋值,在keyReleased()里将其清空,在updateState()里判断该变量的值。这里需要一个keyCode表来对应curKey是什么键,类似这样: public static final int PADDLE_UP = 1; public static final int PADDLE_DOWN = 6; public static final int PADDLE_LEFT = 2; public static final int PADDLE_RIGHT = 5; public static final int PADDLE_FIRE = 20; public static final int PADDLE_SOFT1 = 21; public static final int PADDLE_SOFT2 = 22; public static final int PADDLE_SOFT3 = 23; 演化而来的在波斯王子里由于需要判断连续按键而引入了按键状态的概念:  public static final byte Up_Instant = -1; //瞬间抬起,中间过度状态  public static final byte Up_Continuous = 0; //持续抬起,无按键  public static final byte Dn_Instant = 1; //瞬间按下,走  public static final byte Dn_Continuous = 2; //持续按下,跑  public static final byte Dn_Continuous2 = 3; //连击两下,滚    这种处理方式需要每帧里都更新按键状态,键被按下在第一帧为Dn_Instant瞬间按下,在第二帧变为Dn_Continuous持续按下。这种按键处理方式在CoCoMo的引擎里使用了很长一段时间,估计SF的兄弟们现在还在使用着这种方式。他最大的缺点是在Dn_Instant转Dn_Continuous时容易出错。 到了彩虹六号和Medieval Combat时因为在短时间内需要判断连续不同按键来发大招,所以按键处理引入了虚拟按键的概念: public static final int gk_UP = 1; public static final int gk_DOWN = 1<<1; public static final int gk_LEFT = 1<<2; public static final int gk_RIGHT = 1<<3; public static final int gk_NUM0 = 1<<4; public static final int gk_NUM1 = 1<<5; public static final int gk_NUM2 = 1<<6; public static final int gk_NUM3 = 1<<7; public static final int gk_NUM4 = 1<<8; public static final int gk_NUM5 = 1<<9; public static final int gk_NUM6 = 1<<10; public static final int gk_NUM7 = 1<<11; public static final int gk_NUM8 = 1<<12; public static final int gk_NUM9 = 1<<13; public static final int gk_STAR = 1<<14; public static final int gk_POUND = 1<<15; public static final int gk_LSOFT = 1<<16; public static final int gk_RSOFT = 1<<17; public static final int gk_MSOFT = 1<<18; 在一定时间内用mask对curKey做掩码就可以判断是否按下了一组特定键,时间过了就清空curKey。 CoCoMo因为厌烦每个机型上都需要一个不同的keyCode表,所以CoCoMo用getGameAction()和Canvas自带的keyCode表,只需要一个keyConvert()将按键转换到CoCoMo自定义的keyCode表即可,这个自定义的keyCode表在每个机型上都是一样的: public static final byte KEY_NONE = -1; public static final byte KEY_0 = 0; public static final byte KEY_UL = 1; public static final byte KEY_U = 2; public static final byte KEY_UR = 3; public static final byte KEY_L = 4; public static final byte KEY_ATTACK = 5; public static final byte KEY_R = 6; public static final byte KEY_DL = 7; public static final byte KEY_D = 8; public static final byte KEY_DR = 9; public static final byte KEY_STAR = 10; public static final byte KEY_POUND = 11; public static final byte KEY_SOFT1 = 12; public static final byte KEY_SOFT2 = 13; 这样可以免去移植之苦,对于某些机型例如S700不支持getGameAction()的问题,CoCoMo用此种方法来解决: try { //解决getGameAction不被支持的情况 keyCode = s_game.getGameAction(code); } catch(Exception e) { keyCode = CRes.KEY_NONE; } 不支持的时候会抛出一个异常,让keyCode不做转换即可。哈哈。
努力不生煩惱,認真沒有敵人。

最近正在做鍵盤處理。大家參考一下。
努力不生煩惱,認真沒有敵人。

TOP

好东西~~~~ 除了线程里面的sleep()方法,还有没有什么别的方法获得系统的毫秒时间??? 我想设置这个变量,让他控制毫秒时间,让他在一定时间内循环,怎么做??

TOP

原帖由 溜达到此 于 2007-10-7 18:29 发表 好东西~~~~ 除了线程里面的sleep()方法,还有没有什么别的方法获得系统的毫秒时间??? 我想设置这个变量,让他控制毫秒时间,让他在一定时间内循环,怎么做??
請詳細的講一下,最好有代碼示例,我好詳細看看.
努力不生煩惱,認真沒有敵人。

TOP

原帖由 溜达到此 于 2007-10-7 18:29 发表 好东西~~~~ 除了线程里面的sleep()方法,还有没有什么别的方法获得系统的毫秒时间??? 我想设置这个变量,让他控制毫秒时间,让他在一定时间内循环,怎么做??
这个我觉得可以调用系统时间去解决。

TOP

我没写出来,我只是想这个应该怎么具体作出来。。 我主要是控制人物动作桢的变换顺序 if(i>2){ 处理事件 i=0; } i++; 可是这样子写出来效果不理想

TOP

所以我想把 if(i>2),这个判断换成隔一定毫秒,才执行一次,不知道怎么写

TOP

我大概明白你的意思了,但還是不夠明晰。能否講的詳細一點兒?謝謝。
努力不生煩惱,認真沒有敵人。

TOP

就是人物走路动作有3桢组成,(站立,迈左脚,迈右脚), int [ ]moveState ={0,1,0,2}; int index; if(i>2){ 处理事件 moveState[index] i=0; } i++; 每当i>2,才变换一下桢,但运行起来有点僵硬,所以就想每隔一定毫秒,改变下一桢。 但不知道怎么写毫秒,还有就是storm老大太客气了

TOP

while(b_running) { updateState(); repaint(); serviceRepaints(); long d = System.currentTimeMillis() - s_curFrameTime; if (d < 80) Thread.sleep(80 - d); s_curFrameTime = System.currentTimeMillis(); } 这段话中System.currentTimeMillis();,这个是什么意思??? 还有就是Thread.sleep(int i),里面的数值一般不都是一个固定的常量吗??你这样写是什么样效果??

TOP

回复 10# 的帖子

System.currentTimeMillis();是获得自19xx年到现在的毫秒数 是个长整形 if (d < 80) Thread.sleep(80 - d); 这个是为了保证每次休眠的时间都一致

TOP

实际上keyPressed()是在线程sleep的时候引发的,也就是说当Canvas这个线程在空闲状态时,KVM才有机会向激活的Display传递消息说有人按了某键,或者KVM总能向Display传递消息,但Display需要在空闲状态时才能调用keyPressed(),至于是哪种方式那是底层实现的事情,我们并不得而知,但有一点是可以肯定的:keyPressed()是在Canvas线程sleep的时候被引发的。
好东西,支持 最近做的一个游戏,怪不得pressed严重的延迟....逼我改成getKeyState()....~ 我在想如果游戏比较大的话,逻辑处理必然很庞大吧.......延迟现象是必然的了?
个人技术博客: http://wupei.j2megame.org

TOP

嗯,可以在按鍵處理函數裏面只進行標誌變量的更改。 另外,處理完按鍵的話,除非要做連續按下不放效果,否則最好清空標誌位,以免發生“按鍵粘連”效果。就是玩家按了一下上箭頭,菜單移動了6-7項,太恐怖了。哈哈。
努力不生煩惱,認真沒有敵人。

TOP

呵呵,恩,有体会~ 不过菜单的情况下,一般都不会出现延迟现象~ 大部分菜单还是静态的嘛~
个人技术博客: http://wupei.j2megame.org

TOP

最近正在做鍵盤處理。大家參考一下.

TOP

不错 相当好的资料啊 谢谢了

TOP

有问题啊有问题啊

原帖由 [ i ]storm 于 2007-10-6 16:33 发表 但有一点是可以肯定的:keyPressed()是在Canvas线程sleep的时候被引发的。
在做项目的时候发现,我从来没有sleep过,但是仍然响应keyPressed()的 使用的是nokia的S60模拟器,使用手机顽童模拟器照样响应,使用fullcanvas 作者也说了是使用nokia的~什么问题...? 莫非WTK是这样的吗? 而且如果有sleep的话,也不是在sleep的时候触发的,看代码
  1. while(true)
  2. {
  3. System.out.println("\nBegin");
  4. updateGame();
  5. repaint(0, 0, 172, 208);
  6. serviceRepaints(); //调用System.out.println("paint");
  7. System.out.println("End\n");
  8. Thread.sleep(80);
  9. }
复制代码
Begin 和 End 之间也是可以响应的 我的输出 aaa************ 为 keyPressed() 输出 1. 正常按键 Begin paint End aaa***************** Begin paint End Begin paint End aaa***************** Begin paint End 2. 快速按键 aaa***************** Begin aaa***************** paint aaa***************** End Begin aaa***************** paint End aaa***************** [ 本帖最后由 rablwupei 于 2008-5-9 10:24 编辑 ]
个人技术博客: http://wupei.j2megame.org

TOP

你說的這個問題確實存在。由於本文是我轉載的,所以我只能說,有些真機上面可能如原作者所說,是在sleep的時候才觸發keyPress的。有些真機如你所述,不是這樣。 但是我的一般做法是,在key...函數加上synchronized修飾符,同時加給paint和cycle。這樣大部分機子便可以解決問題。儘管頑固的Moto某個機子仍然不吃同步這一套。
努力不生煩惱,認真沒有敵人。

TOP

恩恩,也有可能,我实在诺基亚上实现的,不同的机型可能会不一样 synchronize 我认为会严重消耗虚拟机使用的CPU资源吧....会吗....? 我一直不用 synchronize
个人技术博客: http://wupei.j2megame.org

TOP

synchronized是不是嚴重消耗内存,大家看看Lea的Java並發編程也許會更清楚些。江湖上流行的説法是會,我對此持保留意見。 但是,如果你遇到手機中的某些“戰鬥機”的話,我想用用還是應該的。
努力不生煩惱,認真沒有敵人。

TOP

返回列表