Area2D¶
a trigger zone that reports bodies entering and leaving.
▶ Run in browserTags: 2d physics area trigger signals
An Area2D is a sensor: it has a shape but no mass, pushes nothing, and tells you when a physics
body starts or stops overlapping it. Give it geometry with the shape= keyword (here a
RectangleShape2D; a CollisionShape2D child works too) and put it under a PhysicsRoot2D
beside the bodies it should see.
Connect to its two signals, which pass the body as their argument:
body_entered(body)fires once when a body begins to overlap the area.body_exited(body)fires once when it stops overlapping, including when it is destroyed.
get_overlapping_bodies() answers the same question on demand, when a snapshot suits better than
events.
Here a PhysicsBody2D in DYNAMIC mode walks left and right at a constant velocity in a world with
no gravity, turning back at the edges of the window, and so keeps crossing the zone. The handlers
count the entries and track whether the walker is inside: the zone turns green while it is
occupied and blue when it is empty, and the label keeps the count. A Camera2D keeps the zone at
the centre of the window at any size.
Source¶
1# /// simvx
2# tags = ["2d", "physics", "area", "trigger", "signals"]
3# web = { width = 800, height = 600, responsive = true }
4# ///
5"""Area2D: a trigger zone that reports bodies entering and leaving.
6
7An `Area2D` is a sensor: it has a shape but no mass, pushes nothing, and tells you when a physics
8body starts or stops overlapping it. Give it geometry with the `shape=` keyword (here a
9`RectangleShape2D`; a `CollisionShape2D` child works too) and put it under a `PhysicsRoot2D`
10beside the bodies it should see.
11
12Connect to its two signals, which pass the body as their argument:
13
14- `body_entered(body)` fires once when a body begins to overlap the area.
15- `body_exited(body)` fires once when it stops overlapping, including when it is destroyed.
16
17`get_overlapping_bodies()` answers the same question on demand, when a snapshot suits better than
18events.
19
20Here a `PhysicsBody2D` in `DYNAMIC` mode walks left and right at a constant velocity in a world with
21no gravity, turning back at the edges of the window, and so keeps crossing the zone. The handlers
22count the entries and track whether the walker is inside: the zone turns green while it is
23occupied and blue when it is empty, and the label keeps the count. A `Camera2D` keeps the zone at
24the centre of the window at any size.
25"""
26
27from simvx.core import (
28 Anchor,
29 Area2D,
30 BodyMode,
31 Camera2D,
32 CanvasLayer,
33 CircleShape2D,
34 Label,
35 Node2D,
36 PhysicsBody2D,
37 PhysicsRoot2D,
38 RectangleShape2D,
39 Vec2,
40)
41from simvx.graphics import App
42
43ZONE_HALF = Vec2(90, 130) # half-extents of the rectangular sensor
44WALKER_RADIUS = 22.0
45SPEED = 260.0
46
47
48class TriggerZoneDemo(Node2D):
49 dynamic = True # the walker moves every frame
50
51 def on_ready(self):
52 self.entries = 0
53 self.inside = False
54 self.add_child(Camera2D()) # world origin at the centre of the window
55
56 world = self.add_child(PhysicsRoot2D(name="World", gravity=Vec2(0, 0)))
57 self.zone = world.add_child(Area2D(name="Zone", shape=RectangleShape2D(half_extents=ZONE_HALF)))
58 self.zone.body_entered.connect(self._on_body_entered)
59 self.zone.body_exited.connect(self._on_body_exited)
60 self.walker = world.add_child(
61 PhysicsBody2D(name="Walker", mode=BodyMode.DYNAMIC, position=(-250, 0), shape=CircleShape2D(WALKER_RADIUS))
62 )
63 self.walker.velocity = (SPEED, 0)
64
65 self.status = self.add_child(CanvasLayer()).add_child(Label(anchor=Anchor.TOP_LEFT, inset=16))
66 self._show_status()
67
68 def _on_body_entered(self, body):
69 self.entries += 1
70 self.inside = True
71 self._show_status()
72
73 def _on_body_exited(self, body):
74 self.inside = False
75 self._show_status()
76
77 def _show_status(self):
78 state = "inside" if self.inside else "outside"
79 self.status.text = f"Walker: {state} Entries: {self.entries}"
80
81 def on_fixed_update(self, dt):
82 # Turn back at the window's edges so the walker keeps crossing the zone.
83 edge = self.viewport_size.x / 2 - WALKER_RADIUS
84 x, vx = float(self.walker.position.x), float(self.walker.velocity.x)
85 if abs(x) > edge and x * vx > 0:
86 self.walker.velocity = (-vx, 0)
87
88 def on_draw(self, renderer):
89 fill = (0.2, 0.7, 0.3, 0.45) if self.inside else (0.2, 0.45, 0.9, 0.35)
90 renderer.rect(-ZONE_HALF, ZONE_HALF * 2, fill=fill, stroke=(0.85, 0.9, 1.0, 1.0))
91 renderer.circle(self.walker.position, WALKER_RADIUS, fill=(1.0, 0.75, 0.2, 1.0))
92
93
94if __name__ == "__main__":
95 App(title="Area2D Trigger Zone", width=800, height=600).run(TriggerZoneDemo())