Class: RGame::Engine::Components::CollisionWorld
- Inherits:
-
RGame::Engine::Component
- Object
- RGame::Engine::Component
- RGame::Engine::Components::CollisionWorld
- Defined in:
- lib/rgame/engine/components/collision_world.rb
Overview
Scene-scoped broadphase collision system: a Component that lives on the scene node (so it is born and torn down with the scene, and rides the normal update traversal). CircleColliders register/unregister with it via their tree lifecycle; each update it buckets them in a SpatialHash and fires on_hit on every overlapping pair. It is layer-agnostic — it reports contacts and lets the colliders' owners decide meaning. See docs/api/systems.md.
Instance Attribute Summary
Attributes inherited from RGame::Engine::Component
Instance Method Summary collapse
-
#initialize(cell_size:) ⇒ CollisionWorld
constructor
A new instance of CollisionWorld.
-
#nearest(x, y, r, layer: nil) ⇒ Object
The registered collider nearest to (x, y) within range
r, or nil when none qualifies. -
#query_circle(x, y, r) ⇒ Object
Yield every registered collider whose centre lies within
rof (x, y), using the spatial index built by the most recent #update. - #register(collider) ⇒ Object
- #unregister(collider) ⇒ Object
- #update(_dt) ⇒ Object
Methods inherited from RGame::Engine::Component
#context, #control, #draw, #on_attach, #on_detach, #sweep_freed
Methods included from Signal::DSL
Constructor Details
#initialize(cell_size:) ⇒ CollisionWorld
Returns a new instance of CollisionWorld.
13 14 15 16 17 |
# File 'lib/rgame/engine/components/collision_world.rb', line 13 def initialize(cell_size:) super() @hash = Engine::SpatialHash.new(cell_size: cell_size) @colliders = [] end |
Instance Method Details
#nearest(x, y, r, layer: nil) ⇒ Object
The registered collider nearest to (x, y) within range r, or nil when none
qualifies. Restrict to a single layer: (the common case: a tower targeting only
:enemy). Dup-safe — it keeps the running minimum, so #query_circle's possible
multi-cell repeats don't matter. Allocation-free.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/rgame/engine/components/collision_world.rb', line 45 def nearest(x, y, r, layer: nil) best = nil best_d2 = nil query_circle(x, y, r) do |collider| next if layer && collider.layer != layer dx = collider.cx - x dy = collider.cy - y d2 = (dx * dx) + (dy * dy) next unless best_d2.nil? || d2 < best_d2 best = collider best_d2 = d2 end best end |
#query_circle(x, y, r) ⇒ Object
Yield every registered collider whose centre lies within r of (x, y), using
the spatial index built by the most recent #update. The narrowphase is a
centre-distance test — the query is a point + range (a tower's range ring), so
the collider's own radius isn't added in. Colliders whose node is queued for
removal are skipped. As with SpatialHash#query a collider spanning several cells
may be yielded more than once, so callers that select (e.g. #nearest) are
written dup-insensitively. Layer-agnostic — filter by collider.layer in the
block. Allocation-free.
30 31 32 33 34 35 36 37 38 39 |
# File 'lib/rgame/engine/components/collision_world.rb', line 30 def query_circle(x, y, r) r2 = r * r @hash.query_circle(x, y, r) do |collider| next if collider.node.freed? dx = collider.cx - x dy = collider.cy - y yield collider if (dx * dx) + (dy * dy) <= r2 end end |
#register(collider) ⇒ Object
19 |
# File 'lib/rgame/engine/components/collision_world.rb', line 19 def register(collider) = @colliders << collider |
#unregister(collider) ⇒ Object
20 |
# File 'lib/rgame/engine/components/collision_world.rb', line 20 def unregister(collider) = @colliders.delete(collider) |
#update(_dt) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/rgame/engine/components/collision_world.rb', line 62 def update(_dt) @hash.clear @colliders.each { |c| insert(c) } # Index-bounded over the count at frame start: an on_hit handler may spawn # entities (a rock splitting), which `register`s new colliders mid-loop; those # appended ones are skipped this frame (processed next) rather than mutating # the array being iterated. The hash was built before the loop, so they're # absent from queries too — consistent. count = @colliders.size i = 0 while i < count a = @colliders[i] i += 1 next if a.node.freed? r = a.radius d = r * 2 @hash.query(a.cx - r, a.cy - r, d, d) do |b| # object_id ordering visits each unordered pair once (and skips self); # the freed? guards skip nodes already queued for removal, so a dead # entity stops colliding and duplicate (multi-cell) yields are ignored. next if a.node.freed? || b.node.freed? || a.object_id >= b.object_id next unless a.overlap?(b) a.emit_hit(b) b.emit_hit(a) end end end |