You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-05 12:10:55 +00:00
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:
@@ -38,6 +38,8 @@ import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
|
|||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.util.SparseIntArray;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.InputDevice.MotionRange;
|
import android.view.InputDevice.MotionRange;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -46,17 +48,24 @@ import android.view.MotionEvent;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles input related events for the {@link GodotRenderView} view.
|
* Handles input related events for the {@link GodotRenderView} view.
|
||||||
*/
|
*/
|
||||||
public class GodotInputHandler implements InputDeviceListener {
|
public class GodotInputHandler implements InputDeviceListener {
|
||||||
private final ArrayList<Joystick> mJoysticksDevices = new ArrayList<Joystick>();
|
|
||||||
|
|
||||||
private final GodotRenderView mRenderView;
|
private final GodotRenderView mRenderView;
|
||||||
private final InputManagerCompat mInputManager;
|
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) {
|
public GodotInputHandler(GodotRenderView godotView) {
|
||||||
mRenderView = godotView;
|
mRenderView = godotView;
|
||||||
mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext());
|
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) {
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
int source = event.getSource();
|
int source = event.getSource();
|
||||||
if (isKeyEvent_GameDevice(source)) {
|
if (isKeyEvent_GameDevice(source)) {
|
||||||
final int button = getGodotButton(keyCode);
|
|
||||||
final int device_id = findJoystickDevice(event.getDeviceId());
|
|
||||||
|
|
||||||
// Check if the device exists
|
// 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() {
|
queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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);
|
GodotLib.key(keyCode, scanCode, chr, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -122,24 +132,25 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
int source = event.getSource();
|
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)));
|
//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 (isKeyEvent_GameDevice(source)) {
|
||||||
if (event.getRepeatCount() > 0) // ignore key echo
|
if (event.getRepeatCount() > 0) // ignore key echo
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
|
||||||
final int button = getGodotButton(keyCode);
|
final int button = getGodotButton(keyCode);
|
||||||
final int device_id = findJoystickDevice(event.getDeviceId());
|
final int godotJoyId = mJoystickIds.get(deviceId);
|
||||||
|
|
||||||
// Check if the device exists
|
|
||||||
if (device_id > -1) {
|
|
||||||
queueEvent(new Runnable() {
|
queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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);
|
GodotLib.key(keyCode, scanCode, chr, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -203,38 +214,52 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||||
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
|
if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
final int device_id = findJoystickDevice(event.getDeviceId());
|
|
||||||
|
|
||||||
// Check if the device exists
|
// Check if the device exists
|
||||||
if (device_id > -1) {
|
final int deviceId = event.getDeviceId();
|
||||||
Joystick joy = mJoysticksDevices.get(device_id);
|
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++) {
|
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||||
InputDevice.MotionRange range = joy.axes.get(i);
|
final int axis = joystick.axes.get(i);
|
||||||
final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
final float value = event.getAxisValue(axis);
|
||||||
final int idx = i;
|
/**
|
||||||
|
* 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() {
|
queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GodotLib.joyaxis(device_id, idx, value);
|
GodotLib.joyaxis(godotJoyId, godotAxisIdx, value);
|
||||||
|
//Log.i(tag, "GodotLib.joyaxis("+godotJoyId+", "+godotAxisIdx+", "+value+");");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < joy.hats.size(); i += 2) {
|
if (joystick.hasAxisHat) {
|
||||||
final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
|
final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X));
|
||||||
final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
|
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() {
|
queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GodotLib.joyhat(device_id, hatX, hatY);
|
GodotLib.joyhat(godotJoyId, hatX, hatY);
|
||||||
|
//Log.i(tag, "GodotLib.joyhat("+godotJoyId+", "+hatX+", "+hatY+");");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
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 x = event.getX();
|
||||||
final float y = event.getY();
|
final float y = event.getY();
|
||||||
final int type = event.getAction();
|
final int type = event.getAction();
|
||||||
@@ -245,6 +270,7 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
|
} else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
return handleMouseEvent(event);
|
return handleMouseEvent(event);
|
||||||
@@ -266,68 +292,99 @@ 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
|
@Override
|
||||||
public void onInputDeviceAdded(int deviceId) {
|
public void onInputDeviceAdded(int deviceId) {
|
||||||
int id = findJoystickDevice(deviceId);
|
|
||||||
|
|
||||||
// Check if the device has not been already added
|
// Check if the device has not been already added
|
||||||
if (id < 0) {
|
|
||||||
|
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
InputDevice device = mInputManager.getInputDevice(deviceId);
|
InputDevice device = mInputManager.getInputDevice(deviceId);
|
||||||
//device can be null if deviceId is not found
|
//device can be null if deviceId is not found
|
||||||
if (device != null) {
|
if (device == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int sources = device.getSources();
|
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();
|
// Device may not be a joystick or gamepad
|
||||||
joy.device_id = deviceId;
|
if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
|
||||||
joy.name = device.getName();
|
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
|
||||||
joy.axes = new ArrayList<InputDevice.MotionRange>();
|
return;
|
||||||
joy.hats = new ArrayList<InputDevice.MotionRange>();
|
}
|
||||||
|
|
||||||
List<InputDevice.MotionRange> ranges = device.getMotionRanges();
|
// Assign first available number. Re-use numbers where possible.
|
||||||
Collections.sort(ranges, new RangeComparator());
|
final int id = assignJoystickIdNumber(deviceId);
|
||||||
|
|
||||||
for (InputDevice.MotionRange range : ranges) {
|
final Joystick joystick = new Joystick();
|
||||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
joystick.device_id = deviceId;
|
||||||
joy.hats.add(range);
|
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 {
|
} else {
|
||||||
joy.axes.add(range);
|
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);
|
||||||
|
|
||||||
mJoysticksDevices.add(joy);
|
|
||||||
|
|
||||||
final int device_id = id;
|
|
||||||
final String name = joy.name;
|
|
||||||
queueEvent(new Runnable() {
|
queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GodotLib.joyconnectionchanged(device_id, true, name);
|
GodotLib.joyconnectionchanged(id, true, joystick.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputDeviceRemoved(int deviceId) {
|
public void onInputDeviceRemoved(int deviceId) {
|
||||||
final int device_id = findJoystickDevice(deviceId);
|
// Check if the device has not been already removed
|
||||||
|
if (mJoystickIds.indexOfKey(deviceId) < 0) {
|
||||||
// Check if the evice has not been already removed
|
return;
|
||||||
if (device_id > -1) {
|
}
|
||||||
mJoysticksDevices.remove(device_id);
|
final int godotJoyId = mJoystickIds.get(deviceId);
|
||||||
|
mJoystickIds.delete(deviceId);
|
||||||
|
mJoysticksDevices.delete(deviceId);
|
||||||
|
|
||||||
queueEvent(new Runnable() {
|
queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
GodotLib.joyconnectionchanged(device_id, false, "");
|
GodotLib.joyconnectionchanged(godotJoyId, false, "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputDeviceChanged(int deviceId) {
|
public void onInputDeviceChanged(int deviceId) {
|
||||||
@@ -407,16 +464,6 @@ public class GodotInputHandler implements InputDeviceListener {
|
|||||||
return button;
|
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) {
|
private boolean handleMouseEvent(final MotionEvent event) {
|
||||||
switch (event.getActionMasked()) {
|
switch (event.getActionMasked()) {
|
||||||
case MotionEvent.ACTION_HOVER_ENTER:
|
case MotionEvent.ACTION_HOVER_ENTER:
|
||||||
|
|||||||
@@ -30,9 +30,10 @@
|
|||||||
|
|
||||||
package org.godotengine.godot.input;
|
package org.godotengine.godot.input;
|
||||||
|
|
||||||
import android.view.InputDevice.MotionRange;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POJO class to represent a Joystick input device.
|
* POJO class to represent a Joystick input device.
|
||||||
@@ -40,6 +41,12 @@ import java.util.ArrayList;
|
|||||||
class Joystick {
|
class Joystick {
|
||||||
int device_id;
|
int device_id;
|
||||||
String name;
|
String name;
|
||||||
ArrayList<MotionRange> axes;
|
List<Integer> axes = new ArrayList<Integer>();
|
||||||
ArrayList<MotionRange> hats;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user