Cómo brindar compatibilidad con controles en diferentes versiones de Android

Si admites controles de juegos en tu juego, es tu responsabilidad para asegurarte de que tu juego responda a los controles de forma coherente en todos los dispositivos que se ejecutan en diferentes versiones de Android. Esto permite que el juego llegue a un público público y los jugadores puedan disfrutar de una experiencia de juego sin interrupciones con sus controles, incluso cuando cambian o actualizan sus dispositivos Android.

En esta lección, se muestra cómo usar las APIs disponibles en Android 4.1 y versiones posteriores de manera retrocompatible, lo que permite que tu juego admita funciones en dispositivos con Android 3.1 y versiones posteriores:

  • El juego puede detectar si se agrega, cambia o quita un nuevo control para videojuegos.
  • El juego puede consultar las capacidades de un control para videojuegos.
  • El juego puede reconocer eventos de movimiento entrantes de un control para videojuegos.

Los ejemplos de esta lección se basan en la implementación de referencia proporcionados por la muestra ControllerSample.zip disponibles para descargar arriba. En este ejemplo, se muestra cómo implementar InputManagerCompat para admitir diferentes versiones de Android. Para compilar la muestra, debe usar Android 4.1 (nivel de API 16) o una versión posterior. Una vez compilada, la app de ejemplo Se ejecuta en cualquier dispositivo con Android 3.1 (nivel de API 12) o versiones posteriores, ya que la compilación objetivo.

Cómo prepararse para abstraer las API a fin de agregar compatibilidad con controles para videojuegos

Supongamos que quieres poder determinar si la conexión de un control de juegos el estado cambió en los dispositivos que ejecutan Android 3.1 (nivel de API 12). Sin embargo, las APIs solo están disponibles en Android 4.1 (nivel de API 16) y versiones posteriores, por lo que debemos proporcionar una implementación que admita Android 4.1 y versiones posteriores, mientras que un mecanismo de resguardo que admita desde Android 3.1 hasta Android 4.0.

Para ayudarte a determinar qué funciones requieren ese mecanismo de resguardo para versiones anteriores, en la tabla 1, se mencionan las diferencias de la compatibilidad con los controles de juegos entre Android 3.1 (nivel de API 12) y 4.1 (nivel de API) 16).

Tabla 1: API de compatibilidad con controles de juegos en en diferentes versiones de Android.

Información del control API del control API nivel 12 API nivel 16
Identificación del dispositivo getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
Estado de la conexión onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Identificación del evento de entrada Pulsación en el pad direccional ( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER).
Presionar el botón del control de mando ( BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1, BUTTON_L1, BUTTON_R2, BUTTON_L2)
Movimiento del joystick y del interruptor de sombreros ( AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y)
Pulsación del gatillo analógico ( AXIS_LTRIGGER, AXIS_RTRIGGER)

Puedes usar la abstracción para compilar una compatibilidad con controles de juegos con reconocimiento de versiones que funciona en todas las plataformas. Este enfoque requiere los siguientes pasos:

  1. Define una interfaz de Java intermediaria que abstraiga la implementación de las funciones de controles de juegos que requiere tu juego.
  2. Cómo crear una implementación de proxy de tu interfaz que use APIs en Android 4.1 y versiones posteriores
  3. Crea una implementación personalizada de tu interfaz que use las APIs disponibles entre Android 3.1 y Android 4.0.
  4. Crear la lógica para alternar entre estas implementaciones en el entorno de ejecución y comenzar a usar la interfaz en tu juego.

Para obtener una descripción general de cómo se puede usar la abstracción para garantizar que las aplicaciones pueden funcionar con retrocompatibilidad en diferentes versiones de Android; consulta Creación IU retrocompatibles.

Cómo agregar una interfaz para lograr la compatibilidad con versiones anteriores

Para ofrecer retrocompatibilidad, puedes crear una interfaz personalizada y luego para agregar implementaciones específicas de la versión. Una ventaja de este enfoque es que te permite duplicar las interfaces públicas en Android 4.1 (nivel de API 16) que admitir controles de juegos.

Kotlin

// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
interface InputManagerCompat {
    val inputDeviceIds: IntArray
    fun getInputDevice(id: Int): InputDevice

    fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    )

    fun unregisterInputDeviceListener(listener:InputManager.InputDeviceListener)

    fun onGenericMotionEvent(event: MotionEvent)

    fun onPause()
    fun onResume()

    interface InputDeviceListener {
        fun onInputDeviceAdded(deviceId: Int)
        fun onInputDeviceChanged(deviceId: Int)
        fun onInputDeviceRemoved(deviceId: Int)
    }
}

Java

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

La interfaz InputManagerCompat proporciona los siguientes métodos:

getInputDevice()
Duplica getInputDevice(). Obtiene el InputDevice que representa las capacidades de un control de juegos.
getInputDeviceIds()
Duplica getInputDeviceIds(). Devuelve un array de números enteros, cada uno de que es un ID para un dispositivo de entrada diferente. Esto es útil si estás creando un juego que admite varios jugadores y quieres detectar cuántos controles están conectados.
registerInputDeviceListener()
Duplica registerInputDeviceListener(). Te permite registrarte para saber cuando un nuevo se agrega, cambia o quita el dispositivo.
unregisterInputDeviceListener()
Duplica unregisterInputDeviceListener(). Cancela el registro de un objeto de escucha de dispositivos de entrada.
onGenericMotionEvent()
Duplica onGenericMotionEvent(). Permite que el juego intercepte y controle Objetos MotionEvent y valores de ejes que representan eventos como movimientos del joystick y pulsaciones de gatillos analógicos.
onPause()
Deja de sondear los eventos de controles para juegos cuando la se pausa la actividad principal o cuando el juego ya no está enfocado.
onResume()
Inicia el sondeo de los eventos de controles para juegos cuando la la actividad principal se reanuda, o cuando el juego se inicia y se ejecuta en la primer plano.
InputDeviceListener
Refleja InputManager.InputDeviceListener interfaz de usuario. Permite que el juego sepa si se agregó, cambió o agregó un control para juegos. o quitarse.

A continuación, crea implementaciones para InputManagerCompat que funcionen en diferentes versiones de la plataforma. Si el juego se ejecuta en Android 4.1 o una superior y llama a un método InputManagerCompat, la implementación del proxy llama al método equivalente en InputManager. Sin embargo, si tu juego se ejecuta desde Android 3.1 hasta Android 4.0, la implementación personalizada procesa las llamadas a los métodos InputManagerCompat mediante solo las APIs introducidas antes de Android 3.1. Independientemente de cuál implementación específica de la versión se usa en el tiempo de ejecución, la implementación pasa los resultados de la llamada de vuelta al juego de manera transparente.

Figura 1: Diagrama de clase de la interfaz y la versión específica de Google Cloud.

Cómo implementar la interfaz en Android 4.1 y versiones posteriores

InputManagerCompatV16 es una implementación de la interfaz de InputManagerCompat que procesa las llamadas de método a un InputManager y InputManager.InputDeviceListener reales. El InputManager se obtiene del sistema. Context

Kotlin

// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16(
        context: Context,
        private val inputManager: InputManager =
            context.getSystemService(Context.INPUT_SERVICE) as InputManager,
        private val listeners:
            MutableMap<InputManager.InputDeviceListener, V16InputDeviceListener> = mutableMapOf()
) : InputManagerCompat {
    override val inputDeviceIds: IntArray = inputManager.inputDeviceIds

    override fun getInputDevice(id: Int): InputDevice = inputManager.getInputDevice(id)

    override fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    ) {
        V16InputDeviceListener(listener).also { v16listener ->
            inputManager.registerInputDeviceListener(v16listener, handler)
            listeners += listener to v16listener
        }
    }

    // Do the same for unregistering an input device listener
    ...

    override fun onGenericMotionEvent(event: MotionEvent) {
        // unused in V16
    }

    override fun onPause() {
        // unused in V16
    }

    override fun onResume() {
        // unused in V16
    }

}

class V16InputDeviceListener(
        private val idl: InputManager.InputDeviceListener
) : InputManager.InputDeviceListener {

    override fun onInputDeviceAdded(deviceId: Int) {
        idl.onInputDeviceAdded(deviceId)
    }
    // Do the same for device change and removal
    ...
}

Java

// 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 inputManager;
    private final Map<InputManagerCompat.InputDeviceListener,
            V16InputDeviceListener> listeners;

    public InputManagerV16(Context context) {
        inputManager = (InputManager)
                context.getSystemService(Context.INPUT_SERVICE);
        listeners = new HashMap<InputManagerCompat.InputDeviceListener,
                V16InputDeviceListener>();
    }

    @Override
    public InputDevice getInputDevice(int id) {
        return inputManager.getInputDevice(id);
    }

    @Override
    public int[] getInputDeviceIds() {
        return inputManager.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);
        inputManager.registerInputDeviceListener(v16Listener, handler);
        listeners.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
    }

}

Cómo implementar la interfaz desde Android 3.1 hasta Android 4.0

Para crear una implementación de InputManagerCompat que admita desde Android 3.1 hasta Android 4.0, puedes usar los siguientes objetos:

  • Un SparseArray de los IDs de dispositivo para hacer un seguimiento del controles de juegos conectados al dispositivo.
  • Un Handler para procesar eventos de dispositivos. Cuando se inicia una app o reanudado, Handler recibe un mensaje para iniciar el sondeo de desconexión de controles de juegos. El Handler iniciará una para comprobar cada control de juegos conectado conocido y ver si el ID de dispositivo es que se devuelven. Un valor de retorno null indica que el control de juegos está desconectado. El Handler deja de realizar el sondeo cuando la app está se pausó.
  • Una Map de InputManagerCompat.InputDeviceListener objetos. Usarás los objetos de escucha para actualizar el estado de conexión del servicio controles de juegos.

Kotlin

// The InputManagerCompatV9 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    private val defaultHandler: Handler = PollingMessageHandler(this)
    
}

Java

// 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[]> devices;
    private final Map<InputDeviceListener, Handler> listeners;
    private final Handler defaultHandler;
    

    public InputManagerV9() {
        devices = new SparseArray<long[]>();
        listeners = new HashMap<InputDeviceListener, Handler>();
        defaultHandler = new PollingMessageHandler(this);
    }
}

Implementa un objeto PollingMessageHandler que extienda Handler y anula el handleMessage() . Este método verifica si se conectó un control de juegos desconectado y notifica a los objetos de escucha registrados.

Kotlin

private class PollingMessageHandler(
        inputManager: InputManagerV9,
        private val mInputManager: WeakReference<InputManagerV9> = WeakReference(inputManager)
) : Handler() {

    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        when (msg.what) {
            MESSAGE_TEST_FOR_DISCONNECT -> {
                mInputManager.get()?.also { imv ->
                    val time = SystemClock.elapsedRealtime()
                    val size = imv.devices.size()
                    for (i in 0 until size) {
                        imv.devices.valueAt(i)?.also { lastContact ->
                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                // check to see if the device has been
                                // disconnected
                                val id = imv.devices.keyAt(i)
                                if (null == InputDevice.getDevice(id)) {
                                    // Notify the registered listeners
                                    // that the game controller is disconnected
                                    imv.devices.remove(id)
                                } else {
                                    lastContact[0] = time
                                }
                            }
                        }
                    }
                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
                }
            }
        }
    }
}

Java

private static class PollingMessageHandler extends Handler {
    private final WeakReference<InputManagerV9> inputManager;

    PollingMessageHandler(InputManagerV9 im) {
        inputManager = new WeakReference<InputManagerV9>(im);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MESSAGE_TEST_FOR_DISCONNECT:
                InputManagerV9 imv = inputManager.get();
                if (null != imv) {
                    long time = SystemClock.elapsedRealtime();
                    int size = imv.devices.size();
                    for (int i = 0; i < size; i++) {
                        long[] lastContact = imv.devices.valueAt(i);
                        if (null != lastContact) {
                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                // check to see if the device has been
                                // disconnected
                                int id = imv.devices.keyAt(i);
                                if (null == InputDevice.getDevice(id)) {
                                    // Notify the registered listeners
                                    // that the game controller is disconnected
                                    imv.devices.remove(id);
                                } else {
                                    lastContact[0] = time;
                                }
                            }
                        }
                    }
                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
                            CHECK_ELAPSED_TIME);
                }
                break;
        }
    }
}

Si quieres iniciar y detener el sondeo de desconexión de controles para juegos, anula estos métodos:

Kotlin

private const val MESSAGE_TEST_FOR_DISCONNECT = 101
private const val CHECK_ELAPSED_TIME = 3000L

class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    ...
    override fun onPause() {
        defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT)
    }

    override fun onResume() {
        defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
    }
    ...
}

Java

private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
private static final long CHECK_ELAPSED_TIME = 3000L;

@Override
public void onPause() {
    defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
}

@Override
public void onResume() {
    defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
            CHECK_ELAPSED_TIME);
}

Para detectar si se agregó un dispositivo de entrada, anula el onGenericMotionEvent(). Cuando el sistema informa un evento de movimiento, verifica si este evento provino de un ID de dispositivo ya registrado o de un nuevo ID de dispositivo. Si es nuevo, infórmalo a los objetos de escucha registrados.

Kotlin

override fun onGenericMotionEvent(event: MotionEvent) {
    // detect new devices
    val id = event.deviceId
    val timeArray: Array<Long> = mDevices.get(id) ?: run {
        // Notify the registered listeners that a game controller is added
        ...
        arrayOf<Long>().also {
            mDevices.put(id, it)
        }
    }
    timeArray[0] = SystemClock.elapsedRealtime()
}

Java

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

Las notificaciones a los objetos de escucha se implementan a través del Handler para enviar un DeviceEvent Runnable a la cola de mensajes. El DeviceEvent contiene una referencia a un InputManagerCompat.InputDeviceListener. Cuándo se ejecuta DeviceEvent, el método de devolución de llamada apropiado del objeto de escucha para indicar si se agregó, cambió o quitó el control de juegos

Kotlin

class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    ...
    override fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    ) {
        listeners[listener] = handler ?: defaultHandler
    }

    override fun unregisterInputDeviceListener(listener: InputManager.InputDeviceListener) {
        listeners.remove(listener)
    }

    private fun notifyListeners(why: Int, deviceId: Int) {
        // the state of some device has changed
        listeners.forEach { listener, handler ->
            DeviceEvent.getDeviceEvent(why, deviceId, listener).also {
                handler?.post(it)
            }
        }
    }
    ...
}

private val sObjectQueue: Queue<DeviceEvent> = ArrayDeque<DeviceEvent>()

private class DeviceEvent(
        private var mMessageType: Int,
        private var mId: Int,
        private var mListener: InputManager.InputDeviceListener
) : Runnable {

    companion object {
        fun getDeviceEvent(messageType: Int, id: Int, listener: InputManager.InputDeviceListener) =
                sObjectQueue.poll()?.apply {
                    mMessageType = messageType
                    mId = id
                    mListener = listener
                } ?: DeviceEvent(messageType, id, listener)

    }

    override fun run() {
        when(mMessageType) {
            ON_DEVICE_ADDED -> mListener.onInputDeviceAdded(mId)
            ON_DEVICE_CHANGED -> mListener.onInputDeviceChanged(mId)
            ON_DEVICE_REMOVED -> mListener.onInputDeviceChanged(mId)
            else -> {
                // Handle unknown message type
            }
        }
    }

}

Java

@Override
public void registerInputDeviceListener(InputDeviceListener listener,
        Handler handler) {
    listeners.remove(listener);
    if (handler == null) {
        handler = defaultHandler;
    }
    listeners.put(listener, handler);
}

@Override
public void unregisterInputDeviceListener(InputDeviceListener listener) {
    listeners.remove(listener);
}

private void notifyListeners(int why, int deviceId) {
    // the state of some device has changed
    if (!listeners.isEmpty()) {
        for (InputDeviceListener listener : listeners.keySet()) {
            Handler handler = listeners.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);
    }
}

Ahora tienes dos implementaciones de InputManagerCompat: una que funciona en dispositivos con Android 4.1 y versiones posteriores, y otro que funciona en dispositivos que ejecutan desde Android 3.1 hasta Android 4.0.

Cómo usar la implementación específica de la versión

La lógica de conmutación específica de la versión se implementa en una clase que actúa como una fábrica.

Kotlin

object Factory {
    fun getInputManager(context: Context): InputManagerCompat =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                InputManagerV16(context)
            } else {
                InputManagerV9()
            }
}

Java

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

Ahora, simplemente, puedes crear una instancia de un objeto InputManagerCompat y registra un InputManagerCompat.InputDeviceListener en tu View Debido a la lógica de cambio de versión el juego usa automáticamente la implementación adecuada para la versión de Android que ejecuta el dispositivo.

Kotlin

class GameView(context: Context) : View(context), InputManager.InputDeviceListener {
    private val inputManager: InputManagerCompat = Factory.getInputManager(context).apply {
        registerInputDeviceListener(this@GameView, null)
        ...
    }
    ...
}

Java

public class GameView extends View implements InputDeviceListener {
    private InputManagerCompat inputManager;
    ...

    public GameView(Context context, AttributeSet attrs) {
        inputManager =
                InputManagerCompat.Factory.getInputManager(this.getContext());
        inputManager.registerInputDeviceListener(this, null);
        ...
    }
}

Luego, anula el onGenericMotionEvent() en la vista principal, como se describe en Cómo controlar un MotionEvent desde un juego Controlador. Tu juego ahora debería poder procesar eventos de controles para juegos de manera consistente en dispositivos con Android 3.1 (nivel de API 12) y versiones posteriores.

Kotlin

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    inputManager.onGenericMotionEvent(event)

    // Handle analog input from the controller as normal
    ...
    return super.onGenericMotionEvent(event)
}

Java

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
    inputManager.onGenericMotionEvent(event);

    // Handle analog input from the controller as normal
    ...
    return super.onGenericMotionEvent(event);
}

Encontrarás una implementación completa de este código de compatibilidad en la Clase GameView proporcionada en el ControllerSample.zip de muestra disponibles para su descarga en la parte superior.