internal/gamepaddb: enable the database for Android

Before this fix, the button and axis IDs are from the OS. These
didn't match with the SDL game controller databaes unfortunately.

This fix changes the assignments of the buttons and the axes to match
with the database.

Closes #2312
This commit is contained in:
Hajime Hoshi 2022-09-08 12:29:02 +09:00
parent 6b1502ee71
commit 47558d20c5
6 changed files with 273 additions and 348 deletions

View File

@ -354,6 +354,11 @@ const viewJava = `// Code generated by ebitenmobile. DO NOT EDIT.
package {{.JavaPkg}}.{{.PrefixLower}}; package {{.JavaPkg}}.{{.PrefixLower}};
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.content.Context; import android.content.Context;
import android.hardware.input.InputManager; import android.hardware.input.InputManager;
import android.os.Handler; import android.os.Handler;
@ -371,6 +376,32 @@ import android.view.WindowManager;
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview; import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
public class EbitenView extends ViewGroup implements InputManager.InputDeviceListener { public class EbitenView extends ViewGroup implements InputManager.InputDeviceListener {
static class Gamepad {
public int deviceId;
public ArrayList<InputDevice.MotionRange> axes;
public ArrayList<InputDevice.MotionRange> hats;
}
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L154-L173
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
@Override
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
int arg0Axis = arg0.getAxis();
int arg1Axis = arg1.getAxis();
if (arg0Axis == MotionEvent.AXIS_GAS) {
arg0Axis = MotionEvent.AXIS_BRAKE;
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
arg0Axis = MotionEvent.AXIS_GAS;
}
if (arg1Axis == MotionEvent.AXIS_GAS) {
arg1Axis = MotionEvent.AXIS_BRAKE;
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
arg1Axis = MotionEvent.AXIS_GAS;
}
return arg0Axis - arg1Axis;
}
}
private static double pxToDp(double x) { private static double pxToDp(double x) {
return x / Ebitenmobileview.deviceScale(); return x / Ebitenmobileview.deviceScale();
} }
@ -386,6 +417,8 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
} }
private void initialize(Context context) { private void initialize(Context context) {
this.gamepads = new ArrayList<Gamepad>();
this.ebitenSurfaceView = new EbitenSurfaceView(getContext()); this.ebitenSurfaceView = new EbitenSurfaceView(getContext());
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
addView(this.ebitenSurfaceView, params); addView(this.ebitenSurfaceView, params);
@ -433,75 +466,14 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
return true; return true;
} }
// The order must be the same as mobile/ebitenmobileview/input_android.go. private Gamepad getGamepad(int deviceId) {
static int[] gamepadButtons = { for (Gamepad gamepad : this.gamepads) {
KeyEvent.KEYCODE_BUTTON_A, if (gamepad.deviceId == deviceId) {
KeyEvent.KEYCODE_BUTTON_B, return gamepad;
KeyEvent.KEYCODE_BUTTON_C, }
KeyEvent.KEYCODE_BUTTON_X, }
KeyEvent.KEYCODE_BUTTON_Y, return null;
KeyEvent.KEYCODE_BUTTON_Z, }
KeyEvent.KEYCODE_BUTTON_L1,
KeyEvent.KEYCODE_BUTTON_R1,
KeyEvent.KEYCODE_BUTTON_L2,
KeyEvent.KEYCODE_BUTTON_R2,
KeyEvent.KEYCODE_BUTTON_THUMBL,
KeyEvent.KEYCODE_BUTTON_THUMBR,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_BUTTON_MODE,
KeyEvent.KEYCODE_BUTTON_1,
KeyEvent.KEYCODE_BUTTON_2,
KeyEvent.KEYCODE_BUTTON_3,
KeyEvent.KEYCODE_BUTTON_4,
KeyEvent.KEYCODE_BUTTON_5,
KeyEvent.KEYCODE_BUTTON_6,
KeyEvent.KEYCODE_BUTTON_7,
KeyEvent.KEYCODE_BUTTON_8,
KeyEvent.KEYCODE_BUTTON_9,
KeyEvent.KEYCODE_BUTTON_10,
KeyEvent.KEYCODE_BUTTON_11,
KeyEvent.KEYCODE_BUTTON_12,
KeyEvent.KEYCODE_BUTTON_13,
KeyEvent.KEYCODE_BUTTON_14,
KeyEvent.KEYCODE_BUTTON_15,
KeyEvent.KEYCODE_BUTTON_16,
};
// The order must be the same as mobile/ebitenmobileview/input_android.go.
static int[] axes = {
MotionEvent.AXIS_X,
MotionEvent.AXIS_Y,
MotionEvent.AXIS_Z,
MotionEvent.AXIS_RX,
MotionEvent.AXIS_RY,
MotionEvent.AXIS_RZ,
MotionEvent.AXIS_HAT_X,
MotionEvent.AXIS_HAT_Y,
MotionEvent.AXIS_LTRIGGER,
MotionEvent.AXIS_RTRIGGER,
MotionEvent.AXIS_THROTTLE,
MotionEvent.AXIS_RUDDER,
MotionEvent.AXIS_WHEEL,
MotionEvent.AXIS_GAS,
MotionEvent.AXIS_BRAKE,
MotionEvent.AXIS_GENERIC_1,
MotionEvent.AXIS_GENERIC_2,
MotionEvent.AXIS_GENERIC_3,
MotionEvent.AXIS_GENERIC_4,
MotionEvent.AXIS_GENERIC_5,
MotionEvent.AXIS_GENERIC_6,
MotionEvent.AXIS_GENERIC_7,
MotionEvent.AXIS_GENERIC_8,
MotionEvent.AXIS_GENERIC_9,
MotionEvent.AXIS_GENERIC_10,
MotionEvent.AXIS_GENERIC_11,
MotionEvent.AXIS_GENERIC_12,
MotionEvent.AXIS_GENERIC_13,
MotionEvent.AXIS_GENERIC_14,
MotionEvent.AXIS_GENERIC_15,
MotionEvent.AXIS_GENERIC_16,
};
@Override @Override
public boolean onGenericMotionEvent(MotionEvent event) { public boolean onGenericMotionEvent(MotionEvent event) {
@ -511,17 +483,24 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
if (event.getAction() != MotionEvent.ACTION_MOVE) { if (event.getAction() != MotionEvent.ACTION_MOVE) {
return super.onGenericMotionEvent(event); return super.onGenericMotionEvent(event);
} }
InputDevice inputDevice = this.inputManager.getInputDevice(event.getDeviceId());
for (int axis : axes) { // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L256-L277
InputDevice.MotionRange motionRange = inputDevice.getMotionRange(axis, event.getSource()); Gamepad gamepad = this.getGamepad(event.getDeviceId());
float value = 0.0f; if (gamepad == null) {
if (motionRange != null) { return true;
value = event.getAxisValue(axis);
if (Math.abs(value) <= motionRange.getFlat()) {
value = 0.0f;
} }
int actionPointerIndex = event.getActionIndex();
for (int i = 0; i < gamepad.axes.size(); i++) {
InputDevice.MotionRange range = gamepad.axes.get(i);
float axisValue = event.getAxisValue(range.getAxis(), actionPointerIndex);
float value = (axisValue - range.getMin()) / range.getRange() * 2.0f - 1.0f;
Ebitenmobileview.onGamepadAxisChanged(gamepad.deviceId, i, value);
} }
Ebitenmobileview.onGamepadAxesOrHatsChanged(event.getDeviceId(), axis, value); for (int i = 0; i < gamepad.hats.size() / 2; i++) {
int hatX = Math.round(event.getAxisValue(gamepad.hats.get(2*i).getAxis(), actionPointerIndex));
int hatY = Math.round(event.getAxisValue(gamepad.hats.get(2*i+1).getAxis(), actionPointerIndex));
Ebitenmobileview.onGamepadHatChanged(gamepad.deviceId, i, hatX, hatY);
} }
return true; return true;
} }
@ -545,34 +524,43 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
return; return;
} }
int naxes = 0; // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L182-L216
int nhats2 = 0; List<InputDevice.MotionRange> ranges = inputDevice.getMotionRanges();
for (int i = 0; i < axes.length; i++) { Collections.sort(ranges, new RangeComparator());
InputDevice.MotionRange range = inputDevice.getMotionRange(axes[i], InputDevice.SOURCE_JOYSTICK);
if (range == null) { Gamepad gamepad = new Gamepad();
continue; gamepad.deviceId = deviceId;
} gamepad.axes = new ArrayList<InputDevice.MotionRange>();
gamepad.hats = new ArrayList<InputDevice.MotionRange>();
for (InputDevice.MotionRange range : ranges) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
nhats2++; gamepad.hats.add(range);
} else { } else {
naxes++; gamepad.axes.add(range);
} }
} }
this.gamepads.add(gamepad);
String descriptor = inputDevice.getDescriptor(); String descriptor = inputDevice.getDescriptor();
int vendorId = inputDevice.getVendorId(); int vendorId = inputDevice.getVendorId();
int productId = inputDevice.getProductId(); int productId = inputDevice.getProductId();
// These values are required to calculate SDL's GUID. // These values are required to calculate SDL's GUID.
int buttonMask = getButtonMask(inputDevice, nhats2/2); int buttonMask = getButtonMask(inputDevice, gamepad.hats.size()/2);
int axisMask = getAxisMask(inputDevice); int axisMask = getAxisMask(inputDevice);
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), naxes, nhats2/2, descriptor, vendorId, productId, buttonMask, axisMask); Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask);
// Initialize the trigger axes values explicitly, or the initial button values would be 0.5 instead of 0.
if (gamepad.axes.size() >= 6) {
Ebitenmobileview.onGamepadAxisChanged(deviceId, 4, -1);
Ebitenmobileview.onGamepadAxisChanged(deviceId, 5, -1);
}
} }
// The implementation is copied from SDL: // The implementation is copied from SDL:
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L308 // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L308
private int getButtonMask(InputDevice joystickDevice, int nhats) { private static int getButtonMask(InputDevice joystickDevice, int nhats) {
int buttonMask = 0; int buttonMask = 0;
int[] keys = new int[] { int[] keys = new int[] {
KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A,
@ -672,7 +660,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
return buttonMask; return buttonMask;
} }
private int getAxisMask(InputDevice joystickDevice) { private static int getAxisMask(InputDevice joystickDevice) {
final int SDL_CONTROLLER_AXIS_LEFTX = 0; final int SDL_CONTROLLER_AXIS_LEFTX = 0;
final int SDL_CONTROLLER_AXIS_LEFTY = 1; final int SDL_CONTROLLER_AXIS_LEFTY = 1;
final int SDL_CONTROLLER_AXIS_RIGHTX = 2; final int SDL_CONTROLLER_AXIS_RIGHTX = 2;
@ -712,6 +700,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
public void onInputDeviceRemoved(int deviceId) { public void onInputDeviceRemoved(int deviceId) {
// Do not call inputManager.getInputDevice(), which returns null (#1185). // Do not call inputManager.getInputDevice(), which returns null (#1185).
Ebitenmobileview.onInputDeviceRemoved(deviceId); Ebitenmobileview.onInputDeviceRemoved(deviceId);
this.gamepads.remove(this.getGamepad(deviceId));
} }
// suspendGame suspends the game. // suspendGame suspends the game.
@ -748,6 +737,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
private EbitenSurfaceView ebitenSurfaceView; private EbitenSurfaceView ebitenSurfaceView;
private InputManager inputManager; private InputManager inputManager;
private ArrayList<Gamepad> gamepads;
} }
` `

File diff suppressed because one or more lines are too long

View File

@ -18,14 +18,7 @@
package gamepad package gamepad
import ( import (
"fmt" "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type AndroidHatDirection int
const (
AndroidHatDirectionX AndroidHatDirection = iota
AndroidHatDirectionY
) )
func AddAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) { func AddAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) {
@ -44,8 +37,8 @@ func UpdateAndroidGamepadButton(androidDeviceID int, button Button, pressed bool
theGamepads.updateAndroidGamepadButton(androidDeviceID, button, pressed) theGamepads.updateAndroidGamepadButton(androidDeviceID, button, pressed)
} }
func UpdateAndroidGamepadHat(androidDeviceID int, hat int, dir AndroidHatDirection, value int) { func UpdateAndroidGamepadHat(androidDeviceID int, hat int, xValue, yValue int) {
theGamepads.updateAndroidGamepadHat(androidDeviceID, hat, dir, value) theGamepads.updateAndroidGamepadHat(androidDeviceID, hat, xValue, yValue)
} }
func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) { func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) {
@ -56,7 +49,7 @@ func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, ax
gp.native = &nativeGamepadImpl{ gp.native = &nativeGamepadImpl{
androidDeviceID: androidDeviceID, androidDeviceID: androidDeviceID,
axes: make([]float64, axisCount), axes: make([]float64, axisCount),
buttons: make([]bool, ButtonCount), buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1),
hats: make([]int, hatCount), hats: make([]int, hatCount),
} }
} }
@ -96,7 +89,7 @@ func (g *gamepads) updateAndroidGamepadButton(androidDeviceID int, button Button
gp.updateAndroidGamepadButton(button, pressed) gp.updateAndroidGamepadButton(button, pressed)
} }
func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, dir AndroidHatDirection, value int) { func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, xValue, yValue int) {
g.m.Lock() g.m.Lock()
defer g.m.Unlock() defer g.m.Unlock()
@ -106,7 +99,7 @@ func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, dir And
if gp == nil { if gp == nil {
return return
} }
gp.updateAndroidGamepadHat(hat, dir, value) gp.updateAndroidGamepadHat(hat, xValue, yValue)
} }
func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) { func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
@ -131,7 +124,7 @@ func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) {
n.buttons[button] = pressed n.buttons[button] = pressed
} }
func (g *Gamepad) updateAndroidGamepadHat(hat int, dir AndroidHatDirection, value int) { func (g *Gamepad) updateAndroidGamepadHat(hat int, xValue, yValue int) {
g.m.Lock() g.m.Lock()
defer g.m.Unlock() defer g.m.Unlock()
@ -139,32 +132,43 @@ func (g *Gamepad) updateAndroidGamepadHat(hat int, dir AndroidHatDirection, valu
if hat < 0 || hat >= len(n.hats) { if hat < 0 || hat >= len(n.hats) {
return return
} }
v := n.hats[hat] var v int
switch dir {
case AndroidHatDirectionX:
switch { switch {
case value < 0: case xValue < 0:
v |= hatLeft v |= hatLeft
v &^= hatRight case xValue > 0:
case value > 0:
v &^= hatLeft
v |= hatRight v |= hatRight
default:
v &^= (hatLeft | hatRight)
} }
case AndroidHatDirectionY:
switch { switch {
case value < 0: case yValue < 0:
v |= hatUp v |= hatUp
v &^= hatDown case yValue > 0:
case value > 0:
v &^= hatUp
v |= hatDown v |= hatDown
default:
v &^= (hatUp | hatDown)
}
default:
panic(fmt.Sprintf("gamepad: invalid direction: %d", dir))
} }
n.hats[hat] = v n.hats[hat] = v
// Update the gamepad buttons in addition to hats.
// See https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L290-L301
switch {
case xValue < 0:
n.buttons[gamepaddb.SDLControllerButtonDpadLeft] = true
n.buttons[gamepaddb.SDLControllerButtonDpadRight] = false
case xValue > 0:
n.buttons[gamepaddb.SDLControllerButtonDpadLeft] = false
n.buttons[gamepaddb.SDLControllerButtonDpadRight] = true
default:
n.buttons[gamepaddb.SDLControllerButtonDpadLeft] = false
n.buttons[gamepaddb.SDLControllerButtonDpadRight] = false
}
switch {
case yValue < 0:
n.buttons[gamepaddb.SDLControllerButtonDpadUp] = true
n.buttons[gamepaddb.SDLControllerButtonDpadDown] = false
case yValue > 0:
n.buttons[gamepaddb.SDLControllerButtonDpadUp] = false
n.buttons[gamepaddb.SDLControllerButtonDpadDown] = true
default:
n.buttons[gamepaddb.SDLControllerButtonDpadUp] = false
n.buttons[gamepaddb.SDLControllerButtonDpadDown] = false
}
} }

View File

@ -341,30 +341,26 @@ func toStandardGamepadAxis(str string) (StandardAxis, bool) {
} }
func buttonMappings(id string) map[StandardButton]*mapping { func buttonMappings(id string) map[StandardButton]*mapping {
// TODO: Use the database instead of the original mapping (#2308). if m, ok := gamepadButtonMappings[id]; ok {
// The buttons and axes assignments should be fixed. return m
}
if currentPlatform == platformAndroid { if currentPlatform == platformAndroid {
if addAndroidDefaultMappings(id) { if addAndroidDefaultMappings(id) {
return gamepadButtonMappings[id] return gamepadButtonMappings[id]
} }
} }
if m, ok := gamepadButtonMappings[id]; ok {
return m
}
return nil return nil
} }
func axisMappings(id string) map[StandardAxis]*mapping { func axisMappings(id string) map[StandardAxis]*mapping {
// TODO: Use the database instead of the original mapping (#2308). if m, ok := gamepadAxisMappings[id]; ok {
// The buttons and axes assignments should be fixed. return m
}
if currentPlatform == platformAndroid { if currentPlatform == platformAndroid {
if addAndroidDefaultMappings(id) { if addAndroidDefaultMappings(id) {
return gamepadAxisMappings[id] return gamepadAxisMappings[id]
} }
} }
if m, ok := gamepadAxisMappings[id]; ok {
return m
}
return nil return nil
} }
@ -585,35 +581,6 @@ func Update(mappingData []byte) error {
} }
func addAndroidDefaultMappings(id string) bool { func addAndroidDefaultMappings(id string) bool {
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L655-L680
const (
SDLControllerButtonA = 0
SDLControllerButtonB = 1
SDLControllerButtonX = 2
SDLControllerButtonY = 3
SDLControllerButtonBack = 4
SDLControllerButtonGuide = 5
SDLControllerButtonStart = 6
SDLControllerButtonLeftStick = 7
SDLControllerButtonRightStick = 8
SDLControllerButtonLeftShoulder = 9
SDLControllerButtonRightShoulder = 10
SDLControllerButtonDpadUp = 11
SDLControllerButtonDpadDown = 12
SDLControllerButtonDpadLeft = 13
SDLControllerButtonDpadRight = 14
)
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L550-L560
const (
SDLControllerAxisLeftX = 0
SDLControllerAxisLeftY = 1
SDLControllerAxisRightX = 2
SDLControllerAxisRightY = 3
SDLControllerAxisTriggerLeft = 4
SDLControllerAxisTriggerRight = 5
)
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/src/joystick/SDL_gamecontroller.c#L468-L568 // See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/src/joystick/SDL_gamecontroller.c#L468-L568
const faceButtonMask = ((1 << SDLControllerButtonA) | const faceButtonMask = ((1 << SDLControllerButtonA) |
@ -642,38 +609,38 @@ func addAndroidDefaultMappings(id string) bool {
if buttonMask&(1<<SDLControllerButtonA) != 0 { if buttonMask&(1<<SDLControllerButtonA) != 0 {
gamepadButtonMappings[id][StandardButtonRightBottom] = &mapping{ gamepadButtonMappings[id][StandardButtonRightBottom] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 0, Index: SDLControllerButtonA,
} }
} }
if buttonMask&(1<<SDLControllerButtonB) != 0 { if buttonMask&(1<<SDLControllerButtonB) != 0 {
gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{ gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 1, Index: SDLControllerButtonB,
} }
} else { } else {
// Use the back button as "B" for easy UI navigation with TV remotes. // Use the back button as "B" for easy UI navigation with TV remotes.
gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{ gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 13, Index: SDLControllerButtonBack,
} }
buttonMask &^= uint16(1) << SDLControllerButtonBack buttonMask &^= uint16(1) << SDLControllerButtonBack
} }
if buttonMask&(1<<SDLControllerButtonX) != 0 { if buttonMask&(1<<SDLControllerButtonX) != 0 {
gamepadButtonMappings[id][StandardButtonRightLeft] = &mapping{ gamepadButtonMappings[id][StandardButtonRightLeft] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 3, Index: SDLControllerButtonX,
} }
} }
if buttonMask&(1<<SDLControllerButtonY) != 0 { if buttonMask&(1<<SDLControllerButtonY) != 0 {
gamepadButtonMappings[id][StandardButtonRightTop] = &mapping{ gamepadButtonMappings[id][StandardButtonRightTop] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 4, Index: SDLControllerButtonY,
} }
} }
if buttonMask&(1<<SDLControllerButtonBack) != 0 { if buttonMask&(1<<SDLControllerButtonBack) != 0 {
gamepadButtonMappings[id][StandardButtonCenterLeft] = &mapping{ gamepadButtonMappings[id][StandardButtonCenterLeft] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 13, Index: SDLControllerButtonBack,
} }
} }
if buttonMask&(1<<SDLControllerButtonGuide) != 0 { if buttonMask&(1<<SDLControllerButtonGuide) != 0 {
@ -687,67 +654,63 @@ func addAndroidDefaultMappings(id string) bool {
if buttonMask&(1<<SDLControllerButtonStart) != 0 { if buttonMask&(1<<SDLControllerButtonStart) != 0 {
gamepadButtonMappings[id][StandardButtonCenterRight] = &mapping{ gamepadButtonMappings[id][StandardButtonCenterRight] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 12, Index: SDLControllerButtonStart,
} }
} }
if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 { if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 {
gamepadButtonMappings[id][StandardButtonLeftStick] = &mapping{ gamepadButtonMappings[id][StandardButtonLeftStick] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 10, Index: SDLControllerButtonLeftStick,
} }
} }
if buttonMask&(1<<SDLControllerButtonRightStick) != 0 { if buttonMask&(1<<SDLControllerButtonRightStick) != 0 {
gamepadButtonMappings[id][StandardButtonRightStick] = &mapping{ gamepadButtonMappings[id][StandardButtonRightStick] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 11, Index: SDLControllerButtonRightStick,
} }
} }
if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 { if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 {
gamepadButtonMappings[id][StandardButtonFrontTopLeft] = &mapping{ gamepadButtonMappings[id][StandardButtonFrontTopLeft] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 6, Index: SDLControllerButtonLeftShoulder,
} }
} }
if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 { if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 {
gamepadButtonMappings[id][StandardButtonFrontTopRight] = &mapping{ gamepadButtonMappings[id][StandardButtonFrontTopRight] = &mapping{
Type: mappingTypeButton, Type: mappingTypeButton,
Index: 7, Index: SDLControllerButtonRightShoulder,
} }
} }
if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 { if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 {
gamepadButtonMappings[id][StandardButtonLeftTop] = &mapping{ gamepadButtonMappings[id][StandardButtonLeftTop] = &mapping{
Type: mappingTypeHat, Type: mappingTypeButton,
Index: 0, Index: SDLControllerButtonDpadUp,
HatState: HatUp,
} }
} }
if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 { if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 {
gamepadButtonMappings[id][StandardButtonLeftBottom] = &mapping{ gamepadButtonMappings[id][StandardButtonLeftBottom] = &mapping{
Type: mappingTypeHat, Type: mappingTypeButton,
Index: 0, Index: SDLControllerButtonDpadDown,
HatState: HatDown,
} }
} }
if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 { if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 {
gamepadButtonMappings[id][StandardButtonLeftLeft] = &mapping{ gamepadButtonMappings[id][StandardButtonLeftLeft] = &mapping{
Type: mappingTypeHat, Type: mappingTypeButton,
Index: 0, Index: SDLControllerButtonDpadLeft,
HatState: HatLeft,
} }
} }
if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 { if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 {
gamepadButtonMappings[id][StandardButtonLeftRight] = &mapping{ gamepadButtonMappings[id][StandardButtonLeftRight] = &mapping{
Type: mappingTypeHat, Type: mappingTypeButton,
Index: 0, Index: SDLControllerButtonDpadRight,
HatState: HatRight,
} }
} }
if axisMask&(1<<SDLControllerAxisLeftX) != 0 { if axisMask&(1<<SDLControllerAxisLeftX) != 0 {
gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = &mapping{ gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = &mapping{
Type: mappingTypeAxis, Type: mappingTypeAxis,
Index: 0, Index: SDLControllerAxisLeftX,
AxisScale: 1, AxisScale: 1,
AxisOffset: 0, AxisOffset: 0,
} }
@ -755,27 +718,23 @@ func addAndroidDefaultMappings(id string) bool {
if axisMask&(1<<SDLControllerAxisLeftY) != 0 { if axisMask&(1<<SDLControllerAxisLeftY) != 0 {
gamepadAxisMappings[id][StandardAxisLeftStickVertical] = &mapping{ gamepadAxisMappings[id][StandardAxisLeftStickVertical] = &mapping{
Type: mappingTypeAxis, Type: mappingTypeAxis,
Index: 1, Index: SDLControllerAxisLeftY,
AxisScale: 1, AxisScale: 1,
AxisOffset: 0, AxisOffset: 0,
} }
} }
if axisMask&(1<<SDLControllerAxisRightX) != 0 { if axisMask&(1<<SDLControllerAxisRightX) != 0 {
// https://developer.android.com/reference/android/view/MotionEvent#AXIS_Z
// > On game pads with two analog joysticks, this axis is often reinterpreted to report the absolute X position of the second joystick instead.
gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = &mapping{ gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = &mapping{
Type: mappingTypeAxis, Type: mappingTypeAxis,
Index: 2, Index: SDLControllerAxisRightX,
AxisScale: 1, AxisScale: 1,
AxisOffset: 0, AxisOffset: 0,
} }
} }
if axisMask&(1<<SDLControllerAxisRightY) != 0 { if axisMask&(1<<SDLControllerAxisRightY) != 0 {
// https://developer.android.com/reference/android/view/MotionEvent#AXIS_RZ
// > On game pads with two analog joysticks, this axis is often reinterpreted to report the absolute Y position of the second joystick instead.
gamepadAxisMappings[id][StandardAxisRightStickVertical] = &mapping{ gamepadAxisMappings[id][StandardAxisRightStickVertical] = &mapping{
Type: mappingTypeAxis, Type: mappingTypeAxis,
Index: 5, Index: SDLControllerAxisRightY,
AxisScale: 1, AxisScale: 1,
AxisOffset: 0, AxisOffset: 0,
} }
@ -783,17 +742,17 @@ func addAndroidDefaultMappings(id string) bool {
if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 { if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 {
gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = &mapping{ gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = &mapping{
Type: mappingTypeAxis, Type: mappingTypeAxis,
Index: 6, Index: SDLControllerAxisTriggerLeft,
AxisScale: 2, AxisScale: 1,
AxisOffset: -1, AxisOffset: 0,
} }
} }
if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 { if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 {
gamepadButtonMappings[id][StandardButtonFrontBottomRight] = &mapping{ gamepadButtonMappings[id][StandardButtonFrontBottomRight] = &mapping{
Type: mappingTypeAxis, Type: mappingTypeAxis,
Index: 7, Index: SDLControllerAxisTriggerRight,
AxisScale: 2, AxisScale: 1,
AxisOffset: -1, AxisOffset: 0,
} }
} }

51
internal/gamepaddb/sdl.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2022 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gamepaddb
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L655-L680
const (
SDLControllerButtonA = 0
SDLControllerButtonB = 1
SDLControllerButtonX = 2
SDLControllerButtonY = 3
SDLControllerButtonBack = 4
SDLControllerButtonGuide = 5
SDLControllerButtonStart = 6
SDLControllerButtonLeftStick = 7
SDLControllerButtonRightStick = 8
SDLControllerButtonLeftShoulder = 9
SDLControllerButtonRightShoulder = 10
SDLControllerButtonDpadUp = 11
SDLControllerButtonDpadDown = 12
SDLControllerButtonDpadLeft = 13
SDLControllerButtonDpadRight = 14
SDLControllerButtonMisc1 = 15
SDLControllerButtonPaddle1 = 16
SDLControllerButtonPaddle2 = 17
SDLControllerButtonPaddle3 = 18
SDLControllerButtonPaddle4 = 19
SDLControllerButtonTouchpad = 20
SDLControllerButtonMax = SDLControllerButtonTouchpad // This is different from the original SDL_CONTROLLER_BUTTON_MAX.
)
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L550-L560
const (
SDLControllerAxisLeftX = 0
SDLControllerAxisLeftY = 1
SDLControllerAxisRightX = 2
SDLControllerAxisRightY = 3
SDLControllerAxisTriggerLeft = 4
SDLControllerAxisTriggerRight = 5
)

View File

@ -17,10 +17,10 @@ package ebitenmobileview
import ( import (
"encoding/hex" "encoding/hex"
"hash/crc32" "hash/crc32"
"math"
"unicode" "unicode"
"github.com/hajimehoshi/ebiten/v2/internal/gamepad" "github.com/hajimehoshi/ebiten/v2/internal/gamepad"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
@ -62,6 +62,10 @@ const (
keycodeDpadDown = 0x00000014 keycodeDpadDown = 0x00000014
keycodeDpadLeft = 0x00000015 keycodeDpadLeft = 0x00000015
keycodeDpadRight = 0x00000016 keycodeDpadRight = 0x00000016
keycodeDpadCenter = 0x00000017
keycodeBack = 0x00000004
keycodeMenu = 0x00000052
) )
// https://developer.android.com/reference/android/view/InputDevice // https://developer.android.com/reference/android/view/InputDevice
@ -71,113 +75,48 @@ const (
sourceJoystick = 0x01000010 sourceJoystick = 0x01000010
) )
// TODO: Can we map these values to the standard gamepad buttons? // See https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L71-L172
// TODO: This exceeds gamepad.SDLControllerButtonMax. Is that OK?
var androidKeyToGamepadButton = map[int]gamepad.Button{ var androidKeyToSDL = map[int]int{
keycodeButtonA: gamepad.Button0, keycodeButtonA: gamepaddb.SDLControllerButtonA,
keycodeButtonB: gamepad.Button1, keycodeButtonB: gamepaddb.SDLControllerButtonB,
keycodeButtonC: gamepad.Button2, keycodeButtonX: gamepaddb.SDLControllerButtonX,
keycodeButtonX: gamepad.Button3, keycodeButtonY: gamepaddb.SDLControllerButtonY,
keycodeButtonY: gamepad.Button4, keycodeButtonL1: gamepaddb.SDLControllerButtonLeftShoulder,
keycodeButtonZ: gamepad.Button5, keycodeButtonR1: gamepaddb.SDLControllerButtonRightShoulder,
keycodeButtonL1: gamepad.Button6, keycodeButtonThumbl: gamepaddb.SDLControllerButtonLeftStick,
keycodeButtonR1: gamepad.Button7, keycodeButtonThumbr: gamepaddb.SDLControllerButtonRightStick,
keycodeButtonL2: gamepad.Button8, keycodeMenu: gamepaddb.SDLControllerButtonStart,
keycodeButtonR2: gamepad.Button9, keycodeButtonStart: gamepaddb.SDLControllerButtonStart,
keycodeButtonThumbl: gamepad.Button10, keycodeBack: gamepaddb.SDLControllerButtonBack,
keycodeButtonThumbr: gamepad.Button11, keycodeButtonSelect: gamepaddb.SDLControllerButtonBack,
keycodeButtonStart: gamepad.Button12, keycodeButtonMode: gamepaddb.SDLControllerButtonGuide,
keycodeButtonSelect: gamepad.Button13, keycodeButtonL2: 15,
keycodeButtonMode: gamepad.Button14, keycodeButtonR2: 16,
keycodeButton1: gamepad.Button15, keycodeButtonC: 17,
keycodeButton2: gamepad.Button16, keycodeButtonZ: 18,
keycodeButton3: gamepad.Button17, keycodeDpadUp: gamepaddb.SDLControllerButtonDpadUp,
keycodeButton4: gamepad.Button18, keycodeDpadDown: gamepaddb.SDLControllerButtonDpadDown,
keycodeButton5: gamepad.Button19, keycodeDpadLeft: gamepaddb.SDLControllerButtonDpadLeft,
keycodeButton6: gamepad.Button20, keycodeDpadRight: gamepaddb.SDLControllerButtonDpadRight,
keycodeButton7: gamepad.Button21, keycodeDpadCenter: gamepaddb.SDLControllerButtonA,
keycodeButton8: gamepad.Button22, keycodeButton1: 20,
keycodeButton9: gamepad.Button23, keycodeButton2: 21,
keycodeButton10: gamepad.Button24, keycodeButton3: 22,
keycodeButton11: gamepad.Button25, keycodeButton4: 23,
keycodeButton12: gamepad.Button26, keycodeButton5: 24,
keycodeButton13: gamepad.Button27, keycodeButton6: 25,
keycodeButton14: gamepad.Button28, keycodeButton7: 26,
keycodeButton15: gamepad.Button29, keycodeButton8: 27,
keycodeButton16: gamepad.Button30, keycodeButton9: 28,
} keycodeButton10: 29,
keycodeButton11: 30,
// Axis constant definitions for joysticks only. keycodeButton12: 31,
// https://developer.android.com/reference/android/view/MotionEvent keycodeButton13: 32,
const ( keycodeButton14: 33,
axisX = 0x00000000 keycodeButton15: 34,
axisY = 0x00000001 keycodeButton16: 35,
axisZ = 0x0000000b
axisRx = 0x0000000c
axisRy = 0x0000000d
axisRz = 0x0000000e
axisHatX = 0x0000000f
axisHatY = 0x00000010
axisLtrigger = 0x00000011
axisRtrigger = 0x00000012
axisThrottle = 0x00000013
axisRudder = 0x00000014
axisWheel = 0x00000015
axisGas = 0x00000016
axisBrake = 0x00000017
axisGeneric1 = 0x00000020
axisGeneric2 = 0x00000021
axisGeneric3 = 0x00000022
axisGeneric4 = 0x00000023
axisGeneric5 = 0x00000024
axisGeneric6 = 0x00000025
axisGeneric7 = 0x00000026
axisGeneric8 = 0x00000027
axisGeneric9 = 0x00000028
axisGeneric10 = 0x00000029
axisGeneric11 = 0x0000002a
axisGeneric12 = 0x0000002b
axisGeneric13 = 0x0000002c
axisGeneric14 = 0x0000002d
axisGeneric15 = 0x0000002e
axisGeneric16 = 0x0000002f
)
var androidAxisIDToAxisID = map[int]int{
axisX: 0,
axisY: 1,
axisZ: 2,
axisRx: 3,
axisRy: 4,
axisRz: 5,
axisLtrigger: 6,
axisRtrigger: 7,
axisThrottle: 8,
axisRudder: 9,
axisWheel: 10,
axisGas: 11,
axisBrake: 12,
axisGeneric1: 13,
axisGeneric2: 14,
axisGeneric3: 15,
axisGeneric4: 16,
axisGeneric5: 17,
axisGeneric6: 18,
axisGeneric7: 19,
axisGeneric8: 20,
axisGeneric9: 21,
axisGeneric10: 22,
axisGeneric11: 23,
axisGeneric12: 24,
axisGeneric13: 25,
axisGeneric14: 26,
axisGeneric15: 27,
axisGeneric16: 28,
}
var androidAxisIDToHatID2 = map[int]int{
axisHatX: 0,
axisHatY: 1,
} }
func UpdateTouchesOnAndroid(action int, id int, x, y int) { func UpdateTouchesOnAndroid(action int, id int, x, y int) {
@ -195,8 +134,8 @@ func OnKeyDownOnAndroid(keyCode int, unicodeChar int, source int, deviceID int)
switch { switch {
case source&sourceGamepad == sourceGamepad: case source&sourceGamepad == sourceGamepad:
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first. // A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
if button, ok := androidKeyToGamepadButton[keyCode]; ok { if button, ok := androidKeyToSDL[keyCode]; ok {
gamepad.UpdateAndroidGamepadButton(deviceID, button, true) gamepad.UpdateAndroidGamepadButton(deviceID, gamepad.Button(button), true)
} }
case source&sourceJoystick == sourceJoystick: case source&sourceJoystick == sourceJoystick:
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them. // DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
@ -215,8 +154,8 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
switch { switch {
case source&sourceGamepad == sourceGamepad: case source&sourceGamepad == sourceGamepad:
// A gamepad can be detected as a keyboard. Detect the device as a gamepad first. // A gamepad can be detected as a keyboard. Detect the device as a gamepad first.
if button, ok := androidKeyToGamepadButton[keyCode]; ok { if button, ok := androidKeyToSDL[keyCode]; ok {
gamepad.UpdateAndroidGamepadButton(deviceID, button, false) gamepad.UpdateAndroidGamepadButton(deviceID, gamepad.Button(button), false)
} }
case source&sourceJoystick == sourceJoystick: case source&sourceJoystick == sourceJoystick:
// DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them. // DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
@ -228,30 +167,12 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
} }
} }
func OnGamepadAxesOrHatsChanged(deviceID int, axisID int, value float32) { func OnGamepadAxisChanged(deviceID int, axisID int, value float32) {
if axis, ok := androidAxisIDToAxisID[axisID]; ok { gamepad.UpdateAndroidGamepadAxis(deviceID, axisID, float64(value))
gamepad.UpdateAndroidGamepadAxis(deviceID, axis, float64(value))
return
} }
if hid2, ok := androidAxisIDToHatID2[axisID]; ok { func OnGamepadHatChanged(deviceID int, hatID int, xValue, yValue int) {
const ( gamepad.UpdateAndroidGamepadHat(deviceID, hatID, xValue, yValue)
hatUp = 1
hatRight = 2
hatDown = 4
hatLeft = 8
)
hatID := hid2 / 2
var dir gamepad.AndroidHatDirection
switch hid2 % 2 {
case 0:
dir = gamepad.AndroidHatDirectionX
case 1:
dir = gamepad.AndroidHatDirectionY
}
gamepad.UpdateAndroidGamepadHat(deviceID, hatID, dir, int(math.Round(float64(value))))
return
}
} }
func OnGamepadAdded(deviceID int, name string, axisCount int, hatCount int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) { func OnGamepadAdded(deviceID int, name string, axisCount int, hatCount int, descriptor string, vendorID int, productID int, buttonMask int, axisMask int) {