Class: RGame::Engine::Actions

Inherits:
Object
  • Object
show all
Defined in:
lib/rgame/engine/input/actions.rb

Overview

An immutable per-frame snapshot of abstract action state. Game logic reads this, never physical keys. Built by ActionMapper (or constructed directly in a spec).

Edge queries (pressed?/released?) compare against the previous frame's held state, so a one-shot action (menu confirm, jump) fires exactly once per press rather than every frame it is held.

Reading an action nobody declared raises

A game declares its actions once, in an InputMap. Asking for one that is not in it is a typo, not a question with an answer, so it raises KeyError rather than reading false forever:

actions.pressed?(:fyre)   # KeyError: no such action :fyre

This matters because the failure it replaces is silent and remote. A misspelled action reads as "never pressed", and what the player sees is a button that does nothing — a bug that looks like it lives in the code that would have run. RGame::Core::Input used to raise KeyError for an unbound action and no longer can: it takes physical ids now, and binding moved up to InputMap. This is where that guarantee went.

The hashes are the declaration. ActionMapper seeds all three from its map at construction, so every action the map knows answers and nothing else does. A spec constructing one directly declares whatever it passes:

Actions.new(axes: { move_x: 1.0, move_y: 0.0 })   # both answer
Actions.new(axes: { move_x: 1.0 }).axis(:move_y)  # KeyError

That is stricter than it needs to be for a spec, and deliberately: a spec that has not said what the action set is cannot claim a component reads the right part of it.

Instance Method Summary collapse

Constructor Details

#initialize(held: {}, axes: {}, prev_held: {}) ⇒ Actions

held, axes and prev_held are mutable hashes the mapper updates in place each poll, so the snapshot stays a single reused, allocation-free object.



42
43
44
45
46
# File 'lib/rgame/engine/input/actions.rb', line 42

def initialize(held: {}, axes: {}, prev_held: {})
  @held = held
  @axes = axes
  @prev_held = prev_held
end

Instance Method Details

#actions_for(_player) ⇒ Object

A snapshot is also a degenerate input source: it answers for whichever player is asking, because there is only one answer. That is what lets node.control(actions) keep working unchanged — a tree with nobody claiming ownership, or a spec that has only one player in mind, passes the snapshot itself where a Players registry would otherwise go. hot-path



57
# File 'lib/rgame/engine/input/actions.rb', line 57

def actions_for(_player) = self

#axis(name) ⇒ Object

Analog value in [-1.0, 1.0]; 0.0 for a declared action at rest. hot-path



78
79
80
# File 'lib/rgame/engine/input/actions.rb', line 78

def axis(name)
  @axes.fetch(name) { undeclared(name) }
end

#declaredObject

Every action this snapshot can answer for.



49
# File 'lib/rgame/engine/input/actions.rb', line 49

def declared = @held.keys | @axes.keys

#held?(name) ⇒ Boolean

hot-path

Returns:

  • (Boolean)


60
61
62
# File 'lib/rgame/engine/input/actions.rb', line 60

def held?(name)
  @held.fetch(name) { undeclared(name) }
end

#pressed?(name) ⇒ Boolean

True only on the frame the action transitions up→down. hot-path

Returns:

  • (Boolean)


66
67
68
# File 'lib/rgame/engine/input/actions.rb', line 66

def pressed?(name)
  @held.fetch(name) { undeclared(name) } && !@prev_held.fetch(name, false)
end

#released?(name) ⇒ Boolean

True only on the frame the action transitions down→up. hot-path

Returns:

  • (Boolean)


72
73
74
# File 'lib/rgame/engine/input/actions.rb', line 72

def released?(name)
  !@held.fetch(name) { undeclared(name) } && @prev_held.fetch(name, false)
end