bevy_new_2d

repository·main·Indexed 19 days ago

https://github.com/thebevyflock/bevy_new_2d

A Bevy game template for 2D development featuring a structured project layout, built-in dev tools, and CI/CD workflows for platforms like itch.io. It includes patterns for plugin organization, screen state management, bundle functions for entity templates, and asset collection resources for preloading.

Tokens
9.8K
Snippets
34
Records
54
Agent score
67%

What's inside bevy_new_2d

  1. Project structure overview

    main

    The template provides a pre-organized directory structure to help manage a 2D game. While you can move files as needed, the following paths are provided by default:

    PathDescription
    src/main.rsApp setup
    src/asset_tracking.rsHigh-level way to load collections of asset handles as resources
    src/audio.rsMarker components for sound effects and music
    src/dev_tools.rsDev tools for dev builds (toggle with ` aka backtick)
    src/demo/Example game mechanics & content (replace with your own code)
    src/menus/Main menu, pause menu, settings menu, etc.
    src/screens/Splash screen, title screen, loading screen, etc.
    src/theme/Reusable UI widgets & theming
  2. Create entity templates with Bundle Functions

    main

    Instead of manually spawning multiple components every time, write functions that return impl Bundle. This allows you to define reusable entity templates and compose them into hierarchies.

    Capabilities:

    • Composition: Combine multiple components into one bundle.
    • Extension: Create a new bundle function that calls an existing one and adds more components.
    • Hierarchies: Use children![] within a bundle function to define complex entity trees.

    Limitations:

    • No Dependency Injection: You must pass required resources (like AssetServer) as arguments through the hierarchy.
    • No Component Replacement: You cannot easily 'override' a component within a bundle function; you must either add it via .insert() after spawning or modify the function arguments.
    pub fn monster(health: u32, transform: Transform) -> impl Bundle {
        (
            Name::new("Monster"),
            Health::new(health),
            transform,
        )
    }
    
    // Extending a bundle
    pub fn boss_monster(transform: Transform) -> impl Bundle {
        (
            monster(1000, transform),
            Better,
            Faster,
            Stronger,
        )
    }
    
    // Composing hierarchies
    pub fn dangerous_forest() -> impl Bundle {
        (
            Name::new("Dangerous Forest"),
            Transform::default(),
            children![
                monster(100, Transform::from_xyz(10.0, 0.0, 0.0)),
                boss_monster(Transform::from_xyz(0.0, 0.0, 0.0)),
            ],
        )
    }
    
    // Spawning
    fn spawn_dangerous_forest(mut commands: Commands) {
        commands.spawn(dangerous_forest());
    }
  3. Manage game screens using the Screen States pattern

    main

    Use a dedicated States enum (e.g., Screen) to represent different logical phases of your game, such as Splash, Loading, Title, or Gameplay.

    To implement a screen:

    1. Define the State: Create an enum deriving States.
    2. Setup/Teardown: Use OnEnter(Screen::Name) and OnExit(Screen::Name) schedules in a plugin to handle spawning and cleaning up entities.
    3. Automatic Cleanup: Attach the DespawnOnExit(Screen::Name) component to entities spawned for a specific screen so they are automatically removed when transitioning away.
    4. Transitions: Use the NextState<Screen> resource to trigger transitions (e.g., next_state.set(Screen::Title)).
    #define the state
    #[derive(States, Debug, Hash, PartialEq, Eq, Clone, Default)]
    pub enum Screen {
        #[default]
        Splash,
        Loading,
        Title,
        Gameplay,
        // ...
    }
    
    # handle setup and teardown
    pub(super) fn plugin(app: &mut App) {
        app.add_systems(OnEnter(Screen::Victory), spawn_victory_screen);
        app.add_systems(OnExit(Screen::Victory), reset_highscore);
    }
    
    fn spawn_victory_screen(mut commands: Commands) {
        commands.spawn((
            widget::ui_root("Victory Screen"),
            DespawnOnExit(Screen::Victory),
            children![
                // UI elements.
            ],
        ));
    }
    
    # transition
    fn enter_title_screen(mut next_state: ResMut<NextState<Screen>>) {
        next_state.set(Screen::Title);
    }
  4. Use the dev_tools plugin for development-only systems

    main

    To ensure debugging tools (like debug lines, FPS counters, or consoles) do not impact performance in production, add them to the dev_tools plugin. This plugin is designed to be included only in development builds, ensuring these systems are stripped from release builds.

    // dev_tools.rs
    pub(super) fn plugin(app: &mut App) {
        app.add_systems(Update, (draw_debug_lines, show_debug_console, show_fps_counter));
    }
  5. Organize code using the Plugin Organization pattern

    main

    Structure your codebase by grouping related systems and resources into lightweight plugins using simple functions. A good rule of thumb is to have one plugin per file. This keeps logic (like player or enemy behavior) locally grouped and easy to manage.

    In your main game module, you can aggregate these individual plugins into a single entry point.

    // game.rs
    mod enemy;
    mod player;
    mod powerup;
    
    use bevy::prelude::*;
    
    pub(super) fn plugin(app: &mut App) {
        app.add_plugins((enemy::plugin, player::plugin, powerup::plugin));
    }
    
    // player.rs
    use bevy::prelude::*;
    
    pub(super) fn plugin(app: &mut App) {
        app.add_systems(Update, (your, systems, here));
    }
  6. Preload assets using Asset Collection Resources

    main

    To avoid gameplay hitches, preload assets by defining a Resource that holds your asset Handles.

    1. Define the Resource: Create a struct with Handle fields. Use the #[dependency] attribute on fields to ensure the resource is only considered 'loaded' once those specific assets are ready.
    2. Implement FromWorld: Use FromWorld to initialize the handles using the AssetServer.
    3. Trigger Loading: Use the app.load_resource::<YourResource>() extension method (provided by src/asset_tracking.rs) in your plugin setup to start the loading process at startup.
    #define the collection
    #[derive(Resource, Asset, Clone, Reflect)]
    #[reflect(Resource)]
    struct ActorAssets {
        #[dependency]
        player: Handle<Image>,
        #[dependency]
        enemies: Vec<Handle<Image>>,
    }
    
    impl FromWorld for ActorAssets {
        fn from_world(world: &mut World) -> Self {
            let assets = world.resource::<AssetServer>();
            Self {
                player: assets.load("images/player.png"),
                enemies: vec![assets.load("images/enemy1.png")],
            }
        }
    }
    
    # start preloading in a plugin
    pub(super) fn plugin(app: &mut App) {
        app.load_resource::<ActorAssets>();
    }
  7. Create a new game using the 2D template

    main

    To start a new project with this template, you must first install the bevy_cli tool. Once installed, use the bevy new command with the --template 2d flag to scaffold your project.

    After scaffolding, initialize a GitHub repository and push your local code to it to enable the built-in CI/CD workflows.

    bevy new my_game --template 2d
  8. Hot-patching with subsecond (Experimental)

    main

    Hot-patching allows you to edit game code while the application is running without recompiling or restarting. This requires following the setup instructions for Bevy 0.17+ hot-patching systems.

    To run the game with hot-patching enabled, use the following command:

    BEVY_ASSET_ROOT='.' dx serve --hot-patch --features "bevy/hotpatching"

    When active, saving a system's code file should trigger Status: Hot-patching... in your CLI.

  9. Attach the RustRover debugger to a running game

    main

    If you started your game using a Shell Script Run Configuration, you can attach the debugger while the game is running:

    1. Go to Run > Attach to Process.
    2. Select the process matching your game's name (do not select the process named bevy).

    Note: This method does not work for web builds.

  10. Enable dynamic linking and dev tools in RustRover debugger

    main

    By default, the 'Run Native Debug' configuration disables dynamic linking and dev tools to ensure the debugger works out of the box. To enable them, follow these steps:

    1. Find your target library directory by running: rustc --print target-libdir (use rustc +nightly --print target-libdir if using nightly).
    2. Edit the Cargo Run Configuration named "Run Native Debug" (the one without a terminal icon).
    3. Add the following Environment Variable:
      • Linux/macOS: LD_LIBRARY_PATH = ./target/debug/deps:<LIBDIR_PATH>
      • Windows: PATH = .\target\debug\deps;<LIBDIR_PATH> (Replace <LIBDIR_PATH> with the output from step 1).
    4. Remove --no-default-features from the command in the Run Configuration.
    5. Click Apply and then Debug.

    Note: If using multiple Rust channels, you must add a LIBDIR_PATH for every channel you intend to use.

    rustc --print target-libdir