# Bouncing Balls Properties, velocity, and screen-edge collision. ```{raw} html ▶ Run in browser ``` **Tags:** `tutorial` `beginner` `properties` # Bouncing Balls You can move one node now. This tutorial spawns *many* nodes that each carry their own state, and introduces **`Property`** descriptors: the engine's way to declare a value that is type-checked, range-validated, editor-visible, and serialised, all at once. By the end you will have a window of colourful balls bouncing off the edges, each running its own physics. ## Step 1: Declare properties A `Property` looks like a class attribute with a default and an optional `range`. The engine validates assignments against the range and shows the value in the editor: ```python class Ball(Node2D): radius = Property(12.0, range=(4, 40)) speed = Property(200.0, range=(50, 500)) ``` You read and write a property like a normal attribute (`self.radius`); the descriptor does the validation behind the scenes. ## Step 2: Give each ball its own state `__init__` runs once per ball. Each picks a random direction and colour, so every ball is independent even though they share a class: ```python def __init__(self, **kwargs): super().__init__(**kwargs) angle = random.uniform(0, math.tau) self.velocity = Vec2(math.cos(angle), math.sin(angle)) * self.speed ``` ## Step 3: Move and bounce each frame `on_update(dt)` advances the ball and reflects its velocity when it reaches an edge, clamping the position so it never escapes the window: ```python def on_update(self, dt): self.position += self.velocity * dt if self.position.x < self.radius or self.position.x > WIDTH - self.radius: self.velocity.x *= -1 self.position.x = max(self.radius, min(WIDTH - self.radius, self.position.x)) # ... same for y ``` ## Step 4: Spawn a crowd The root builds the scene tree in `on_ready()`, adding eight balls at random positions. Each child then updates and draws itself: you write the behaviour once, and every instance runs it. ```python class BouncingBalls(Node2D): def on_ready(self): for i in range(8): self.add_child(Ball(name=f"Ball{i}", position=Vec2(random.uniform(50, WIDTH - 50), random.uniform(50, HEIGHT - 50)))) ``` ## Run it ```bash uv run python examples/tutorials/bouncing_balls/main.py ``` ## What's next - **Pong** -- combine input, signals, and collision into a complete game. ## Source ```{literalinclude} ../../examples/tutorials/bouncing_balls/main.py :language: python :linenos: ```