Skip to main content
Version: Next 🚧

Common First Mistakes

Learning Murali is straightforward, but there are a few common pitfalls that trip up newcomers. This guide helps you avoid them.

1. Forgetting .spawn()

The Mistake

// This does NOTHING!
timeline
.animate(circle_id)
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(3.0, 0.0, 0.0));
// Missing .spawn()

Why It Happens

The animation builder uses a fluent API. Without .spawn(), the animation is built but never added to the timeline.

The Fix

timeline
.animate(circle_id)
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn(); // ✅ Don't forget this!

How to Remember

Think of .spawn() as "commit this animation to the timeline." Every animation needs it.

:::tip Learn More See the Animations guide for all available animation verbs and their usage. :::


2. World Space vs Pixels

The Mistake

// Trying to use pixel coordinates
let circle = Circle::new(100.0, 48, color); // ❌ 100 pixels?
scene.add_tattva(circle, Vec3::new(640.0, 360.0, 0.0)); // ❌ Screen center?

Why It Happens

Coming from other tools that use pixel coordinates.

The Fix

// Use world units
let circle = Circle::new(1.5, 48, color); // ✅ 1.5 world units
scene.add_tattva(circle, Vec3::new(0.0, 0.0, 0.0)); // ✅ World origin

// Set camera view width to control scale
scene.camera_mut().set_view_width(16.0); // 16 units fit horizontally

Key Insight

  • World space = Mathematical units (1.0 means 1.0)
  • Pixels = Screen resolution (handled by camera)
  • The camera maps world space to pixels

Rule of thumb: If your numbers are in the hundreds or thousands, you're probably thinking in pixels.

:::tip Learn More See Coordinate System for a complete explanation of world space. :::


3. Camera Position and Visibility

The Mistake

let mut scene = Scene::new();

// Add a circle at origin
let circle_id = scene.add_tattva(
Circle::new(1.0, 48, color),
Vec3::ZERO,
);

// Forgot to position camera!
// Default camera is at origin, can't see anything

Why It Happens

Forgetting that the camera needs to be positioned to see objects.

The Fix

let mut scene = Scene::new();

let circle_id = scene.add_tattva(
Circle::new(1.0, 48, color),
Vec3::ZERO,
);

// Position camera away from objects
scene.camera_mut().position = Vec3::new(0.0, 0.0, 10.0); // ✅ 10 units back
scene.camera_mut().set_view_width(16.0);

Standard 2D Setup

// This works for most 2D scenes
scene.camera_mut().position = Vec3::new(0.0, 0.0, 10.0);
scene.camera_mut().set_view_width(16.0);

:::tip Learn More See the Camera guide for advanced camera control and 3D scenes. :::


4. Opacity vs Visibility Confusion

The Mistake

// Trying to hide an object
scene.set_opacity(id, 0.0); // ❌ Still in render pipeline

// Or trying to fade in without staging
timeline.animate(id).at(0.0).for_duration(1.0).appear().spawn();
// ❌ Object is already visible, appear() does nothing

Why It Happens

Confusion between opacity (transparency) and visibility (render state).

The Fix

// To hide instantly
scene.hide(id); // ✅ Removes from render pipeline

// To fade in, stage first
scene.hide(id); // Stage it
timeline
.animate(id)
.at(0.0)
.for_duration(1.0)
.appear() // ✅ Now it fades in
.spawn();

The Difference

  • Opacity = How transparent (0.0 to 1.0)
  • Visibility = Whether it's rendered at all (hidden/shown)
  • Staging = Hiding before reveal animation

5. Using .draw() on Filled Shapes

The Mistake

let circle_id = scene.add_tattva(
Circle::new(1.0, 48, color),
Vec3::ZERO,
);

// Trying to "draw" a filled circle
timeline
.animate(circle_id)
.at(0.0)
.for_duration(2.0)
.draw() // ❌ Circles don't have a natural "draw" progression
.spawn();

Why It Happens

Thinking all objects can be "drawn" progressively.

The Fix

// For filled shapes, use appear()
timeline
.animate(circle_id)
.at(0.0)
.for_duration(1.0)
.appear() // ✅ Fade in
.spawn();

// .draw() is for paths, lines, arrows
let line_id = scene.add_tattva(Line::new(...), Vec3::ZERO);
timeline
.animate(line_id)
.at(0.0)
.for_duration(2.0)
.draw() // ✅ Lines can be drawn
.spawn();

When to Use What

  • .draw() → Lines, arrows, paths, curves
  • .appear() → Circles, squares, filled shapes, text

6. Not Calling scene.play(timeline)

The Mistake

let mut scene = Scene::new();
let mut timeline = Timeline::new();

// Add tattvas...
// Add animations to timeline...

// Forgot to play the timeline!
App::new()?.with_scene(scene).run_app() // ❌ Nothing animates

Why It Happens

Building the timeline but forgetting to attach it to the scene.

The Fix

let mut scene = Scene::new();
let mut timeline = Timeline::new();

// Add tattvas...
// Add animations to timeline...

scene.play(timeline); // ✅ Don't forget this!

App::new()?.with_scene(scene).run_app()

7. Animation Duration = 0

The Mistake

timeline
.animate(id)
.at(0.0)
.for_duration(0.0) // ❌ Instant, no animation
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn();

Why It Happens

Typo or misunderstanding of duration.

The Fix

timeline
.animate(id)
.at(0.0)
.for_duration(2.0) // ✅ Takes 2 seconds
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn();

Typical Durations

  • 0.5-1.0s → Quick transitions
  • 1.0-2.0s → Standard animations
  • 2.0-3.0s → Slow, dramatic effects

8. Animating Non-Existent Tattvas

The Mistake

let circle_id = scene.add_tattva(Circle::new(1.0, 48, color), Vec3::ZERO);

// Typo in variable name
timeline
.animate(circl_id) // ❌ Wrong variable
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn();

Why It Happens

Typos, copy-paste errors, or using IDs from removed tattvas.

The Fix

// Save IDs in clear variable names
let circle_id = scene.add_tattva(Circle::new(1.0, 48, color), Vec3::ZERO);
let square_id = scene.add_tattva(Square::new(1.2, color), Vec3::new(2.0, 0.0, 0.0));

// Use the correct ID
timeline
.animate(circle_id) // ✅ Correct variable
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn();

9. Expecting Animations to Stack

The Mistake

// Trying to move an object twice
timeline
.animate(id)
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(2.0, 0.0, 0.0))
.spawn();

timeline
.animate(id)
.at(0.0) // ❌ Same time, conflicts!
.for_duration(2.0)
.move_to(Vec3::new(0.0, 2.0, 0.0))
.spawn();

Why It Happens

Expecting animations to combine or add together.

What Actually Happens

The last animation to apply wins. Animations don't stack—they overwrite.

The Fix

// Sequential animations
timeline
.animate(id)
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(2.0, 0.0, 0.0))
.spawn();

timeline
.animate(id)
.at(2.0) // ✅ Starts after first ends
.for_duration(2.0)
.move_to(Vec3::new(0.0, 2.0, 0.0))
.spawn();

// Or animate different properties
timeline
.animate(id)
.at(0.0)
.for_duration(2.0)
.move_to(Vec3::new(2.0, 0.0, 0.0)) // Position
.spawn();

timeline
.animate(id)
.at(0.0)
.for_duration(2.0)
.scale_to(Vec3::splat(2.0)) // Scale (different property)
.spawn();

10. Colors with Alpha = 0

The Mistake

// Creating an invisible object
let circle = Circle::new(
1.0,
48,
Vec4::new(1.0, 0.0, 0.0, 0.0) // ❌ Alpha = 0, invisible!
);

Why It Happens

Forgetting that the fourth component is alpha (transparency).

The Fix

// Fully opaque color
let circle = Circle::new(
1.0,
48,
Vec4::new(1.0, 0.0, 0.0, 1.0) // ✅ Alpha = 1.0, fully visible
);

Color Format

Vec4::new(r, g, b, a)
// r, g, b = 0.0 to 1.0 (color channels)
// a = 0.0 to 1.0 (alpha: 0.0 = transparent, 1.0 = opaque)

11. Running in Debug Mode

The Mistake

# Running without --release
cargo run --example my_scene

Why It Happens

Not knowing that debug builds are much slower.

The Fix

# Always use --release for preview
cargo run --example my_scene --release

Performance Difference

  • Debug mode → 5-10 FPS (unusable)
  • Release mode → 60+ FPS (smooth)

Rule: Always use --release when previewing or exporting.


12. Morphing Without Hiding Target

The Mistake

// Both shapes are visible
let square_id = scene.add_tattva(Square::new(1.0, color), Vec3::new(-2.0, 0.0, 0.0));
let circle_id = scene.add_tattva(Circle::new(1.0, 48, color), Vec3::new(2.0, 0.0, 0.0));

// Morph square into circle
timeline
.animate(circle_id)
.at(0.0)
.for_duration(2.0)
.morph_from(square_id)
.spawn();
// ❌ Circle is visible at final position, looks wrong

Why It Happens

Not understanding that morph reveals the target.

The Fix

let square_id = scene.add_tattva(Square::new(1.0, color), Vec3::new(-2.0, 0.0, 0.0));
let circle_id = scene.add_tattva(Circle::new(1.0, 48, color), Vec3::new(2.0, 0.0, 0.0));

// Hide the target first
scene.hide(circle_id); // ✅ Stage it

timeline
.animate(circle_id)
.at(0.0)
.for_duration(2.0)
.morph_from(square_id)
.spawn();

13. Confusing Preview and Export Flags

The Mistake

# Expecting preview but getting export
cargo run --example my_scene --release
# (Default is export mode)

Why It Happens

Not understanding the default behavior.

The Fix

# For preview (interactive window)
cargo run --example my_scene --release -- --preview

# For export (video)
cargo run --example my_scene --release -- --export

Remember

  • No flag → Export (default)
  • --preview → Interactive window
  • --export → Explicit export

14. Expecting Frame-Based Animation

The Mistake

// Thinking in frames
timeline
.animate(id)
.at(60.0) // ❌ Thinking "frame 60"
.for_duration(120.0) // ❌ Thinking "120 frames"
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn();

Why It Happens

Coming from frame-based animation tools.

The Fix

// Think in seconds
timeline
.animate(id)
.at(1.0) // ✅ 1 second
.for_duration(2.0) // ✅ 2 seconds
.move_to(Vec3::new(3.0, 0.0, 0.0))
.spawn();

Key Insight

Murali is time-based, not frame-based:

  • .at(1.0) = 1 second, not frame 1
  • .for_duration(2.0) = 2 seconds, not 2 frames

15. Not Checking Tattva Types

The Mistake

// Trying to access Circle properties on a Square
if let Some(circle) = scene.get_tattva_typed_mut::<Circle>(square_id) {
circle.radius = 2.0; // ❌ square_id is not a Circle!
}

Why It Happens

Mixing up IDs or not tracking tattva types.

The Fix

// Keep track of what type each ID is
let circle_id = scene.add_tattva(Circle::new(1.0, 48, color), Vec3::ZERO);
let square_id = scene.add_tattva(Square::new(1.2, color), Vec3::new(2.0, 0.0, 0.0));

// Use the correct type
if let Some(circle) = scene.get_tattva_typed_mut::<Circle>(circle_id) {
circle.radius = 2.0; // ✅ Correct type
}

Quick Checklist

Before running your scene, check:

  • All animations end with .spawn()
  • Camera is positioned (usually Vec3::new(0.0, 0.0, 10.0))
  • Using world units, not pixels
  • Called scene.play(timeline)
  • Animation durations > 0
  • Colors have alpha = 1.0 (unless intentionally transparent)
  • Running with --release flag
  • Using --preview flag for interactive window
  • Staged objects (hidden) before .appear() animations
  • Using .draw() only on paths/lines, not filled shapes

What's Next?