Guides Animation 2D 2D Animation and Controllers

Animation 2D 8 min read Updated Apr 2026

2D Animation and Controllers

Lenga's 2D animation workflow is built to handle the common character loop without forcing you into a heavyweight graph tool before you need one.

You can:

  • build clips from individual sprites or a sprite sheet
  • add events to exact frames
  • create animation controllers with named states
  • switch states with bool, int, float, trigger, and On Complete conditions
  • preview the result in the editor before wiring full gameplay code

Animation controller workflow

Start With a Clip

Create an Animation Clip from the Assets panel, then open it in the Animator window.

Use a clip when you want a single sequence such as:

  • idle
  • walk
  • jump
  • torch flame
  • coin spin

Typical clip setup:

  1. create the .anim asset
  2. add frames manually or slice them from a sheet
  3. set FPS
  4. enable or disable looping
  5. preview the clip immediately

Build Frames From a Sprite Sheet

If your art tool exports one sheet, you do not need to split it by hand before bringing it into Lenga.

In the Animator:

  1. choose the sprite sheet
  2. set the frame width and height
  3. set margin and spacing if the export uses padding
  4. generate the frame list

That is especially good for large action sets where each animation arrives as a sheet.

Idle sheet  -> slice -> save as KnightIdle.anim
Walk sheet  -> slice -> save as KnightWalk.anim
Jump sheet  -> slice -> save as KnightJump.anim

Add Animation Events to the Exact Frame

Events let the animation timeline tell gameplay when something meaningful happened.

Good event names are short and specific:

  • footstep
  • attackHit
  • drawSword
  • land
  • spawnDust

Example event data inside a clip:

{
  "frame": 5,
  "name": "attackHit"
}

And the matching gameplay hook:

public function onAnimationEvent(string $eventName): void
{
    if ($eventName === 'footstep') {
        // play a footstep sound
    }

    if ($eventName === 'attackHit') {
        // enable damage, spawn particles, or shake the camera
    }
}

Create a Controller When the Object Has Multiple States

Clips are great for one animation. Controllers are better when a GameObject needs to move between many animations in response to gameplay.

Common controller states:

  • Idle
  • Walk
  • Jump
  • Fall
  • Attack
  • Hit

Set one state as the default, then add transitions between them.

Animation state flow

Understand How Lenga Transitions Work

Before you start clicking around in the Animator, it helps to know the exact transition model Lenga uses.

A transition in Lenga is:

  • one-way
  • owned by one source state
  • evaluated only from the current state
  • driven by one or more conditions

That means:

  • Idle -> Walk does not automatically create Walk -> Idle
  • a state with no outgoing transitions will stay there
  • all conditions inside one transition must pass before that transition can fire
  • if two transitions from the same state can both match, the first matching transition wins

This is the most important mental model to keep in mind while you author a controller.

Bool vs Int vs Float vs Trigger vs On Complete

Lenga supports five useful transition styles.

Bool

Use a bool for a condition that stays true until gameplay changes it.

Examples:

  • isWalking
  • isGrounded
  • isAiming

Bool transitions use:

  • If
  • If Not

Int

Use an int when the controller should react to a numbered gameplay state.

Examples:

  • comboStep
  • weaponIndex
  • stanceLevel

Int transitions use:

  • Greater
  • Greater Or Equal
  • Less
  • Less Or Equal
  • Equals
  • Not Equal

Float

Use a float when animation should respond to a continuously changing value.

Examples:

  • speed
  • verticalSpeed
  • aimWeight

Float transitions use:

  • Greater
  • Greater Or Equal
  • Less
  • Less Or Equal
  • Equals
  • Not Equal

Trigger

Use a trigger for one-shot moments.

Examples:

  • attack
  • takeHit
  • die

On Complete

Use On Complete when you want a non-looping clip to move to another state after it finishes.

Examples:

  • AttackLight -> Idle
  • Hit -> Idle
  • Land -> Idle

On Complete only makes sense for clips that do not loop. If the clip loops forever, it never completes, so the transition will never fire.

Step-by-Step: Build a Working Controller

This walkthrough uses a simple character with Idle, Walk, Run, and AttackLight.

1. Create the clips first

Make sure you already have .anim clips for the motions you want to use.

Example:

  • KnightIdle.anim
  • KnightWalk.anim
  • KnightRun.anim
  • KnightAttackLight.anim

2. Create or open a .controller asset

Select the controller asset and open it in the Animator tab.

If you only look at the Inspector, you will only see a summary. The full transition editor lives in the Animator tab.

3. Add parameters

Use the Parameters section first.

For this example:

  • add a bool named isWalking
  • add a bool named isRunning
  • add a float named speed
  • add an int named comboStep
  • add a trigger named lightAttack

Use bools for stable conditions, ints for discrete steps, floats for continuously changing values, and triggers for one-shot actions.

4. Add states

In the States section:

  1. click +
  2. name the state
  3. assign its clip
  4. repeat for each state you need

For this example:

  • Idle -> KnightIdle.anim
  • Walk -> KnightWalk.anim
  • Dash -> KnightRun.anim
  • Attack Light -> KnightAttackLight.anim

5. Set the default state

Mark the state you want the controller to start in.

For most characters, that will be Idle.

6. Select the source state you want to edit

This is the part many people miss the first time.

In the States section, click the state card whose outgoing transitions you want to edit.

The Outgoing Transitions section below does not edit the whole controller at once. It only edits transitions leaving the currently selected state card.

The selected state card is highlighted, and the Outgoing Transitions header shows the current source state.

7. Add transitions for that selected state

With the source state selected:

  1. go to Outgoing Transitions
  2. click +
  3. choose the target state in To
  4. add one or more condition rows
  5. choose the condition in When
  6. set the expected value when the condition uses a bool, int, or float

If a transition has multiple conditions, all of them must match.

For Idle, add:

  • Idle -> Walk when isWalking If
  • Idle -> Dash when isRunning If
  • Idle -> Dash when speed Greater 6.0
  • Idle -> Attack Light when lightAttack Fire

8. Repeat for the other source states

Now click Walk and author its outgoing transitions.

For Walk, add:

  • Walk -> Dash when isRunning If
  • Walk -> Idle when isWalking If Not
  • Walk -> Attack Light when lightAttack Fire

Then click Dash and add:

  • Dash -> Walk when isWalking If
  • Dash -> Idle when isRunning If Not
  • Dash -> Attack Light when lightAttack Fire

If you prefer one movement parameter instead of multiple bools, you can also author a controller around a single float such as speed:

  • Idle -> Walk when speed Greater 0.1
  • Walk -> Dash when speed Greater 6.0
  • Dash -> Walk when speed Less 6.0
  • Walk -> Idle when speed Less 0.1

9. Use On Complete for one-shot states

Now click the Attack Light state card.

If the attack clip is a one-shot animation, make sure the clip itself has Loop turned off.

Then add:

  • Attack Light -> Idle when On Complete

That gives you a clean way back into locomotion after the attack animation finishes.

A Complete Example Flow

This setup gives a solid starting point:

  • Idle -> Walk when isWalking If
  • Idle -> Dash when isRunning If
  • Idle -> Attack Light when lightAttack Fire
  • Walk -> Dash when isRunning If
  • Walk -> Idle when isWalking If Not
  • Walk -> Attack Light when lightAttack Fire
  • Dash -> Walk when isWalking If
  • Dash -> Idle when isRunning If Not
  • Dash -> Attack Light when lightAttack Fire
  • Attack Light -> Idle when On Complete

Assign the Animation to a GameObject

Add Sprite Animation to the same GameObject that already has a Sprite Renderer.

You can author it in one of two ways:

  • assign a single clipPath
  • assign a controllerPath and let the controller choose the current clip

Typical player setup:

Player
  Transform
  Sprite Renderer
  Rigidbody2D
  BoxCollider2D
  Sprite Animation
  Behaviour (PlayerController)

Drive the Controller From PHP

Once the assets are in place, gameplay code only needs to express intent.

Simple movement example:

use Lenga\Engine\Core\Input;
use Lenga\Engine\Core\SpriteAnimation;
use Lenga\Engine\Core\SpriteRenderer;

$animation = $gameObject->getComponent(SpriteAnimation::class);
$sprite = $gameObject->getComponent(SpriteRenderer::class);

$move = Input::getAxis('Horizontal');
$isWalking = abs($move) > 0.05;
$speed = abs($move) * 8.0;

$animation?->setBool('isWalking', $isWalking);
$animation?->setBool('isRunning', $speed > 6.0);
$animation?->setFloat('speed', $speed);

if ($move < 0.0) {
    $sprite?->flipX = true;
} elseif ($move > 0.0) {
    $sprite?->flipX = false;
}

One-shot action example:

if (Input::getButtonDown('Jump')) {
    $animation?->setTrigger('jump');
}

if (Input::getButtonDown('Attack')) {
    $animation?->setTrigger('lightAttack');
}

Integer-driven example:

$comboStep = 0;

if (Input::getButtonDown('Attack')) {
    $comboStep++;
    $animation?->setInt('comboStep', $comboStep);
    $animation?->setTrigger('lightAttack');
}

Preview Before You Write More Code

The editor preview is valuable because it lets you answer questions early:

  • are the transitions named clearly?
  • does the default state make sense?
  • do the parameters drive the right clips?
  • are the frame events on the right frames?

Use the preview controls to toggle bools, type exact Int and Float values, and fire triggers before you touch the full gameplay script.

The preview is designed to let you test controller logic, not just clip playback:

  • if the preview is already playing, changing a parameter, firing a trigger, or switching preview state keeps the preview running
  • if the preview is stopped, changing a parameter updates the selected state without automatically pressing play for you

That makes it easier to test transitions without constantly restarting playback.

Common Gotchas

These are the places people most often get stuck.

1. Editing the wrong source state

If a transition is not doing what you expect, first confirm that you clicked the correct state card before editing the Outgoing Transitions list.

2. Forgetting the return transition

Transitions are one-way.

If you add:

  • Idle -> Walk

you still need:

  • Walk -> Idle

3. Using On Complete on a looping clip

If the clip loops, it never completes.

For attacks, hit reactions, and similar one-shot states, turn looping off in the clip asset first.

4. Creating overlapping conditions from the same state

If more than one transition could match from the current state, the first matching transition wins.

That means transition order matters.

If you need a transition to be more specific, prefer adding more conditions to the same transition instead of creating several overlapping transitions that all point to the same destination.

5. Mismatched names between code and controller

Parameter names in PHP must match the controller exactly.

For example:

$animation?->setTrigger('lightAttack');

will not match a controller parameter named Light Attack.

6. Expecting controller editing from the Inspector

The Inspector shows a summary for .controller assets.

The actual controller authoring workflow lives in the Animator tab.

Tips for Cleaner Animation Data

These habits make clips and controllers easier to maintain:

  1. Keep clip names literal: KnightIdle, KnightWalk, KnightJump.
  2. Keep controller parameter names intent-based: isWalking, speed, comboStep, lightAttack.
  3. Use frame events for timing, not for whole gameplay systems.
  4. Let the controller own visual transitions; let gameplay code own intent.

This workflow is especially effective for practical, state-driven 2D animation where gameplay code supplies clear intent and the controller owns the visual transitions.