Part 5 — Move the paddle with physics
In this part, we will add the paddle and make it respond to player input.
The paddle is the first object the player controls, so we will introduce a PHP script here instead of adding scripts before they are useful.
Add the Paddle Object
Create a rectangle primitive and rename it Paddle.
Place it near the bottom of the play area. Make it wide and short.
Add or confirm these components:
Rectangle RendererBoxCollider2DRigidbody2D
Set the Rigidbody2D body type to Kinematic and set Gravity Scale to 0.
A kinematic paddle is moved by our code. The physics system still lets the dynamic ball collide with it, but gravity will not pull the paddle down.
Add the Paddle Script
Create a PHP script under Assets/Scripts named Paddle.php.
Start with this class:
<?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\Input;
use Lenga\Engine\Core\Rigidbody2D;
use Lenga\Engine\Core\Signal;
use Lenga\Engine\Core\Vector3;
#[AddComponentMenu('Gameplay/Paddle')]
#[RequireComponent(Rigidbody2D::class)]
final class Paddle extends Behaviour
{
#[Header('Movement')]
public float $paddleSpeed = 60.0;
public float $horizontalLimit = 43.0;
public Signal $onLaunch;
private ?Rigidbody2D $body = null;
private Vector3 $startPosition;
private float $horizontalInput = 0.0;
private bool $canLaunch = true;
public function awake(): void
{
$this->onLaunch = $this->createSignal();
}
public function start(): void
{
$this->body = $this->gameObject->getComponent(Rigidbody2D::class);
$this->startPosition = clone $this->transform->position;
}
}
RequireComponent tells the editor and runtime that this script expects a Rigidbody2D on the same GameObject.
Public fields appear in the Inspector automatically. Use #[SerializeField] only when you deliberately want a non-public field serialized.
Read Input in Update
Add an update method:
public function update(): void
{
$this->horizontalInput = Input::getAxis('Horizontal');
if ($this->canLaunch && Input::getButtonDown('Launch')) {
$this->canLaunch = false;
$this->onLaunch->dispatch($this);
}
}
Use update for input because it runs once per frame. We store the input and apply movement in the physics step.
Move in Fixed Update
Add fixedUpdate:
public function fixedUpdate(): void
{
if (!$this->body instanceof Rigidbody2D) {
return;
}
$position = $this->transform->position;
$atLeftEdge = $position->x <= -$this->horizontalLimit;
$atRightEdge = $position->x >= $this->horizontalLimit;
if (($atLeftEdge && $this->horizontalInput < 0.0) || ($atRightEdge && $this->horizontalInput > 0.0)) {
$this->body->velocity = Vector3::zero();
return;
}
$this->body->velocity = new Vector3(
x: $this->horizontalInput * $this->paddleSpeed,
y: 0.0,
z: 0.0,
);
}
The paddle moves through rigidbody velocity, not by teleporting its transform every frame.
Attach and Test
Add the Paddle script to the Paddle GameObject.
Press Play and test the Horizontal input action. The paddle should move left and right, then stop when it reaches the configured horizontal limit.
Tip: Tune
horizontalLimitafter the side walls are placed. The limit should keep the paddle inside the playable area.
Checkpoint
At the end of this part, you should have:
- a visible
Paddle - a
BoxCollider2D - a
Rigidbody2Dset toKinematic - a
PaddlePHP script attached - horizontal input moving the paddle
- a launch signal ready for the ball to use later
Next, we will launch and reset the ball.