Bunyip a game engine in Go GitHub

Package github.com/matjam/bunyip/input

input

Package input reports the state of the keyboard, mouse and gamepads, and names the keys, buttons and axes the platform layers fill that state with.

State holds every key, button, stick and the pointer. Level queries (KeyDown, MouseDown, Gamepad.Down) report what is held now. Edge queries (KeyPressed, KeyReleased and their mouse and gamepad equivalents) report what changed since the last update, and the engine clears them after each one, so a press is reported exactly once however many updates or frames it spans. During Draw the edges cover the whole drawn frame instead, so an immediate-mode interface built in Draw sees every change. KeyHeld, KeysDown, KeyRepeated and MouseDoubleClicked cover charged shots, combos, scrolling menus and double clicks. Chars and Composition carry typed text with the keyboard layout and input method applied.

Keys are named by physical position, so KeyW is the key in W's place on a US keyboard whatever it prints, which is what movement bindings need. Key.String names physical positions; the engine's Context.KeyboardLayout supplies native binding labels and physical/logical lookup snapshots. Actions maps named actions to any keys, buttons and axes, with dead zones, rebinding through Listen and JSON bindings for a settings file, so game code asks for "jump" and supports a gamepad without a second set of checks.

Package input reports the state of the keyboard, mouse and gamepads: what is held, what changed this update, where the pointer is and what text was typed, in view units. Read Context.Input on the game loop goroutine. During Update, edge accessors cover events since the preceding update; during Draw, keyboard and mouse edges, text and motion cover the drawn frame. These views are cleared independently, so drawing does not consume input pending for Update. Repeated OS key events count as presses.

Index

Constants

const (
	DoubleClickTime     = 0.4
	DoubleClickDistance = 6
)

DoubleClickTime is how close two presses must be to count as a double click, in seconds, and DoubleClickDistance how close in view units.

const MaxGamepads = 4

MaxGamepads is how many controllers the engine tracks.

Types

type Action source

type Action struct {
	// contains filtered or unexported fields
}

Action is a resolved handle to one named action. Looking the name up once at startup and keeping the handle spares the string hash on every query, which a game makes several times per action per frame. The zero Action is bound to nothing and reads as off.

A handle stays valid across Bind, Rebind and Unbind: it names the action, not the sources behind it.

Bindings source

func (h Action) Bindings() []Source

Bindings returns the handle's sources, as Actions.Bindings does.

Down source

func (h Action) Down(s *State) bool

Down reports whether the handle's action is on, as Actions.Down does.

Name source

func (h Action) Name() string

Name is the action the handle was resolved from, empty for the zero Action.

Pressed source

func (h Action) Pressed(s *State) bool

Pressed reports whether the handle's action went on since the last update, as Actions.Pressed does.

Released source

func (h Action) Released(s *State) bool

Released reports whether the handle's action went off since the last update, as Actions.Released does.

Value source

func (h Action) Value(s *State) float32

Value is the handle's value now, as Actions.Value describes it.

type Actions source

type Actions struct {

	// Pad is which gamepad the pad sources read; the first by default.
	Pad int
	// DeadZone is the stick travel ignored around centre; zero means 0.2.
	DeadZone float32
	// contains filtered or unexported fields
}

Actions maps named actions ("jump", "fire", "move_x") to the keys, buttons and axes that trigger them, so game code asks for actions and a settings screen rebinds them. An action bound to several sources fires from any; an axis action sums its sources (the D key and the A key with Neg, plus a stick) and clamps to -1..1. Bindings save and load as JSON, and Listen captures the next input for rebinding. Use Actions on the game loop goroutine; concurrent binding changes and queries require external synchronization.

NewActions source

func NewActions() *Actions

NewActions makes an empty map.

Action source

func (a *Actions) Action(name string) Action

Action resolves a name to a handle, which Value, Down, Pressed and Released read without hashing the name again. Resolve the handles a game uses once, in Init, and keep them:

g.jump = actions.Action("jump")
if g.jump.Pressed(ctx.Input) { ... }

The name need not be bound yet; binding it later fills the same handle.

Bind source

func (a *Actions) Bind(action string, sources ...Source)

Bind adds sources to an action, keeping any it has.

Bindings source

func (a *Actions) Bindings(action string) []Source

Bindings returns an action's sources, for showing in a settings screen or a prompt ("press [Space]").

Down source

func (a *Actions) Down(s *State, action string) bool

Down reports whether the action is on: any bound key or button held, or a bound axis past half.

Listen source

func (a *Actions) Listen(s *State) (Source, bool)

Listen returns the first input that went on this update, for a rebinding screen: press a key, click a button, push a stick past half. Escape is never captured, so a screen can cancel with it.

MarshalJSON source

func (a *Actions) MarshalJSON() ([]byte, error)

MarshalJSON writes the bindings as {"action": ["key:Space", ...]}.

Names source

func (a *Actions) Names() []string

Names returns every bound action, sorted.

Pressed source

func (a *Actions) Pressed(s *State, action string) bool

Pressed reports whether any of the action's sources went on since the last update: a key or button press, or an axis crossing half.

Rebind source

func (a *Actions) Rebind(action string, sources ...Source)

Rebind replaces an action's sources.

Released source

func (a *Actions) Released(s *State, action string) bool

Released reports whether any of the action's sources went off since the last update.

Unbind source

func (a *Actions) Unbind(action string)

Unbind removes an action's sources. Handles taken for the name stay valid and read as off until it is bound again.

UnmarshalJSON source

func (a *Actions) UnmarshalJSON(b []byte) error

UnmarshalJSON reads what MarshalJSON wrote, replacing the bindings. Handles taken with Action keep pointing at their names, so loading a player's saved bindings does not invalidate them.

Value source

func (a *Actions) Value(s *State, action string) float32

Value is the action's value now: 1 while a bound key or button is held, a stick's travel for an axis, the sum over sources clamped to -1..1. An axis source contributes its positive side; bind its Neg for the other, so "move_x" is (D, A.Neg, LeftX, LeftX.Neg).

This looks the name up on every call. A game querying the same actions every frame resolves them once with Action and asks the handle.

type Gamepad source

type Gamepad struct {
	Info      GamepadInfo               // native metadata and mapped capabilities; empty when disconnected
	Connected bool                      // controller is currently present
	Name      string                    // device-provided display name
	Buttons   [GamepadButtonCount]bool  // held buttons
	Axes      [GamepadAxisCount]float32 // raw axes before Axis applies its dead zone
	// contains filtered or unexported fields
}

Gamepad is one controller's current state.

Axis source

func (g *Gamepad) Axis(a GamepadAxis) float32

Axis returns an analogue value, replacing values strictly between -0.08 and 0.08 with zero; values outside that range are not rescaled.

Down source

func (g *Gamepad) Down(b GamepadButton) bool

Down reports whether the button is held.

JustConnected source

func (g *Gamepad) JustConnected() bool

JustConnected reports whether the controller appeared since the last update, for a "player 2 joined" prompt.

JustDisconnected source

func (g *Gamepad) JustDisconnected() bool

JustDisconnected reports whether the controller went away since the last update, so a game can pause.

Pressed source

func (g *Gamepad) Pressed(b GamepadButton) bool

Pressed reports whether the button went down since the last update.

Released source

func (g *Gamepad) Released(b GamepadButton) bool

Released reports whether the button went up since the last update.

type GamepadAxis source

type GamepadAxis uint8

GamepadAxis indexes the analogue inputs, each in -1..1 (triggers 0..1).

const (
	AxisLeftX GamepadAxis = iota
	AxisLeftY             // +1 is up, as the hardware reports it
	AxisRightX
	AxisRightY
	AxisLeftTrigger
	AxisRightTrigger
	GamepadAxisCount
)

Standard axes: stick X is positive right, stick Y is positive up, and triggers run from released (0) to pressed (1).

type GamepadButton source

type GamepadButton uint8

GamepadButton indexes the buttons of a standard extended gamepad.

const (
	ButtonA GamepadButton = iota // bottom face button (Cross on PlayStation)
	ButtonB                      // right face button (Circle)
	ButtonX                      // left face button (Square)
	ButtonY                      // top face button (Triangle)
	ButtonLeftShoulder
	ButtonRightShoulder
	ButtonLeftStick
	ButtonRightStick
	ButtonMenu    // Start / Options
	ButtonOptions // Back / Share
	ButtonHome
	ButtonDpadUp
	ButtonDpadDown
	ButtonDpadLeft
	ButtonDpadRight
	GamepadButtonCount
)

Standard controller buttons. GamepadButtonCount is the array bound, not a button; face-button names describe positions across brands.

type GamepadInfo source

type GamepadInfo struct {
	Name, Backend       string
	VendorID, ProductID uint16
	Buttons             [GamepadButtonCount]bool
	Axes                [GamepadAxisCount]bool
}

GamepadInfo describes the controls mapped into Gamepad, not every hardware input. Zero numeric IDs and false masks mean unknown or unavailable. Names and backend identifiers are descriptive, not persistent device identity.

HasAxis source

func (i GamepadInfo) HasAxis(axis GamepadAxis) bool

HasAxis reports whether this backend maps the requested axis.

HasButton source

func (i GamepadInfo) HasButton(button GamepadButton) bool

HasButton reports whether this backend maps the requested button.

type Key source

type Key uint8

Key identifies a physical key by position, independent of the active keyboard layout, using the names of the equivalent US layout key.

const (
	KeyUnknown Key = iota
	KeyA
	KeyB
	KeyC
	KeyD
	KeyE
	KeyF
	KeyG
	KeyH
	KeyI
	KeyJ
	KeyK
	KeyL
	KeyM
	KeyN
	KeyO
	KeyP
	KeyQ
	KeyR
	KeyS
	KeyT
	KeyU
	KeyV
	KeyW
	KeyX
	KeyY
	KeyZ
	Key0
	Key1
	Key2
	Key3
	Key4
	Key5
	Key6
	Key7
	Key8
	Key9
	KeyF1
	KeyF2
	KeyF3
	KeyF4
	KeyF5
	KeyF6
	KeyF7
	KeyF8
	KeyF9
	KeyF10
	KeyF11
	KeyF12
	KeyF13
	KeyF14
	KeyF15
	KeyF16
	KeyF17
	KeyF18
	KeyF19
	KeyF20
	KeySpace
	KeyEnter
	KeyEscape
	KeyTab
	KeyBackspace
	KeyDelete
	KeyInsert
	KeyHome
	KeyEnd
	KeyPageUp
	KeyPageDown
	KeyLeft
	KeyRight
	KeyUp
	KeyDown
	KeyMinus
	KeyEqual
	KeyLeftBracket
	KeyRightBracket
	KeyBackslash
	KeySemicolon
	KeyApostrophe
	KeyGrave
	KeyComma
	KeyPeriod
	KeySlash
	KeyCapsLock
	KeyNumLock
	KeyScrollLock
	KeyPrintScreen
	KeyPause
	KeyMenu
	KeyLeftShift
	KeyRightShift
	KeyLeftControl
	KeyRightControl
	KeyLeftAlt
	KeyRightAlt
	KeyLeftSuper
	KeyRightSuper
	KeyFunction
	KeyWorld1 // the key left of Z on ISO layouts
	KeyWorld2
	KeyKeypad0
	KeyKeypad1
	KeyKeypad2
	KeyKeypad3
	KeyKeypad4
	KeyKeypad5
	KeyKeypad6
	KeyKeypad7
	KeyKeypad8
	KeyKeypad9
	KeyKeypadDecimal
	KeyKeypadDivide
	KeyKeypadMultiply
	KeyKeypadSubtract
	KeyKeypadAdd
	KeyKeypadEnter
	KeyKeypadEqual
	KeyCount // number of key codes; not a key
)

Physical key identifiers in US-layout positions. KeyUnknown means no recognized key; KeyCount is an array bound, not a key.

String source

func (k Key) String() string

String returns a stable physical-key name for prompts and bindings, independent of the active keyboard layout. Unknown values use Key(n).

type KeyDescription source

type KeyDescription struct {
	Label  string    // native/layout label, empty if unavailable
	Symbol KeySymbol // unmodified level-zero logical symbol, empty if unknown
}

KeyDescription describes one physical key in a keyboard-layout snapshot.

type KeySymbol source

type KeySymbol string

KeySymbol is a layout-dependent logical key. Namespaces are disjoint: text:<UTF-8> is unmodified printable text, key:<name> is a named nonprinting key, and dead:<native name> is a dead key. Empty means unknown. Dead-key names are backend-specific; these symbols do not describe IME composition. Named keys use physical-key names where equivalents exist (for example key:Enter), or a native name otherwise. Printable names cannot collide.

TextSymbol source

func TextSymbol(text string) KeySymbol

TextSymbol constructs a printable logical key for reverse lookup. Empty text produces the unknown symbol. Text may contain more than one rune.

type KeyboardLayout source

type KeyboardLayout struct {
	Name string // system-provided layout name or identifier, empty if unavailable
	Keys [KeyCount]KeyDescription
}

KeyboardLayout is a snapshot of the active native layout. Refresh it when displaying bindings after a layout change. Queries do not change keyboard modifiers, dead-key state or IME composition. Symbols use the native layout's unmodified level with locks off: on Windows and common XKB layouts keypad digits therefore map to navigation, while macOS keypads remain numeric. Keys are indexed by physical Key. This is for binding UI, not per-frame polling.

KeysFor source

func (l KeyboardLayout) KeysFor(symbol KeySymbol) []Key

KeysFor returns every physical key producing symbol, in Key order. A symbol may have several keys (for example a digit and its keypad equivalent). Unknown symbols return no keys.

Label source

func (l KeyboardLayout) Label(key Key) string

Label returns the layout label, falling back to the stable physical name when no native label was supplied. The fallback is not a logical mapping.

Symbol source

func (l KeyboardLayout) Symbol(key Key) KeySymbol

Symbol returns the logical symbol, or empty for an unmapped key.

type Mods source

type Mods uint8

Mods is a set of modifier keys held during an event.

const (
	ModShift Mods = 1 << iota
	ModControl
	ModAlt   // Option on macOS
	ModSuper // Command on macOS, the Windows key elsewhere
	ModCapsLock
	ModNumLock
)

String source

func (m Mods) String() string

String joins active modifier names with "+", or returns an empty string when no recognized modifiers are set.

type MouseButton source

type MouseButton uint8

MouseButton numbers buttons from the left; Left, Right and Middle are the conventional three and further buttons follow in device order.

const (
	MouseLeft MouseButton = iota
	MouseRight
	MouseMiddle
	MouseButton4
	MouseButton5
	MouseButtonCount
)

type Source source

type Source struct {
	Kind  SourceKind // device input category
	Code  int        // Key, MouseButton, GamepadButton or GamepadAxis
	Scale float32    // signed contribution at full travel; zero means 1
}

Source is one physical input an action can be bound to: a key, a mouse button, a gamepad button, or a gamepad axis in one direction. Scale is what it contributes to the action's value when fully on (1 by default, -1 for the negative side of an axis pair).

KeySource source

func KeySource(k Key) Source

KeySource binds a key.

MouseSource source

func MouseSource(b MouseButton) Source

MouseSource binds a mouse button.

PadAxis source

func PadAxis(a GamepadAxis) Source

PadAxis binds the positive side of a gamepad axis; Neg flips it.

PadButton source

func PadButton(b GamepadButton) Source

PadButton binds a gamepad button.

ParseSource source

func ParseSource(text string) (Source, error)

ParseSource reads a source written by String.

MarshalText source

func (s Source) MarshalText() ([]byte, error)

MarshalText writes the source as String does.

Neg source

func (s Source) Neg() Source

Neg returns the source contributing the opposite sign: the A key of an A and D pair, the left half of a stick's x axis.

String source

func (s Source) String() string

String names the source the way bindings are saved: "key:Space", "mouse:Left", "pad:A", "axis:LeftX", "-axis:LeftX" for the negative side, with a scale other than 1 or -1 appended as "*0.5".

UnmarshalText source

func (s *Source) UnmarshalText(b []byte) error

UnmarshalText reads what MarshalText wrote.

type SourceKind source

type SourceKind uint8

SourceKind is what a Source reads.

const (
	SourceKey       SourceKind = iota // a keyboard key
	SourceMouse                       // a mouse button
	SourcePadButton                   // a gamepad button
	SourcePadAxis                     // a gamepad axis, one direction
)

type State source

type State struct {
	// contains filtered or unexported fields
}

State is the keyboard and mouse as a game sees them each update: what is held, what changed since the last update, where the pointer is and what text was typed. The engine feeds it from platform events. The zero value is idle. State is not safe for concurrent access. Key and mouse accessors require valid enum values below their Count.

Example
package main

import (
	"fmt"

	"github.com/matjam/bunyip/input"
)

func main() {
	// The engine fills the state from platform events and hands it to the
	// game as ctx.Input. Levels say what is held now, edges what changed
	// since the last update, so a press is seen exactly once.
	move := func(in *input.State) (dx float32, jump bool) {
		if in.KeyDown(input.KeyD) {
			dx++
		}
		if in.KeyDown(input.KeyA) {
			dx--
		}
		return dx, in.KeyPressed(input.KeySpace)
	}
	var in input.State // ctx.Input under engine.Run
	fmt.Println(move(&in))
}
Output
0 false

AppendKeysDown source

func (s *State) AppendKeysDown(dst []Key) []Key

AppendKeysDown appends every key currently held to dst, in key order, and returns the extended slice. Pass a slice kept between frames, truncated to zero length, and the scan allocates nothing:

g.held = in.AppendKeysDown(g.held[:0])

Chars source

func (s *State) Chars() []rune

Chars returns committed text typed since the last update, in order (or, during Draw, since the last drawn frame). The returned slice is borrowed from State; copy it to retain text beyond the current call to the game's Update or Draw, and do not modify it.

Composition source

func (s *State) Composition() string

Composition returns the input method's uncommitted text, such as the syllables of a Japanese word still being converted. Text fields show it after the committed text; it is empty when nothing is being composed.

Gamepad source

func (s *State) Gamepad(i int) *Gamepad

Gamepad returns a snapshot of controller i (0..MaxGamepads-1); a disconnected one reads as idle. Out-of-range indices return an idle snapshot. Changing the snapshot does not modify State. During Draw button edges cover the whole frame, as for keys and mouse buttons; connection flags and axis transitions still cover the update only.

KeyDown source

func (s *State) KeyDown(k Key) bool

KeyDown reports whether the key is held.

KeyHeld source

func (s *State) KeyHeld(k Key) float32

KeyHeld reports how long the key has been held, in seconds of updates, and zero when it is up: charge-up attacks, hold-to-confirm, cheat codes that want a long press.

KeyPressed source

func (s *State) KeyPressed(k Key) bool

KeyPressed reports whether the key went down since the last update (or, during Draw, since the last frame). Key repeats count as presses so held keys scroll and step.

KeyReleased source

func (s *State) KeyReleased(k Key) bool

KeyReleased reports whether the key went up since the last update (or, during Draw, since the last drawn frame).

KeyRepeated source

func (s *State) KeyRepeated(k Key) bool

KeyRepeated reports whether the key produced an operating-system repeat since the last update (or, during Draw, since the last frame). KeyPressed already counts repeats. KeyPressed(k) && !KeyRepeated(k) filters repeats, but also filters an initial press if both kinds of event arrived in the same update or frame.

KeysDown source

func (s *State) KeysDown() []Key

KeysDown returns every key currently held, in key order: for rebinding screens and combos.

It allocates the slice it returns, so it is for a settings screen or a combo check that runs on demand, not for every frame of play. To ask every frame, keep a slice and use AppendKeysDown, or ask about the keys the game cares about with KeyDown.

Mods source

func (s *State) Mods() Mods

Mods returns the active modifiers, including Caps Lock and Num Lock. Modifier changes are reported even when no key is pressed or released.

Mouse source

func (s *State) Mouse() (x, y float32)

Mouse returns the pointer position in view units.

MouseDelta source

func (s *State) MouseDelta() (dx, dy float32)

MouseDelta returns pointer movement since the last update, in view units; it keeps reporting while the cursor is captured.

MouseDoubleClicked source

func (s *State) MouseDoubleClicked(b MouseButton) bool

MouseDoubleClicked reports whether the button was pressed twice within DoubleClickTime and DoubleClickDistance, on the second press.

MouseDown source

func (s *State) MouseDown(b MouseButton) bool

MouseDown reports whether the button is held.

MousePos source

func (s *State) MousePos() lin.Vec2

MousePos returns the pointer position as a vector.

MousePressed source

func (s *State) MousePressed(b MouseButton) bool

MousePressed reports whether the button went down since the last update (or, during Draw, since the last frame).

MouseReleased source

func (s *State) MouseReleased(b MouseButton) bool

MouseReleased reports whether the button went up since the last update (or, during Draw, since the last drawn frame).

Scroll source

func (s *State) Scroll() (dx, dy float32)

Scroll returns wheel movement since the last update, in lines. A trackpad's smooth scrolling is scaled to lines by the engine. During Draw it covers movement since the last drawn frame.

SetStep source

func (s *State) SetStep(seconds float32)

SetStep tells the state how many seconds each update covers, for held times and double-click timing. The engine sets it from the fixed step; zero means a sixtieth of a second.

Source files

actions.go actions_test.go bench_test.go edges_test.go example_test.go gamepad.go gamepad_frame_test.go gamepad_info.go hook.go keys.go keys_string.go layout.go layout_test.go state.go state_test.go