Guides Physics 3D 3D Collisions and Kinematic Movement

Physics 3D 3 min read Updated Apr 2026

3D Collisions and Kinematic Movement

Lenga's 3D collision workflow is built around **colliders, queries, and `CharacterController`**.

That means:

  • you add collider components such as BoxCollider3D, CapsuleCollider3D, and SphereCollider3D
  • you either move through CharacterController or use Physics3D queries from PHP
  • your gameplay code stays explicit about how movement and responses should work

This is a good fit for 3D gameplay that needs reliable blocking and scene awareness without a full rigidbody 3D physics stack.

Before you start writing movement code, it also helps to know the core scene API well:

Core 3D Collision Surface

  • BoxCollider3D
  • CapsuleCollider3D
  • SphereCollider3D
  • CharacterController
  • Physics3D::overlapSphereAll(...)
  • Physics3D::overlapBoxAll(...)
  • Physics3D::overlapCapsuleAll(...)
  • Physics3D::raycast(...)
  • Physics3D::raycastAll(...)
  • collider isTouching(...)
  • collider getContacts(...)
  • Collision3D
  • onCollisionEnter / onCollisionStay / onCollisionExit
  • onTriggerEnter / onTriggerStay / onTriggerExit

Basic Scene Setup

For a simple rolling or moving actor:

  1. Add a visible render component such as SphereRenderer or CubeRenderer.
  2. Add a matching collider, usually SphereCollider3D, CapsuleCollider3D, or BoxCollider3D.
  3. Add colliders to the level geometry you want to block movement.
  4. Drive movement from PHP with CharacterController when you want built-in blocking and slide handling, or with explicit Physics3D queries when you want lower-level control.

For example:

  • the player ball in RollerWorld uses SphereCollider3D
  • the ground and lane walls use BoxCollider3D
  • upright characters can use CapsuleCollider3D when a sphere is too blunt

Recommended Movement Pattern

For most gameplay movement, CharacterController is the recommended starting point:

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\CharacterController;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Core\Time;

final class RollerController extends Behaviour
{
    private ?CharacterController $controller = null;

    public float $moveSpeed = 8.0;

    public function start(): void
    {
        $this->controller = $this->gameObject->getComponent(CharacterController::class);
    }

    public function update(): void
    {
        if (!$this->controller instanceof CharacterController) {
            return;
        }

        $motion = new Vector3(0.0, 0.0, -1.0);
        $motion = Vector3::scaleNew($motion, $this->moveSpeed * Time::deltaTime());
        $this->controller->move($motion);
    }
}

This gives you a practical kinematic movement workflow built around CharacterController.

RollerWorld is the shipped example of this pattern. Its player movement uses CharacterController for movement, and still uses Physics3D queries for obstacle probing and scene interaction.

When To Use Queries Directly

Use direct Physics3D queries when you want behavior beyond simple blocking and sliding, such as:

  • anticipatory probes in front of a moving actor
  • line-of-sight checks
  • custom stepping, climbing, or grounding logic
  • click picking and interaction

That lower-level query path is also how RollerWorld supplements its controller movement.

The key idea is:

  • start with CharacterController for movement
  • use Physics3D queries when you need custom behavior

Contact Reporting and Callbacks

You can also inspect contacts directly from 3D colliders:

$contacts = $this->collider?->getContacts(false) ?? [];
foreach ($contacts as $contact) {
    Debug::info($contact->otherGameObject?->name ?? 'Unknown');
}

And you can respond to state changes with optional behaviour methods:

use Lenga\Engine\Core\Collision3D;

public function onCollisionEnter(Collision3D $collision): void
{
    Debug::info('Touched ' . ($collision->otherGameObject?->name ?? 'Unknown'));
}

public function onTriggerEnter(Collision3D $collision): void
{
    Debug::info('Entered trigger: ' . ($collision->otherGameObject?->name ?? 'Unknown'));
}

For familiarity, 3D uses the unsuffixed callback names, while 2D keeps the 2D suffix.

Raycasts

Raycasts are useful when you want to detect something in front of an object without asking for every overlapping collider in a volume.

Common uses:

  • forward obstacle checks
  • click or cursor picking
  • line-of-sight checks
  • ground probes from a character or vehicle
use Lenga\Engine\Core\Physics3D;
use Lenga\Engine\Core\Vector3;

$hit = Physics3D::raycast(
    $this->transform->position,
    $this->transform->forward,
    5.0,
    false
);

if ($hit !== null) {
    Debug::info('Hit ' . ($hit->gameObject?->name ?? 'Unknown'));
}

If you want every hit along a line instead of only the first one, use raycastAll(...).

Recommended Next Steps

After you have a moving character or object:

  • try the RollerWorld sample and inspect its player and camera scripts
  • add lane walls or blockers with BoxCollider3D
  • use separate overlap checks to support sliding
  • keep your gameplay logic kinematic and explicit
  • avoid baking backend assumptions directly into your game code
  • keep the Transform direction helpers in mind for camera-relative movement, especially forward, right, and up