Collision Shapes¶
A circle and a box body falling onto a static floor.
▶ Run in browserTags: 2d physics collision rigidbody
A PhysicsBody2D gets its collision geometry from a Shape2D resource, such as
CircleShape2D(radius) or RectangleShape2D(half_extents=...). Inside a
PhysicsRoot2D(gravity=...) the world moves every BodyMode.DYNAMIC body under
gravity and resolves its contacts, while a BodyMode.STATIC body stays where it
is put. Here a ball and a box drop onto a static floor and each comes to rest
exactly on its top face, the ball one radius above it and the box one half-extent.
A body takes its collider in one of two equivalent ways:
the
shape=Property, the short form for a body with one collider. Assigning a new shape to it swaps the live body’s geometry in place;a
CollisionShape2D(shape=...)child node, which is what a scene built in the editor looks like. A body usesshape=when it is set, otherwise its firstCollisionShape2Dchild.
The ball uses the first and the box the second. Screen 2D is Y-down, so gravity
is +Y. continuous=True turns on continuous collision detection, so a fast
body cannot tunnel through a thin floor between two steps. PhysicsMaterial
sets each surface’s friction.
A physics body draws nothing by itself, so Solid draws its own collider in its
own space: its position and rotation apply to what it draws, and the box turns
on screen whenever it turns in the world.
Press R, or click or tap anywhere, to drop both bodies again.
Source¶
1# /// simvx
2# tags = ["2d", "physics", "collision", "rigidbody"]
3# web = { width = 800, height = 600, responsive = true }
4# ///
5"""Collision Shapes: A circle and a box body falling onto a static floor.
6
7A `PhysicsBody2D` gets its collision geometry from a `Shape2D` resource, such as
8`CircleShape2D(radius)` or `RectangleShape2D(half_extents=...)`. Inside a
9`PhysicsRoot2D(gravity=...)` the world moves every `BodyMode.DYNAMIC` body under
10gravity and resolves its contacts, while a `BodyMode.STATIC` body stays where it
11is put. Here a ball and a box drop onto a static floor and each comes to rest
12exactly on its top face, the ball one radius above it and the box one half-extent.
13
14A body takes its collider in one of two equivalent ways:
15
16- the `shape=` Property, the short form for a body with one collider. Assigning
17 a new shape to it swaps the live body's geometry in place;
18- a `CollisionShape2D(shape=...)` child node, which is what a scene built in the
19 editor looks like. A body uses `shape=` when it is set, otherwise its first
20 `CollisionShape2D` child.
21
22The ball uses the first and the box the second. Screen 2D is Y-down, so gravity
23is `+Y`. `continuous=True` turns on continuous collision detection, so a fast
24body cannot tunnel through a thin floor between two steps. `PhysicsMaterial`
25sets each surface's friction.
26
27A physics body draws nothing by itself, so `Solid` draws its own collider in its
28own space: its position and rotation apply to what it draws, and the box turns
29on screen whenever it turns in the world.
30
31Press R, or click or tap anywhere, to drop both bodies again.
32"""
33
34from simvx.core import (
35 Anchor,
36 BodyMode,
37 CanvasLayer,
38 CircleShape2D,
39 CollisionShape2D,
40 Key,
41 Label,
42 Node2D,
43 PhysicsBody2D,
44 PhysicsMaterial,
45 PhysicsRoot2D,
46 Pointer,
47 Property,
48 RectangleShape2D,
49 Vec2,
50 on_input,
51)
52from simvx.graphics import App
53
54
55class Solid(PhysicsBody2D):
56 """A physics body that draws its circle or rectangle collider in its own colour."""
57
58 colour = Property((1.0, 1.0, 1.0, 1.0))
59
60 def on_draw(self, renderer):
61 shape = self.shape or self.find(CollisionShape2D).shape
62 if isinstance(shape, CircleShape2D):
63 renderer.circle((0, 0), shape.radius, fill=self.colour)
64 else:
65 w, h = shape.half_extents
66 renderer.rect((-w, -h), (2 * w, 2 * h), fill=self.colour)
67
68
69class CollisionShapesDemo(Node2D):
70 input_context = {"reset": [Key.R, Pointer.PRIMARY]}
71
72 def on_ready(self):
73 width, height = self.viewport_size
74 world = self.add_child(PhysicsRoot2D(name="World", gravity=(0.0, 1600.0)))
75
76 world.add_child(
77 Solid(
78 name="Floor",
79 mode=BodyMode.STATIC,
80 position=(width / 2, height - 100),
81 shape=RectangleShape2D(half_extents=(width * 0.4, 20.0)),
82 material=PhysicsMaterial(friction=0.7),
83 colour=(0.4, 0.4, 0.45, 1.0),
84 )
85 )
86
87 self.ball_drop = Vec2(width / 2 - 110, 100)
88 self.ball = world.add_child(
89 Solid(
90 name="Ball",
91 mode=BodyMode.DYNAMIC,
92 continuous=True,
93 position=self.ball_drop,
94 shape=CircleShape2D(30.0),
95 material=PhysicsMaterial(friction=0.5),
96 colour=(0.95, 0.45, 0.3, 1.0),
97 )
98 )
99
100 self.box_drop = Vec2(width / 2 + 110, 100)
101 self.box = Solid(
102 name="Box",
103 mode=BodyMode.DYNAMIC,
104 continuous=True,
105 position=self.box_drop,
106 material=PhysicsMaterial(friction=0.5),
107 colour=(0.4, 0.7, 0.95, 1.0),
108 )
109 # The collider goes on before the body enters the world, which builds it from its shape.
110 self.box.add_child(CollisionShape2D(shape=RectangleShape2D(half_extents=(35.0, 35.0))))
111 world.add_child(self.box)
112
113 # Example help overlay: not part of the technique shown.
114 hud = self.add_child(CanvasLayer())
115 hud.add_child(Label("R / click: drop again", anchor=Anchor.TOP_LEFT, inset=10))
116
117 @on_input("reset")
118 def reset(self, _event):
119 for body, drop in ((self.ball, self.ball_drop), (self.box, self.box_drop)):
120 body.position = drop
121 body.rotation = 0.0
122 body.velocity = Vec2()
123 body.spin = 0.0
124
125
126if __name__ == "__main__":
127 App(title="2D Collision Shapes", width=800, height=600).run(CollisionShapesDemo())