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