Blog Tutorial

Part 6 — Launch and reset the ball

In this part, we will connect the ball to the paddle's launch signal.

Before launch, the ball will sit just above the paddle. When the player presses the Launch input action, the paddle will dispatch its signal and the ball will apply an impulse.

Add the Ball Script

Create a PHP script under Assets/Scripts named Ball.php.

<?php

declare(strict_types=1);

namespace BreakOut\Game\Scripts;

use Lenga\Engine\Attributes\AddComponentMenu;
use Lenga\Engine\Attributes\Header;
use Lenga\Engine\Attributes\RequireComponent;
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Rigidbody2D;
use Lenga\Engine\Core\Vector2;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Enumerations\ForceMode2D;
use Random\Randomizer;

#[AddComponentMenu('Gameplay/Ball')]
#[RequireComponent(Rigidbody2D::class)]
final class Ball extends Behaviour
{
    #[Header('References')]
    public ?Paddle $paddle = null;

    #[Header('Launch')]
    public float $speed = 20.0;
    public float $xVariance = 10.0;
    public float $yVariance = 5.0;
    public ?Vector2 $spawnOffset = null;

    private ?Rigidbody2D $body = null;
    private bool $launched = false;

    public function start(): void
    {
        $this->body = $this->gameObject->getComponent(Rigidbody2D::class);
        $this->spawnOffset ??= new Vector2(x: 4.0, y: -2.0);

        $this->paddle?->onLaunch->add(fn (Paddle $paddle): bool => $this->launch($paddle));
        $this->resetBall();
    }
}

Attach this script to Ball, then assign the Paddle reference in the Inspector.

Follow the Paddle Before Launch

Add this method pair:

public function update(): void
{
    if (!$this->launched) {
        $this->followPaddle();
    }
}

private function followPaddle(): void
{
    if (!$this->paddle instanceof Paddle || !$this->spawnOffset instanceof Vector2) {
        return;
    }

    $paddlePosition = $this->paddle->transform->position;

    $this->transform->position = new Vector3(
        x: $paddlePosition->x + $this->spawnOffset->x,
        y: $paddlePosition->y + $this->spawnOffset->y,
        z: $paddlePosition->z,
    );
}

Now the ball stays attached to the paddle until it is launched.

Launch with an Impulse

Add the launch method:

private function launch(Paddle $paddle): bool
{
    if ($this->launched || !$this->body instanceof Rigidbody2D) {
        return false;
    }

    $this->followPaddle();

    $randomizer = new Randomizer();
    $horizontal = $randomizer->getFloat(-$this->xVariance, $this->xVariance);
    $vertical = -($this->speed + $randomizer->getFloat(0.0, $this->yVariance));

    $this->body->velocity = Vector3::zero();
    $this->body->addForce(
        new Vector2(x: $horizontal, y: $vertical),
        ForceMode2D::Impulse,
    );

    $this->launched = true;

    return true;
}

In this 2D setup, negative Y launches the ball upward. The small random X variance keeps each launch from feeling identical.

Add a Reset Helper

Add this helper now. We will call it from the out-of-bounds event in the next part.

private function resetBall(): bool
{
    $this->launched = false;

    if ($this->body instanceof Rigidbody2D) {
        $this->body->velocity = Vector3::zero();
    }

    $this->followPaddle();

    return true;
}

Checkpoint

At the end of this part:

  • the ball follows the paddle before launch
  • pressing Launch fires the paddle signal
  • the ball launches with an impulse
  • the ball has a reset helper ready for the next mechanic

Next, we will handle out-of-bounds and lives.