internal/gamepad: ignore the very first MotionEvent with 0 value for Android

On Android, MotionEvent with 0 values might come for axes when connecting
a gamepad, even though a user didn't touch any axes. This is problematic
especially for tirgger axes, where the default value should be -1.

This change fixes the issue by adding a new state `axesReady` to check
if an axis is really touched or not. If an axis is not touched yet,
a button value for a standard (trigger) button always returns 0.

This change also removes an old hack to initialize axis values for
triggers.

Closes #2598
This commit is contained in:
Hajime Hoshi 2024-03-21 11:49:30 +09:00
parent 4b1c0526a7
commit b7dd45c0e4
13 changed files with 67 additions and 9 deletions

View File

@ -244,12 +244,6 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
int axisMask = getAxisMask(inputDevice); int axisMask = getAxisMask(inputDevice);
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/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:

View File

@ -45,6 +45,7 @@ func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, ax
gp := g.add(name, sdlID) gp := g.add(name, sdlID)
gp.native = &nativeGamepadImpl{ gp.native = &nativeGamepadImpl{
androidDeviceID: androidDeviceID, androidDeviceID: androidDeviceID,
axesReady: make([]bool, axisCount),
axes: make([]float64, axisCount), axes: make([]float64, axisCount),
buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1), buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1),
hats: make([]int, hatCount), hats: make([]int, hatCount),
@ -108,6 +109,13 @@ func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
return return
} }
n.axes[axis] = value n.axes[axis] = value
// MotionEvent with 0 value can be sent when a gamepad is connected even though an axis is not touched (#2598).
// This is problematic when an axis is a trigger button where -1 should be the default value.
// When MotionEvent with non-0 value is sent, it seems fine to assume that the axis is actually touched and ready.
if value != 0 {
n.axesReady[axis] = true
}
} }
func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) { func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) {

View File

@ -243,6 +243,7 @@ type nativeGamepad interface {
axisCount() int axisCount() int
buttonCount() int buttonCount() int
hatCount() int hatCount() int
isAxisReady(axis int) bool
axisValue(axis int) float64 axisValue(axis int) float64
buttonValue(button int) float64 buttonValue(button int) float64
isButtonPressed(button int) bool isButtonPressed(button int) bool
@ -296,6 +297,14 @@ func (g *Gamepad) HatCount() int {
return g.native.hatCount() return g.native.hatCount()
} }
// IsAxisReady is concurrent-safe.
func (g *Gamepad) IsAxisReady(axis int) bool {
g.m.Lock()
defer g.m.Unlock()
return g.native.isAxisReady(axis)
}
// Axis is concurrent-safe. // Axis is concurrent-safe.
func (g *Gamepad) Axis(axis int) float64 { func (g *Gamepad) Axis(axis int) float64 {
g.m.Lock() g.m.Lock()

View File

@ -37,9 +37,10 @@ func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
type nativeGamepadImpl struct { type nativeGamepadImpl struct {
androidDeviceID int androidDeviceID int
axes []float64 axesReady []bool
buttons []bool axes []float64
hats []int buttons []bool
hats []int
} }
func (*nativeGamepadImpl) update(gamepad *gamepads) error { func (*nativeGamepadImpl) update(gamepad *gamepads) error {
@ -71,6 +72,13 @@ func (g *nativeGamepadImpl) hatCount() int {
return len(g.hats) return len(g.hats)
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
if axis < 0 || axis >= len(g.axesReady) {
return false
}
return g.axesReady[axis]
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 { func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axes) { if axis < 0 || axis >= len(g.axes) {
return 0 return 0

View File

@ -399,6 +399,10 @@ func (g *nativeGamepadImpl) hatCount() int {
return len(g.hatValues) return len(g.hatValues)
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 { func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axisValues) { if axis < 0 || axis >= len(g.axisValues) {
return 0 return 0

View File

@ -719,6 +719,10 @@ func (g *nativeGamepadDesktop) hatCount() int {
return 1 return 1
} }
func (g *nativeGamepadDesktop) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadDesktop) axisValue(axis int) float64 { func (g *nativeGamepadDesktop) axisValue(axis int) float64 {
if g.usesDInput() { if g.usesDInput() {
if axis < 0 || axis >= len(g.dinputAxes) { if axis < 0 || axis >= len(g.dinputAxes) {

View File

@ -76,6 +76,10 @@ func (g *nativeGamepadImpl) hatCount() int {
return len(g.hats) return len(g.hats)
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 { func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axes) { if axis < 0 || axis >= len(g.axes) {
return 0 return 0

View File

@ -152,6 +152,10 @@ func (g *nativeGamepadImpl) hatCount() int {
return 0 return 0
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 { func (g *nativeGamepadImpl) axisValue(axis int) float64 {
axes := g.value.Get("axes") axes := g.value.Get("axes")
if axis < 0 || axis >= axes.Length() { if axis < 0 || axis >= axes.Length() {

View File

@ -592,6 +592,10 @@ func (g *nativeGamepadImpl) hatCount() int {
return g.hatCount_ return g.hatCount_
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 { func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= g.axisCount_ { if axis < 0 || axis >= g.axisCount_ {
return 0 return 0

View File

@ -146,6 +146,10 @@ func (g *nativeGamepadImpl) hatCount() int {
return 0 return 0
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 { func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axisValues) { if axis < 0 || axis >= len(g.axisValues) {
return 0 return 0

View File

@ -66,6 +66,10 @@ func (*nativeGamepadImpl) hatCount() int {
return 0 return 0
} }
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return false
}
func (*nativeGamepadImpl) axisValue(axis int) float64 { func (*nativeGamepadImpl) axisValue(axis int) float64 {
return 0 return 0
} }

View File

@ -190,6 +190,10 @@ func (n *nativeGamepadXbox) hatCount() int {
return 0 return 0
} }
func (g *nativeGamepadXbox) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (n *nativeGamepadXbox) axisValue(axis int) float64 { func (n *nativeGamepadXbox) axisValue(axis int) float64 {
switch gamepaddb.StandardAxis(axis) { switch gamepaddb.StandardAxis(axis) {
case gamepaddb.StandardAxisLeftStickHorizontal: case gamepaddb.StandardAxisLeftStickHorizontal:

View File

@ -390,6 +390,7 @@ func HasStandardLayoutMapping(id string) bool {
} }
type GamepadState interface { type GamepadState interface {
IsAxisReady(index int) bool
Axis(index int) float64 Axis(index int) float64
Button(index int) bool Button(index int) bool
Hat(index int) int Hat(index int) int
@ -430,6 +431,9 @@ func StandardAxisValue(id string, axis StandardAxis, state GamepadState) float64
switch mapping.Type { switch mapping.Type {
case mappingTypeAxis: case mappingTypeAxis:
if !state.IsAxisReady(mapping.Index) {
return 0
}
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
if v > 1 { if v > 1 {
return 1 return 1
@ -486,6 +490,9 @@ func standardButtonValue(id string, button StandardButton, state GamepadState) f
switch mapping.Type { switch mapping.Type {
case mappingTypeAxis: case mappingTypeAxis:
if !state.IsAxisReady(mapping.Index) {
return 0
}
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
if v > 1 { if v > 1 {
v = 1 v = 1