Bouncing Balls

Properties, velocity, and screen-edge collision.

▶ Run in browser

Tags: getting_started

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

What You Will Learn

  • Property – Declare editor-visible properties with validation ranges

  • on_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 on_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, on_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 on_ready(), placing them at random positions. The parent also draws a title and ball count via draw_text().

Source

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