⚠️ Beta State

PyBevy is in an early and experimental stage. The API is incomplete, subject to breaking changes without notice, and you should expect bugs. Many features are still under development.

Sprite Animation

Animate sprites in response to keyboard input with configurable FPS.

Introduction

This example extends sprite sheet animation with keyboard-triggered playback. Two sprites run at different frame rates, and pressing arrow keys triggers their animations.

from dataclasses import dataclass
from pybevy.prelude import *
from pybevy.image import TextureAtlasLayout, TextureAtlas
from pybevy.math import UVec2

Animation Configuration

AnimationConfig stores frame range, FPS, and a timer that controls playback speed.

@component
@dataclass
class AnimationConfig(Component):
    first_sprite_index: int
    last_sprite_index: int
    fps: int
    frame_timer: Timer
 
    @staticmethod
    def new(first: int, last: int, fps: int) -> "AnimationConfig":
        return AnimationConfig(
            first_sprite_index=first,
            last_sprite_index=last,
            fps=fps,
            frame_timer=Timer(1.0 / float(fps), TimerMode.ONCE),
        )

Marker Components

Separate markers for left and right sprites so each can be triggered independently.

@component
class LeftSprite(Component):
    pass
 
 
@component
class RightSprite(Component):
    pass

Setup

Load the sprite sheet and spawn two sprites — one on the left at 10 FPS, one on the right at 20 FPS.

def setup(
    commands: Commands,
    asset_server: AssetServer,
    atlas_layouts: ResMut[Assets[TextureAtlasLayout]],
) -> None:
    commands.spawn(Camera2d())
    texture = asset_server.load_image("textures/rpg/chars/gabe/gabe-idle-run.png")
    layout = TextureAtlasLayout.from_grid(
        tile_size=UVec2(24, 24), columns=7, rows=1, padding=None, offset=None
    )
    atlas_layout = atlas_layouts.add(layout)
 
    config_left = AnimationConfig.new(1, 6, 10)
    commands.spawn(
        Sprite.from_atlas_image(
            texture, TextureAtlas(layout=atlas_layout, index=config_left.first_sprite_index)
        ),
        Transform.from_scale(Vec3.splat(6.0)).with_translation(Vec3(-70.0, 0.0, 0.0)),
        LeftSprite(),
        config_left,
    )
 
    config_right = AnimationConfig.new(1, 6, 20)
    commands.spawn(
        Sprite.from_atlas_image(
            texture, TextureAtlas(layout=atlas_layout, index=config_right.first_sprite_index)
        ),
        Transform.from_scale(Vec3.splat(6.0)).with_translation(Vec3(70.0, 0.0, 0.0)),
        RightSprite(),
        config_right,
    )

Animation System

Advance frames when the timer completes. Reset the timer to repeat the animation.

def execute_animations(time: Res[Time], query: Query[tuple[Mut[AnimationConfig], Mut[Sprite]]]) -> None:
    for config, sprite in query:
        config.frame_timer.tick(time.delta_secs())
        if config.frame_timer.just_finished() and sprite.texture_atlas is not None:
            atlas = sprite.texture_atlas
            if atlas.index == config.last_sprite_index:
                atlas.index = config.first_sprite_index
            else:
                atlas.index += 1
            config.frame_timer = Timer(1.0 / float(config.fps), TimerMode.ONCE)

Running the App

@entrypoint
def main(app: App) -> App:
    return (
        app
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, execute_animations)
    )
 
if __name__ == "__main__":
    main().run()

Running this example

Use PyBevy's hot reload feature to run and develop this example. If you don't have PyBevy installed, check out the Quick Start guide.

$pybevy watch sprite_animation.py

The code will reload automatically when you make changes to the file.


From Python to Rust

Notice how the core concepts in the code—Commands, Assets, App, and Systems—are identical to the original Bevy example?

This is the power of pybevy! It lets you learn Bevy's powerful, data-driven architecture in friendly Python.

When your project grows and you're ready for maximum, native performance, you'll already know the concepts to start writing systems in Bevy Engine with Rust.