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 UVec2Animation 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):
passSetup
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.
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.