Bouncing Balls

Play Demo

Spawn colourful balls that bounce off screen edges. Demonstrates the Property descriptor for editor-visible values and basic frame-by-frame movement with process().

What You Will Learn

  • Property – Declare editor-visible properties with validation ranges

  • process(dt) – Per-frame update callback with delta time

  • position – Move nodes by updating self.position each frame

  • add_child() – Build a scene tree dynamically in ready()

  • draw_circle() – Render filled circles

How It Works

Ball declares radius and speed as Property descriptors with value ranges. In __init__, a random velocity vector is created. Each frame, process(dt) advances the ball’s position and reflects velocity when the ball hits a screen edge.

BouncingBalls is the root node that spawns 8 Ball children in ready(), placing them at random positions. The parent also draws a title and ball count via draw_text().

Source Code

 1#!/usr/bin/env python3
 2"""Bouncing Balls -- Properties, Velocity, and Screen-Edge Collision
 3
 4[Play Demo](/demos/start_bouncing_ball.html)
 5
 6Spawn colourful balls that bounce off screen edges. Demonstrates the
 7`Property` descriptor for editor-visible values and basic frame-by-frame
 8movement with `process()`.
 9
10## What You Will Learn
11
12- **Property** -- Declare editor-visible properties with validation ranges
13- **process(dt)** -- Per-frame update callback with delta time
14- **position** -- Move nodes by updating `self.position` each frame
15- **add_child()** -- Build a scene tree dynamically in `ready()`
16- **draw_circle()** -- Render filled circles
17
18## How It Works
19
20`Ball` declares `radius` and `speed` as `Property` descriptors with value
21ranges. In `__init__`, a random velocity vector is created. Each frame,
22`process(dt)` advances the ball's position and reflects velocity when the
23ball hits a screen edge.
24
25`BouncingBalls` is the root node that spawns 8 `Ball` children in `ready()`,
26placing them at random positions. The parent also draws a title and ball
27count via `draw_text()`.
28"""
29
30import math
31import random
32
33from simvx.core import Node2D, Property, Vec2
34from simvx.graphics import App
35
36WIDTH, HEIGHT = 800, 600
37
38
39class Ball(Node2D):
40    radius = Property(12.0, range=(4, 40))
41    speed = Property(200.0, range=(50, 500))
42
43    def __init__(self, **kwargs):
44        super().__init__(**kwargs)
45        angle = random.uniform(0, math.tau)
46        self.velocity = Vec2(math.cos(angle), math.sin(angle)) * self.speed
47        self.colour = (
48            random.uniform(0.4, 1.0),
49            random.uniform(0.4, 1.0),
50            random.uniform(0.4, 1.0),
51            1.0,
52        )
53
54    def process(self, dt: float):
55        self.position += self.velocity * dt
56
57        # Bounce off walls
58        if self.position.x < self.radius or self.position.x > WIDTH - self.radius:
59            self.velocity.x *= -1
60            self.position.x = max(self.radius, min(WIDTH - self.radius, self.position.x))
61        if self.position.y < self.radius or self.position.y > HEIGHT - self.radius:
62            self.velocity.y *= -1
63            self.position.y = max(self.radius, min(HEIGHT - self.radius, self.position.y))
64
65    def draw(self, renderer):
66        renderer.draw_circle(self.position, self.radius, colour=self.colour)
67
68
69class BouncingBalls(Node2D):
70    def ready(self):
71        for i in range(8):
72            self.add_child(
73                Ball(
74                    name=f"Ball{i}",
75                    position=Vec2(random.uniform(50, WIDTH - 50), random.uniform(50, HEIGHT - 50)),
76                )
77            )
78
79    def draw(self, renderer):
80        renderer.draw_text("Bouncing Balls", (10, 10), scale=2, colour=(1.0, 1.0, 1.0))
81        renderer.draw_text(f"{len(self.children)} balls", (10, 35), scale=1, colour=(0.71, 0.71, 0.71))
82
83
84if __name__ == "__main__":
85    App(title="Bouncing Balls", width=WIDTH, height=HEIGHT).run(BouncingBalls())