鍍金池/ 教程/ Android/ 處理控制器輸入動(dòng)作
檢測(cè)常用的手勢(shì)
優(yōu)化layout的層級(jí)
用戶輸入
管理應(yīng)用的內(nèi)存
聯(lián)系人信息
開發(fā)輔助程序
Android多媒體
添加語音功能
顯示位置地址
提供向下與橫向?qū)Ш?/span>
支持游戲控制器
訪問可穿戴數(shù)據(jù)層
處理多點(diǎn)觸控手勢(shì)
全屏沉浸式應(yīng)用
為多線程創(chuàng)建管理器
數(shù)據(jù)保存
Intent的發(fā)送
更新Notification
優(yōu)化下載以高效地訪問網(wǎng)絡(luò)
打印
打包可穿戴應(yīng)用
接收從其他App傳送來的數(shù)據(jù)
發(fā)送與接收消息
建立靈活動(dòng)態(tài)的UI
處理鍵盤輸入
Building a Work Policy Controller
建立測(cè)試環(huán)境
創(chuàng)建表盤
分享文件
顯示Notification進(jìn)度
實(shí)現(xiàn)自適應(yīng)UI流(Flows)
使用設(shè)備管理策略增強(qiáng)安全性
使用能感知版本的組件
執(zhí)行網(wǎng)絡(luò)操作
建立文件分享
添加移動(dòng)
更新你的Security Provider來對(duì)抗SSL漏洞利用
支持鍵盤導(dǎo)航
創(chuàng)建和監(jiān)視地理圍欄
發(fā)送并同步數(shù)據(jù)
使用BigView樣式
無線連接設(shè)備
提供向上導(dǎo)航與歷史導(dǎo)航
最小化定期更新造成的影響
實(shí)現(xiàn)向下的導(dǎo)航
支持不同的屏幕大小
Android 可穿戴應(yīng)用
添加動(dòng)畫
顯示聯(lián)系人頭像
使用OpenGL ES顯示圖像
處理輸入法可見性
分享文件
保持設(shè)備喚醒
淡化系統(tǒng)Bar
使用NFC分享文件
保存到Preference
Android聯(lián)系人信息與位置信息
創(chuàng)建標(biāo)準(zhǔn)的網(wǎng)絡(luò)請(qǐng)求
使用Drawables
管理Bitmap的內(nèi)存使用
管理Activity的生命周期
按需加載視圖
傳輸資源
為可穿戴設(shè)備創(chuàng)建自定義UI
在一個(gè)線程中執(zhí)行一段特定的代碼
性能優(yōu)化
隱藏導(dǎo)航欄
創(chuàng)建目錄瀏覽器
為多種大小的屏幕進(jìn)行規(guī)劃
View間漸變
使用觸摸手勢(shì)
高效加載大圖
使用CursorLoader在后臺(tái)加載數(shù)據(jù)
創(chuàng)建抽屜式導(dǎo)航(navigation drawer)
管理音頻焦點(diǎn)
創(chuàng)建后臺(tái)服務(wù)
創(chuàng)建功能測(cè)試
創(chuàng)建使用Material Design的應(yīng)用
停止與重啟Activity
添加一個(gè)簡(jiǎn)便的分享功能
啟動(dòng)Activity時(shí)保留導(dǎo)航
TV應(yīng)用清單
創(chuàng)建向后兼容的UI
?# 優(yōu)化自定義View
創(chuàng)建單元測(cè)試
在UI上顯示Bitmap
建立OpenGL ES的環(huán)境
構(gòu)建表盤服務(wù)
JNI Tips
建立搜索界面
實(shí)現(xiàn)自定義View的繪制
使用HTTPS與SSL
按需操控BroadcastReceiver
分享簡(jiǎn)單的數(shù)據(jù)
繪制形狀
Android位置信息
創(chuàng)建并運(yùn)行可穿戴應(yīng)用
執(zhí)行 Sync Adpater
獲取最后可知位置
創(chuàng)建 Android 項(xiàng)目
實(shí)現(xiàn)高效的導(dǎo)航
退出全屏的Activity
創(chuàng)建Card
兼容音頻輸出設(shè)備
同步數(shù)據(jù)單元
傳輸數(shù)據(jù)時(shí)避免消耗大量電量
保存到文件
緩存Bitmap
提供配置 Activity
調(diào)度重復(fù)的鬧鐘
實(shí)現(xiàn)輔助功能
重復(fù)的下載是冗余的
隱藏狀態(tài)欄
實(shí)現(xiàn)自定義的網(wǎng)絡(luò)請(qǐng)求
規(guī)劃界面和他們之間的關(guān)系
使用Sync Adapter傳輸數(shù)據(jù)
TV應(yīng)用內(nèi)搜索
響應(yīng)觸摸事件
使用Google Cloud Messaging(已廢棄)
控制相機(jī)
Android網(wǎng)絡(luò)連接與云服務(wù)
請(qǐng)求分享一個(gè)文件
處理TV硬件
響應(yīng)UI可見性的變化
使用網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)
指定輸入法類型
優(yōu)化電池壽命
創(chuàng)建TV應(yīng)用
獲取聯(lián)系人列表
拖拽與縮放
啟動(dòng)與停止線程池中的線程
創(chuàng)建 Sync Adpater
使用 WiFi P2P 服務(wù)發(fā)現(xiàn)
開始使用Material Design
代理至新的APIs
使用include標(biāo)簽重用layouts
使得View可交互
高效顯示Bitmap
創(chuàng)建企業(yè)級(jí)應(yīng)用
Fragments之間的交互
創(chuàng)建與執(zhí)行測(cè)試用例
綜合:設(shè)計(jì)我們的樣例 App
繪制表盤
建立簡(jiǎn)單的用戶界面
自定義動(dòng)畫
開發(fā)輔助服務(wù)
避免出現(xiàn)程序無響應(yīng)ANR(Keeping Your App Responsive)
使用ViewPager實(shí)現(xiàn)屏幕滑動(dòng)
設(shè)計(jì)高效的導(dǎo)航
Android分享操作(Building Apps with Content Sharing)
提供向后的導(dǎo)航
保持向下兼容
創(chuàng)建TV播放應(yīng)用
縮放View
使用 WiFi 建立 P2P 連接
Android后臺(tái)任務(wù)
連接到網(wǎng)絡(luò)
為 Notification 添加頁面
使TV應(yīng)用是可被搜索的
添加Action Bar
使用Material的主題
啟動(dòng)另一個(gè)Activity
顯示正在播放卡片
適配不同的系統(tǒng)版本
輕松錄制視頻
創(chuàng)建可穿戴的應(yīng)用
創(chuàng)建自定義的布局
重新創(chuàng)建Activity
使用CursorLoader執(zhí)行查詢?nèi)蝿?wù)
使用舊的APIs實(shí)現(xiàn)新API的效果
使用備份API
安全要點(diǎn)
Android入門基礎(chǔ):從這里開始
保存并搜索數(shù)據(jù)
根據(jù)網(wǎng)絡(luò)連接類型來調(diào)整下載模式
使用Tabs創(chuàng)建Swipe視圖
SMP(Symmetric Multi-Processor) Primer for Android
解析 XML 數(shù)據(jù)
使用 Volley 傳輸網(wǎng)絡(luò)數(shù)據(jù)
建立ActionBar
Android交互設(shè)計(jì)
使用Intent修改聯(lián)系人信息
增加搜索功能
輕松拍攝照片
定義形狀
測(cè)試你的Activity
在 Notifcation 中接收語音輸入
與其他應(yīng)用的交互
管理系統(tǒng)UI
追蹤手勢(shì)移動(dòng)
Android界面設(shè)計(jì)
執(zhí)行 Android 程序
顯示確認(rèn)界面
創(chuàng)建Lists與Cards
打印HTML文檔
創(chuàng)建TV應(yīng)用
為多屏幕設(shè)計(jì)
定義Shadows與Clipping視圖
使用Fragment建立動(dòng)態(tài)UI
接收Activity返回的結(jié)果
布局變更動(dòng)畫
定位常見的問題
自定義ActionBar的風(fēng)格
定義Layouts
發(fā)送簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求
啟動(dòng)與銷毀Activity
與UI線程通信
非UI線程處理Bitmap
創(chuàng)建TV布局
提升Layout的性能
報(bào)告任務(wù)執(zhí)行狀態(tài)
判斷并監(jiān)測(cè)網(wǎng)絡(luò)連接狀態(tài)
兼容不同的設(shè)備
處理按鍵動(dòng)作
優(yōu)化性能和電池使用時(shí)間
給其他App發(fā)送簡(jiǎn)單的數(shù)據(jù)
Implementing App Restrictions
向后臺(tái)服務(wù)發(fā)送任務(wù)請(qǐng)求
展示Card翻轉(zhuǎn)動(dòng)畫
管理ViewGroup中的觸摸事件
兼容不同的屏幕密度
通過藍(lán)牙進(jìn)行調(diào)試
為可穿戴設(shè)備創(chuàng)建Notification
控制音量與音頻播放
獲取聯(lián)系人詳情
在表盤上顯示信息
提供向上的導(dǎo)航
滾動(dòng)手勢(shì)動(dòng)畫
幫助用戶在TV上找到內(nèi)容
創(chuàng)建TV導(dǎo)航
為索引指定App內(nèi)容
ActionBar的覆蓋疊加
Android Wear 上的位置檢測(cè)
保護(hù)安全與隱私的最佳策略
Ensuring Compatibility with Managed Profiles
解決云同步的保存沖突
獲取位置更新
創(chuàng)建List
測(cè)試程序
管理網(wǎng)絡(luò)的使用情況
為App內(nèi)容開啟深度鏈接
推薦TV內(nèi)容
建立一個(gè)Notification
管理音頻播放
設(shè)計(jì)表盤
拍照
處理控制器輸入動(dòng)作
判斷并監(jiān)測(cè)設(shè)備的底座狀態(tài)與類型
處理查詢的結(jié)果
保存到數(shù)據(jù)庫
支持多個(gè)游戲控制器
創(chuàng)建 Stub Content Provider
使得ListView滑動(dòng)順暢
處理數(shù)據(jù)層的事件
創(chuàng)建TV應(yīng)用的第一步
使得你的App內(nèi)容可被Google搜索
將 Notification 放成一疊
創(chuàng)建 Stub 授權(quán)器
暫停與恢復(fù)Activity
管理設(shè)備的喚醒狀態(tài)
Android圖像與動(dòng)畫
打印照片
云同步
創(chuàng)建TV直播應(yīng)用
為Notification賦加可穿戴特性
提供一個(gè)Card視圖
建立請(qǐng)求隊(duì)列(RequestQueue)
適配不同的語言
創(chuàng)建詳情頁
測(cè)試UI組件
接收其他設(shè)備的文件
創(chuàng)建自定義View
建立第一個(gè)App
創(chuàng)建2D Picker
監(jiān)測(cè)電池的電量與充電狀態(tài)
打印自定義文檔
抽象出新的APIs
通知提示用戶
獲取文件信息
運(yùn)用投影與相機(jī)視角
在IntentService中執(zhí)行后臺(tái)任務(wù)
多線程操作
創(chuàng)建一個(gè)Fragment
添加Action按鈕
在不同的 Android 系統(tǒng)版本支持控制器
維護(hù)兼容性
發(fā)送文件給其他設(shè)備
創(chuàng)建TV游戲應(yīng)用
創(chuàng)建自定義的View類
代碼性能優(yōu)化建議
Intent過濾
適配不同的屏幕

處理控制器輸入動(dòng)作

編寫:heray1990 - 原文:http://developer.android.com/training/game-controllers/controller-input.html

在系統(tǒng)層面上,Android 會(huì)以 Android 按鍵碼值和坐標(biāo)值的形式來報(bào)告來自游戲控制器的輸入事件。在我們的游戲應(yīng)用里,我們可以接收這些碼值和坐標(biāo)值,并將它們轉(zhuǎn)化成特定的游戲行為。

當(dāng)玩家將一個(gè)游戲控制器通過有線連接或者無線配對(duì)到 Android 設(shè)備時(shí),系統(tǒng)會(huì)自動(dòng)檢測(cè)控制器,將它設(shè)置成輸入設(shè)備并且開始報(bào)告它的輸入事件。我們的游戲應(yīng)用可以通過在活動(dòng)的 Activity 或者被選中的 View 里調(diào)用下面這些回調(diào)方法,來接收上述輸入事件(要么在 Activity,要么在 View 中實(shí)現(xiàn)實(shí)現(xiàn)這些回調(diào)方法,不要兩個(gè)地方都實(shí)現(xiàn)回調(diào))。

建議的方法是從與用戶交互的 View 對(duì)象捕獲事件。請(qǐng)查看下面回調(diào)函數(shù)的對(duì)象,來獲取關(guān)于接收到輸入事件的類型:

KeyEvent:描述方向按鍵(D-pad)和游戲按鍵事件的對(duì)象。按鍵事件伴隨著一個(gè)表示特定按鍵觸發(fā)的按鍵碼值(key code),如 DPAD_DOWN 或者 BUTTON_A。我們可以通過調(diào)用 getKeyCode() 或者從按鍵事件回調(diào)方法(如 onKeyDown())來獲得按鍵碼值。

MotionEvent:描述搖桿和肩鍵運(yùn)動(dòng)的輸入。動(dòng)作事件伴隨著一個(gè)動(dòng)作碼(action code)和一系列坐標(biāo)值axis values)。動(dòng)作碼表示出現(xiàn)變化的狀態(tài),例如搖動(dòng)一個(gè)搖桿。坐標(biāo)值描述了特定物理操控的位置和其它運(yùn)動(dòng)屬性,例如 AXIS_X 或者 AXIS_RTRIGGER。我們可以通過調(diào)用 getAction() 來獲得動(dòng)作碼,通過調(diào)用 getAxisValue() 來獲得坐標(biāo)值。

這節(jié)課主要介紹如何通過實(shí)現(xiàn)上述的 View 回調(diào)方法與處理 KeyEventMotionEvent 對(duì)象,來處理常用控制器(游戲鍵盤按鍵、方向按鍵和搖桿)的輸入。

<a name="input=>

驗(yàn)證游戲控制器是否已連接

在報(bào)告輸入事件的時(shí)候,Android 不會(huì)區(qū)分游戲控制器事件與非游戲控制器事件。例如,一個(gè)觸屏動(dòng)作會(huì)產(chǎn)生一個(gè)表示觸摸表面上 X 坐標(biāo)的 AXIS_X,但是一個(gè)搖桿動(dòng)作產(chǎn)生的 AXIS_X 則表示搖桿水平移動(dòng)的位置。如果我們的游戲關(guān)注游戲控制器的輸入,那么我們應(yīng)該首先檢測(cè)相應(yīng)的事件來源類型。

通過調(diào)用 getSources() 來獲得設(shè)備上支持的輸入類型的位字段,來判斷一個(gè)已連接的輸入設(shè)備是不是一個(gè)游戲控制器。我們可以通過測(cè)試以查看下面的字段是否被設(shè)置:

  • SOURCE_GAMEPAD 源類型表示輸入設(shè)備有游戲手柄按鍵(如,BUTTON_A)。注意雖然一般的游戲手柄都會(huì)有方向控制鍵,但是這個(gè)源類型并不代表游戲控制器具有 D-pad 按鈕。
  • SOURCE_DPAD 源類型表示輸入設(shè)備有 D-pad 按鈕(如,DPAD_UP)。
  • SOURCE_JOYSTICK 源類型表示輸入設(shè)備有遙控桿(如,會(huì)通過 AXIS_XAXIS_Y 記錄動(dòng)作的搖桿)。

下面的一小段代碼介紹了一個(gè) helper 方法,它的作用是讓我們檢驗(yàn)已接入的輸入設(shè)備是否是游戲控制器。如果檢測(cè)到是游戲控制器,那么這個(gè)方法會(huì)獲得游戲控制器的設(shè)備 ID。然后,我們應(yīng)該將每個(gè)設(shè)備 ID 與游戲中的玩家關(guān)聯(lián)起來,并且單獨(dú)處理每個(gè)已接入的玩家的游戲操作。想更詳細(xì)地了解關(guān)于在一臺(tái)Android設(shè)備中同時(shí)支持多個(gè)游戲控制器的方法,請(qǐng)見支持多個(gè)游戲控制器

public ArrayList getGameControllerIds() {
    ArrayList gameControllerDeviceIds = new ArrayList();
    int[] deviceIds = InputDevice.getDeviceIds();
    for (int deviceId : deviceIds) {
        InputDevice dev = InputDevice.getDevice(deviceId);
        int sources = dev.getSources();

        // Verify that the device has gamepad buttons, control sticks, or both.
        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
                || ((sources & InputDevice.SOURCE_JOYSTICK)
                == InputDevice.SOURCE_JOYSTICK)) {
            // This device is a game controller. Store its device ID.
            if (!gameControllerDeviceIds.contains(deviceId)) {
                gameControllerDeviceIds.add(deviceId);
            }
        }
    }
    return gameControllerDeviceIds;
}

另外,我們可能想去檢查已接入的單個(gè)游戲控制器的輸入性能。這種檢查在某些場(chǎng)合會(huì)很有用,例如,我們希望游戲只用到兼容的物理操控。

用下面這些方法檢測(cè)一個(gè)游戲控制器是否支持一個(gè)特定的按鍵碼或者坐標(biāo)碼:

  • 在Android 4.4(API level 19)或者更高的系統(tǒng)中,調(diào)用 hasKeys(int) 來確定游戲控制器是否支持某個(gè)按鍵碼。
  • 在Android 3.1(API level 12)或者更高的系統(tǒng)中,首先調(diào)用 getMotionRanges(),然后在每個(gè)返回的 InputDevice.MotionRange 對(duì)象中調(diào)用 getAxis() 來獲得坐標(biāo) ID。這樣就可以得到游戲控制器支持的所有可用坐標(biāo)軸。

處理游戲手柄按鍵

Figure 1介紹了 Android 如何將按鍵碼和坐標(biāo)值映射到實(shí)際的游戲手柄上。

http://wiki.jikexueyuan.com/project/android-training-geek/images/game-controller-profiles.png" alt="game-controller-profiles" title="Figure 1. Profile for a generic game controller." />

Figure 1. 一個(gè)常用的游戲手柄的外形

上圖的標(biāo)注對(duì)應(yīng)下面的內(nèi)容:

  1. AXIS_HAT_X, AXIS_HAT_Y, DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT
  2. AXIS_X, AXIS_Y, BUTTON_THUMBL
  3. AXIS_Z, AXIS_RZ, BUTTON_THUMBR
  4. BUTTON_X
  5. BUTTON_A
  6. BUTTON_Y
  7. BUTTON_B
  8. BUTTON_R1
  9. AXIS_RTRIGGER, AXIS_THROTTLE
  10. AXIS_LTRIGGER, AXIS_BRAKE
  11. BUTTON_L1

游戲手柄產(chǎn)生的通用的按鍵碼包括 BUTTON_ABUTTON_B、BUTTON_SELECTBUTTON_START。當(dāng)按下 D-pad 中間的交叉按鍵時(shí),一些游戲控制器會(huì)觸發(fā) DPAD_CENTER 按鍵碼。我們的游戲可以通過調(diào)用 getKeyCode() 或者從按鍵事件回調(diào)(如onKeyDown())得到按鍵碼。如果一個(gè)事件與我們的游戲相關(guān),那么將其處理成一個(gè)游戲動(dòng)作。Table 1列出供大多數(shù)通用游戲手柄按鈕使用的推薦游戲動(dòng)作。

Table 1. 供游戲手柄使用的推薦游戲動(dòng)作

游戲動(dòng)作 按鍵碼
在主菜單中啟動(dòng)游戲,或者在游戲過程中暫停/取消暫停 BUTTON_START
顯示菜單 BUTTON_SELECTKEYCODE_MENU
跟Android導(dǎo)航設(shè)計(jì)指導(dǎo)中的Back導(dǎo)航行為一樣 KEYCODE_BACK
返回到菜單中上一項(xiàng) BUTTON_B
確認(rèn)選擇,或者執(zhí)行主要的游戲動(dòng)作 BUTTON_ADPAD_CENTER

* 我們的游戲不應(yīng)該依賴于Start、Select或者M(jìn)enu按鍵的存在。

Tip: 可以考慮在游戲中提供一個(gè)配置界面,使得用戶可以個(gè)性化游戲控制器與游戲動(dòng)作的映射。

下面的代碼介紹了如何重寫 onKeyDown() 來將 BUTTON_ADPAD_CENTER 按鈕結(jié)合到一個(gè)游戲動(dòng)作。

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

Note: 在 Android 4.2(API level 17)和更低版本的系統(tǒng)中,系統(tǒng)默認(rèn)會(huì)把 BUTTON_A 當(dāng)作 Android Back返回)鍵。如果我們的應(yīng)用支持這些 Android 版本,請(qǐng)確保將 BUTTON_A 轉(zhuǎn)換成主要的游戲動(dòng)作。引用 Build.VERSION.SDK_INT 值來決定設(shè)備上當(dāng)前的 Android SDK 版本。

處理 D-pad 輸入

四方向的方向鍵(D-pad)在很多游戲控制器中是一種很常見的物理控制。Android 將 D-pad 的上和下按鍵按壓報(bào)告成 AXIS_HAT_Y 事件(范圍從-1.0(上)到1.0(下)),將 D-pad 的左或者右按鍵按壓報(bào)告成 AXIS_HAT_X 事件(范圍從-1.0(左)到1.0(右))。

一些游戲控制器會(huì)將 D-pad 按壓報(bào)告成一個(gè)按鍵碼。如果我們的游戲有檢測(cè) D-pad 的按壓,那么我們應(yīng)該將坐標(biāo)值事件和 D-pad 按鍵碼當(dāng)成一樣的輸入事件,如 table 2 介紹的一樣。

Table 2. D-pad 按鍵碼和坐標(biāo)值的推薦默認(rèn)游戲動(dòng)作。

游戲動(dòng)作 D-pad 按鍵碼 坐標(biāo)值
向上 KEYCODE_DPAD_UP AXIS_HAT_Y (從 0 到 -1.0)
向下 KEYCODE_DPAD_DOWN AXIS_HAT_Y (從 0 到 1.0)
向左 KEYCODE_DPAD_LEFT AXIS_HAT_X (從 0 到 -1.0)
向右 KEYCODE_DPAD_RIGHT AXIS_HAT_X (從 0 到 1.0)

下面的代碼介紹了通過一個(gè) helper 類,來檢查從一個(gè)輸入事件來決定 D-pad 方向的坐標(biāo)值和按鍵碼。

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

我們可以在任意想要處理 D-pad 輸入(例如,在 onGenericMotionEvent() 或者 onKeyDown() 回調(diào)函數(shù))的地方使用這個(gè) helper 類。

例如:

Dpad mDpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = mDpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

處理搖桿動(dòng)作

當(dāng)玩家移動(dòng)游戲控制器上的搖桿時(shí),Android 會(huì)報(bào)告一個(gè)包含 ACTION_MOVE 動(dòng)作碼和更新?lián)u桿在坐標(biāo)軸的位置的 MotionEvent。我們的游戲可以使用 MotionEvent 提供的數(shù)據(jù)來確定是否發(fā)生搖桿的動(dòng)作。

注意到搖桿移動(dòng)會(huì)在單個(gè)對(duì)象中批處理多個(gè)移動(dòng)示例。MotionEvent 對(duì)象包含每個(gè)搖桿坐標(biāo)當(dāng)前的位置和每個(gè)坐標(biāo)軸上的多個(gè)歷史位置。當(dāng)用 ACTION_MOVE 動(dòng)作碼(例如搖桿移動(dòng))來報(bào)告移動(dòng)事件時(shí),Android 會(huì)高效地批處理坐標(biāo)值。由坐標(biāo)值組成的不同的歷史值比當(dāng)前的坐標(biāo)值要舊,比之前報(bào)告的任意移動(dòng)事件要新。詳情見 MotionEvent 參考文檔。

我們可以使用歷史信息,根據(jù)搖桿輸入更精確地表達(dá)游戲?qū)ο蟮幕顒?dòng)。調(diào)用 getAxisValue() 或者 getHistoricalAxisValue() 來獲取現(xiàn)在和歷史的值。我們也可以通過調(diào)用 getHistorySize() 來找到搖桿事件的歷史點(diǎn)號(hào)碼。

下面的代碼介紹了如何重寫 onGenericMotionEvent() 回調(diào)函數(shù)來處理搖桿輸入。我們應(yīng)該首先處理歷史坐標(biāo)值,然后處理當(dāng)前值。

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

在使用搖桿輸入之前,我們需要確定搖桿是否居中,然后計(jì)算相應(yīng)的坐標(biāo)移動(dòng)距離。一般搖桿會(huì)有一個(gè)平面區(qū),即在坐標(biāo) (0, 0) 附近一個(gè)值范圍內(nèi)的坐標(biāo)點(diǎn)都被當(dāng)作是中點(diǎn)。如果 Android 系統(tǒng)報(bào)告坐標(biāo)值掉落在平面區(qū)內(nèi),那么我們應(yīng)該認(rèn)為控制器處于靜止(即沿著 x、y 兩個(gè)坐標(biāo)軸都是靜止的)。

下面的代碼介紹了一個(gè)用于計(jì)算沿著每個(gè)坐標(biāo)軸的移動(dòng)距離的 helper 方法。我們將在后面討論的 processJoystickInput() 方法中調(diào)用這個(gè) helper 方法。

private static float getCenteredAxis(MotionEvent event,
        InputDevice device, int axis, int historyPos) {
    final InputDevice.MotionRange range =
            device.getMotionRange(axis, event.getSource());

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    if (range != null) {
        final float flat = range.getFlat();
        final float value =
                historyPos < 0 ? event.getAxisValue(axis):
                event.getHistoricalAxisValue(axis, historyPos);

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value;
        }
    }
    return 0;
}

將它們都放在一起,下面是我們?nèi)绾卧谟螒蛑刑幚頁u桿移動(dòng):

private void processJoystickInput(MotionEvent event,
        int historyPos) {

    InputDevice mInputDevice = event.getDevice();

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    float x = getCenteredAxis(event, mInputDevice,
            MotionEvent.AXIS_X, historyPos);
    if (x == 0) {
        x = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_HAT_X, historyPos);
    }
    if (x == 0) {
        x = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_Z, historyPos);
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    float y = getCenteredAxis(event, mInputDevice,
            MotionEvent.AXIS_Y, historyPos);
    if (y == 0) {
        y = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_HAT_Y, historyPos);
    }
    if (y == 0) {
        y = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_RZ, historyPos);
    }

    // Update the ship object based on the new x and y values
}

為了支持除了單個(gè)搖桿之外更多復(fù)雜的功能,按照下面的做法:

  • 處理兩個(gè)控制器搖桿。很多游戲控制器左右兩邊都有搖桿。對(duì)于左搖桿,Android 會(huì)報(bào)告水平方向的移動(dòng)為 AXIS_X 事件,垂直方向的移動(dòng)為 AXIS_Y 事件。對(duì)于右搖桿,Android 會(huì)報(bào)告水平方向的移動(dòng)為 AXIS_Z 事件,垂直方向的移動(dòng)為 AXIS_RZ 事件。確保在代碼中處理兩個(gè)搖桿。

  • 處理肩鍵按壓(但需要提供另一種輸入方法)。一些控制器會(huì)有左右肩鍵。如果存在這些按鍵,那么 Android 報(bào)告左肩鍵按壓為一個(gè) AXIS_LTRIGGER 事件,右肩鍵按壓為一個(gè) AXIS_RTRIGGER 事件。在 Android 4.3(API level 18)中,一個(gè)產(chǎn)生了 AXIS_LTRIGGER 事件的控制器也會(huì)報(bào)告一個(gè)完全一樣的 AXIS_BRAKE 坐標(biāo)值。同樣,AXIS_RTRIGGER 對(duì)應(yīng) AXIS_GAS。Android 會(huì)報(bào)告模擬按鍵按壓為從 0.0(釋放)到 1.0(按下)的標(biāo)準(zhǔn)值。并不是所有的控制器都有肩鍵,所以需要允許玩家用其它按鈕來執(zhí)行那些游戲動(dòng)作。