SDL2 GameController Support
This commit is contained in:
parent
9e640ab5c3
commit
1f4af682e1
|
@ -40,12 +40,18 @@ type State struct {
|
|||
TouchCenterY int
|
||||
GestureRotated float64
|
||||
GesturePinched float64
|
||||
|
||||
// Game controller events.
|
||||
// NOTE: for SDL2 you will need to call GameControllerEventState(1)
|
||||
// from veandco/go-sdl2/sdl for events to be read by SDL2.
|
||||
Controllers map[int]GameController
|
||||
}
|
||||
|
||||
// NewState creates a new event.State.
|
||||
func NewState() *State {
|
||||
return &State{
|
||||
keydown: map[string]interface{}{},
|
||||
keydown: map[string]interface{}{},
|
||||
Controllers: map[int]GameController{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,3 +124,30 @@ var shiftMap = map[string]string{
|
|||
".": ">",
|
||||
"/": "?",
|
||||
}
|
||||
|
||||
// AddController adds a new controller to the event state. This is typically called
|
||||
// automatically by the render engine, e.g. on an SDL ControllerDeviceEvent.
|
||||
func (s *State) AddController(index int, v GameController) bool {
|
||||
if _, ok := s.Controllers[index]; ok {
|
||||
return false
|
||||
}
|
||||
s.Controllers[index] = v
|
||||
return true
|
||||
}
|
||||
|
||||
// RemoveController removes the available controller.
|
||||
func (s *State) RemoveController(index int) bool {
|
||||
if _, ok := s.Controllers[index]; ok {
|
||||
delete(s.Controllers, index)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetController gets a registered controller by index.
|
||||
func (s *State) GetController(index int) (GameController, bool) {
|
||||
if c, ok := s.Controllers[index]; ok {
|
||||
return c, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
|
41
event/game_controller.go
Normal file
41
event/game_controller.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package event
|
||||
|
||||
// GameController holds event state for one or more (Xbox-style) controllers.
|
||||
type GameController interface {
|
||||
ID() int // Usually the controller index number
|
||||
Name() string // Friendly name of the controller
|
||||
|
||||
// State setters, to be called by the engine.
|
||||
// Note: button names are implementation-specific, use Button*() methods in your code.
|
||||
SetButtonState(name string, pressed bool)
|
||||
GetButtonState(name string) bool
|
||||
SetAxisState(name string, value int) // value maybe -32768 to 32767
|
||||
|
||||
// State getters.
|
||||
ButtonA() bool
|
||||
ButtonB() bool
|
||||
ButtonX() bool
|
||||
ButtonY() bool
|
||||
ButtonL1() bool // Left shoulder
|
||||
ButtonR1() bool // Right shoulder
|
||||
ButtonL2() bool // Left trigger (digital)
|
||||
ButtonR2() bool // Right trigger (digital)
|
||||
ButtonLStick() bool
|
||||
ButtonRStick() bool
|
||||
ButtonStart() bool
|
||||
ButtonSelect() bool // Back button
|
||||
ButtonHome() bool // Guide button
|
||||
|
||||
// D-Pad buttons.
|
||||
ButtonUp() bool
|
||||
ButtonLeft() bool
|
||||
ButtonRight() bool
|
||||
ButtonDown() bool
|
||||
|
||||
// Axis getters. Returns Vectors ranging from -1.0 to 1.0 being a
|
||||
// percentage of the axis between neutral and maxed out.
|
||||
LeftStick() Vector
|
||||
RightStick() Vector
|
||||
LeftTrigger() float64
|
||||
RightTrigger() float64
|
||||
}
|
7
event/vector.go
Normal file
7
event/vector.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package event
|
||||
|
||||
// Vector holds a floating point vector along an X and Y axis.
|
||||
type Vector struct {
|
||||
X float64
|
||||
Y float64
|
||||
}
|
|
@ -10,11 +10,12 @@ import (
|
|||
|
||||
// Debug certain SDL events
|
||||
var (
|
||||
DebugWindowEvents = false
|
||||
DebugTouchEvents = false
|
||||
DebugMouseEvents = false
|
||||
DebugClickEvents = false
|
||||
DebugKeyEvents = false
|
||||
DebugWindowEvents = false
|
||||
DebugTouchEvents = false
|
||||
DebugMouseEvents = false
|
||||
DebugClickEvents = false
|
||||
DebugKeyEvents = false
|
||||
DebugControllerEvents = false
|
||||
)
|
||||
|
||||
// Poll for events.
|
||||
|
@ -39,7 +40,7 @@ func (r *Renderer) Poll() (*event.State, error) {
|
|||
if t.Event == sdl.WINDOWEVENT_RESIZED {
|
||||
fmt.Printf("[%d ms] tick:%d Window Resized to %dx%d\n",
|
||||
t.Timestamp,
|
||||
r.ticks,
|
||||
sdl.GetTicks(),
|
||||
t.Data1,
|
||||
t.Data2,
|
||||
)
|
||||
|
@ -52,7 +53,7 @@ func (r *Renderer) Poll() (*event.State, error) {
|
|||
case *sdl.MouseMotionEvent:
|
||||
if DebugMouseEvents {
|
||||
fmt.Printf("[%d ms] tick:%d MouseMotion type:%d id:%d x:%d y:%d xrel:%d yrel:%d\n",
|
||||
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y, t.XRel, t.YRel,
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Which, t.X, t.Y, t.XRel, t.YRel,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -62,7 +63,7 @@ func (r *Renderer) Poll() (*event.State, error) {
|
|||
case *sdl.MouseButtonEvent:
|
||||
if DebugClickEvents {
|
||||
fmt.Printf("[%d ms] tick:%d MouseButton type:%d id:%d x:%d y:%d button:%d state:%d\n",
|
||||
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y, t.Button, t.State,
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Which, t.X, t.Y, t.Button, t.State,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -104,13 +105,13 @@ func (r *Renderer) Poll() (*event.State, error) {
|
|||
case *sdl.MouseWheelEvent:
|
||||
if DebugMouseEvents {
|
||||
fmt.Printf("[%d ms] tick:%d MouseWheel type:%d id:%d x:%d y:%d\n",
|
||||
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y,
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Which, t.X, t.Y,
|
||||
)
|
||||
}
|
||||
case *sdl.MultiGestureEvent:
|
||||
if DebugTouchEvents {
|
||||
fmt.Printf("[%d ms] tick:%d MultiGesture type:%d Num=%d TouchID=%+v Dt=%f Dd=%f XY=%f,%f\n",
|
||||
t.Timestamp, r.ticks, t.Type, t.NumFingers, t.TouchID, t.DTheta, t.DDist, t.X, t.Y,
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.NumFingers, t.TouchID, t.DTheta, t.DDist, t.X, t.Y,
|
||||
)
|
||||
}
|
||||
s.Touching = true
|
||||
|
@ -122,7 +123,7 @@ func (r *Renderer) Poll() (*event.State, error) {
|
|||
case *sdl.KeyboardEvent:
|
||||
if DebugKeyEvents {
|
||||
fmt.Printf("[%d ms] tick:%d Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
||||
t.Timestamp, r.ticks, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -190,6 +191,80 @@ func (r *Renderer) Poll() (*event.State, error) {
|
|||
// Push the string value of the key.
|
||||
s.SetKeyDown(string(t.Keysym.Sym), t.State == 1 || t.Repeat == 1)
|
||||
}
|
||||
case *sdl.ControllerDeviceEvent:
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d ControllerDevice type:%d timestamp:%d which:%d\n",
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Timestamp, t.Which,
|
||||
)
|
||||
}
|
||||
|
||||
var index = int(t.Which)
|
||||
|
||||
// Update the catalog of available controllers.
|
||||
switch t.Type {
|
||||
case sdl.CONTROLLERDEVICEADDED:
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d AddController: %s\n",
|
||||
t.Timestamp, sdl.GetTicks(), sdl.GameControllerNameForIndex(index),
|
||||
)
|
||||
}
|
||||
|
||||
// Add and open the GameController.
|
||||
var (
|
||||
name = sdl.GameControllerNameForIndex(index)
|
||||
ctrlImpl = sdl.GameControllerOpen(index)
|
||||
ctrl = NewGameController(index, name, ctrlImpl)
|
||||
)
|
||||
s.AddController(index, ctrl)
|
||||
case sdl.CONTROLLERDEVICEREMOVED:
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d RemoveController: %s\n",
|
||||
t.Timestamp, sdl.GetTicks(), sdl.GameControllerNameForIndex(index),
|
||||
)
|
||||
}
|
||||
s.RemoveController(index)
|
||||
}
|
||||
case *sdl.ControllerButtonEvent:
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d ControllerButton type:%d timestamp:%d which:%d btn:%d state:%d\n",
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Timestamp, t.Which, t.Button, t.State,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
index = int(t.Which)
|
||||
buttonName = sdl.GameControllerGetStringForButton(sdl.GameControllerButton(t.Button))
|
||||
)
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d ControllerButton: index:%d name:%s pressed:%d\n",
|
||||
t.Timestamp, sdl.GetTicks(), index, buttonName, t.State,
|
||||
)
|
||||
}
|
||||
|
||||
if ctrl, ok := s.GetController(index); ok {
|
||||
ctrl.SetButtonState(buttonName, t.State == sdl.PRESSED)
|
||||
}
|
||||
case *sdl.ControllerAxisEvent:
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d ControllerAxis type:%d timestamp:%d which:%d axis:%d value:%d\n",
|
||||
t.Timestamp, sdl.GetTicks(), t.Type, t.Timestamp, t.Which, t.Axis, t.Value,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
index = int(t.Which)
|
||||
axisName = sdl.GameControllerGetStringForAxis(sdl.GameControllerAxis(t.Axis))
|
||||
value = int(t.Value)
|
||||
)
|
||||
if DebugControllerEvents {
|
||||
fmt.Printf("[%d ms] tick:%d ControllerAxis: index:%d name:%s value:%d\n",
|
||||
t.Timestamp, sdl.GetTicks(), index, axisName, value,
|
||||
)
|
||||
}
|
||||
|
||||
if ctrl, ok := s.GetController(index); ok {
|
||||
ctrl.SetAxisState(axisName, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
199
sdl/game_controller.go
Normal file
199
sdl/game_controller.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
package sdl
|
||||
|
||||
import (
|
||||
"git.kirsle.net/go/render/event"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
// User tuneable properties.
|
||||
var (
|
||||
// For controllers having a digital (non-analog) Left/Right Trigger, the press percentage
|
||||
// for which to consider it a boolean press.
|
||||
TriggerAxisBooleanThreshold float64 = 0.5
|
||||
)
|
||||
|
||||
// GameController holds an abstraction around SDL2 GameControllers.
|
||||
type GameController struct {
|
||||
id int
|
||||
name string
|
||||
active bool
|
||||
|
||||
// Underlying SDL2 GameController.
|
||||
ctrl *sdl.GameController
|
||||
|
||||
// Button states.
|
||||
buttons map[string]bool
|
||||
axes map[string]int
|
||||
}
|
||||
|
||||
// NewGameController creates a GameController from an SDL2 controller.
|
||||
func NewGameController(index int, name string, ctrl *sdl.GameController) *GameController {
|
||||
return &GameController{
|
||||
id: index,
|
||||
name: name,
|
||||
ctrl: ctrl,
|
||||
buttons: map[string]bool{},
|
||||
axes: map[string]int{},
|
||||
}
|
||||
}
|
||||
|
||||
// ID returns the controller index as SDL2 knows it.
|
||||
func (gc *GameController) ID() int {
|
||||
return gc.id
|
||||
}
|
||||
|
||||
// Name returns the controller name.
|
||||
func (gc *GameController) Name() string {
|
||||
return gc.name
|
||||
}
|
||||
|
||||
// SetButtonState sets the state using the SDL2 button names.
|
||||
func (gc *GameController) SetButtonState(name string, pressed bool) {
|
||||
gc.buttons[name] = pressed
|
||||
}
|
||||
|
||||
// GetButtonState returns the button state by SDL2 button name.
|
||||
func (gc *GameController) GetButtonState(name string) bool {
|
||||
if v, ok := gc.buttons[name]; ok {
|
||||
return v
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAxisState sets the axis state.
|
||||
func (gc *GameController) SetAxisState(name string, value int) {
|
||||
gc.axes[name] = value
|
||||
}
|
||||
|
||||
// GetAxisState returns the underlying SDL2 axis state.
|
||||
func (gc *GameController) GetAxisState(name string) int {
|
||||
if v, ok := gc.axes[name]; ok {
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ButtonA returns whether the logical Xbox button is pressed.
|
||||
func (gc *GameController) ButtonA() bool {
|
||||
return gc.GetButtonState("a")
|
||||
}
|
||||
|
||||
// ButtonB returns whether the logical Xbox button is pressed.
|
||||
func (gc *GameController) ButtonB() bool {
|
||||
return gc.GetButtonState("b")
|
||||
}
|
||||
|
||||
// ButtonX returns whether the logical Xbox button is pressed.
|
||||
func (gc *GameController) ButtonX() bool {
|
||||
return gc.GetButtonState("x")
|
||||
}
|
||||
|
||||
// ButtonY returns whether the logical Xbox button is pressed.
|
||||
func (gc *GameController) ButtonY() bool {
|
||||
return gc.GetButtonState("y")
|
||||
}
|
||||
|
||||
// ButtonL1 returns whether the Left Shoulder button is pressed.
|
||||
func (gc *GameController) ButtonL1() bool {
|
||||
return gc.GetButtonState("leftshoulder")
|
||||
}
|
||||
|
||||
// ButtonR1 returns whether the Right Shoulder button is pressed.
|
||||
func (gc *GameController) ButtonR1() bool {
|
||||
return gc.GetButtonState("rightshoulder")
|
||||
}
|
||||
|
||||
// ButtonL2 returns whether the Left Trigger (digital) button is pressed.
|
||||
// Returns true if the LeftTrigger is 50% pressed or TriggerAxisBooleanThreshold.
|
||||
func (gc *GameController) ButtonL2() bool {
|
||||
return gc.axisToFloat("lefttrigger") > TriggerAxisBooleanThreshold
|
||||
}
|
||||
|
||||
// ButtonR2 returns whether the Left Trigger (digital) button is pressed.
|
||||
// Returns true if the LeftTrigger is 50% pressed or TriggerAxisBooleanThreshold.
|
||||
func (gc *GameController) ButtonR2() bool {
|
||||
return gc.axisToFloat("righttrigger") > TriggerAxisBooleanThreshold
|
||||
}
|
||||
|
||||
// ButtonLStick returns whether the Left Stick button is pressed.
|
||||
func (gc *GameController) ButtonLStick() bool {
|
||||
return gc.GetButtonState("leftstick")
|
||||
}
|
||||
|
||||
// ButtonRStick returns whether the Right Stick button is pressed.
|
||||
func (gc *GameController) ButtonRStick() bool {
|
||||
return gc.GetButtonState("rightstick")
|
||||
}
|
||||
|
||||
// ButtonStart returns whether the logical Xbox button is pressed.
|
||||
func (gc *GameController) ButtonStart() bool {
|
||||
return gc.GetButtonState("start")
|
||||
}
|
||||
|
||||
// ButtonSelect returns whether the Xbox "back" button is pressed.
|
||||
func (gc *GameController) ButtonSelect() bool {
|
||||
return gc.GetButtonState("back")
|
||||
}
|
||||
|
||||
// ButtonUp returns whether the Xbox D-Pad button is pressed.
|
||||
func (gc *GameController) ButtonUp() bool {
|
||||
return gc.GetButtonState("dpup")
|
||||
}
|
||||
|
||||
// ButtonDown returns whether the Xbox D-Pad button is pressed.
|
||||
func (gc *GameController) ButtonDown() bool {
|
||||
return gc.GetButtonState("dpdown")
|
||||
}
|
||||
|
||||
// ButtonLeft returns whether the Xbox D-Pad button is pressed.
|
||||
func (gc *GameController) ButtonLeft() bool {
|
||||
return gc.GetButtonState("dpleft")
|
||||
}
|
||||
|
||||
// ButtonRight returns whether the Xbox D-Pad button is pressed.
|
||||
func (gc *GameController) ButtonRight() bool {
|
||||
return gc.GetButtonState("dpright")
|
||||
}
|
||||
|
||||
// ButtonHome returns whether the Xbox "guide" button is pressed.
|
||||
func (gc *GameController) ButtonHome() bool {
|
||||
return gc.GetButtonState("guide")
|
||||
}
|
||||
|
||||
// LeftStick returns the vector of X and Y of the left analog stick.
|
||||
func (gc *GameController) LeftStick() event.Vector {
|
||||
return event.Vector{
|
||||
X: gc.axisToFloat("leftx"),
|
||||
Y: gc.axisToFloat("lefty"),
|
||||
}
|
||||
}
|
||||
|
||||
// RightStick returns the vector of X and Y of the right analog stick.
|
||||
func (gc *GameController) RightStick() event.Vector {
|
||||
return event.Vector{
|
||||
X: gc.axisToFloat("rightx"),
|
||||
Y: gc.axisToFloat("righty"),
|
||||
}
|
||||
}
|
||||
|
||||
// LeftTrigger returns the vector of the left analog trigger.
|
||||
func (gc *GameController) LeftTrigger() float64 {
|
||||
return gc.axisToFloat("lefttrigger")
|
||||
}
|
||||
|
||||
// RightTrigger returns the vector of the left analog trigger.
|
||||
func (gc *GameController) RightTrigger() float64 {
|
||||
return gc.axisToFloat("righttrigger")
|
||||
}
|
||||
|
||||
// axisToFloat converts an SDL2 Axis value to a float between -1.0..1.0
|
||||
func (gc *GameController) axisToFloat(name string) float64 {
|
||||
// SDL2 Axis is an int16 ranging from -32768 to 32767,
|
||||
// convert this into a percentage +- 0 to 1.
|
||||
axis := gc.GetAxisState(name)
|
||||
if axis < 0 {
|
||||
return float64(axis) / 32768
|
||||
} else {
|
||||
return float64(axis) / 32767
|
||||
}
|
||||
}
|
10
sdl/sdl.go
10
sdl/sdl.go
|
@ -5,7 +5,6 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/event"
|
||||
|
@ -17,17 +16,14 @@ import (
|
|||
// Renderer manages the SDL state.
|
||||
type Renderer struct {
|
||||
// Configurable fields.
|
||||
title string
|
||||
width int32
|
||||
height int32
|
||||
startTime time.Time
|
||||
title string
|
||||
width int32
|
||||
height int32
|
||||
|
||||
// Private fields.
|
||||
events *event.State
|
||||
window *sdl.Window
|
||||
renderer *sdl.Renderer
|
||||
running bool
|
||||
ticks uint64
|
||||
textures map[string]*Texture // cached textures
|
||||
|
||||
// Optimizations to minimize SDL calls.
|
||||
|
|
|
@ -52,6 +52,8 @@ func LoadFont(filename string, size int) (*ttf.Font, error) {
|
|||
err error
|
||||
)
|
||||
|
||||
fontsMu.Lock()
|
||||
defer fontsMu.Unlock()
|
||||
if binary, ok := installedFont[filename]; ok {
|
||||
var RWops *sdl.RWops
|
||||
RWops, err = sdl.RWFromMem(binary)
|
||||
|
|
Loading…
Reference in New Issue
Block a user