Class: RGame::Engine::Node2D
- Inherits:
-
Object
- Object
- RGame::Engine::Node2D
- Extended by:
- Signal::DSL
- Defined in:
- lib/rgame/engine/node2d.rb
Overview
A node in a scene graph. We currently have only 2D nodes, but the name reflects this should there ever be a 3D space. Nodes are containers for both containers and nodes. They extend the signal DSL to allow for easy signal usage.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#abs_angle ⇒ Object
readonly
Returns the value of attribute abs_angle.
-
#abs_x ⇒ Object
readonly
Returns the value of attribute abs_x.
-
#abs_y ⇒ Object
readonly
Returns the value of attribute abs_y.
-
#abs_z ⇒ Object
readonly
Returns the value of attribute abs_z.
-
#angle ⇒ Object
Returns the value of attribute angle.
-
#children ⇒ Object
readonly
Returns the value of attribute children.
-
#components ⇒ Object
readonly
Returns the value of attribute components.
- #context ⇒ Object
-
#height ⇒ Object
Returns the value of attribute height.
-
#parent ⇒ Object
Returns the value of attribute parent.
-
#scene ⇒ Object
The nearest enclosing scene node, marked as a boundary by SceneStack (#scene= self).
-
#width ⇒ Object
Returns the value of attribute width.
-
#x ⇒ Object
Returns the value of attribute x.
-
#y ⇒ Object
Returns the value of attribute y.
-
#z ⇒ Object
Returns the value of attribute z.
Instance Method Summary collapse
-
#add_component(component, as: nil) ⇒ Object
Attach a component in a named slot.
- #add_node(node) ⇒ Object
-
#control(actions) ⇒ Object
updates input, both from player (readings actions) as well as AI-driven node control.
-
#draw(renderer) ⇒ Object
update visual game state, drawing the node.
-
#enter_tree ⇒ Object
Entered-tree cascade: anchors (root/scene) and sibling systems are now reachable, so components attach (register with systems) before this node's own on_add, and the whole subtree enters depth-first.
-
#exit_tree ⇒ Object
Leaving-tree cascade: mirror of #enter_tree (children first, then this node's on_remove, then component on_detach to release registrations).
- #freed? ⇒ Boolean
-
#get_component(key) ⇒ Object
Look a component up by its slot.
- #in_tree? ⇒ Boolean
-
#initialize(x: 0, y: 0, z: 0, angle: 0, width: 0, height: 0) ⇒ Node2D
constructor
A new instance of Node2D.
- #on_add ⇒ Object
-
#on_control(actions) ⇒ Object
Lifecycle hooks: Subclasses should implement these instead of overwriting the public interface draw/update/add etc.
- #on_draw(renderer) ⇒ Object
- #on_remove ⇒ Object
- #on_update(dt) ⇒ Object
-
#queue_free ⇒ Object
Deferred removal (à la Godot's queue_free): mark this node for removal instead of detaching it now.
- #remove_component(key) ⇒ Object
- #remove_node(node) ⇒ Object
-
#root ⇒ Object
The top-most node — a node with no parent is its own root.
-
#sweep_freed ⇒ Object
Detach every node marked by #queue_free, depth-first, from a point outside the update traversal.
-
#system(klass) ⇒ Object
Nearest system of a class: scene scope first, then the global root.
-
#update(dt) ⇒ Object
update game logic and physics (might become two calls with time, but for now works in one step).
Methods included from Signal::DSL
Constructor Details
#initialize(x: 0, y: 0, z: 0, angle: 0, width: 0, height: 0) ⇒ Node2D
Returns a new instance of Node2D.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/rgame/engine/node2d.rb', line 16 def initialize(x: 0, y: 0, z: 0, angle: 0, width: 0, height: 0) @x = x @y = y @z = z @angle = angle @width = width @height = height @children = [] @components = [] @component_slots = {} # slot (Class by default, or a Symbol name) => component @parent = nil @scene = nil @in_tree = false @freed = false end |
Instance Attribute Details
#abs_angle ⇒ Object (readonly)
Returns the value of attribute abs_angle.
14 15 16 |
# File 'lib/rgame/engine/node2d.rb', line 14 def abs_angle @abs_angle end |
#abs_x ⇒ Object (readonly)
Returns the value of attribute abs_x.
14 15 16 |
# File 'lib/rgame/engine/node2d.rb', line 14 def abs_x @abs_x end |
#abs_y ⇒ Object (readonly)
Returns the value of attribute abs_y.
14 15 16 |
# File 'lib/rgame/engine/node2d.rb', line 14 def abs_y @abs_y end |
#abs_z ⇒ Object (readonly)
Returns the value of attribute abs_z.
14 15 16 |
# File 'lib/rgame/engine/node2d.rb', line 14 def abs_z @abs_z end |
#angle ⇒ Object
Returns the value of attribute angle.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def angle @angle end |
#children ⇒ Object (readonly)
Returns the value of attribute children.
14 15 16 |
# File 'lib/rgame/engine/node2d.rb', line 14 def children @children end |
#components ⇒ Object (readonly)
Returns the value of attribute components.
14 15 16 |
# File 'lib/rgame/engine/node2d.rb', line 14 def components @components end |
#context ⇒ Object
110 111 112 |
# File 'lib/rgame/engine/node2d.rb', line 110 def context @context ||= root.context end |
#height ⇒ Object
Returns the value of attribute height.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def height @height end |
#parent ⇒ Object
Returns the value of attribute parent.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def parent @parent end |
#scene ⇒ Object
The nearest enclosing scene node, marked as a boundary by SceneStack (#scene= self). Scene-lifetime systems live on it as components.
106 107 108 |
# File 'lib/rgame/engine/node2d.rb', line 106 def scene @scene || @parent&.scene end |
#width ⇒ Object
Returns the value of attribute width.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def width @width end |
#x ⇒ Object
Returns the value of attribute x.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def x @x end |
#y ⇒ Object
Returns the value of attribute y.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def y @y end |
#z ⇒ Object
Returns the value of attribute z.
12 13 14 |
# File 'lib/rgame/engine/node2d.rb', line 12 def z @z end |
Instance Method Details
#add_component(component, as: nil) ⇒ Object
Attach a component in a named slot. The slot defaults to the component's class, so a
node still holds at most one component per class — until you give them distinct
names: add_component(Timer.new, as: :spawn) / add_component(Timer.new, as: :wave).
A taken slot raises, so an accidental duplicate is still caught.
71 72 73 74 75 76 77 78 79 80 |
# File 'lib/rgame/engine/node2d.rb', line 71 def add_component(component, as: nil) slot = as || component.class raise ArgumentError, "Node already has a component in slot #{slot.inspect}" if @component_slots.key?(slot) @components << component @component_slots[slot] = component component.node = self component.on_attach if @in_tree component end |
#add_node(node) ⇒ Object
32 33 34 35 36 37 38 39 40 |
# File 'lib/rgame/engine/node2d.rb', line 32 def add_node(node) @children << node node.parent = self # Defer the entered-tree cascade until this node is itself live; otherwise it # fires when an ancestor enters (see #enter_tree). This is the construct-vs-enter # split — a node built inside another node's initialize is not yet in the tree. node.enter_tree if @in_tree node end |
#control(actions) ⇒ Object
updates input, both from player (readings actions) as well as AI-driven node control. This run first in a game tick Each phase settles this node first (components, then the node's own hook), then descends into children. Self-before-subtree keeps the transform flowing downward: a component or hook that moves this node does so before children resolve their origin from it.
126 127 128 129 130 131 |
# File 'lib/rgame/engine/node2d.rb', line 126 def control(actions) resolve_origin @components.each { it.control(actions) } on_control(actions) @children.each { it.control(actions) } end |
#draw(renderer) ⇒ Object
update visual game state, drawing the node. This runs last in a game tick
145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/rgame/engine/node2d.rb', line 145 def draw(renderer) resolve_origin # Draw this node's own visuals oriented by its absolute angle, then descend. # Children resolve their own world transform (resolve_origin already baked this # node's rotation into their abs_x/abs_y), so they draw in flat world space and # must NOT be nested inside this node's rotation — nesting would apply that # rotation to them a second time. Unrotated nodes skip the wrapper entirely. if abs_angle.zero? draw_content(renderer) else renderer.rotated(abs_angle * 180.0 / Math::PI, abs_x, abs_y) { draw_content(renderer) } end draw_children(renderer) end |
#enter_tree ⇒ Object
Entered-tree cascade: anchors (root/scene) and sibling systems are now reachable, so components attach (register with systems) before this node's own on_add, and the whole subtree enters depth-first. The engine fires this — the user never calls it — so registration can't be forgotten. Idempotent.
191 192 193 194 195 196 197 198 199 |
# File 'lib/rgame/engine/node2d.rb', line 191 def enter_tree return if @in_tree @in_tree = true @freed = false # revive: a pooled node reacquired after death re-enters here @components.each(&:on_attach) on_add @children.each(&:enter_tree) end |
#exit_tree ⇒ Object
Leaving-tree cascade: mirror of #enter_tree (children first, then this node's on_remove, then component on_detach to release registrations).
203 204 205 206 207 208 209 210 |
# File 'lib/rgame/engine/node2d.rb', line 203 def exit_tree return unless @in_tree @children.each(&:exit_tree) on_remove @components.each(&:on_detach) @in_tree = false end |
#freed? ⇒ Boolean
168 |
# File 'lib/rgame/engine/node2d.rb', line 168 def freed? = @freed |
#get_component(key) ⇒ Object
Look a component up by its slot. A Class/Module is matched by ancestry across every
component (so a base class finds a subclass instance); a Symbol names a specific slot
(see #add_component's as:). A class lookup raises when it's ambiguous — two
components share that type — so the caller reaches for the name instead. The scan is
allocation-free, so it's safe to call on the per-frame path.
54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/rgame/engine/node2d.rb', line 54 def get_component(key) return @component_slots[key] unless key.is_a?(Module) found = nil @components.each do |component| next unless component.is_a?(key) raise ArgumentError, "Multiple components match #{key}; look one up by name" if found found = component end found end |
#in_tree? ⇒ Boolean
160 |
# File 'lib/rgame/engine/node2d.rb', line 160 def in_tree? = @in_tree |
#on_add ⇒ Object
220 |
# File 'lib/rgame/engine/node2d.rb', line 220 def on_add; end |
#on_control(actions) ⇒ Object
Lifecycle hooks: Subclasses should implement these instead of overwriting the public interface draw/update/add etc. on_add/on_remove fire when the node enters/leaves the live tree (see #enter_tree), not at construction — so anchors and systems are available inside them.
217 |
# File 'lib/rgame/engine/node2d.rb', line 217 def on_control(actions); end |
#on_draw(renderer) ⇒ Object
219 |
# File 'lib/rgame/engine/node2d.rb', line 219 def on_draw(renderer); end |
#on_remove ⇒ Object
221 |
# File 'lib/rgame/engine/node2d.rb', line 221 def on_remove; end |
#on_update(dt) ⇒ Object
218 |
# File 'lib/rgame/engine/node2d.rb', line 218 def on_update(dt); end |
#queue_free ⇒ Object
Deferred removal (à la Godot's queue_free): mark this node for removal instead of detaching it now. A node that removes itself or a sibling mid-traversal would mutate the parent's @children while it's being iterated; marking instead and sweeping once after the tick (see #sweep_freed, flushed by the platform loop) keeps removal safe and allocation-free.
167 |
# File 'lib/rgame/engine/node2d.rb', line 167 def queue_free = @freed = true |
#remove_component(key) ⇒ Object
82 83 84 85 86 87 88 89 90 91 |
# File 'lib/rgame/engine/node2d.rb', line 82 def remove_component(key) component = get_component(key) return nil unless component component.on_detach if @in_tree @components.delete(component) @component_slots.delete(@component_slots.key(component)) component.node = nil component end |
#remove_node(node) ⇒ Object
42 43 44 45 46 47 |
# File 'lib/rgame/engine/node2d.rb', line 42 def remove_node(node) node.exit_tree if @in_tree @children.delete(node) node.parent = nil node end |
#root ⇒ Object
The top-most node — a node with no parent is its own root. Global, program-lifetime systems live here as components.
100 101 102 |
# File 'lib/rgame/engine/node2d.rb', line 100 def root @parent ? @parent.root : self end |
#sweep_freed ⇒ Object
Detach every node marked by #queue_free, depth-first, from a point outside the update traversal. Components get a hook too, so a container-style component (e.g. SceneStack) can flush the subtree it owns off the normal child list.
173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/rgame/engine/node2d.rb', line 173 def sweep_freed @components.each(&:sweep_freed) i = 0 while i < @children.size child = @children[i] if child.freed? remove_node(child) # detaches + exit_tree; @children shrinks, so don't advance i else child.sweep_freed i += 1 end end end |
#system(klass) ⇒ Object
Nearest system of a class: scene scope first, then the global root.
115 116 117 |
# File 'lib/rgame/engine/node2d.rb', line 115 def system(klass) scene&.get_component(klass) || root.get_component(klass) end |
#update(dt) ⇒ Object
update game logic and physics (might become two calls with time, but for now works in one step). This runs second in a game tick
136 137 138 139 140 141 |
# File 'lib/rgame/engine/node2d.rb', line 136 def update(dt) resolve_origin @components.each { it.update(dt) } on_update(dt) @children.each { it.update(dt) } end |