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::Linearfor constant motionEasingFunction::EaseOutQuadfor quick response that settlesEasingFunction::EaseInQuadfor motion that starts gently and acceleratesEasingFunction::EaseInOutSinefor calm, symmetric motionEasingFunction::EaseOutCubicfor 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.