如果您在游戏中支持游戏控制器,则有责任确保您的游戏在不同版本的Android上运行的设备上始终如一地响应控制器。这可以让您的游戏覆盖更广泛的受众群体,即使他们切换或升级了Android设备,您的玩家也可以通过其控制器享受无缝的游戏体验。
本课程演示如何以向后兼容的方式使用Android 4.1及更高版本中提供的API,使您的游戏能够在运行Android 3.1及更高版本的设备上支持以下功能:
- 游戏可以检测是否添加,更改或删除了新的游戏控制器。
- 游戏可以查询游戏控制器的功能。
- 游戏可以识别来自游戏控制器的传入运动事件。
本课中的示例基于上面提供的示例ControllerSample.zip提供的示例提供的参考实现。本示例演示如何实现InputManagerCompat 接口以支持不同版本的Android。要编译示例,您必须使用Android 4.1(API级别16)或更高。一旦编译完成,示例应用程序将运行在运行Android 3.1(API级别12)或更高版本的任何设备上作为构建目标。
准备抽象API以支持游戏控制器
假设您希望能够确定在Android 3.1(API级别12)上运行的设备上游戏控制器的连接状态是否已更改。但是,API仅适用于Android 4.1(API级别16)及更高版本,所以您需要提供支持Android 4.1及更高版本的实现,同时提供支持Android 3.1至Android 4.0的回退机制。
为了帮助您确定哪些功能需要旧版本的回退机制,表1列出了Android 3.1(API级别12)和4.1(API级别16)之间游戏控制器支持的差异。
表1.不同Android版本的游戏控制器支持的API。
您可以使用抽象来构建可跨平台使用的版本感知游戏控制器支持。这种方法涉及以下步骤:
- 定义一个中介Java接口,抽象出游戏所需的游戏控制器功能的实现。
- 创建一个使用Android 4.1和更高版本API的接口的代理实现。
- 创建一个自定义的界面实现,使用Android 3.1到Android 4.0之间的API。
- 创建在运行时在这些实现之间切换的逻辑,并开始在游戏中使用该接口。
有关如何使用抽象来确保应用程序可以跨不同版本的Android以向后兼容的方式工作的概述,请参阅 创建向后兼容的UI。
添加一个用于向后兼容的界面
为了提供向后兼容性,您可以创建自定义界面,然后添加特定于版本的实现。这种方法的一个优点是,它可以让您镜像支持游戏控制器的Android 4.1(API级别16)上的公共接口。
// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
public interface InputManagerCompat {
...
public InputDevice getInputDevice(int id);
public int[] getInputDeviceIds();
public void registerInputDeviceListener(
InputManagerCompat.InputDeviceListener listener,
Handler handler);
public void unregisterInputDeviceListener(
InputManagerCompat.InputDeviceListener listener);
public void onGenericMotionEvent(MotionEvent event);
public void onPause();
public void onResume();
public interface InputDeviceListener {
void onInputDeviceAdded(int deviceId);
void onInputDeviceChanged(int deviceId);
void onInputDeviceRemoved(int deviceId);
}
...
}
该InputManagerCompat界面提供了以下方法:
getInputDevice()
镜子getInputDevice()。获取InputDevice 表示游戏控制器功能的对象。
getInputDeviceIds()
镜子getInputDeviceIds()。返回一个整数数组,每个整数都是不同输入设备的ID。如果您正在构建支持多个玩家的游戏并且您想要检测连接了多少个控制器,这非常有用。
registerInputDeviceListener()
镜子registerInputDeviceListener()。允许您注册以在添加,更改或移除新设备时得到通知。
unregisterInputDeviceListener()
镜子unregisterInputDeviceListener()。取消注册输入设备监听器。
onGenericMotionEvent()
镜子onGenericMotionEvent()。让您的游戏拦截并处理 MotionEvent表示事件的对象和轴值,如操纵杆移动和模拟触发器按下。
onPause()
当主要活动暂停时或当游戏不再有焦点时,停止轮询游戏控制器事件。
onResume()
当主要活动恢复时,或游戏开始并在前台运行时,开始对游戏控制器事件进行轮询。
InputDeviceListener
镜像InputManager.InputDeviceListener 界面。让您的游戏知道游戏控制器何时添加,更改或移除。
接下来,为InputManagerCompat不同平台版本创建该工作的实现。如果您的游戏在Android 4.1或更高版本上运行并调用InputManagerCompat方法,则代理实现将调用等效方法InputManager。但是,如果您的游戏在Android 3.1之前运行到Android 4.0,则自定义实现会InputManagerCompat通过仅使用不晚于Android 3.1引入的API 来调用方法。无论在运行时使用哪个特定于版本的实现,实现都会将调用结果透明地传递回游戏。
图1.接口和版本特定实现的类图。
在Android 4.1及更高版本上实现界面
// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16 implements InputManagerCompat {
private final InputManager mInputManager;
private final Map<InputManagerCompat.InputDeviceListener,
V16InputDeviceListener> mListeners;
public InputManagerV16(Context context) {
mInputManager = (InputManager)
context.getSystemService(Context.INPUT_SERVICE);
mListeners = new HashMap<InputManagerCompat.InputDeviceListener,
V16InputDeviceListener>();
}
@Override
public InputDevice getInputDevice(int id) {
return mInputManager.getInputDevice(id);
}
@Override
public int[] getInputDeviceIds() {
return mInputManager.getInputDeviceIds();
}
static class V16InputDeviceListener implements
InputManager.InputDeviceListener {
final InputManagerCompat.InputDeviceListener mIDL;
public V16InputDeviceListener(InputDeviceListener idl) {
mIDL = idl;
}
@Override
public void onInputDeviceAdded(int deviceId) {
mIDL.onInputDeviceAdded(deviceId);
}
// Do the same for device change and removal
...
}
@Override
public void registerInputDeviceListener(InputDeviceListener listener,
Handler handler) {
V16InputDeviceListener v16Listener = new
V16InputDeviceListener(listener);
mInputManager.registerInputDeviceListener(v16Listener, handler);
mListeners.put(listener, v16Listener);
}
// Do the same for unregistering an input device listener
...
@Override
public void onGenericMotionEvent(MotionEvent event) {
// unused in V16
}
@Override
public void onPause() {
// unused in V16
}
@Override
public void onResume() {
// unused in V16
}
}
在Android 3.1之前实现Android 4.0以上的界面
要创建InputManagerCompat支持Android 3.1直到Android 4.0的实现,可以使用以下对象:
- 甲SparseArray设备ID来跟踪被连接到设备的游戏控制器。
- A Handler处理设备事件。当应用程序启动或恢复时,会Handler收到一条消息,开始轮询游戏控制器断开连接。该Handler将启动一个循环来检查每个已知的连接游戏控制器,并查看是否返回设备ID。一个null返回值表示游戏控制器断开。在Handler当应用程序被暂停停止探询。
- 一个Map的InputManagerCompat.InputDeviceListener 对象。您将使用听众更新跟踪的游戏控制器的连接状态。
// The InputManagerCompatV9 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV9 implements InputManagerCompat {
private final SparseArray<long[]> mDevices;
private final Map<InputDeviceListener, Handler> mListeners;
private final Handler mDefaultHandler;
…
public InputManagerV9() {
mDevices = new SparseArray<long[]>();
mListeners = new HashMap<InputDeviceListener, Handler>();
mDefaultHandler = new PollingMessageHandler(this);
}
}
实现一个PollingMessageHandler扩展的对象 Handler,并覆盖该 handleMessage() 方法。此方法检查附加的游戏控制器是否已断开连接并通知注册的侦听器。
private static class PollingMessageHandler extends Handler {
private final WeakReference<InputManagerV9> mInputManager;
PollingMessageHandler(InputManagerV9 im) {
mInputManager = new WeakReference<InputManagerV9>(im);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESSAGE_TEST_FOR_DISCONNECT:
InputManagerV9 imv = mInputManager.get();
if (null != imv) {
long time = SystemClock.elapsedRealtime();
int size = imv.mDevices.size();
for (int i = 0; i < size; i++) {
long[] lastContact = imv.mDevices.valueAt(i);
if (null != lastContact) {
if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
// check to see if the device has been
// disconnected
int id = imv.mDevices.keyAt(i);
if (null == InputDevice.getDevice(id)) {
// Notify the registered listeners
// that the game controller is disconnected
...
imv.mDevices.remove(id);
} else {
lastContact[0] = time;
}
}
}
}
sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
CHECK_ELAPSED_TIME);
}
break;
}
}
}
要启动和停止轮询游戏控制器断开连接,请覆盖这些方法:
private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
private static final long CHECK_ELAPSED_TIME = 3000L;
@Override
public void onPause() {
mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
}
@Override
public void onResume() {
mDefaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
CHECK_ELAPSED_TIME);
}
要检测输入设备是否已添加,请覆盖该 onGenericMotionEvent()方法。当系统报告动作事件时,请检查此事件是来自已经跟踪的设备ID还是来自新设备ID。如果设备ID是新的,则通知注册的监听者。
@Override
public void onGenericMotionEvent(MotionEvent event) {
// detect new devices
int id = event.getDeviceId();
long[] timeArray = mDevices.get(id);
if (null == timeArray) {
// Notify the registered listeners that a game controller is added
...
timeArray = new long[1];
mDevices.put(id, timeArray);
}
long time = SystemClock.elapsedRealtime();
timeArray[0] = time;
}
监听器的通知是通过使用 对象将消息Handler发送DeviceEvent Runnable到消息队列来实现的。在DeviceEvent 包含对一个参考InputManagerCompat.InputDeviceListener。当DeviceEvent运行时,收听者的适当的回调方法被调用,如果加入的游戏控制器,更改或删除的信号。
@Override
public void registerInputDeviceListener(InputDeviceListener listener,
Handler handler) {
mListeners.remove(listener);
if (handler == null) {
handler = mDefaultHandler;
}
mListeners.put(listener, handler);
}
@Override
public void unregisterInputDeviceListener(InputDeviceListener listener) {
mListeners.remove(listener);
}
private void notifyListeners(int why, int deviceId) {
// the state of some device has changed
if (!mListeners.isEmpty()) {
for (InputDeviceListener listener : mListeners.keySet()) {
Handler handler = mListeners.get(listener);
DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId,
listener);
handler.post(odc);
}
}
}
private static class DeviceEvent implements Runnable {
private int mMessageType;
private int mId;
private InputDeviceListener mListener;
private static Queue<DeviceEvent> sObjectQueue =
new ArrayDeque<DeviceEvent>();
...
static DeviceEvent getDeviceEvent(int messageType, int id,
InputDeviceListener listener) {
DeviceEvent curChanged = sObjectQueue.poll();
if (null == curChanged) {
curChanged = new DeviceEvent();
}
curChanged.mMessageType = messageType;
curChanged.mId = id;
curChanged.mListener = listener;
return curChanged;
}
@Override
public void run() {
switch (mMessageType) {
case ON_DEVICE_ADDED:
mListener.onInputDeviceAdded(mId);
break;
case ON_DEVICE_CHANGED:
mListener.onInputDeviceChanged(mId);
break;
case ON_DEVICE_REMOVED:
mListener.onInputDeviceRemoved(mId);
break;
default:
// Handle unknown message type
...
break;
}
// Put this runnable back in the queue
sObjectQueue.offer(this);
}
}
您现在有两种实现方式InputManagerCompat:一种适用于运行Android 4.1及更高版本的设备,另一种适用于运行Android 3.1的设备,适用于Android 4.0。
使用版本特定的实现
特定于版本的切换逻辑在作为工厂的类中实现。
public static class Factory {
public static InputManagerCompat getInputManager(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return new InputManagerV16(context);
} else {
return new InputManagerV9();
}
}
}
现在你可以简单地实例化一个InputManagerCompat对象并InputManagerCompat.InputDeviceListener在你的main中 注册一个对象View。由于您设置的版本切换逻辑,您的游戏会自动使用适用于该设备运行的Android版本的实现。
public class GameView extends View implements InputDeviceListener {
private InputManagerCompat mInputManager;
...
public GameView(Context context, AttributeSet attrs) {
mInputManager =
InputManagerCompat.Factory.getInputManager(this.getContext());
mInputManager.registerInputDeviceListener(this, null);
...
}
}
接下来,重写onGenericMotionEvent()主视图中的 方法,如 从游戏控制器处理MotionEvent中所述。您的游戏现在应该能够在运行Android 3.1(API级别12)及更高版本的设备上持续处理游戏控制器事件。
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
mInputManager.onGenericMotionEvent(event);
// Handle analog input from the controller as normal
...
return super.onGenericMotionEvent(event);
}
您可以GameView在ControllerSample.zip 上面提供的示例中提供的类中找到此兼容性代码的完整实现 。
Lastest Update:2018.04.23
联系我
QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ
公众号推荐:
转载于:https://blog.51cto.com/4789781/2125394