Fix joystick axis mapping issues with NVIDIA shield. Probably others.
Issues addressed: a) Axis mappings were including virtual mouse axes on NVIDIA Shield TV. The virtual mouse axes have the same axis numbers as the normal analog stick numbers. This was completely breaking joypad support on NVIDIA Shield TV. b) Joypads were being tracked in a List with the index in the list being treated as the Godot device id. If a device were to be removed, any device later in the list would be shifted, potentially causing future events with the shifted joypads to have incorrect IDs according to the Godot engine. c) Unnecessary events were being sent to the Godot engine. A check was added (per Joystick) that will prevent sending events for all axes when only a single axis value changed. A similar check was added for "HATs". See #45712
This commit is contained in:
parent
fa2f7693bb
commit
cc2547a9e9
2 changed files with 153 additions and 99 deletions
|
@ -38,6 +38,8 @@ import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
|
|||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputDevice.MotionRange;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -46,17 +48,24 @@ import android.view.MotionEvent;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Handles input related events for the {@link GodotRenderView} view.
|
||||
*/
|
||||
public class GodotInputHandler implements InputDeviceListener {
|
||||
private final ArrayList<Joystick> mJoysticksDevices = new ArrayList<Joystick>();
|
||||
|
||||
private final GodotRenderView mRenderView;
|
||||
private final InputManagerCompat mInputManager;
|
||||
|
||||
private final String tag = this.getClass().getSimpleName();
|
||||
|
||||
private final SparseIntArray mJoystickIds = new SparseIntArray(4);
|
||||
private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<Joystick>(4);
|
||||
|
||||
public GodotInputHandler(GodotRenderView godotView) {
|
||||
mRenderView = godotView;
|
||||
mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext());
|
||||
|
@ -82,19 +91,20 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
int source = event.getSource();
|
||||
if (isKeyEvent_GameDevice(source)) {
|
||||
final int button = getGodotButton(keyCode);
|
||||
final int device_id = findJoystickDevice(event.getDeviceId());
|
||||
|
||||
// Check if the device exists
|
||||
if (device_id > -1) {
|
||||
final int deviceId = event.getDeviceId();
|
||||
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
|
||||
final int button = getGodotButton(keyCode);
|
||||
final int godotJoyId = mJoystickIds.get(deviceId);
|
||||
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joybutton(device_id, button, false);
|
||||
GodotLib.joybutton(godotJoyId, button, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -107,7 +117,7 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
GodotLib.key(keyCode, scanCode, chr, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -122,24 +132,25 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
int source = event.getSource();
|
||||
//Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
|
||||
|
||||
final int deviceId = event.getDeviceId();
|
||||
// Check if source is a game device and that the device is a registered gamepad
|
||||
if (isKeyEvent_GameDevice(source)) {
|
||||
if (event.getRepeatCount() > 0) // ignore key echo
|
||||
return true;
|
||||
|
||||
final int button = getGodotButton(keyCode);
|
||||
final int device_id = findJoystickDevice(event.getDeviceId());
|
||||
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
|
||||
final int button = getGodotButton(keyCode);
|
||||
final int godotJoyId = mJoystickIds.get(deviceId);
|
||||
|
||||
// Check if the device exists
|
||||
if (device_id > -1) {
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joybutton(device_id, button, true);
|
||||
GodotLib.joybutton(godotJoyId, button, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -152,7 +163,7 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
GodotLib.key(keyCode, scanCode, chr, true);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -203,38 +214,52 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
}
|
||||
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
final int device_id = findJoystickDevice(event.getDeviceId());
|
||||
|
||||
if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
// Check if the device exists
|
||||
if (device_id > -1) {
|
||||
Joystick joy = mJoysticksDevices.get(device_id);
|
||||
final int deviceId = event.getDeviceId();
|
||||
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
|
||||
final int godotJoyId = mJoystickIds.get(deviceId);
|
||||
Joystick joystick = mJoysticksDevices.get(deviceId);
|
||||
|
||||
for (int i = 0; i < joy.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joy.axes.get(i);
|
||||
final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
final int idx = i;
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyaxis(device_id, idx, value);
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
final int axis = joystick.axes.get(i);
|
||||
final float value = event.getAxisValue(axis);
|
||||
/**
|
||||
* As all axes are polled for each event, only fire an axis event if the value has actually changed.
|
||||
* Prevents flooding Godot with repeated events.
|
||||
*/
|
||||
if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) {
|
||||
// save value to prevent repeats
|
||||
joystick.axesValues.put(axis, value);
|
||||
final int godotAxisIdx = i;
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyaxis(godotJoyId, godotAxisIdx, value);
|
||||
//Log.i(tag, "GodotLib.joyaxis("+godotJoyId+", "+godotAxisIdx+", "+value+");");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < joy.hats.size(); i += 2) {
|
||||
final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
|
||||
final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyhat(device_id, hatX, hatY);
|
||||
}
|
||||
});
|
||||
if (joystick.hasAxisHat) {
|
||||
final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X));
|
||||
final int hatY = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_Y));
|
||||
if (joystick.hatX != hatX || joystick.hatY != hatY) {
|
||||
joystick.hatX = hatX;
|
||||
joystick.hatY = hatY;
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyhat(godotJoyId, hatX, hatY);
|
||||
//Log.i(tag, "GodotLib.joyhat("+godotJoyId+", "+hatX+", "+hatY+");");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) {
|
||||
} else if (event.isFromSource(InputDevice.SOURCE_STYLUS)) {
|
||||
final float x = event.getX();
|
||||
final float y = event.getY();
|
||||
final int type = event.getAction();
|
||||
|
@ -245,6 +270,7 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
} else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return handleMouseEvent(event);
|
||||
|
@ -266,67 +292,98 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
}
|
||||
}
|
||||
|
||||
private int assignJoystickIdNumber(int deviceId) {
|
||||
int godotJoyId = 0;
|
||||
while (mJoystickIds.indexOfValue(godotJoyId) >= 0) {
|
||||
godotJoyId++;
|
||||
}
|
||||
mJoystickIds.put(deviceId, godotJoyId);
|
||||
return godotJoyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
int id = findJoystickDevice(deviceId);
|
||||
|
||||
// Check if the device has not been already added
|
||||
if (id < 0) {
|
||||
InputDevice device = mInputManager.getInputDevice(deviceId);
|
||||
//device can be null if deviceId is not found
|
||||
if (device != null) {
|
||||
int sources = device.getSources();
|
||||
if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
|
||||
((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
|
||||
id = mJoysticksDevices.size();
|
||||
|
||||
Joystick joy = new Joystick();
|
||||
joy.device_id = deviceId;
|
||||
joy.name = device.getName();
|
||||
joy.axes = new ArrayList<InputDevice.MotionRange>();
|
||||
joy.hats = new ArrayList<InputDevice.MotionRange>();
|
||||
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<InputDevice.MotionRange> ranges = device.getMotionRanges();
|
||||
Collections.sort(ranges, new RangeComparator());
|
||||
InputDevice device = mInputManager.getInputDevice(deviceId);
|
||||
//device can be null if deviceId is not found
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||
joy.hats.add(range);
|
||||
} else {
|
||||
joy.axes.add(range);
|
||||
}
|
||||
}
|
||||
int sources = device.getSources();
|
||||
|
||||
mJoysticksDevices.add(joy);
|
||||
// Device may not be a joystick or gamepad
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
|
||||
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int device_id = id;
|
||||
final String name = joy.name;
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyconnectionchanged(device_id, true, name);
|
||||
}
|
||||
});
|
||||
// Assign first available number. Re-use numbers where possible.
|
||||
final int id = assignJoystickIdNumber(deviceId);
|
||||
|
||||
final Joystick joystick = new Joystick();
|
||||
joystick.device_id = deviceId;
|
||||
joystick.name = device.getName();
|
||||
|
||||
//Helps with creating new joypad mappings.
|
||||
Log.i(tag, "=== New Input Device: " + joystick.name);
|
||||
|
||||
Set<Integer> already = new HashSet<Integer>();
|
||||
for (InputDevice.MotionRange range : device.getMotionRanges()) {
|
||||
boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK);
|
||||
boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD);
|
||||
//Log.i(tag, "axis: "+range.getAxis()+ ", isJoystick: "+isJoystick+", isGamepad: "+isGamepad);
|
||||
if (!isJoystick && !isGamepad) {
|
||||
continue;
|
||||
}
|
||||
final int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) {
|
||||
joystick.hasAxisHat = true;
|
||||
} else {
|
||||
if (!already.contains(axis)) {
|
||||
already.add(axis);
|
||||
joystick.axes.add(axis);
|
||||
} else {
|
||||
Log.w(tag, " - DUPLICATE AXIS VALUE IN LIST: " + axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(joystick.axes);
|
||||
for (int idx = 0; idx < joystick.axes.size(); idx++) {
|
||||
//Helps with creating new joypad mappings.
|
||||
Log.i(tag, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx);
|
||||
}
|
||||
mJoysticksDevices.put(deviceId, joystick);
|
||||
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyconnectionchanged(id, true, joystick.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
final int device_id = findJoystickDevice(deviceId);
|
||||
|
||||
// Check if the evice has not been already removed
|
||||
if (device_id > -1) {
|
||||
mJoysticksDevices.remove(device_id);
|
||||
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyconnectionchanged(device_id, false, "");
|
||||
}
|
||||
});
|
||||
// Check if the device has not been already removed
|
||||
if (mJoystickIds.indexOfKey(deviceId) < 0) {
|
||||
return;
|
||||
}
|
||||
final int godotJoyId = mJoystickIds.get(deviceId);
|
||||
mJoystickIds.delete(deviceId);
|
||||
mJoysticksDevices.delete(deviceId);
|
||||
|
||||
queueEvent(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GodotLib.joyconnectionchanged(godotJoyId, false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -407,16 +464,6 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||
return button;
|
||||
}
|
||||
|
||||
private int findJoystickDevice(int device_id) {
|
||||
for (int i = 0; i < mJoysticksDevices.size(); i++) {
|
||||
if (mJoysticksDevices.get(i).device_id == device_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private boolean handleMouseEvent(final MotionEvent event) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
|
|
|
@ -30,9 +30,10 @@
|
|||
|
||||
package org.godotengine.godot.input;
|
||||
|
||||
import android.view.InputDevice.MotionRange;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* POJO class to represent a Joystick input device.
|
||||
|
@ -40,6 +41,12 @@ import java.util.ArrayList;
|
|||
class Joystick {
|
||||
int device_id;
|
||||
String name;
|
||||
ArrayList<MotionRange> axes;
|
||||
ArrayList<MotionRange> hats;
|
||||
List<Integer> axes = new ArrayList<Integer>();
|
||||
protected boolean hasAxisHat = false;
|
||||
/*
|
||||
* Keep track of values so we can prevent flooding the engine with useless events.
|
||||
*/
|
||||
protected final SparseArray axesValues = new SparseArray<Float>(4);
|
||||
protected int hatX;
|
||||
protected int hatY;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue