mobile/ebitenmobileview: Implement Android gamepad axes

Updates #1083
This commit is contained in:
Hajime Hoshi 2020-03-24 02:06:56 +09:00
parent 237498e51f
commit 4ec49dd4cf
5 changed files with 195 additions and 15 deletions

View File

@ -448,6 +448,64 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
KeyEvent.KEYCODE_BUTTON_16, 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
public boolean onGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
return super.onGenericMotionEvent(event);
}
if (event.getAction() != MotionEvent.ACTION_MOVE) {
return super.onGenericMotionEvent(event);
}
InputDevice inputDevice = this.inputManager.getInputDevice(event.getDeviceId());
for (int axis : axes) {
InputDevice.MotionRange motionRange = inputDevice.getMotionRange(axis, event.getSource());
float value = 0.0f;
if (motionRange != null) {
value = event.getAxisValue(axis);
if (Math.abs(value) <= motionRange.getFlat()) {
value = 0.0f;
}
}
Ebitenmobileview.onGamepadAxesChanged(event.getDeviceId(), axis, value);
}
return true;
}
@Override @Override
public void onInputDeviceAdded(int deviceId) { public void onInputDeviceAdded(int deviceId) {
InputDevice inputDevice = this.inputManager.getInputDevice(deviceId); InputDevice inputDevice = this.inputManager.getInputDevice(deviceId);
@ -456,6 +514,7 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
return; return;
} }
boolean[] keyExistences = inputDevice.hasKeys(gamepadButtons); boolean[] keyExistences = inputDevice.hasKeys(gamepadButtons);
int buttonNum = gamepadButtons.length - 1; int buttonNum = gamepadButtons.length - 1;
for (int i = gamepadButtons.length - 1; i >= 0; i--) { for (int i = gamepadButtons.length - 1; i >= 0; i--) {
@ -464,7 +523,16 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
} }
buttonNum--; buttonNum--;
} }
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), buttonNum);
int axisNum = axes.length - 1;
for (int i = axes.length - 1; i >= 0; i--) {
if (inputDevice.getMotionRange(axes[i], InputDevice.SOURCE_JOYSTICK) != null) {
break;
}
axisNum--;
}
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), buttonNum, axisNum);
} }
@Override @Override

File diff suppressed because one or more lines are too long

View File

@ -79,12 +79,31 @@ func (i *Input) GamepadName(id int) string {
} }
func (i *Input) GamepadAxisNum(id int) int { func (i *Input) GamepadAxisNum(id int) int {
// TODO: Implement this i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.AxisNum
}
return 0 return 0
} }
func (i *Input) GamepadAxis(id int, axis int) float64 { func (i *Input) GamepadAxis(id int, axis int) float64 {
// TODO: Implement this i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.AxisNum <= int(axis) {
return 0
}
return float64(g.Axes[axis])
}
return 0 return 0
} }

View File

@ -457,6 +457,8 @@ type Gamepad struct {
Name string Name string
Buttons [driver.GamepadButtonNum]bool Buttons [driver.GamepadButtonNum]bool
ButtonNum int ButtonNum int
Axes [32]float32
AxisNum int
} }
func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []*Touch, gamepads []Gamepad) { func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []*Touch, gamepads []Gamepad) {

View File

@ -54,6 +54,11 @@ const (
keycodeButton14 = 0x000000c9 keycodeButton14 = 0x000000c9
keycodeButton15 = 0x000000ca keycodeButton15 = 0x000000ca
keycodeButton16 = 0x000000cb keycodeButton16 = 0x000000cb
keycodeDpadUp = 0x00000013
keycodeDpadDown = 0x00000014
keycodeDpadLeft = 0x00000015
keycodeDpadRight = 0x00000016
) )
// https://developer.android.com/reference/android/view/InputDevice // https://developer.android.com/reference/android/view/InputDevice
@ -97,6 +102,76 @@ var androidKeyToGamepadButton = map[int]driver.GamepadButton{
keycodeButton16: driver.GamepadButton30, keycodeButton16: driver.GamepadButton30,
} }
// Axis constant definitions for joysticks only.
// https://developer.android.com/reference/android/view/MotionEvent
const (
axisX = 0x00000000
axisY = 0x00000001
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,
axisHatX: 6,
axisHatY: 7,
axisLtrigger: 8,
axisRtrigger: 9,
axisThrottle: 10,
axisRudder: 11,
axisWheel: 12,
axisGas: 13,
axisBrake: 14,
axisGeneric1: 15,
axisGeneric2: 16,
axisGeneric3: 17,
axisGeneric4: 18,
axisGeneric5: 19,
axisGeneric6: 20,
axisGeneric7: 21,
axisGeneric8: 22,
axisGeneric9: 23,
axisGeneric10: 24,
axisGeneric11: 25,
axisGeneric12: 26,
axisGeneric13: 27,
axisGeneric14: 28,
axisGeneric15: 29,
axisGeneric16: 30,
}
var ( var (
// deviceIDToGamepadID is a map from Android device IDs to Ebiten gamepad IDs. // deviceIDToGamepadID is a map from Android device IDs to Ebiten gamepad IDs.
// As convention, Ebiten gamepad IDs start with 0, and many applications depend on this fact. // As convention, Ebiten gamepad IDs start with 0, and many applications depend on this fact.
@ -144,15 +219,15 @@ func OnKeyDownOnAndroid(keyCode int, unicodeChar int, source int, deviceID int)
// 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 := androidKeyToGamepadButton[keyCode]; ok {
id := gamepadIDFromDeviceID(deviceID) id := gamepadIDFromDeviceID(deviceID)
if _, ok := gamepads[id]; !ok { g, ok := gamepads[id]
// Can this happen? if !ok {
gamepads[id] = &mobile.Gamepad{} return
} }
gamepads[id].Buttons[button] = true g.Buttons[button] = true
updateInput() updateInput()
} }
case source&sourceJoystick == sourceJoystick: case source&sourceJoystick == sourceJoystick:
// TODO: Handle DPAD keys // DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
case source&sourceKeyboard == sourceKeyboard: case source&sourceKeyboard == sourceKeyboard:
if key, ok := androidKeyToDriverKey[keyCode]; ok { if key, ok := androidKeyToDriverKey[keyCode]; ok {
keys[key] = struct{}{} keys[key] = struct{}{}
@ -170,15 +245,15 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
// 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 := androidKeyToGamepadButton[keyCode]; ok {
id := gamepadIDFromDeviceID(deviceID) id := gamepadIDFromDeviceID(deviceID)
if _, ok := gamepads[id]; !ok { g, ok := gamepads[id]
// Can this happen? if !ok {
gamepads[id] = &mobile.Gamepad{} return
} }
gamepads[id].Buttons[button] = false g.Buttons[button] = false
updateInput() updateInput()
} }
case source&sourceJoystick == sourceJoystick: case source&sourceJoystick == sourceJoystick:
// TODO: Handle DPAD keys // DPAD keys can come here, but they are also treated as an axis at a motion event. Ignore them.
case source&sourceKeyboard == sourceKeyboard: case source&sourceKeyboard == sourceKeyboard:
if key, ok := androidKeyToDriverKey[keyCode]; ok { if key, ok := androidKeyToDriverKey[keyCode]; ok {
delete(keys, key) delete(keys, key)
@ -187,13 +262,29 @@ func OnKeyUpOnAndroid(keyCode int, source int, deviceID int) {
} }
} }
func OnGamepadAdded(deviceID int, name string, buttonNum int) { func OnGamepadAxesChanged(deviceID int, axisID int, value float32) {
did := gamepadIDFromDeviceID(deviceID)
g, ok := gamepads[did]
if !ok {
return
}
aid, ok := androidAxisIDToAxisID[axisID]
if !ok {
// Unexpected axis value.
return
}
g.Axes[aid] = value
updateInput()
}
func OnGamepadAdded(deviceID int, name string, buttonNum int, axisNum int) {
id := gamepadIDFromDeviceID(deviceID) id := gamepadIDFromDeviceID(deviceID)
gamepads[id] = &mobile.Gamepad{ gamepads[id] = &mobile.Gamepad{
ID: id, ID: id,
SDLID: "", // TODO: Implement this SDLID: "", // TODO: Implement this
Name: name, Name: name,
ButtonNum: buttonNum, ButtonNum: buttonNum,
AxisNum: axisNum,
} }
} }