Skip to content

Commit cd98747

Browse files
committed
Gamepad support
1 parent d172cc0 commit cd98747

File tree

4 files changed

+121
-21
lines changed

4 files changed

+121
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
in time in 5 second steps to quickly recover from mistakes. Can be used by
1919
pressing Ctrl+Z or ⌘+Z.
2020
* Game Genie codes support via the -gg flag.
21+
* Gamepad support.
2122

2223
## v1.0.0 - 2024-01-26
2324

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ follows. Multiplayer on a single keyboard is not supported.
8787
└───────────────────────────────────────┘
8888
```
8989

90+
If a gamepad is connected, it will likely be detected automatically and can be used
91+
instead of the keyboard. I tested it with a PS4 controller connected via Bluetooth.
92+
9093
### Zapper (Light Gun)
9194

9295
Zapper is emulated using the mouse and can be used in games like Duck Hunt. Just

ui/joystick.go

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,107 @@ import (
66
"github.com/maxpoletaev/dendy/input"
77
)
88

9-
var keyMap = map[int32]input.Button{
10-
rl.KeyW: input.ButtonUp,
11-
rl.KeyS: input.ButtonDown,
12-
rl.KeyA: input.ButtonLeft,
13-
rl.KeyD: input.ButtonRight,
14-
rl.KeyK: input.ButtonA,
15-
rl.KeyJ: input.ButtonB,
16-
rl.KeyEnter: input.ButtonStart,
17-
rl.KeyRightShift: input.ButtonSelect,
9+
const (
10+
turboRate = 4
11+
gamepadIndex = 0
12+
stickDeadzone = 0.25
13+
stickThreshold = 0.5
14+
)
15+
16+
type keyMapping struct {
17+
key int32
18+
button input.Button
19+
turbo bool
20+
}
21+
22+
var keyboardMappings = []keyMapping{
23+
{rl.KeyW, input.ButtonUp, false},
24+
{rl.KeyS, input.ButtonDown, false},
25+
{rl.KeyA, input.ButtonLeft, false},
26+
{rl.KeyD, input.ButtonRight, false},
27+
{rl.KeyJ, input.ButtonB, false},
28+
{rl.KeyK, input.ButtonA, false},
29+
{rl.KeyU, input.ButtonB, true},
30+
{rl.KeyI, input.ButtonA, true},
31+
{rl.KeyEnter, input.ButtonStart, false},
32+
{rl.KeyRightShift, input.ButtonSelect, false},
33+
}
34+
35+
var gamepadMappings = []keyMapping{
36+
{rl.GamepadButtonLeftFaceUp, input.ButtonUp, false},
37+
{rl.GamepadButtonLeftFaceDown, input.ButtonDown, false},
38+
{rl.GamepadButtonLeftFaceLeft, input.ButtonLeft, false},
39+
{rl.GamepadButtonLeftFaceRight, input.ButtonRight, false},
40+
{rl.GamepadButtonRightFaceDown, input.ButtonA, false},
41+
{rl.GamepadButtonRightFaceLeft, input.ButtonB, false},
42+
{rl.GamepadButtonRightFaceRight, input.ButtonB, true},
43+
{rl.GamepadButtonRightFaceUp, input.ButtonA, true},
44+
{rl.GamepadButtonMiddleRight, input.ButtonStart, false},
45+
{rl.GamepadButtonMiddleLeft, input.ButtonSelect, false},
1846
}
1947

2048
func (w *Window) UpdateJoystick() {
2149
if w.InputDelegate == nil {
2250
return
2351
}
2452

53+
w.turboCounter++
54+
if w.turboCounter >= turboRate {
55+
w.turboCounter = 0
56+
}
57+
2558
var buttons uint8
2659

27-
for key, button := range keyMap {
28-
if rl.IsKeyDown(key) {
29-
buttons |= button
60+
if w.gamepadAvailable {
61+
buttons = readAnalogInput(buttons)
62+
63+
for _, km := range gamepadMappings {
64+
if km.turbo && w.turboCounter != 0 {
65+
continue
66+
}
67+
if rl.IsGamepadButtonDown(gamepadIndex, km.key) {
68+
buttons |= km.button
69+
}
70+
}
71+
}
72+
73+
for _, km := range keyboardMappings {
74+
if km.turbo && w.turboCounter != 0 {
75+
continue
76+
}
77+
if rl.IsKeyDown(km.key) {
78+
buttons |= km.button
3079
}
3180
}
3281

3382
w.InputDelegate(buttons)
3483
}
84+
85+
func readAnalogInput(buttons uint8) uint8 {
86+
leftX := rl.GetGamepadAxisMovement(gamepadIndex, rl.GamepadAxisLeftX)
87+
leftY := rl.GetGamepadAxisMovement(gamepadIndex, rl.GamepadAxisLeftY)
88+
89+
// Apply deadzone
90+
if leftX < stickDeadzone && leftX > -stickDeadzone {
91+
leftX = 0
92+
}
93+
if leftY < stickDeadzone && leftY > -stickDeadzone {
94+
leftY = 0
95+
}
96+
97+
// Convert analog input to digital directional buttons
98+
if leftX < -stickThreshold {
99+
buttons |= input.ButtonLeft
100+
}
101+
if leftX > stickThreshold {
102+
buttons |= input.ButtonRight
103+
}
104+
if leftY < -stickThreshold {
105+
buttons |= input.ButtonUp
106+
}
107+
if leftY > stickThreshold {
108+
buttons |= input.ButtonDown
109+
}
110+
111+
return buttons
112+
}

ui/window.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ type Window struct {
2727
ShowFPS bool
2828
FPS int
2929

30-
viewport rl.RenderTexture2D
31-
shader *shaderFacade
32-
remotePing int64
33-
shouldClose bool
34-
grayscale bool
35-
scale int
36-
width int
37-
height int
30+
gamepadAvailable bool
31+
viewport rl.RenderTexture2D
32+
shader *shaderFacade
33+
remotePing int64
34+
shouldClose bool
35+
grayscale bool
36+
scale int
37+
width int
38+
height int
39+
turboCounter int
3840
}
3941

4042
func CreateWindow(scale int, verbose bool) *Window {
@@ -51,12 +53,23 @@ func CreateWindow(scale int, verbose bool) *Window {
5153
viewport := rl.LoadRenderTexture(ppu.FrameWidth, ppu.FrameHeight)
5254
rl.SetTextureFilter(viewport.Texture, rl.FilterPoint)
5355

54-
return &Window{
56+
w := &Window{
5557
viewport: viewport,
5658
scale: scale,
5759
width: windowWidth,
5860
height: windowHeight,
5961
}
62+
63+
// poll initial input events
64+
rl.BeginDrawing()
65+
rl.EndDrawing()
66+
67+
if rl.IsGamepadAvailable(gamepadIndex) {
68+
log.Printf("[INFO] gamepad detected: %s", rl.GetGamepadName(gamepadIndex))
69+
w.gamepadAvailable = true
70+
}
71+
72+
return w
6073
}
6174

6275
func (w *Window) EnableCRT() {
@@ -214,5 +227,10 @@ func (w *Window) HandleHotKeys() {
214227
if w.RewindDelegate != nil {
215228
w.RewindDelegate()
216229
}
230+
231+
case w.gamepadAvailable && rl.IsGamepadButtonPressed(gamepadIndex, rl.GamepadButtonLeftTrigger1):
232+
if w.RewindDelegate != nil {
233+
w.RewindDelegate()
234+
}
217235
}
218236
}

0 commit comments

Comments
 (0)