【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)

转载于:https://blog.51cto.com/4789781/2125393

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值