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時輕易出錯。
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不做轉換即可。