From b7dd45c0e4d76773854e0f19b912729e639eb5f6 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 21 Mar 2024 11:49:30 +0900 Subject: [PATCH] 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 --- cmd/ebitenmobile/_files/EbitenView.java | 6 ------ internal/gamepad/extern_android.go | 8 ++++++++ internal/gamepad/gamepad.go | 9 +++++++++ internal/gamepad/gamepad_android.go | 14 +++++++++++--- internal/gamepad/gamepad_darwin.go | 4 ++++ internal/gamepad/gamepad_desktop_windows.go | 4 ++++ internal/gamepad/gamepad_ios.go | 4 ++++ internal/gamepad/gamepad_js.go | 4 ++++ internal/gamepad/gamepad_linux.go | 4 ++++ internal/gamepad/gamepad_nintendosdk.go | 4 ++++ internal/gamepad/gamepad_null.go | 4 ++++ internal/gamepad/gamepad_xbox_windows.go | 4 ++++ internal/gamepaddb/gamepaddb.go | 7 +++++++ 13 files changed, 67 insertions(+), 9 deletions(-) diff --git a/cmd/ebitenmobile/_files/EbitenView.java b/cmd/ebitenmobile/_files/EbitenView.java index fe797bf0d..4cb4c3f22 100644 --- a/cmd/ebitenmobile/_files/EbitenView.java +++ b/cmd/ebitenmobile/_files/EbitenView.java @@ -244,12 +244,6 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis int axisMask = getAxisMask(inputDevice); 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: diff --git a/internal/gamepad/extern_android.go b/internal/gamepad/extern_android.go index 51dc288be..2f1c03e3b 100644 --- a/internal/gamepad/extern_android.go +++ b/internal/gamepad/extern_android.go @@ -45,6 +45,7 @@ func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, ax gp := g.add(name, sdlID) gp.native = &nativeGamepadImpl{ androidDeviceID: androidDeviceID, + axesReady: make([]bool, axisCount), axes: make([]float64, axisCount), buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1), hats: make([]int, hatCount), @@ -108,6 +109,13 @@ func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) { return } 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) { diff --git a/internal/gamepad/gamepad.go b/internal/gamepad/gamepad.go index f0f0189e3..e4e2b38fd 100644 --- a/internal/gamepad/gamepad.go +++ b/internal/gamepad/gamepad.go @@ -243,6 +243,7 @@ type nativeGamepad interface { axisCount() int buttonCount() int hatCount() int + isAxisReady(axis int) bool axisValue(axis int) float64 buttonValue(button int) float64 isButtonPressed(button int) bool @@ -296,6 +297,14 @@ func (g *Gamepad) HatCount() int { 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. func (g *Gamepad) Axis(axis int) float64 { g.m.Lock() diff --git a/internal/gamepad/gamepad_android.go b/internal/gamepad/gamepad_android.go index 2abb101eb..4adaa555f 100644 --- a/internal/gamepad/gamepad_android.go +++ b/internal/gamepad/gamepad_android.go @@ -37,9 +37,10 @@ func (*nativeGamepadsImpl) update(gamepads *gamepads) error { type nativeGamepadImpl struct { androidDeviceID int - axes []float64 - buttons []bool - hats []int + axesReady []bool + axes []float64 + buttons []bool + hats []int } func (*nativeGamepadImpl) update(gamepad *gamepads) error { @@ -71,6 +72,13 @@ func (g *nativeGamepadImpl) hatCount() int { 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 { if axis < 0 || axis >= len(g.axes) { return 0 diff --git a/internal/gamepad/gamepad_darwin.go b/internal/gamepad/gamepad_darwin.go index 3e8b156d3..a571922b8 100644 --- a/internal/gamepad/gamepad_darwin.go +++ b/internal/gamepad/gamepad_darwin.go @@ -399,6 +399,10 @@ func (g *nativeGamepadImpl) hatCount() int { return len(g.hatValues) } +func (g *nativeGamepadImpl) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (g *nativeGamepadImpl) axisValue(axis int) float64 { if axis < 0 || axis >= len(g.axisValues) { return 0 diff --git a/internal/gamepad/gamepad_desktop_windows.go b/internal/gamepad/gamepad_desktop_windows.go index 2e420dfd7..56f95cc82 100644 --- a/internal/gamepad/gamepad_desktop_windows.go +++ b/internal/gamepad/gamepad_desktop_windows.go @@ -719,6 +719,10 @@ func (g *nativeGamepadDesktop) hatCount() int { return 1 } +func (g *nativeGamepadDesktop) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (g *nativeGamepadDesktop) axisValue(axis int) float64 { if g.usesDInput() { if axis < 0 || axis >= len(g.dinputAxes) { diff --git a/internal/gamepad/gamepad_ios.go b/internal/gamepad/gamepad_ios.go index 7f6e73f3e..a521dc000 100644 --- a/internal/gamepad/gamepad_ios.go +++ b/internal/gamepad/gamepad_ios.go @@ -76,6 +76,10 @@ func (g *nativeGamepadImpl) hatCount() int { return len(g.hats) } +func (g *nativeGamepadImpl) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (g *nativeGamepadImpl) axisValue(axis int) float64 { if axis < 0 || axis >= len(g.axes) { return 0 diff --git a/internal/gamepad/gamepad_js.go b/internal/gamepad/gamepad_js.go index 203ce5e98..650d3ac12 100644 --- a/internal/gamepad/gamepad_js.go +++ b/internal/gamepad/gamepad_js.go @@ -152,6 +152,10 @@ func (g *nativeGamepadImpl) hatCount() int { return 0 } +func (g *nativeGamepadImpl) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (g *nativeGamepadImpl) axisValue(axis int) float64 { axes := g.value.Get("axes") if axis < 0 || axis >= axes.Length() { diff --git a/internal/gamepad/gamepad_linux.go b/internal/gamepad/gamepad_linux.go index 0134f7c7e..b9d5fb3f9 100644 --- a/internal/gamepad/gamepad_linux.go +++ b/internal/gamepad/gamepad_linux.go @@ -592,6 +592,10 @@ func (g *nativeGamepadImpl) hatCount() int { return g.hatCount_ } +func (g *nativeGamepadImpl) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (g *nativeGamepadImpl) axisValue(axis int) float64 { if axis < 0 || axis >= g.axisCount_ { return 0 diff --git a/internal/gamepad/gamepad_nintendosdk.go b/internal/gamepad/gamepad_nintendosdk.go index c09ca22b7..795b2f13a 100644 --- a/internal/gamepad/gamepad_nintendosdk.go +++ b/internal/gamepad/gamepad_nintendosdk.go @@ -146,6 +146,10 @@ func (g *nativeGamepadImpl) hatCount() int { return 0 } +func (g *nativeGamepadImpl) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (g *nativeGamepadImpl) axisValue(axis int) float64 { if axis < 0 || axis >= len(g.axisValues) { return 0 diff --git a/internal/gamepad/gamepad_null.go b/internal/gamepad/gamepad_null.go index 6ebfc7a52..c195411de 100644 --- a/internal/gamepad/gamepad_null.go +++ b/internal/gamepad/gamepad_null.go @@ -66,6 +66,10 @@ func (*nativeGamepadImpl) hatCount() int { return 0 } +func (g *nativeGamepadImpl) isAxisReady(axis int) bool { + return false +} + func (*nativeGamepadImpl) axisValue(axis int) float64 { return 0 } diff --git a/internal/gamepad/gamepad_xbox_windows.go b/internal/gamepad/gamepad_xbox_windows.go index 650d76954..f4d331663 100644 --- a/internal/gamepad/gamepad_xbox_windows.go +++ b/internal/gamepad/gamepad_xbox_windows.go @@ -190,6 +190,10 @@ func (n *nativeGamepadXbox) hatCount() int { return 0 } +func (g *nativeGamepadXbox) isAxisReady(axis int) bool { + return axis >= 0 && axis < g.axisCount() +} + func (n *nativeGamepadXbox) axisValue(axis int) float64 { switch gamepaddb.StandardAxis(axis) { case gamepaddb.StandardAxisLeftStickHorizontal: diff --git a/internal/gamepaddb/gamepaddb.go b/internal/gamepaddb/gamepaddb.go index d4d287041..781c29eb1 100644 --- a/internal/gamepaddb/gamepaddb.go +++ b/internal/gamepaddb/gamepaddb.go @@ -390,6 +390,7 @@ func HasStandardLayoutMapping(id string) bool { } type GamepadState interface { + IsAxisReady(index int) bool Axis(index int) float64 Button(index int) bool Hat(index int) int @@ -430,6 +431,9 @@ func StandardAxisValue(id string, axis StandardAxis, state GamepadState) float64 switch mapping.Type { case mappingTypeAxis: + if !state.IsAxisReady(mapping.Index) { + return 0 + } v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset if v > 1 { return 1 @@ -486,6 +490,9 @@ func standardButtonValue(id string, button StandardButton, state GamepadState) f switch mapping.Type { case mappingTypeAxis: + if !state.IsAxisReady(mapping.Index) { + return 0 + } v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset if v > 1 { v = 1