#bevy #scripting #lua #lua-script #scripting-language #gamedev

bin+lib bevy_mod_scripting

Bevy中的多语言脚本

10个版本 (5个破坏性更新)

0.6.0 2024年4月4日
0.5.0 2024年3月6日
0.4.0 2024年3月6日
0.3.0 2023年5月8日
0.1.1 2022年8月3日

#291 in 游戏开发

Download history 21/week @ 2024-04-22 2/week @ 2024-05-13 8/week @ 2024-05-20 7/week @ 2024-06-03 10/week @ 2024-06-10 2/week @ 2024-06-17 8/week @ 2024-06-24 5/week @ 2024-07-01 59/week @ 2024-07-29 11/week @ 2024-08-05

每月 70次下载

MIT 许可证

355KB
9K SLoC

Bevy 脚本

尽管 Bevy 不会直接支持脚本,但是正在努力将其整合。这个工具包代表了在 Bevy 现有框架中启用脚本的初步尝试。需要注意的是,这还是一个正在进行中的工作,还没有优化或完成。随着 Bevy 的发展,这个 API 预计将会有重大变化。

要详细了解这个工具包的工作方式,请参阅 architecture.md

为什么使用脚本?

  • 无需重新编译整个工具包即可刷新游戏机制
  • 将游戏逻辑封装在脚本中,为模组制作者轻松创建自定义内容铺平了道路
  • 使用更简单的语言在脚本中实现游戏逻辑/UI,使您的团队的非程序员也能参与开发

功能

  • 热重载脚本
  • Lua、Teal、Rhai 和 Rune 集成
  • 为 Lua 自动生成 Bevy 绑定
  • CLI rustc 扩展,用于生成自己的 Lua 绑定
  • 基于事件的钩子(例如 on_update
  • 灵活的事件调度(例如根据事件处理事件的处理阶段)
  • 每个实体可以有多个脚本
  • 一个实体可以有相同脚本的多个实例
  • 广泛的回调参数类型支持
  • 用于生成脚本原生文档的实用程序
  • 通过 require 加载外部 lua 库(由于潜在的不安全性,启用 unsafe_lua_modules cargo 功能)

支持

对语言的支持分为三个级别

  1. ScriptHost 实现,在此语言中可以加载、调度和运行脚本,支持自定义 APIProvider
  2. 实现了 Bevy API 提供者,它使可以访问 entityworld 等,并提供对至少基本操作的支持,如 get_componentadd_componentspawn
  3. 存在用于生成代理包装结构的宏,可用于自定义类型,并能够添加脚本侧功能
  4. 为原生 Bevy 结构自动生成宏实例化

目前支持的语言如下

语言 支持级别 文档生成
Lua 4
Rhai 2
Rune 1

用法

安装

要安装

  • 将此crate添加到您的Cargo.toml文件中的依赖项
    • 该crate仍在开发中,因此我建议将版本锁定到git提交
  • 将ScriptingPlugin添加到您的应用中
  • 添加您计划使用的ScriptHosts(add_script_hostadd_script_host_to_set
    • 确保将其附加到在可能生成、修改/创建/删除脚本组件的系统之后运行的系统集
  • 添加脚本处理程序以捕获您期望的优先级范围内的事件(add_script_handler_to_setadd_script_handler
  • 添加与您的脚本宿主对应的生成ScriptEvents的系统
  • 添加向实体添加ScriptCollection组件并将它们填充脚本的系统

以下是一个示例


fn main() -> std::io::Result<()> {
    let mut app = App::new();
        app.add_plugins(ScriptingPlugin)
        .add_plugins(DefaultPlugins)
        // pick and register only the hosts you want to use
        // use any system set AFTER any systems which add/remove/modify script components
        // in order for your script updates to propagate in a single frame
        .add_script_host::<RhaiScriptHost<MyRhaiArgStruct>>(PostUpdate)
        .add_script_host::<LuaScriptHost<MyLuaArgStruct>>(PostUpdate)

        // the handlers should be ran after any systems which produce script events.
        // The PostUpdate set is okay only if your API doesn't require the core Bevy systems' commands
        // to run beforehand.
        // Note, this setup assumes a single script handler system set with all events having identical
        // priority of zero (see examples for more complex scenarios)
        .add_script_handler::<LuaScriptHost<MyLuaArg>, 0, 0>(
            CoreSet::PostUpdate,
        )
        .add_script_handler::<RhaiScriptHost<RhaiEventArgs>, 0, 0>(
            CoreSet::PostUpdate,
        )

        // generate events for scripts to pickup
        .add_system(trigger_on_update_lua)
        .add_system(trigger_on_update_rhai)

        // attach script components to entities
        .add_startup_system(load_a_script);
    app.run();

    Ok(())
}

触发脚本回调

脚本通过分发ScriptEvents被激活。此crate使用自定义优先级事件写入器和读取器,这意味着事件带有相关的优先级。这个优先级与您的事件管道结合,影响事件处理的顺序。优先级为0被认为是最高。

此机制可用于构建类似于Unity或其他游戏引擎中找到的游戏循环。

以下是一个示例事件分发系统

use bevy::prelude::*;
use bevy_mod_scripting::prelude::*;


// event callback generator for lua
#[cfg(feature = "lua")]
pub fn trigger_on_update_lua(mut w: PriorityEventWriter<LuaEvent<()>>) {
    let event = LuaEvent::<()> {
        hook_name: "on_update".to_string(),
        args: (),
        recipients: Recipients::All
    };

    w.send(event,0);
}

添加脚本

脚本由以下组成

  • 对其代码文件的引用,表示为资产句柄
  • 名称,通常是相对于资产文件夹的路径

脚本通过bevy_mod_scripting::ScriptCollection组件与实体关联,如下所示

use std::sync::Mutex;
use bevy::prelude::*;
use bevy_mod_scripting::prelude::*;

// An example of a startup system which loads the lua script "console_integration.lua"
// placed in "assets/scripts/" and attaches it to a new entity
#[cfg(feature = "lua")]
pub fn load_a_script(
    server: Res<AssetServer>,
    mut commands: Commands,
) {
    // this handle is kept by the script so it will not be unloaded
    let path = "scripts/console_integration.lua".to_string();
    let handle = server.load::<LuaFile>(&path);


    commands.spawn(()).insert(ScriptCollection::<LuaFile> {
        scripts: vec![Script::<LuaFile>::new(
            path, handle,
        )],
    });
}

定义API

要使API可供脚本使用,您需要实现APIProvider特质。这可以通过Appadd_api_provider方法进行注册。与插件类似,APIProviders功能相同

use ::std::sync::Mutex;
use bevy_mod_scripting::prelude::*;

#[cfg(feature = "lua")]
#[derive(Default)]
pub struct LuaAPI;

#[cfg(feature = "lua")]
impl APIProvider for LuaAPI {
    type APITarget = Mutex<Lua>;
    type DocTarget = LuaDocFragment;
    type ScriptContext = Mutex<Lua>;

    fn attach_api(&mut self, ctx: &mut Self::APITarget) -> Result<(),ScriptError> {
        // ... access the lua context here when the script loads
        Ok(())
    }
}

按如下方式注册您的API提供者

    app.add_plugins(DefaultPlugins)
        .add_plugins(ScriptingPlugin)
        .add_script_host::<LuaScriptHost<MyLuaArg>>(PostUpdate)
        .add_api_provider::<LuaScriptHost<MyLuaArg>>(Box::new(LuaAPI))
        //...

APIProvider接口还包括setup_scriptget_doc_fragment方法。默认情况下,这些方法不执行任何操作。但是,它们可以用于特定目的。例如,可以使用get_doc_fragment来生成文档(参见图例),并且setup_script可以确保每个脚本的一次性设置,例如设置Lua包路径。

文档生成

通过Appupdate_documentation构建器特质方法在运行时公开文档功能

use bevy::prelude::*;
use bevy_mod_scripting::prelude::*;

#[cfg(feature = "lua")]
fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.add_plugins(DefaultPlugins)
        .add_plugins(ScriptingPlugin)
        .add_script_host::<LuaScriptHost<()>>(PostUpdate)
        // Note: This is a noop in optimized builds unless the `doc_always` feature is enabled!
        // this will pickup any API providers added *BEFOREHAND* like this one
        .add_api_provider::<LuaScriptHost<()>>(Box::new(LuaBevyAPIProvider))
        .add_api_provider::<LuaScriptHost<()>>(Box::new(LuaCoreBevyAPIProvider))
        .update_documentation::<LuaScriptHost<()>>()
        .add_script_handler::<LuaScriptHost<()>, 0, 0>(PostUpdate);

    Ok(())
}

Lua

tealrmluacrate的包装,提供了Lua文档生成的机制。它可以生成Lua的静态类型d.tl文件,通过teal项目,但使用teal不是生成文档所必需的。

请参阅此示例进行演示。

此crate的Bevy API文档在每个版本发布时自动生成,可以在这里这里找到。您可能需要调整自动生成的assets/doc/tealr_doc_gen_config.json文件中的page_root,将其设置为路径,例如assets/doc/YourAPI

Teal - Lua静态类型

青绿色是向您的bevy游戏引入lua的推荐方式。然而,由于它对您的资源结构(scriptscripts/build,位于assets下的文件夹)有很强的观点,并且还需要安装lua + teal + tealr_doc_gen(使用以下命令安装:cargo install --git https://github.com/lenscas/tealr_doc_gen --rev 91afd4a528e7f5b746ac3a6b299c422b42c05db6)来解锁此功能,因此该功能被锁定在teal cargo功能之后。(请参阅https://github.com/teal-language/tltealr)。

一旦启用,.tl文件可以作为lua脚本加载,除了.lua文件外,还可以在运行时编译。支持完整的热重载。当您准备好发布游戏时,只需从assets/scripts目录运行tl build来编译您的teal文件。这将在assets/scripts/build下生成.lua文件。您可以使用bevy_mod_scripting::lua_path宏来管理加载脚本。

如果启用了teal并且您已将update_documentation步骤添加到您的应用程序中,每次在开发过程中运行/构建您的应用程序时,都会生成/同步以下内容:- 一个包含您公开的lua API文档的scripts/doc目录;- 一个包含您的lua IDE的.d.tl文件的scripts/types目录;- 如果尚不存在,将生成一个scripts/tlconfig.lua文件;- 带有.tl扩展的任何脚本都将编译为lua代码并进行类型检查。在优化后的发布构建中,不会发生这些操作(没有debug_asserts)。

推荐的工作流程是使用vscode和官方的teal扩展,并在工作空间的根目录中添加一个额外的tlconfig.lua文件,内容如下:

return {
    include_dir = {
        "path_to_your_lib/",
    }
}

配置

  • SCRIPT_DOC_DIR - 文档在assets/scripts/docs生成,或如果设置了此环境变量,则生成到该路径。

示例

要查看此库的更复杂的应用,请查看示例

以下是展示游戏_of_life示例的视频:观看视频

依赖关系

~39–79MB
~1.5M SLoC