internal/ui: bug fix: key/mouse button inputs were sometimes missed

If a key down and a key up happened in one tick, Ebitengine ignored
these events and users failed to notice the key press. This was
problematic in real world use caess, e.g. click by a tap on macOS
browsers.

This change fixes this issue by recording the tick for both a press
and a release, and determine whether the key/button is pressed or
not more precisely.

Updates #2898
Closes #3137
Closes #3249
Updates #3317
This commit is contained in:
Hajime Hoshi
2025-09-20 23:49:36 +09:00
parent c15408bc6e
commit 27b6b3c633
15 changed files with 337 additions and 199 deletions
+11 -73
View File
@@ -21,6 +21,8 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/hook"
"github.com/hajimehoshi/ebiten/v2/internal/inputstate"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
type gamepadState struct {
@@ -35,12 +37,6 @@ type touchState struct {
}
type inputState struct {
keyDurations [ebiten.KeyMax + 1]int
prevKeyDurations [ebiten.KeyMax + 1]int
mouseButtonDurations [ebiten.MouseButtonMax + 1]int
prevMouseButtonDurations [ebiten.MouseButtonMax + 1]int
gamepadStates map[ebiten.GamepadID]gamepadState
prevGamepadStates map[ebiten.GamepadID]gamepadState
@@ -71,26 +67,6 @@ func (i *inputState) update() {
i.m.Lock()
defer i.m.Unlock()
// Keyboard
copy(i.prevKeyDurations[:], i.keyDurations[:])
for idx := range i.keyDurations {
if ebiten.IsKeyPressed(ebiten.Key(idx)) {
i.keyDurations[idx]++
} else {
i.keyDurations[idx] = 0
}
}
// Mouse
copy(i.prevMouseButtonDurations[:], i.mouseButtonDurations[:])
for idx := range i.mouseButtonDurations {
if ebiten.IsMouseButtonPressed(ebiten.MouseButton(idx)) {
i.mouseButtonDurations[idx]++
} else {
i.mouseButtonDurations[idx] = 0
}
}
// Gamepads
// Copy the gamepad states.
@@ -160,16 +136,7 @@ func (i *inputState) update() {
//
// AppendPressedKeys is concurrent safe.
func AppendPressedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i, d := range theInputState.keyDurations {
if d == 0 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
return inputstate.AppendPressedKeys(keys)
}
// PressedKeys returns a set of currently pressed keyboard keys.
@@ -188,16 +155,7 @@ func PressedKeys() []ebiten.Key {
//
// AppendJustPressedKeys is concurrent safe.
func AppendJustPressedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i, d := range theInputState.keyDurations {
if d != 1 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
return inputstate.AppendJustPressedKeys(keys)
}
// AppendJustReleasedKeys append just released keyboard keys to keys and returns the extended buffer.
@@ -207,19 +165,7 @@ func AppendJustPressedKeys(keys []ebiten.Key) []ebiten.Key {
//
// AppendJustReleasedKeys is concurrent safe.
func AppendJustReleasedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i := range theInputState.keyDurations {
if theInputState.keyDurations[i] != 0 {
continue
}
if theInputState.prevKeyDurations[i] == 0 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
return inputstate.AppendJustReleasedKeys(keys)
}
// IsKeyJustPressed returns a boolean value indicating
@@ -229,7 +175,7 @@ func AppendJustReleasedKeys(keys []ebiten.Key) []ebiten.Key {
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
return KeyPressDuration(key) == 1
return inputstate.Get().IsKeyJustPressed(ui.Key(key))
}
// IsKeyJustReleased returns a boolean value indicating
@@ -239,9 +185,7 @@ func IsKeyJustPressed(key ebiten.Key) bool {
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
return theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
return inputstate.Get().IsKeyJustReleased(ui.Key(key))
}
// KeyPressDuration returns how long the key is pressed in ticks (Update).
@@ -250,9 +194,7 @@ func IsKeyJustReleased(key ebiten.Key) bool {
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
return theInputState.keyDurations[key]
return int(inputstate.Get().KeyPressDuration(ui.Key(key)))
}
// IsMouseButtonJustPressed returns a boolean value indicating
@@ -262,7 +204,7 @@ func KeyPressDuration(key ebiten.Key) int {
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
return MouseButtonPressDuration(button) == 1
return inputstate.Get().IsMouseButtonJustPressed(ui.MouseButton(button))
}
// IsMouseButtonJustReleased returns a boolean value indicating
@@ -272,9 +214,7 @@ func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
return theInputState.mouseButtonDurations[button] == 0 && theInputState.prevMouseButtonDurations[button] > 0
return inputstate.Get().IsMouseButtonJustReleased(ui.MouseButton(button))
}
// MouseButtonPressDuration returns how long the mouse button is pressed in ticks (Update).
@@ -283,9 +223,7 @@ func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
return theInputState.mouseButtonDurations[button]
return int(inputstate.Get().MouseButtonPressDuration(ui.MouseButton(button)))
}
// AppendJustConnectedGamepadIDs appends gamepad IDs that are connected just in the current tick to gamepadIDs,