【Touch&input 】處理控制器操作(16)

在系統級別,Android會將來自遊戲控制器的事件代碼作爲Android密鑰代碼和軸值報告。在您的遊戲中,您可以接收這些代碼和值,並將其轉換爲特定的遊戲內操作。

當玩家將遊戲控制器與其Android設備進行物理連接或無線配對時,系統會自動將該控制器作爲輸入設備進行檢測並開始報告其輸入事件。您的遊戲可以通過在您的活動Activity或關注中實現以下回調方法來接收這些輸入事件 View(您應該針對Activity或 View兩者執行回調):

來自Activity
dispatchGenericMotionEvent(android.view. MotionEvent)
被調用來處理通用運動事件,例如操縱桿運動。
dispatchKeyEvent(android.view.KeyEvent)
被調用來處理關鍵事件,例如按下或釋放遊戲手柄或D-pad按鈕。

來自View
onGenericMotionEvent(android.view.MotionEvent)
被調用來處理通用運動事件,例如操縱桿運動。
onKeyDown(int, android.view.KeyEvent)
被調用來處理按下物理按鍵,例如遊戲手柄或D-pad按鈕。
onKeyUp(int, android.view.KeyEvent)
被調用來處理物理鍵的釋放,例如遊戲手柄或D鍵按鈕。

推薦的方法是捕捉View用戶與之交互的特定對象的事件。檢查回調提供的以下對象,以獲取有關所接收輸入事件類型的信息:

KeyEvent
描述方向墊(D-pad)和遊戲板按鈕事件的對象。關鍵事件伴隨着指示特定按鈕觸發的 關鍵代碼,例如 DPAD_DOWN 或BUTTON_A。您可以通過調用getKeyCode()或從關鍵事件回調獲取關鍵代碼,如 onKeyDown()。

MotionEvent
描述操縱桿輸入和肩部觸發移動的對象。運動事件伴隨着一個動作代碼和一組 軸值。操作代碼指定發生的狀態更改,例如正在移動的操縱桿。軸值描述特定物理控制的位置和其他運動屬性,例如 AXIS_X或 AXIS_RTRIGGER。您可以通過調用來獲取動作代碼並調用getAction()軸值getAxisValue()。

本課着重介紹如何通過實現上述View回調方法和處理 KeyEvent以及MotionEvent對象,在遊戲屏幕中處理來自最常見類型的物理控制(遊戲手柄按鈕,方向鍵和遊戲杆)的輸入 。

驗證遊戲控制器已連接


在報告輸入事件時,Android不會區分來自非遊戲控制器設備的事件和來自遊戲控制器的事件。例如,觸摸屏操作會生成 AXIS_X表示觸摸表面X座標的AXIS_X事件,但操縱桿會生成表示操縱桿X位置的 事件。如果您的遊戲關心處理遊戲控制器輸入,則應首先檢查輸入事件是否來自相關源類型。

要驗證連接的輸入設備是否爲遊戲控制器,請致電 getSources()以獲取該設備支持的輸入源類型的組合位字段。然後您可以測試以查看是否設置了以下字段:

  • 源類型SOURCE_GAMEPAD表示輸入設備具有遊戲手柄按鈕(例如 BUTTON_A)。請注意,此源類型並不嚴格指示遊戲控制器是否有D-pad按鈕,但大多數遊戲手柄通常都有方向控制。
  • 源類型SOURCE_DPAD表示輸入設備具有D-pad按鈕(例如 DPAD_UP)。
  • 源類型SOURCE_JOYSTICK 表示輸入設備具有模擬控制桿(例如,可以沿着AXIS_X 和記錄移動的操縱桿AXIS_Y)。

以下代碼片段顯示了一個助手方法,可以讓您檢查連接的輸入設備是否爲遊戲控制器。如果是這樣,該方法檢索遊戲控制器的設備ID。然後,您可以將每個設備ID與遊戲中的玩家相關聯,並分別處理每個連接的玩家的遊戲操作。要了解有關支持同一Android設備上同時連接的多個遊戲控制器的更多信息,請參閱 支持多個遊戲控制器。

public ArrayList<Integer> getGameControllerIds() {
    ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
    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;
}

此外,您可能需要檢查連接的遊戲控制器支持的各種輸入功能。例如,如果您希望您的遊戲僅使用它理解的一組物理控件中的輸入,這可能很有用。

要檢測連接的遊戲控制器是否支持特定的密鑰代碼或軸代碼,請使用以下技術:

  • 在Android 4.4(API級別19)或更高版本中,您可以通過調用來確定連接的遊戲控制器是否支持密鑰代碼 hasKeys(int...)。
  • 在Android 3.1(API級別12)或更高版本中,您可以通過首次調用找到連接的遊戲控制器上支持的所有可用軸 getMotionRanges()。然後,在InputDevice.MotionRange返回的每個 對象上,調用 getAxis()以獲取其軸ID。

處理手柄按鈕按下


圖1顯示了Android如何將關鍵代碼和軸值映射到大多數遊戲控制器上的物理控件。
【Touch&input 】處理控制器操作(16)

圖1.通用遊戲控制器的配置文件

圖中的標註參考以下內容:

  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

通過手柄按鈕按壓生成的共用密鑰碼包括 BUTTON_A, BUTTON_B, BUTTON_SELECT,和BUTTON_START。某些遊戲控制器在DPAD_CENTER按下D-pad橫條的中心時也會觸發按鍵代碼。您的遊戲可以通過調用getKeyCode() 或從關鍵事件回調 來檢查關鍵代碼onKeyDown(),如果它代表與您的遊戲相關的事件,則將其作爲遊戲操作進行處理。表1列出了最常見的遊戲板按鈕的推薦遊戲操作。

表1.遊戲板按鈕的推薦遊戲操作。
【Touch&input 】處理控制器操作(16)
*您的遊戲不應該依賴開始,選擇或菜單按鈕的存在。

提示:考慮在遊戲中提供配置屏幕,以允許用戶針對遊戲操作個性化自己的遊戲控制器映射。

以下代碼片段顯示瞭如何重寫 onKeyDown()將按鈕BUTTON_A和 DPAD_CENTER按鈕與遊戲操作相關聯。

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;
    }
}

注意:在Android 4.2(API級別17)及更低版本上,默認情況下系統將 BUTTON_A視爲Android Back鍵。如果您的應用支持這些Android版本,請確保將其 BUTTON_A視爲主要遊戲操作。要確定設備上當前的Android SDK版本,請參閱該 Build.VERSION.SDK_INT值。

處理方向鍵盤輸入


4路方向鍵盤(D-pad)是許多遊戲控制器中的常見物理控制器。Android將D-pad UP和DOWN按鈕報告爲 AXIS_HAT_Y範圍從-1.0(上)至1.0(下)的事件,D-pad LEFT或RIGHT按下 AXIS_HAT_X範圍從-1.0(左)至1.0(右) 。

有些控制器會報告帶鍵碼的D-pad印刷機。如果您的遊戲關注D-Pad印刷機,則應按照表2中的建議將帽子軸事件和D-pad鍵代碼視爲相同的輸入事件。

表2.推薦的D-pad鍵碼和帽子軸值的默認遊戲動作。
【Touch&input 】處理控制器操作(16)
下面的代碼片段顯示了一個幫助類,它允許您檢查輸入事件中的帽子軸和鍵代碼值,以確定D墊方向。

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墊輸入(例如,在您可以在遊戲中使用這個輔助類 onGenericMotionEvent()或 onKeyDown() 回調)。

例如:

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.
    ...
}

處理遊戲杆移動


當玩家在其遊戲控制器上移動遊戲杆時,Android會報告一個 MotionEvent包含 ACTION_MOVE操作代碼和遊戲杆軸的更新位置的遊戲杆。您的遊戲可以使用由其提供的數據MotionEvent來確定它所關心的遊戲杆移動是否發生。

請注意,操縱桿運動事件可以將多個運動樣本一起分批處理到一個對象中。該MotionEvent對象包含每個操縱桿軸的當前位置以及每個軸的多個歷史位置。使用動作代碼ACTION_MOVE(例如遊戲杆動作)報告動作事件時,Android會對軸的值進行批處理以提高效率。軸的歷史值包括比當前軸值更早的不同值的集合,以及比任何以前的運動事件中報告的值更近的值。詳情請參閱 MotionEvent參考資料。

您可以使用歷史信息來根據遊戲杆輸入更準確地呈現遊戲對象的移動。要檢索當前值和歷史值,請調用 getAxisValue()或getHistoricalAxisValue()。您也可以通過調用來查找操縱桿事件中的歷史點數 getHistorySize()。

以下代碼片段顯示瞭如何覆蓋 onGenericMotionEvent()回調來處理遊戲杆輸入。您應該先處理軸的歷史值,然後處理其當前位置。

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);
    }
}

在使用遊戲杆輸入之前,您需要確定遊戲杆是否居中,然後相應地計算其軸向移動。操縱桿通常有一個平坦的區域,也就是說,軸線被認爲是居中的(0,0)座標附近的一系列值。如果Android報告的軸值落在平坦區域內,則應該讓控制器處於靜止狀態(即,沿着兩個軸靜止)。

下面的片段顯示了一個幫助方法,可以計算沿每個軸的移動。你在processJoystickInput()下面進一步描述的方法中調用這個助手。

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;
}

把它放在一起,這裏是你如何處理遊戲中的遊戲杆動作:

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
}

要支持具有超越單個遊戲杆的更復雜功能的遊戲控制器,請遵循以下最佳實踐:

處理雙控制器棒。許多遊戲控制器都有左右手柄。對於左側的棒,Android將AXIS_X事件和垂直移動的水平移動報告爲AXIS_Y事件。對於正確的操作杆,Android會將AXIS_Z事件和垂直移動等水平移動報告 爲 AXIS_RZ事件。確保在您的代碼中處理兩個控制桿。
處理肩膀扳機按壓(但提供替代輸入方法)。一些控制器有左肩和右肩觸發器。如果存在這些觸發器,則Android會將左側觸發按鈕報告爲AXIS_LTRIGGER事件,將右側觸發按鈕報告爲 AXIS_RTRIGGER事件。在Android 4.3(API級別18)上,生成a的控制器 AXIS_LTRIGGER也會爲該AXIS_BRAKE軸報告相同的值。AXIS_RTRIGGER和 也是如此AXIS_GAS。Android會報告所有模擬觸發器按下0.0(釋放)到1.0(完全按下)的歸一化值。並非所有的控制器都有觸發器,因此考慮允許玩家使用其他按鈕執行這些遊戲操作。

Lastest Update:2018.04.23

聯繫我

QQ:94297366
微信打賞:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公衆號推薦:

【Touch&input 】處理控制器操作(16)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章