Collision Shapes

A circle and a box body falling onto a static floor.

▶ Run in browser

Tags: 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 uses shape= when it is set, otherwise its first CollisionShape2D child.

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())