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.
States and Run Conditions
Manage game states (menu, playing, paused) and conditionally run systems.
Introduction
Most games have distinct phases: a main menu, gameplay, a pause screen. pybevy
provides States to model these phases and run conditions to control which
systems execute in each phase.
@statedecorator +Enum: Define your game states.OnEnter(state)/OnExit(state): Run systems on state transitions.run_if(): Only run a system when a condition is true.in_state(): A built-in condition that checks the current state.
from enum import Enum, auto
from pybevy.prelude import *
from pybevy.ecs import state, in_state, NextState, State, run_if, OnEnter, OnExitDefining States
Use the @state decorator on an Enum. The first variant is the default
when you use app.init_state().
@state
class GameState(Enum):
MENU = auto()
PLAYING = auto()
PAUSED = auto()Setup Systems for Each State
OnEnter(GameState.MENU) systems run once when the state changes TO MENU.
OnExit(GameState.MENU) systems run once when the state changes AWAY FROM MENU.
def setup_menu(commands: Commands):
print(">> Entering MENU: Press Enter to start")
def cleanup_menu(commands: Commands):
print(">> Leaving MENU")
def setup_game(commands: Commands):
commands.spawn(Camera3d(), Transform.from_xyz(0.0, 5.0, 10.0).looking_at(Vec3.ZERO, Vec3.Y))
commands.spawn(DirectionalLight(illuminance=5000.0), Transform.IDENTITY.looking_at(Vec3(-1.0, -1.0, -1.0), Vec3.Y))
print(">> Entering PLAYING: Press P to pause, Escape to quit to menu")
def setup_pause(commands: Commands):
print(">> PAUSED: Press P to resume")Conditional Systems
run_if() wraps a system so it only executes when a condition returns True.
in_state(value) is a built-in condition that checks the current game state.
def menu_input(
keyboard: Res[ButtonInput],
next_state: ResMut[NextState],
):
if keyboard.just_pressed(KeyCode.Enter):
next_state.set(GameState.PLAYING)
def game_input(
keyboard: Res[ButtonInput],
next_state: ResMut[NextState],
):
if keyboard.just_pressed(KeyCode.KeyP):
next_state.set(GameState.PAUSED)
if keyboard.just_pressed(KeyCode.Escape):
next_state.set(GameState.MENU)
def pause_input(
keyboard: Res[ButtonInput],
next_state: ResMut[NextState],
):
if keyboard.just_pressed(KeyCode.KeyP):
next_state.set(GameState.PLAYING)Custom Run Conditions
You can write any function that returns bool as a run condition.
Condition functions can take system parameters just like regular systems.
def is_debug_mode() -> bool:
return True # Toggle this for debugging
def debug_system():
pass # Only runs when is_debug_mode() returns TrueRunning the App
We register state-specific systems with OnEnter/OnExit, and use run_if
with in_state to gate update systems to the correct state.
@entrypoint
def main(app: App) -> App:
return (
app
.add_plugins(DefaultPlugins)
.init_state(GameState)
# Menu state
.add_systems(OnEnter(GameState.MENU), setup_menu)
.add_systems(OnExit(GameState.MENU), cleanup_menu)
.add_systems(Update, run_if(menu_input, in_state(GameState.MENU)))
# Playing state
.add_systems(OnEnter(GameState.PLAYING), setup_game)
.add_systems(Update, run_if(game_input, in_state(GameState.PLAYING)))
# Paused state
.add_systems(OnEnter(GameState.PAUSED), setup_pause)
.add_systems(Update, run_if(pause_input, in_state(GameState.PAUSED)))
# Debug system with custom condition
.add_systems(Update, run_if(debug_system, is_debug_mode))
)
if __name__ == "__main__":
main().run()When you run this, the app starts in MENU state. Press Enter to transition
to PLAYING, then P to toggle PAUSED. Each transition triggers the matching
OnEnter/OnExit systems, and only the systems guarded by the correct
in_state() condition will run each frame.