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);
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:

View File

@ -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) {

View File

@ -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()

View File

@ -37,6 +37,7 @@ func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
type nativeGamepadImpl struct {
androidDeviceID int
axesReady []bool
axes []float64
buttons []bool
hats []int
@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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:

View File

@ -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