AppleyAppley Docs
Docs/Animation

Animation

Build animation state machines with blending, transitions, and events.

19 min read Intermediate Updated 2026-07-01 v2.0 ~63 min to complete

Introduction

Animation brings characters to life. This guide covers state machines, blend trees, transition conditions, and animation events for responsive character animation.

Guide 8 of 25

This is part of the Intermediate Guides collection. Category: Animation. Estimated completion: 63 minutes. Difficulty: Intermediate.

Screenshot

Animation
Animation

Step-by-Step Guide

1

Import animations

Load FBX files with animation data. Each file becomes a clip (idle, walk, run, jump, attack).

2

Create a state machine

In the Animator component, create states for each clip. Set idle as the default entry point.

3

Add transitions

Connect states with conditions (speed > 0.1 to walk, speed > 5 to run, !isGrounded to jump).

4

Configure blend times

0.1-0.2s for responsive actions (jump, attack), 0.3-0.5s for relaxed transitions (idle to walk).

5

Use blend trees

For directional movement, create a blend tree parameterized by speed and angle for smooth blending.

6

Add animation events

Attach events at specific frames (frame 15 of attack = deal damage). Events trigger FluxScript functions.

FluxScript Example

Here is a complete FluxScript implementation for this guide:

Animation — FluxScript
on start {
  player = scene.find("Player")
  player.loadAnimation("idle", "anims/idle.fbx")
  player.loadAnimation("walk", "anims/walk.fbx")
  player.loadAnimation("run", "anims/run.fbx")
  player.loadAnimation("jump", "anims/jump.fbx")
  player.loadAnimation("attack", "anims/attack.fbx")
  player.setStateMachine({
    default: "idle",
    transitions: [
      { from: "idle", to: "walk", when: "speed > 0.5", blend: 0.2 },
      { from: "walk", to: "run", when: "speed > 5", blend: 0.15 },
      { from: "*", to: "jump", when: "!isGrounded", blend: 0.05 },
      { from: "*", to: "attack", when: "attacking", blend: 0.1 }
    ]
  })
}

on update(dt) {
  player.setAnimParam("speed", player.getSpeed())
  player.setAnimParam("isGrounded", player.isGrounded)
}

on animEvent("attack", "hit") {
  enemies = world.findAll("enemy")
  for i = 0 to enemies.length {
    if math.distance(player.position, enemies[i].position) < 3 {
      enemies[i].takeDamage(25)
    }
  }
}

Node Graph Example

Prefer visual programming? Here is the equivalent Node Graph:

Animation — Node Graph
[On Start] --> [Load 5 Animation Clips]
   --> [Set State Machine: 4 transitions]

[On Update] --> [Set Anim Params: speed, isGrounded]

[On Anim Event "attack" "hit"] --> [Find All: enemy]
   --> [For Each: distance < 3] --> [Take Damage: 25]

Optimization Tips

Keep your implementation fast and efficient with these optimization tips:

  • Compress animation data to reduce file size and memory. Use keyframe reduction on idle and walk loops.
  • Use additive blending for upper-body animations. This lets characters attack while walking.
  • Limit concurrent animated characters. Each skeleton costs CPU. Use simpler rigs for background NPCs.
  • Use animation events instead of polling. Trigger damage/sound at exact frames — polling is less precise.

Common Mistakes

Transitions too slow

Combat animations need 0.05-0.1s blends. Slower transitions make controls feel sluggish.

Missing animation events

Without events, damage applies during the entire attack. Use events for exact-frame damage.