|
| 1 | +// (c) 2022 Jacek Olszak |
| 2 | +// This code is licensed under MIT license (see LICENSE for details) |
| 3 | + |
| 4 | +package pi |
| 5 | + |
| 6 | +import ( |
| 7 | + "github.com/hajimehoshi/ebiten/v2" |
| 8 | +) |
| 9 | + |
| 10 | +// Button is a virtual button on any game controller. The game controller can be a gamepad or a keyboard. |
| 11 | +// |
| 12 | +// Button is used by Btn, Btnp, BtnPlayer, BtnpPlayer, BtnBits and BtnpBits. |
| 13 | +type Button int |
| 14 | + |
| 15 | +// Keyboard mappings: |
| 16 | +// |
| 17 | +// player 0: [DPAD] - cursors [O] - Z C N [X] - X V M |
| 18 | +// player 1: [DPAD] - SFED [O] - LSHIFT [X] - TAB W Q A |
| 19 | +// |
| 20 | +// First connected gamepad controller is player 0, second player 1 and so on. |
| 21 | +// On XBox controller [O] is A and Y, [X] is B and X. |
| 22 | +const ( |
| 23 | + Left Button = 0 |
| 24 | + Right Button = 1 |
| 25 | + Up Button = 2 |
| 26 | + Down Button = 3 |
| 27 | + O Button = 4 // O is a first fire button |
| 28 | + X Button = 5 // X is a second fire button |
| 29 | +) |
| 30 | + |
| 31 | +// Btn returns true if a button is being pressed at this moment by player 0. |
| 32 | +func Btn(button Button) bool { |
| 33 | + return BtnPlayer(button, 0) |
| 34 | +} |
| 35 | + |
| 36 | +// BtnPlayer returns true if a button is being pressed at this moment |
| 37 | +// by specific player. The player can be 0..7. |
| 38 | +func BtnPlayer(button Button, player int) bool { |
| 39 | + return isPressed(buttonDuration(player, button)) |
| 40 | +} |
| 41 | + |
| 42 | +// Btnp returns true when the button has just been pressed. |
| 43 | +// It also returns true after the next 15 frames, and then every 4 frames. |
| 44 | +// This simulates keyboard-like repeating. |
| 45 | +func Btnp(button Button) bool { |
| 46 | + return BtnpPlayer(button, 0) |
| 47 | +} |
| 48 | + |
| 49 | +// BtnpPlayer returns true when the button has just been pressed. |
| 50 | +// It also returns true after the next 15 frames, and then every 4 frames. |
| 51 | +// This simulates keyboard-like repeating. The player can be 0..7. |
| 52 | +func BtnpPlayer(button Button, player int) bool { |
| 53 | + return isPressedRepeatably(buttonDuration(player, button)) |
| 54 | +} |
| 55 | + |
| 56 | +// BtnBits returns the state of all buttons for players 0 and 1 as bitset. |
| 57 | +// |
| 58 | +// The first byte contains the button states for player 0 (bits 0 through 5, bits 6 and 7 are unused). |
| 59 | +// The second byte contains the button states for player 1 (bits 8 through 13). |
| 60 | +// |
| 61 | +// Bit 0 is Left, 1 is Right, bit 5 is the X button. |
| 62 | +// |
| 63 | +// A bit of 1 means the button is pressed. |
| 64 | +func BtnBits() int { |
| 65 | + return controllers[0].bits(isPressed) + controllers[1].bits(isPressed)<<8 |
| 66 | +} |
| 67 | + |
| 68 | +// BtnpBits returns the state of all buttons for players 0 and 1 as bitset. |
| 69 | +// |
| 70 | +// The first byte contains the button states for player 0 (bits 0 through 5, bits 6 and 7 are unused). |
| 71 | +// The second byte contains the button states for player 1 (bits 8 through 13). |
| 72 | +// |
| 73 | +// Bit 0 is Left, 1 is Right, bit 5 is the X button. |
| 74 | +// |
| 75 | +// A bit of 1 means the button has just been pressed. |
| 76 | +func BtnpBits() int { |
| 77 | + return controllers[0].bits(isPressedRepeatably) + controllers[1].bits(isPressedRepeatably)<<8 |
| 78 | +} |
| 79 | + |
| 80 | +func buttonDuration(player int, button Button) int { |
| 81 | + if button < Left || button > X { |
| 82 | + return 0 |
| 83 | + } |
| 84 | + |
| 85 | + if player < 0 || player > 7 { |
| 86 | + return 0 |
| 87 | + } |
| 88 | + |
| 89 | + return controllers[player].buttonDuration[button] |
| 90 | +} |
| 91 | + |
| 92 | +func isPressed(duration int) bool { |
| 93 | + return duration > 0 |
| 94 | +} |
| 95 | + |
| 96 | +func isPressedRepeatably(duration int) bool { |
| 97 | + const ( |
| 98 | + pressDuration = 15 // make it configurable |
| 99 | + pressInterval = 4 // make it configurable |
| 100 | + ) |
| 101 | + |
| 102 | + if duration == 1 { |
| 103 | + return true |
| 104 | + } |
| 105 | + |
| 106 | + return duration >= pressDuration+1 && duration%pressInterval == 0 |
| 107 | +} |
| 108 | + |
| 109 | +func updateController() { |
| 110 | + for player := 0; player < 8; player++ { |
| 111 | + controllers[player].update(player) |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +var controllers [8]controller |
| 116 | + |
| 117 | +type controller struct { |
| 118 | + buttonDuration [6]int // left, right, up, down, o, x |
| 119 | +} |
| 120 | + |
| 121 | +func (c *controller) update(player int) { |
| 122 | + c.updateDirections(player) |
| 123 | + c.updateFireButtons(player) |
| 124 | +} |
| 125 | + |
| 126 | +func (c *controller) updateDirections(player int) { |
| 127 | + gamepadID := ebiten.GamepadID(player) |
| 128 | + |
| 129 | + axisX := ebiten.StandardGamepadAxisValue(gamepadID, ebiten.StandardGamepadAxisLeftStickHorizontal) |
| 130 | + axisY := ebiten.StandardGamepadAxisValue(gamepadID, ebiten.StandardGamepadAxisLeftStickVertical) |
| 131 | + |
| 132 | + if axisX < -0.5 || |
| 133 | + ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonLeftLeft) || |
| 134 | + isKeyboardPressed(player, Left) { |
| 135 | + c.buttonDuration[Left] += 1 |
| 136 | + c.buttonDuration[Right] = 0 |
| 137 | + } else if axisX > 0.5 || |
| 138 | + ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonLeftRight) || |
| 139 | + isKeyboardPressed(player, Right) { |
| 140 | + c.buttonDuration[Right] += 1 |
| 141 | + c.buttonDuration[Left] = 0 |
| 142 | + } else { |
| 143 | + c.buttonDuration[Right] = 0 |
| 144 | + c.buttonDuration[Left] = 0 |
| 145 | + } |
| 146 | + |
| 147 | + if axisY < -0.5 || |
| 148 | + ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonLeftTop) || |
| 149 | + isKeyboardPressed(player, Up) { |
| 150 | + c.buttonDuration[Up] += 1 |
| 151 | + c.buttonDuration[Down] = 0 |
| 152 | + } else if axisY > 0.5 || |
| 153 | + ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonLeftBottom) || |
| 154 | + isKeyboardPressed(player, Down) { |
| 155 | + c.buttonDuration[Down] += 1 |
| 156 | + c.buttonDuration[Up] = 0 |
| 157 | + } else { |
| 158 | + c.buttonDuration[Up] = 0 |
| 159 | + c.buttonDuration[Down] = 0 |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +func (c *controller) updateFireButtons(player int) { |
| 164 | + gamepadID := ebiten.GamepadID(player) |
| 165 | + |
| 166 | + if ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonRightBottom) || |
| 167 | + ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonRightTop) || |
| 168 | + isKeyboardPressed(player, O) { |
| 169 | + c.buttonDuration[O] += 1 |
| 170 | + } else { |
| 171 | + c.buttonDuration[O] = 0 |
| 172 | + } |
| 173 | + |
| 174 | + if ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonRightRight) || |
| 175 | + ebiten.IsStandardGamepadButtonPressed(gamepadID, ebiten.StandardGamepadButtonRightLeft) || |
| 176 | + isKeyboardPressed(player, X) { |
| 177 | + c.buttonDuration[X] += 1 |
| 178 | + } else { |
| 179 | + c.buttonDuration[X] = 0 |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +func (c *controller) bits(isSet func(int) bool) int { |
| 184 | + var b int |
| 185 | + for i := 0; i <= int(X); i++ { |
| 186 | + if isSet(c.buttonDuration[i]) { |
| 187 | + b += 1 << i |
| 188 | + } |
| 189 | + } |
| 190 | + return b |
| 191 | +} |
| 192 | + |
| 193 | +// first array is player, then π key, then slice of Ebitengine keys. |
| 194 | +var keyboardMapping = [...][6][]ebiten.Key{ |
| 195 | + // player0: |
| 196 | + { |
| 197 | + {ebiten.KeyLeft}, // left |
| 198 | + {ebiten.KeyRight}, // right |
| 199 | + {ebiten.KeyUp}, // up |
| 200 | + {ebiten.KeyDown}, // down |
| 201 | + {ebiten.KeyZ, ebiten.KeyC, ebiten.KeyN}, // o |
| 202 | + {ebiten.KeyX, ebiten.KeyV, ebiten.KeyM}, // x |
| 203 | + }, |
| 204 | + // player1: |
| 205 | + { |
| 206 | + {ebiten.KeyS}, // left |
| 207 | + {ebiten.KeyF}, // right |
| 208 | + {ebiten.KeyE}, // up |
| 209 | + {ebiten.KeyD}, // down |
| 210 | + {ebiten.KeyShiftLeft}, // o |
| 211 | + {ebiten.KeyTab, ebiten.KeyW, ebiten.KeyQ, ebiten.KeyA}, // x |
| 212 | + }, |
| 213 | +} |
| 214 | + |
| 215 | +func isKeyboardPressed(player int, button Button) bool { |
| 216 | + if player >= len(keyboardMapping) { |
| 217 | + return false |
| 218 | + } |
| 219 | + |
| 220 | + keys := keyboardMapping[player][button] |
| 221 | + for _, k := range keys { |
| 222 | + if ebiten.IsKeyPressed(k) { |
| 223 | + return true |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | + return false |
| 228 | +} |
0 commit comments