金庸群侠传3D重制版 jynew 的 xLua 功能特性全解析:虚拟机、互操作、热补丁与性能优化指南
【免费下载链接】jynewJinYongLegend-like RPG Game Framework with full Modding support and 10+ hours playable samples of game.项目地址: https://gitcode.com/GitHub_Trending/jy/jynew
xLua 是 Tencent 开源的高性能 Lua 与 C# 双向互操作框架,也是本项目 jynew(金庸群侠传3D重制版)Mod 体系与业务逻辑的核心脚本引擎。本篇文章以仓库内 jyx2/Assets/XLua/Doc/features.md 为主线,结合项目源码(LuaManager.cs、LuaExecutor.cs、InitLuaScripts.lua 等)纵深展开,帮助你系统掌握 xLua 的虚拟机支持、C#/Lua 互访技术、无 GC 性能优化、热补丁机制与工具链,并理解它们在 jynew 中如何支撑 Mod 化和 10+ 小时可玩内容。
一、总体能力:虚拟机、版本与平台支持
xLua 的总体设计目标是"在 Unity 中提供接近原生的 Lua 执行与互操作能力"。根据 features.md 的「总体」章节,其能力可归纳为四个维度:
| 维度 | 支持范围 |
|---|---|
| Lua 虚拟机 | Lua 5.3、LuaJIT 2.1 |
| Unity3D 版本 | 各版本均支持 |
| 平台 | Windows 64/32、Android、iOS 64/32/bitcode、OSX、UWP、WebGL |
| 互访技术 | 生成适配代码、反射 |
在 jynew 中的实际配置:项目在 LuaManager.cs 中以常量LUAJIT_ENABLE = false关闭 LuaJIT,默认走Lua 5.3虚拟机;同时在 LuaManager.cs 中保留了一段对jit的探测代码(if jit then jit.off(); jit.flush() end),说明两套虚拟机可以在代码层面共存与切换。由于 Lua 5.3 原生支持 64 位整数(long/ulong 无精度损失传递),这在处理游戏存档、数值计算等场景上是重要前提(详见后文"64 位整数"小节)。
二、互访技术:生成适配代码 vs 反射
xLua 提供两种 C#/Lua 互访技术,这是理解 xLua 一切行为的总开关:
- 生成适配代码(推荐):在编译期针对白名单内的 C# 类型生成双向适配代码,性能好、类型安全,是官方推荐方式。
- 反射:不生成代码,对安装包体积影响更小,但性能较低,且在 il2cpp 下可能受代码剪裁影响(可借助
ReflectionUse生成 link.xml 规避)。
两者的适用边界在 faq.md 中有明确说明:开发期不生成代码即可运行;build 手机版本前必须执行代码生成;性能调优/性能测试前也必须生成代码,因为"生成和不生成性能的区别还是很大的"。
在 jynew 中,反射路径真实存在:LuaManager.getCachedFunction通过luaEnv.Global.Get<LuaFunction>(name)(LuaManager.cs)按名获取 Lua 函数并缓存复用,这正是"避免每次跨语言查找全局"的工程实践(详见第七节)。
三、易用性设计:解压即用与无缝切换
features.md「易用性」小节列出的设计要点,构成了 xLua 的低上手门槛:
- 解压即可用:xLua 以 zip 包形式发布,在工程目录解压即可,无需额外配置(见 faq.md)。
- 开发期无需生成代码:编辑器下可零生成直接跑通业务逻辑。
- 生成代码与反射间可无缝切换:同一份业务代码不感知底层实现差异。
- 更简单的无 GC API:提供
LuaTable/LuaFunction的泛化无 GC 访问接口。 - 菜单简单易懂:
XLua/Generate Code、XLua/Hotfix Inject In Editor、XLua/Clear Generated Code等一键菜单。 - 配置可多份、按模块划分:支持打 Attribute 标签、静态列表、动态列表三种配置方式(详见 configure.md)。
- 自动生成 link.xml 防止代码剪裁:为
ReflectionUse类型自动写 link.xml,规避 il2cpp stripping 问题。 - Plugins 部分采用 cmake 编译:原生库源码位于
xLua_Project_Root/build,执行make_xxxx_yyyy.zz(xxxx 为平台,yyyy 为 lua53/luajit)即可编译。 - 核心代码不依赖生成代码,可随时删除生成目录:便于清理与重建,faq.md 也建议"开发期不生成代码,避免不一致导致的编译失败"。
工程提示:若手动删除生成目录后找不到
Clear Generated Code菜单,直接删除整个生成代码目录,待编译完成后重新执行生成即可(faq.md)。
四、性能设计:Lazyload 与无 GC 传递
性能是 xLua 的核心卖点,features.md「性能」小节的技术点值得逐一拆解:
Lazyload 技术:
CS命名空间下的所有 C# API 是"虚拟存在"——只有第一次访问CS.UnityEngine.GameObject或第一次把实例传给 Lua 时,才会真正加载该类型的方法、属性等元数据(faq.md 明确说明),避免用不上类型的开销。delegate/interface 映射零分配:lua 函数映射到 C# delegate、lua table 映射到 interface,在接口层面无 C# gc alloc 开销。这一点在 jynew 的
LuaToCsBridge设计中体现得淋漓尽致——项目把 C# 侧回调声明为 C# delegate(如cs_calllua.Action<string>(funName)),由 Lua 侧在初始化时注入实现。值类型 struct 传递无 gc:所有基本值类型、所有枚举、字段均为值类型的 struct(可嵌套),在 Lua 和 C# 间传递不产生 C# gc alloc。
无 GC 访问接口:
LuaTable、LuaFunction提供泛化 Get/Set 与调用接口。代码生成期静态分析:生成最优适配代码。
支持 C# 与 Lua 间指针传递:供高性能场景使用。
自动解除已 Destroy 的 UnityEngine.Object 引用:避免 Lua 侧悬挂引用问题。
关于值类型的无 GC 前提,faq.md 给出了精确定义:枚举类型与"字段只包含值类型的 struct(可嵌套)"必须加入GCOptimize配置,否则不会生成 GC 优化代码。具体配置见 configure.md 的XLua.GCOptimize小节,配合XLua.AdditionalProperties可覆盖"私有字段 + 公有属性"的 struct 场景。
五、扩展性:第三方库与生成引擎二次开发
features.md「扩展性」小节承诺两点:
- 不用改代码就可以加入 Lua 第三方扩展:借助 Lua 原生机制或自定义 loader(详见第六节)。
- 生成引擎提供接口做二次开发:
CSObjectWrapEditor.GenCodeMenu允许注册无参数函数,在执行XLua/Generate Code菜单时被触发(configure.md);CSObjectWrapEditor.GenPath可配置生成代码放置路径(默认Assets/XLua/Gen/)。
六、Lua 代码加载机制
features.md「Lua 代码加载」与 XLua教程.md 的「Lua文件加载」章节互相印证,加载路径有四条:
加载字符串:
luaenv.DoString("print('hello world')"),可加载后立即执行;也可加载后返回 delegate 或LuaFunction,调用时传脚本参数。教程明确"这种方式并不建议"日常大量使用。Resources 目录的文件:直接
require即可。注意 Unity 不认 .lua 后缀,Resources 下的 Lua 文件需加 .txt 后缀(以 TextAsset 形式打包)。自定义 loader:Lua 中
require时触发,require 参数透传给 loader,loader 读取 Lua 代码以byte[]返回;空返回表示未命中。支持将filepath改为真实路径以支持调试。完整签名与注册方式(XLua教程.md):
public delegate byte[] CustomLoader(ref string filepath); public void LuaEnv.AddLoader(CustomLoader loader)- Lua 原有的方式:
package.path等原生机制全部保留。
jynew 的落地实现:LuaManager.Init中注册了两级 loader(LuaManager.cs)——第一级从Assets/LuaScripts/{filename}.lua加载(对应框架自带脚本,如InitLuaScripts.lua、Jyx2Battle、Jyx2Configs),第二级从Assets/BuildSource/Lua/{filename}.lua加载(对应打包用基础脚本)。两者都通过ResLoader.LoadAssetSync<TextAsset>读取并以 UTF-8 字节流返回,完全符合CustomLoader契约。
推荐的启动方式:整个程序只执行一次DoString("require 'main'"),由 main.lua 负责 require 其余脚本(类似命令行lua main.lua)。jynew 的 InitLuaScripts.lua 正是这一模式的工程化:它require "LuaModuleList"(LuaModuleList.lua 中注册了ConfigMgr、Battle两个模块),通过Jyx2:AddModule(name, path)惰性加载、Jyx2:GetModule(name)按需获取、Jyx2:Init()批量初始化、Jyx2:DeInit()反初始化,所有模块挂在Jyx2全局表上。
此外,jynew 还实现了编辑器热重载:HOTRELOAD_LUA_IN_EDITOR = true时(LuaManager.cs),LoadLua直接从Assets/Mods/{curMod}/Lua/读文件(LuaManager.cs),无需 repack Lua 即可调试——这正是 Mod 化开发的关键体验。
七、Lua 调用 C#:能力全景
features.md「Lua 调用 C#」一节是 xLua 最丰富的部分,XLua教程.md 给出了对应代码示例,以下逐项展开:
7.1 对象、属性、方法、继承
- 创建对象:Lua 无
new关键字,统一用CS.UnityEngine.GameObject();构造函数支持重载,CS.UnityEngine.GameObject('helloworld')调用带 string 参数的构造。 - 静态/成员属性字段方法:
CS.UnityEngine.Time.deltaTime、CS.UnityEngine.Time.timeScale = 0.5、CS.UnityEngine.GameObject.Find('helloworld');成员方法用冒号语法糖testobj:DMFunc()(冒号自动把对象作为 self 传入)。 - 继承:子类对象可直接调用父类方法、访问父类属性;子类模块可直接调用父类静态方法/属性。
- 扩展方法:C# 定义的扩展方法在 Lua 中像普通成员方法一样使用。
性能小技巧(来自教程):频繁访问的类先用局部变量引用,如
local GameObject = CS.UnityEngine.GameObject,既省输入又提性能。
7.2 参数语义:out、ref、默认值、可变参数
- out:对应 Lua 的一个返回值。
- ref:对应 Lua 的一个参数以及一个返回值(输入 + 输出)。
- 参数默认值:C# 参数有默认值时,Lua 侧可不传。
- 可变参数:直接逐个传入即可,无需先包装成数组。例如 C# 的
void VariableParamsFunc(int a, params string[] strs)在 Lua 中调用testobj:VariableParamsFunc(5, 'hello', 'john')。
7.3 函数重载与操作符
- 重载:支持,但因 Lua 数据类型远少于 C#(int/float/double 都对应 number),可能出现无法区分重载的情况——生成代码中排前面的那个生效。解决方案是借助扩展方法区分,或参考 faq.md 的
xlua.tofunction+ 反射方案精确指定重载(注意同一MethodBase只 tofunction 一次并重复使用)。 - 操作符重载:支持
+,-,*,/,==,一元-,<,<=,%,[];其余操作符可借助扩展方法调用。
7.4 泛型、枚举、delegate、event
- 泛化方法调用:静态方法可自行封装;成员函数可通过扩展方法封装。xLua ≥ 2.1.12 后新增反射调用泛型方法的支持(
xlua.get_generic_method),但有平台限制:il2cpp 下值类型泛型参数需 C# 侧已用同样参数调用过(faq.md)。 - 枚举:支持数字/字符串到枚举转换,
CS.Tutorial.TestEnum.__CastFrom(1)、CS.Tutorial.TestEnum.__CastFrom('E1')。 - delegate:可调用、支持
+/-操作符(组合/移除调用链,右操作数可为同类型 C# delegate 或 lua 函数);一个 lua 函数可作为 C# delegate 传给 C#。 - event:
testobj:TestEvent('+', lua_event_callback)增加回调、'-'移除回调。
7.5 64 位整数与 decimal
- 64 位整数:传递无 gc 且无精度损失;Lua 5.3 下使用原生 64 位支持;可与 number 直接运算;无符号 64 位整数按 Java 方式提供一组 API(ulong 先强转 long 再传 Lua)。
- decimal:传递无 gc 且无精度损失。
7.6 table 自动转换、typeof、强转
- table 自动转换:
obj.complexField = {a = 1, b = {c = 1}}(两层嵌套 struct/class)——C# 复杂类型只要有无参构造函数,Lua 侧可直接用 table 替代,支持函数参数传递与属性赋值。 - typeof:
typeof(CS.UnityEngine.ParticleSystem)对应 C# 的 typeof,返回 Type 对象。 - Lua 侧直接 clone:支持对象克隆。
- cast "强转":当第三方库只暴露 interface/抽象类、实现类隐藏时,用
cast(calc, typeof(CS.Tutorial.Calc))指定用某类型的生成代码访问,避免落入慢速反射路径。
八、C# 调用 Lua
features.md「C# 调用 Lua」一节相对精炼,结合 XLua教程.md 可展开为:
- 调用 Lua 函数:
- delegate 方式(推荐):性能好、类型安全,但依赖生成代码(未生成代码时抛
InvalidCastException)。delegate 声明规则:每个参数对应一个输入参数;多返回值从左往右映射到 C# 的输出参数(含返回值、out、ref)。 - LuaFunction 方式:无需生成代码,通过变参
Call函数传任意类型/任意个数参数,返回object[]对应 Lua 多返回值。缺点:比 delegate 慢一个数量级、无类型检查。
- delegate 方式(推荐):性能好、类型安全,但依赖生成代码(未生成代码时抛
- 访问 Lua table:
LuaTable泛化 Get/Set,调用无 gc,可指明 Key/Value 类型;- 映射到标注了
CSharpCallLua的 interface(依赖生成代码,可读写属性、经 interface 方法调 Lua 函数); - 值拷贝到 struct/class(
luaenv.Global.Get<int>("a")、Get<string>("b")等); - 轻量 by-value 方式:映射到
Dictionary<>/List<>(要求 key/value 类型一致); - by-ref 方式:直接映射
LuaTable。
jynew 的落地实现:LuaManager通过luaEnv.Global.Get<LuaFunction>(name)获取函数后缓存进_cachedFunc(LuaManager.cs),后续Call(functionName, paras)直接调用缓存的LuaFunction.Call——这正是教程"尽量少做全局查找,初始化时获取一次并保存"建议的实践。LuaExecutor则封装了 Lua 侧异步协作(LuaExecutor.cs),把待执行脚本包进local function temp_lua_func() ... end,再用util.coroutine_call(combine(temp_lua_func, LuaExecFinished))()跑在协程里,与 C# 侧UniTaskCompletionSource桥接,从而让 C# 能await一段 Lua 逻辑执行完毕(LuaExecutor.Execute/ExecuteLuaAsync)。
架构建议(来自教程):如果 Lua 侧实现均以 delegate 和 interface 方式提供,使用方可与 xLua 完全解耦——由专门模块负责 xLua 初始化与 delegate/interface 映射,再注入到业务侧。jynew 的 LuaToCsBridge.cs(cs_calllua等 C# delegate)正是这一解耦模式的载体。
九、热补丁(Hotfix):为 C# 实现打补丁
features.md「支持为如下 C# 实现打补丁」列出:构造函数、析构函数、成员函数、静态函数、泛化函数、操作符重载、成员属性、静态属性、事件。完整操作指南见 hotfix.md,核心步骤:
9.1 启用三步走
- 添加
HOTFIX_ENABLE宏(File → Build Settings → Scripting Define Symbols);编辑器与各手机平台需分别设置,自动化打包时用代码 API 设宏不生效。 - 执行
XLua/Generate Code菜单。 - 注入:build 手机包时构建过程自动注入;编辑器下开发补丁需手动执行
XLua/Hotfix Inject In Editor,打印hotfix inject finish!或had injected!才算成功。
9.2 核心 API
xlua.hotfix(class, [method_name], fix):注入补丁。class 可用CS.Namespace.TypeName或字符串"Namespace.TypeName"(与Type.GetType格式一致,嵌套类型用Namespace.TypeName+NestedTypeName);传 method_name 时 fix 为 function,否则 fix 为按method_name = function组织的 table(可一次性替换整个类)。base(csobj):子类 override 函数中调用父类实现,base(self):Foo(p)。util.hotfix_ex(class, method_name, fix):增强版,可在 fix 内执行原函数(略慢)。xlua.private_accessible(>2.1.11 无需调用):开启私有成员访问。
9.3 各类成员的补丁语义
| 目标 | method_name | 说明 |
|---|---|---|
| 构造函数 | .ctor | 不替换,执行原有逻辑后调用 lua |
| 析构函数 | Finalize | 开头调用 lua 后继续原有逻辑,传 self |
| 属性 | get_AProp/set_AProp | getter/setter 分离 |
| [] 操作符 | get_Item/set_Item | 赋值/取值 |
| 其它操作符 | op_Addition等 | C# 操作符内部表示 |
| 事件 | add_AEvent/remove_AEvent | 第一个参数 self,第二个是 delegate |
| 泛化类型 | 实例化后的类型 | 只能对GenericClass<double>、GenericClass<int>分别打补丁 |
| Unity 协程 | 原方法名 | 用util.cs_generator(function() ... coroutine.yield(...) end)模拟 IEnumerator |
约束(hotfix.md):不支持静态构造函数;目前只支持 Assets 下代码的热补丁,不支持引擎与 C# 系统库。
9.4 Hotfix Flag 定制
Hotfix标签可设标志位:Stateless/Stateful(Stateful 已删除,用xlua.util.state达到类似效果,默认 Stateless);ValueTypeBoxing(值类型适配 delegate 收敛到 object,省代码但产生 boxing);IgnoreProperty(不注入属性,建议不注入);IgnoreNotPublic(不注入非 public 方法,但被反射调用的私有方法如 MonoBehaviour 必须注入);Inline(不生成适配 delegate,函数体直接注入);IntKey(不生成静态字段,注入点集中到数组管理,通过CS.XLua.HotfixDelegateBridge.Set(id, func)指定,id 映射保存在Gen/Resources/hotfix_id_map.lua.txt,发布后需妥善保存;可执行(require 'xlua.util').auto_id_map()恢复按名字修补)。
9.5 使用建议(直接引自文档)
- 对所有较大可能变动的类型加
Hotfix标识; - 用反射找出所有函数参数、字段、属性、事件涉及的 delegate 类型,标注
CSharpCallLua; - 业务代码、引擎 API、系统 API 中需在 Lua 补丁内高性能访问的类型,加
LuaCallCSharp; - 引擎 API、系统 API 可能被代码剪裁(C# 无引用处都会被剪裁),可能新增调用的 API 所在类型加
LuaCallCSharp或ReflectionUse。
十、Lua 虚拟机与工具链
虚拟机 gc 参数读取及设置:xLua 暴露了 Lua 虚拟机的 GC 参数接口。关于 GC 的行为特性,faq.md 给出了重要工程提示:Lua 不像 C# 有后台 GC 线程,gc 被拆成小步骤插入内存分配点;默认
GcPause = 200(内存达到上次回收时两倍才开启新一轮 gc),持有大 C# 对象引用(lua 侧仅 4 字节)很难触发 gc 周期,可调低GcPause、调高GcStepmul,或在场景切换等低性能敏感处做全量 gc(LuaEnv.FullGc()或 lua 侧collectgarbage('collect'))。jynew 的LuaManager.GC()封装了luaEnv.GC()并前后对比get_lua_memory_cost(LuaManager.cs)。Lua Profiler:可按函数调用总时长、平均每次调用时长、调用次数排序;显示 lua 函数名、所在文件及行号;C# 函数会标注"C# 函数"。
支持真机调试:xLua 支持移动真机上的调试能力;同时
CustomLoader可通过返回真实文件路径配合调试器(faq.md:require 'a.b' 时设置 filepath 为a/b.lua,返回 UTF-8 字节流)。
十一、配置体系:白名单驱动的代码生成
所有功能特性的"开关"都集中在配置上。configure.md 规定:所有配置支持打标签、静态列表、动态列表三种方式;列表方式必须是 static 字段/属性且放在 static 类中;建议列表配置放 Editor 目录(Hotfix 配置若涉及 Assembly-CSharp 之外的程序集,必须放 Editor 目录)。核心配置项:
| 配置 | 作用 |
|---|---|
XLua.LuaCallCSharp | 生成该类型适配代码(构造、成员/静态属性方法),否则走慢速反射;扩展方法加此配置后追加到被扩展类型 |
XLua.CSharpCallLua | 允许 lua 函数适配到 C# delegate、lua table 适配到 C# interface(UI 回调、List<T>.ForEach、LuaTable.Get等场景) |
XLua.GCOptimize | 纯值类型 struct/枚举生成 gc 优化代码,Lua/C# 间传递零 C# gc alloc |
XLua.AdditionalProperties | GCOptimize 的扩展:对私有字段 + 公有 property 的 struct 打解包 |
XLua.ReflectionUse | 生成 link.xml 阻止 il2cpp 代码剪裁 |
XLua.DoNotGen | 指定类内部分函数/字段/属性不生成代码(比 ReflectionUse 更 lazy,按成员 wrap) |
XLua.BlackList | 把类型部分成员(可精确到某个重载)排除出生成 |
CSObjectWrapEditor.GenPath | 生成代码路径,默认Assets/XLua/Gen/ |
CSObjectWrapEditor.GenCodeMenu | 生成引擎二次开发:XLua/Generate Code时触发注册的无参函数 |
动态列表示例(configure.md,可按 Namespace 做白名单):
[Hotfix] public static List<Type> by_property { get { return (from type in Assembly.Load("Assembly-CSharp").GetTypes() where type.Namespace == "XXXX" select type).ToList(); } }判断何时用哪种配置,faq.md 给出口诀:"看调用者和被调用者"——Lua 要调用 C# 的GameObject.Find,GameObject加LuaCallCSharp;lua 函数要挂到 UI 回调,回调声明的 delegate 加CSharpCallLua(调用者在 C#,被调用者是 lua 函数)。List<int>.Find(Predicate<int>)中List<int>加LuaCallCSharp,Predicate<int>加CSharpCallLua。更无脑的方式:看到 "This delegate/interface must add to CSharpCallLua : XXX" 就把 XXX 加进CSharpCallLua。
十二、在 jynew 中的综合实践:Mod 化的脚本基石
回到项目整体视角,jynew 的 Mod 体系建立在"C# 框架 + Lua 内容"的分层上:
- 框架自带脚本位于 jyx2/Assets/LuaScripts(含 InitLuaScripts.lua、LuaModuleList.lua、
Jyx2Battle/、Jyx2Configs/),通过LuaManager的两级 loader 加载。 - 各 Mod 的 Lua 脚本位于 jyx2/Assets/Mods,其中 JYX2 主 Mod 含 1020 个 .lua 文件、SAMPLE 与 xiastart_roguelike 各含数百个 .lua 与 .asset——大量游戏逻辑(战斗、配置、任务)以 Lua 实现,这正是 features.md 中"Lua 调用 C#"(战斗数值、UI 交互)、"C# 调用 Lua"(配置读取、事件驱动)、"热补丁"(线上修复)三组能力的直接受益者。
- 编辑器热重载(
HOTRELOAD_LUA_IN_EDITOR)让 Mod 作者无需重启游戏即可迭代脚本,配合LuaExecutor的协程桥接实现 C#/Lua 异步协作。
给 Mod 开发者的落地建议(综合 features.md、configure.md、hotfix.md 与 faq.md):
- 频繁从 Lua 访问的 C# 类型(如角色、物品、战斗系统类)统一加
LuaCallCSharp;涉及回调的 delegate 加CSharpCallLua;纯值 struct 加GCOptimize,以发挥无 GC 传递优势。 - 开发期不生成代码、不开启
HOTFIX_ENABLE;build 手机版本前必须执行XLua/Generate Code,并建议把生成集成进自动化打包流程(参考 faq.md 的 CLI 出包方式)。 - 发布 il2cpp 平台前,为可能被剪裁的 API 所在类型加
ReflectionUse或LuaCallCSharp,避免运行时 "attempt to call a nil value"。 - 热补丁场景注意
hotfix_id_map.lua.txt(IntKey 模式)的留存,以及LuaEnv.Dispose前释放所有指向 Lua 函数的 delegate(可借助util.print_func_ref_by_csharp()排查,faq.md)。
结语
features.md 虽然只是一份"特性清单",但背后是 xLua 一整套经过生产验证的互操作设计:生成代码与反射双轨并行、Lazyload 与无 GC 值传递的性能工程、三级配置体系驱动的代码生成、以及覆盖构造函数到事件的完整热补丁能力。在 jynew 中,这套能力通过 LuaManager.cs、LuaExecutor.cs 与 InitLuaScripts.lua 等基建落地为可运行的 Mod 脚本框架。继续深入可阅读仓库内同目录文档:configure.md(配置体系)、hotfix.md(热补丁)、faq.md(FAQ 与排错)、XLua教程.md(入门教程)及 XLua_API.md(API 参考)。
【免费下载链接】jynewJinYongLegend-like RPG Game Framework with full Modding support and 10+ hours playable samples of game.项目地址: https://gitcode.com/GitHub_Trending/jy/jynew
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考