#plugin #bevy-plugin #plugin-system #mod #boilerplate #startup #game-engine

mod_plugins_macros

减少 Bevy 游戏引擎中的插件样板代码

1 个不稳定版本

0.1.0 2024 年 3 月 23 日

#2223游戏开发


mod_plugins 中使用

MIT 许可证

21KB
412

文件 Bevy 插件

这是一个从 Bevy 游戏引擎插件系统中移除更多样板代码的实验/项目。虽然插件系统没有太多样板代码,但这是一个使用属性宏进一步减少它的实验。这是通过将 plugin 属性宏应用到您的代码中的 mod 模块来实现的,mod 块被有效地删除,并生成了一个插件。以下是一个示例

// Create a plugin named `TestPlugin` (snake case names of the mod is turned into cammel case).
#[plugin]
mod test_plugin {

    // This system will be run in the `Startup` schedule.
    #[startup]
    fn setup() { some system ... }
}

标记属性

系统、函数、结构体和枚举可以通过属性(如上面所示)进行标记,这些属性应用了各种插件相关功能。

启动和更新系统

StartupUpdate 调度对于 Bevy 插件至关重要。因此,要向这些调度添加系统,只需使用 #[startup]#[update] 进行标记。

// Create a plugin named `TestPlugin`.
#[plugin]
mod test_plugin {

    // This system will be run in the `Startup` schedule.
    #[startup]
    fn setup() { some system ... }

    // This system will be run in the `Update` schedule.
    #[update]
    fn update() { some system ... }
}

在进入/退出状态时运行系统

能够在从中心到Bevy的状态中运行系统。通常,这可以通过使用 OnEnter(<某种状态>)OnExit(<某种状态>) 调度来完成。这可以通过模插件通过将以下内容应用于您的系统 #[enter(<某种状态>)]#[exit(<某种状态>)] 来完成,就像您可以使用 #[startup]#[update] 上面那样。

在事件系统中运行

当使用Bevy时,在事件被触发时运行某些内容是很常见的。这个标记属性可以用来在事件被触发时运行一个系统。此属性是 #[event(<某种事件类型>)]。这些系统将自动读取添加到世界中的临时资源 Res<Current<相同的事件类型>>,以便这些系统可以访问已触发的事件。

// Create a plugin named `TestPlugin`.
#[plugin]
mod test_plugin {
    // This system is run every time a `KeyboardInput` is fired.
    #[event(KeyboardInput)]
    fn keyboard_input() {
        println!("Input {current:?}");
    }
}

资源初始化工厂和系统

Bevy插件还负责初始化资源,虽然您可以按默认实现初始化资源,我们将在后面讨论,插件宏提供了两个标记属性,可用于初始化这些资源。

第一个是 #[resource_factory],它允许您标记一个不接受任何输入并返回创建的资源的功能。此函数在插件构建时运行,并将返回的资源添加到应用中。以下是一个示例

#[plugin]
mod test_plugin {
    // When the plugin is built, this creates and adds `ResourceB` to the app.
    #[resource_factory]
    fn create_b() -> ResourceB { ResourceB(2) }
}

第二个是 #[resource_system],它允许您标记一个返回资源的系统。该系统在启动时运行,并将返回的资源添加到世界。以下是一个示例

#[plugin]
mod test_plugin {
    // On `Startup`, this creates and adds `ResourceC` to the app.
    #[resource_system]
    fn create_resource(
        mut commands: Commands,
        a: Res<ResourceA>,
        b: Res<ResourceB>
    ) -> ResourceC {
        // camera
        commands.spawn(Camera3dBundle {
            transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
            ..default()
        });

        ResourceC(a.0 + b.0)
    }
}

构建函数

然而,有时在插件构建时访问应用可能是必要的,就像您使用正常的Bevy插件那样。您可以通过标记一个返回无内容且接受对 App 的可变引用的函数来实现,该函数用 #[build] 标记。以下是如何实现此操作的示例

#[plugin]
mod test_plugin {
    // This is run when the plugin is built.
    #[build]
    fn on_build(app: &mut App) {
        ... do whatever with the app
    }
}

自动初始化事件

插件需要能够将它们的事件添加到 App。您可以通过在创建的事件中添加 #[init_event] 标记属性来实现这一点,并在 #[plugin] 模块中创建该事件。当插件构建时,事件将通过插件添加到 App

#[plugin]
mod test_plugin {
    #[init_event]
    #[derive(Event)]
    pub struct SomeEvent(pub i32);
}

自动初始化资源

资源可以选择通过它们自己的 Default 实现进行初始化。要这样做,只需在带有 #[plugin] 标记的模中创建的资源上标记 #[init_resource]。然后,当插件构建时,资源将通过其 Default 实现添加到 App

#[plugin]
mod test_plugin {
    #[init_resource]
    #[derive(Resource)]
    pub struct SomeResource {
        name: String,
        num: i32
    }
}

自动初始化状态

当插件构建时,插件还负责将 State 添加到 App。您可以通过两种方式实现这一点,要么通过 StateDefault 实现或通过指定一个起始 State。您可以通过在 #[plugin] 模中创建的 State 上标记 #[init_state] 来实现这一点,如果 State 有一个 Default 实现的话。否则,您需要通过在 State 上标记 #[init_state(State::Kind)] 来指定起始 State

#[plugin]
mod test_plugin {
    #[init_state]
    #[derive(States, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
    pub enum ExampleState {
        #[default]
        StateA,
        StateB
    }

    ----------- OR -----------

    #[init_state(ExampleState::StateA)]
    #[derive(States, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
    pub enum ExampleState {
        StateA,
        StateB
    }
}

注册类型

为了反射,您需要将类型注册到 App。通常您会在插件构建时通过在 #[plugin] 模中标记一个要注册的结构来调用 register_type 函数。您可以这样做,通过标记一个带有 #[register] 属性标记的结构。

#[plugin]
mod test_plugin {
    #[register]
    pub struct SomeType(pub u32);
}

可执行文件

可以为类似结构体或枚举等数据添加一些有用的系统。对于需要根据实际类型运行不同系统的泛型类型,这非常有用。例如,服务器向客户端发送一个 "action",客户端运行系统来应用该 "action"。下面是如何将一个结构体变为 "可执行" 结构体的示例

pub struct ExecutableData {
    name: String,
    data: i32
}

#[executable(ExecutableData)]
fn execute_data() { ... some system }

要执行上述系统,我可以创建一个 ExecutableData 实例并调用为 ExecutableData 实现的 execute 函数,该函数接受一个对 World 的可变引用。这将在给定世界中运行该系统。

fn use_executable(world: &mut World, data: ExecutableData) {
    data.execute(world);
}

依赖关系

~19–46MB
~728K SLoC