Rust游戏开发库bevy_asset_loader的使用:简化资源加载流程,优化Bevy引擎资产管理

Rust游戏开发库bevy_asset_loader的使用:简化资源加载流程,优化Bevy引擎资产管理

基本介绍

bevy_asset_loader是一个Bevy引擎插件,通过可衍生的AssetCollection特性减少了游戏资产管理的样板代码。它可以自动加载实现了该特性的结构体,这些资产集合包含对游戏资产的句柄,并在加载后作为资源提供给系统使用。

加载状态的使用

加载状态负责在可配置的Bevy状态中管理加载过程。以下是一个基本示例:

use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<MyStates>()
        .add_loading_state(
            LoadingState::new(MyStates::AssetLoading)
                .continue_to_state(MyStates::Next)
                .load_collection::<AudioAssets>(),
        )
        .add_systems(OnEnter(MyStates::Next), start_background_audio)
        .run();
}

#[derive(AssetCollection, Resource)]
struct AudioAssets {
    #[asset(path = "audio/background.ogg")]
    background: Handle<AudioSource>,
}

fn start_background_audio(mut commands: Commands, audio_assets: Res<AudioAssets>) {
    commands.spawn((AudioPlayer(audio_assets.background.clone()), PlaybackSettings::LOOP));
}

#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum MyStates {
    #[default]
    AssetLoading,
    Next,
}

动态添加资产集合

我们可以在应用程序的任何位置使用configure_loading_state向加载状态添加额外的资产集合:

use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

struct PlayerPlugin;

impl Plugin for PlayerPlugin {
    fn build(&self, app: &mut App) {
        app
            .configure_loading_state(
                LoadingStateConfig::new(MyStates::AssetLoading)
                    .load_collection::<PlayerAssets>(),
            );
    }
}

#[derive(AssetCollection, Resource)]
struct PlayerAssets {
    #[asset(path = "images/player.png")]
    sprite: Handle<Image>,
}

#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum MyStates {
    #[default]
    AssetLoading,
    Next,
}

完整示例代码

以下是一个完整的示例,展示了如何使用bevy_asset_loader加载不同类型的资产:

use bevy::prelude::;*
use bevy_asset_loader::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<GameState>()
        .add_loading_state(
            LoadingState::new(GameState::Loading)
                .continue_to_state(GameState::Playing)
                .load_collection::<GameAssets>()
        )
        .add_systems(OnEnter(GameState::Playing), setup_game)
        .run();
}

#[derive(AssetCollection, Resource)]
struct GameAssets {
    // 单个音频文件
    #[asset(path = "audio/background.ogg")]
    background_music: Handle<AudioSource>,
    
    // 纹理图集
    #[asset(texture_atlas_layout(tile_size_x = 64, tile_size_y = 64, columns = 8, rows = 1))]
    atlas_layout: Handle<TextureAtlasLayout>,
    #[asset(path = "images/spritesheet.png")]
    spritesheet: Handle<Image>,
    
    // 多个图像文件
    #[asset(paths("images/player.png", "images/enemy.png"), collection(typed))]
    character_images: Vec<Handle<Image>>,
    
    // 动态资产
    #[asset(key = "ui.font")]
    font: Handle<Font>,
}

#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum GameState {
    #[default]
    Loading,
    Playing,
}

fn setup_game(
    mut commands: Commands,
    assets: Res<GameAssets>,
    mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
) {
    // 创建纹理图集
    let atlas = TextureAtlasLayout::from_grid(
        Vec2::new(64.0, 64.0), 
        8, 
        1, 
        None, 
        None
    );
    let atlas_handle = texture_atlases.add(atlas);
    
    // 播放背景音乐
    commands.spawn(AudioBundle {
        source: assets.background_music.clone(),
        settings: PlaybackSettings::LOOP,
    });
    
    // 使用加载的资产创建精灵
    for (i, image) in assets.character_images.iter().enumerate() {
        commands.spawn(SpriteBundle {
            texture: image.clone(),
            transform: Transform::from_xyz(i as f32 * 100.0, 0.0, 0.0),
            ..default()
        });
    }
}

动态资产配置

动态资产允许将资产配置存储在资产文件中,而不是硬编码在代码中:

#[derive(AssetCollection, Resource)]
struct ImageAssets {
    #[asset(key = "player")]
    player: Handle<Image>,
    #[asset(key = "tree")]
    tree: Handle<Image>,
}

对应的动态资产文件(.assets.ron):

({
    "player": File (
        path: "images/player.png",
    ),
    "tree": File (
        path: "images/tree.png",
    ),
})

不使用加载状态的用法

虽然加载状态模式很有用,但也可以在不使用加载状态的情况下使用bevy_asset_loader:

use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_collection::<MyAssets>()
        .run();
}

#[derive(AssetCollection, Resource)]
struct MyAssets {
    #[asset(texture_atlas_layout(tile_size_x = 64, tile_size_y = 64, columns = 8, rows = 1))]
    layout: Handle<TextureAtlasLayout>,
    #[asset(path = "images/sprite_sheet.png")]
    sprite: Handle<Image>,
}

支持的资产字段类型

bevy_asset_loader支持多种资产字段类型,包括:

  • 单个文件资产(Handle<T>
  • 纹理图集(需启用2d特性)
  • 标准材质(需启用3d特性)
  • 文件夹内容(不适用于Web构建)
  • 路径列表
  • 实现FromWorld的类型

进度跟踪

启用progress_tracking特性后,可以与iyes_progress集成,在加载状态期间跟踪资产加载进度,例如用于显示进度条。

许可证

bevy_asset_loader采用双重许可:

  • MIT许可证
  • Apache-2.0许可证

兼容性

bevy_asset_loader的主分支与最新的Bevy版本兼容。当前版本支持Bevy 0.16。


1 回复

Rust游戏开发库bevy_asset_loader的使用指南

简介

bevy_asset_loader是一个为Bevy游戏引擎设计的扩展库,旨在简化资源加载流程并优化资产管理。它提供了一种声明式的方式来定义和加载游戏资源,减少了手动管理资源状态的样板代码。

主要特性

  • 简化资源加载状态管理
  • 声明式资源定义
  • 支持进度跟踪
  • 与Bevy的ECS系统无缝集成
  • 支持多种资源类型

安装

Cargo.toml中添加依赖:

[dependencies]
bevy = "0.10"
bevy_asset_loader = "0.17"

完整示例代码

use bevy::prelude::*;
use bevy_asset_loader::prelude::*;

// 定义游戏状态
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum GameState {
    #[default]
    Loading,  // 加载状态
    Menu,     // 菜单状态
    Game,     // 游戏状态
}

// 定义游戏资源集合
#[derive(AssetCollection, Resource)]
struct GameAssets {
    #[asset(path = "textures/player.png")]
    player_texture: Handle<Image>,
    
    #[asset(path = "sounds/background.ogg")]
    background_music: Handle<AudioSource>,
    
    #[asset(path = "fonts/arial.ttf")]
    font: Handle<Font>,
}

// 定义菜单资源集合
#[derive(AssetCollection, Resource)]
struct MenuAssets {
    #[asset(path = "textures/button.png")]
    button_texture: Handle<Image>,
    
    #[asset(path = "fonts/menu.ttf")]
    menu_font: Handle<Font>,
}

fn main() {
    App::new()
        // 添加默认插件
        .add_plugins(DefaultPlugins)
        
        // 设置加载状态
        .add_loading_state(
            LoadingState::new(GameState::Loading)
                .continue_to_state(GameState::Menu)
                // 加载游戏资源
                .load_collection::<GameAssets>()
                // 加载菜单资源
                .load_collection::<MenuAssets>()
                // 添加进度跟踪
                .with_progress(Progress::new(ProgressCounter::default()))
        )
        
        // 添加游戏状态
        .add_state::<GameState>()
        
        // 添加系统
        .add_systems(OnEnter(GameState::Menu), setup_menu)
        .add_systems(OnEnter(GameState::Game), setup_game)
        .add_systems(Update, handle_menu_input)
        
        .run();
}

// 菜单系统
fn setup_menu(
    mut commands: Commands,
    menu_assets: Res<MenuAssets>,
) {
    commands.spawn(Camera2dBundle::default());
    
    // 创建按钮
    commands.spawn(SpriteBundle {
        texture: menu_assets.button_texture.clone(),
        transform: Transform::from_xyz(0.0, 0.0, 0.0),
        ..default()
    });
    
    // 创建菜单文本
    commands.spawn(Text2dBundle {
        text: Text::from_section(
            "Press SPACE to Start",
            TextStyle {
                font: menu_assets.menu_font.clone(),
                font_size: 30.0,
                color: Color::WHITE,
            },
        ),
        transform: Transform::from_xyz(0.0, -100.0, 0.0),
        ..default()
    });
}

// 游戏系统
fn setup_game(
    mut commands: Commands,
    game_assets: Res<GameAssets>,
) {
    commands.spawn(Camera2dBundle::default());
    
    // 创建玩家精灵
    commands.spawn(SpriteBundle {
        texture: game_assets.player_texture.clone(),
        transform: Transform::from_xyz(0.0, 0.0, 0.0),
        ..default()
    });
    
    // 创建游戏文本
    commands.spawn(Text2dBundle {
        text: Text::from_section(
            "Hello Bevy!",
            TextStyle {
                font: game_assets.font.clone(),
                font_size: 60.0,
                color: Color::WHITE,
            },
        ),
        transform: Transform::from_xyz(0.0, 200.0, 0.0),
        ..default()
    });
}

// 处理菜单输入
fn handle_menu_input(
    keyboard_input: Res<Input<KeyCode>>,
    mut next_state: ResMut<NextState<GameState>>,
) {
    if keyboard_input.just_pressed(KeyCode::Space) {
        next_state.set(GameState::Game);
    }
}

最佳实践

  1. 按功能模块组织资源集合,而不是将所有资源放在一个集合中
  2. 为不同的游戏状态(菜单、游戏、结束等)使用不同的资源集合
  3. 对于大型资源,考虑使用动态加载和卸载
  4. 利用进度跟踪提供加载反馈给玩家

总结

bevy_asset_loader通过提供声明式的资源管理方式,大大简化了Bevy游戏开发中的资源加载流程。它减少了手动状态管理的代码量,使开发者能够更专注于游戏逻辑的实现。

回到顶部