Guides Tweening Move, Rotate, and Scale with Tweens

Tweening 3 min read Updated Apr 2026

Move, Rotate, and Scale with Tweens

Use transform tweens when you want a GameObject to move, rotate, or scale smoothly without writing timer code by hand.

This guide shows the practical workflow: create one tween when something happens, choose the right target space, then tune the motion with easing and options.

If the concept is new, read What Are Tweens? first.

Import the Tweening Classes

Most tween scripts need these imports:

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Tweening\EasingFunction;
use Lenga\Engine\Tweening\Tween;
use Lenga\Engine\Tweening\TweenOptions;

Tween creates the motion. TweenOptions describes how the motion should feel.

Move to an Exact World Position

Use moveTo when the destination is a world-space scene position.

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Tweening\EasingFunction;
use Lenga\Engine\Tweening\Tween;
use Lenga\Engine\Tweening\TweenOptions;

final class DoorOpener extends Behaviour
{
    public function open(): void
    {
        Tween::moveTo(
            $this->transform,
            new Vector3(6.0, 0.0, 0.0),
            0.45,
            TweenOptions::make()->ease(EasingFunction::EaseOutCubic),
        );
    }
}

Use this when the target is independent of the object's parent. For example, a platform may move to a known position in the scene.

Move Relative to the Current Position

Often you do not care about the exact final coordinate. You just want an object to move by an offset.

Use relative() for that:

Tween::moveLocalTo(
    $this->transform,
    new Vector3(0.0, 1.5, 0.0),
    0.3,
    TweenOptions::make()
        ->relative()
        ->ease(EasingFunction::EaseOutQuad),
);

This means "move 1.5 units up from wherever the object is now."

Relative tweens are especially useful for:

  • hit bumps
  • pickup bounce effects
  • small door or drawer motions
  • recoil nudges
  • objects that may start from different authored positions

Choose Local Space for Parented Objects

Use moveLocalTo when the object is parented under another object and you want the motion to stay relative to that parent.

final class WeaponRecoil extends Behaviour
{
    public function kick(): void
    {
        Tween::moveLocalTo(
            $this->transform,
            new Vector3(-0.12, 0.0, 0.0),
            0.05,
            TweenOptions::make()
                ->relative()
                ->ease(EasingFunction::EaseOutQuad),
        );
    }
}

Local space keeps the behaviour understandable when the parent moves, rotates, or is reused in another scene.

Scale for Feedback

Scaling tweens are good for quick response effects.

final class PickupPulse extends Behaviour
{
    public function start(): void
    {
        Tween::scaleTo(
            $this->transform,
            new Vector3(1.2, 1.2, 1.0),
            0.18,
            TweenOptions::make()->ease(EasingFunction::EaseOutQuad),
        );
    }
}

This changes local scale. For 2D sprites, leave z at 1.0 unless the object is part of a 3D setup.

Rotate in Degrees

Rotation tweens use Euler angles in degrees, matching the values most developers expect from the Inspector.

final class CoinFlip extends Behaviour
{
    public function start(): void
    {
        Tween::rotateLocalTo(
            $this->transform,
            new Vector3(0.0, 0.0, 360.0),
            0.6,
            TweenOptions::make()
                ->relative()
                ->ease(EasingFunction::EaseInOutSine),
        );
    }
}

For 2D rotation, tween the z angle.

For 3D rotation, use tweened Euler angles for clear authored turns. If an object needs complex orientation logic, keep that logic explicit in code so the tween is only responsible for the visible motion you can reason about.

Add a Delay

Use delay() when the motion should start after a pause.

Tween::moveLocalTo(
    $this->transform,
    new Vector3(0.0, 0.4, 0.0),
    0.2,
    TweenOptions::make()
        ->delay(0.5)
        ->relative()
        ->ease(EasingFunction::EaseOutQuad),
);

This is useful for staggered effects, delayed reveals, or enemy warning cues.

Pick an Easing Function

Easing changes how the tween progresses over time.

Good starting choices:

  • EasingFunction::Linear for constant motion
  • EasingFunction::EaseOutQuad for quick response that settles
  • EasingFunction::EaseInQuad for motion that starts gently and accelerates
  • EasingFunction::EaseInOutSine for calm, symmetric motion
  • EasingFunction::EaseOutCubic for snappy UI-like movement

If you are not sure, start with EasingFunction::EaseOutQuad for feedback and EasingFunction::EaseInOutSine for looping or decorative motion.

A Complete Example: Open a Gate Once

This example opens a gate when another script calls open(). It guards against repeated calls so the tween is not restarted over and over.

use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Vector3;
use Lenga\Engine\Tweening\EasingFunction;
use Lenga\Engine\Tweening\Tween;
use Lenga\Engine\Tweening\TweenOptions;

final class Gate extends Behaviour
{
    private bool $opened = false;

    public function open(): void
    {
        if ($this->opened) {
            return;
        }

        $this->opened = true;

        Tween::moveLocalTo(
            $this->transform,
            new Vector3(0.0, 3.0, 0.0),
            0.5,
            TweenOptions::make()
                ->relative()
                ->ease(EasingFunction::EaseOutCubic),
        );
    }
}

The state is still explicit: the gate knows it has opened. The tween only handles the presentation of that state change.

Checklist

Before adding a tween, decide:

  • Is this world-space or local-space motion?
  • Is the target absolute or relative?
  • Should the motion respect game pause and time scale?
  • Does this code need the handle later?
  • What should happen if the event fires again before the tween finishes?

If those answers are clear, the tween is likely to stay maintainable.

Read Next