2026年9月发布
Title: "2026 September Release"
Date: 2026-10-01
Tags:
release
engine
studio
dotnet
Params:
Cover: 2026-10-01-2026-09-cover.webp
Description: "Turian 回归 C#,发布 1.0,并在八天后达到 2.0:bricks、依赖注入、作为设计师支柱的 DataAssets、撤销、保持关联的预制体、快 12 倍的变换层级,以及基础更坚实的 Guinevere。"
SocialMedia: |
九月:Turian 回归 C#,发布 1.0,八天后达到 2.0。bricks——带签名注册表的开源包格式。依赖注入取代全局单例。DataAssets 成为设计师的支柱。撤销与重做、嵌套预制体与变体、快 12 倍的变换层级,以及 Studio 中紧凑的应用栏。#Turian #dotnet #csharp #gamedev
一两年前,我决定只用自己搭建的技术来做一款游戏。这意味着我需要一个游戏引擎。我想按自己习惯的方式工作——在一个类似 Unity 的编辑器里,场景周围有设计师工具——所以引擎需要一个 studio。studio 需要一个 GUI,于是有了 Guinevere。在这个过程中我意识到,与其再写一个单一用途的编辑器,不如构建在一个像 VSCode 那样已经提供了大量基础工作的平台上。于是 studio 变成了一个通用的插件宿主,叫做 Gaya,而 Turian 成为它的一个插件。再加上游戏本身,这套技术栈就是 MEGA4。
几个月前我卡住了,去探索了 Zig。我取得了很大的进展,那个引擎以 TurianZ 的身份延续着,回归 C# 之家 讲述了我回来的原因。这次回归花掉了最近两个月。
在 1.0 的文章里我开玩笑说,几个月内看到 v23 也不会让我惊讶。达到 v2 经历了六个破坏性变更。那篇文章里列出的两个缺口——Studio 没有撤销功能、预制体副本不与源保持关联——现在都已补上。

- 为什么是 2.0,为什么是现在
- Bricks:开放的包格式
- 告别全局:依赖注入
- DataAssets:设计师的支柱
- 保持关联的预制体
- 撤销、重做,不丢任何东西
- 性能
- 自己写出来的表单:Autoformers
- 更少装饰的 Studio
- 引擎内部
- Guinevere:更坚实的地基
- 展望未来
为什么是 2.0,为什么是现在
Turian 遵循语义化版本:第一个数字,主版本,只有当某个变更可能破坏你的项目时才会上升。这是写在一个数字里的承诺。如果你在 2.0 之后看到 2.3,什么都不用读就可以升级。如果你看到 3.0,请先读指南。
九月有六项变更对 1.x 项目打破了这一承诺:
| 变更 | 破坏什么 | 升级步骤 |
|---|---|---|
| 相机机架和游戏内 UI 变成了 bricks | 使用它们的项目必须声明 | Bricks |
| 依赖注入取代了全局服务 | 静态的 Input、Localization、RuntimeServices |
Services |
Transform 变成了不可变的值 |
node.Transform.Position = … |
Transform |
| Inspector 特性移到了共享包 | 特性全名,[HideInEditor] |
Attributes |
IdClass 改名为 IdObject |
使用身份 API 的插件 | IdObject |
| 应用栏和外观移入 Gaya | 自定义 Gaya 宿主和装饰 | Gaya |
我在 9 月 30 日打上了 2.0.0 的标签,就在第一项变更落地之后,又在同一天把标签撤了下来。更多破坏性的工作已经在进行中。如果在一周内以 2.0、3.0、4.0 的形式发布,你就要为一个月的工作做三次迁移。所以这些破坏性变更今天一起发布,并附上一份迁移指南。场景、预制体和数据资产文件保留它们的 id,所以你的内容都不需要转换。从现在起,2.x 只会有兼容的变更。下一次破坏,无论何时到来,都将是 3.0。
Bricks:开放的包格式
2.0 最大的一块是 bricks。一个 brick 就是一个包:代码、资源和编辑器工具装在一个文件夹里,带有版本、依赖和许可证,任何项目都可以安装、更新或移除。Unity 有它的 Package Manager,Godot 有它的 Asset Library。Bricks 是我们的答案,区别在于:它们不绑定 Turian。
Bricks 属于 Studio 之下的平台 Gaya,其格式是一个开放规范:package.json 清单、锁文件和注册表索引,全部附带 JSON schema 文档化。任何引擎,或任何根本不是引擎的应用,都可以读取和发布它们。Turian 只是第一个用户。这也是我们今后分发东西的方式:示例项目、游戏 mod 和 Studio 扩展都将是 bricks,这样你们可以互相叠加、彼此构建。
Bricks 面板列出这台机器上的 bricks,Inspector 显示某个 brick 的清单
一个 brick 可以装什么
brick 可以装下项目文件夹里能装的任何东西:
- 脚本,在各自的程序集定义下编译,因此 brick 的代码与你的代码相互隔离。
- 资源:模型、纹理、预制体、场景、声音、DataAssets。每个都附带一个携带永久 id 的
.meta文件,所以使用某个 brick 预制体的场景在更新后仍然指向它。 - Studio 扩展:菜单、面板和 Inspector 页面。生产者示例的
com.acme.inventorybrick 在安装的那一刻就为 Studio 添加一个 Acme 菜单。 - 预编译代码。
turian-cli brick pack --precast提前编译 brick 的脚本,安装者加载现成的程序集,而不是编译你的代码。
引擎吃自己的 bricks
第一批 bricks 是 Turian 自己的。Follow、Orbit、Free Fly 和 FPS 相机机架移出了引擎核心,进入 org.mass4.turian.cameras,游戏内 UI(.ui 文档、.uss 样式表及绘制它们的一切)移入了 org.mass4.turian.ui。两者都随 Turian 发布,新项目会安装它们,但不需要的游戏可以移除。没有 UI 的游戏不再附带 Guinevere 或 Skia,其可执行文件去掉了 libSkiaSharp,每个平台约 13 MB。
当某个 brick 缺失时,什么都不会被破坏。它的组件作为占位符加载,它的资源被跳过,.meta 文件原封不动,因此重新安装该 brick 就能恢复一切。1.x 项目需要声明这两个 bricks:见迁移指南。
安装和管理 bricks
项目在 Bricks/manifest.json 中列出它的 bricks。旁边的 packages-lock.json 精确记录解析结果,细到提交或文件哈希,所以团队里每台机器构建的都是同一个游戏。两者都提交。一个 brick 可以来自五个地方:
| 来源 | 示例 | 用途 |
|---|---|---|
| 内置 | builtin:org.mass4.turian.cameras |
随引擎发布的 bricks |
| 文件夹 | file:../bricks/com.acme.levels |
你在游戏旁边开发中的 brick |
| 打包文件 | file:levels-1.0.0.brick |
别人发给你的 brick |
| Git | git+https://github.com/acme/rules.git#v1.2.0 |
仓库中的 brick,固定到某个提交 |
| 注册表 | ^1.0.0 |
注册表中最新兼容的发布版本 |
bricks 每台机器只存储一次,在 ~/.gaya/bricks 中,永远不会复制进项目,所以十个使用同一 brick 的项目在磁盘上共享一份副本。设置 GAYA_BRICKS 可以把存储放到别处,比如 CI 缓存里。
在 Studio 中,Project → Bricks… 打开 Bricks 面板。它列出项目安装了什么以及每个 brick 为什么在那里——也就是什么依赖它——并且无需离开编辑器即可安装、更新、移除或嵌入 bricks。Inspector 显示所选 brick 的清单。Assets 面板在 Assets 旁边新增一个 Bricks 文件夹,因此你可以像其他资源一样,把 brick 的预制体、纹理和脚本拖进场景和 Inspector 字段。一切也都能在终端使用:turian-cli brick add、remove、list、update、search,以及 restore --locked——当清单和锁不一致时,它会让 CI 构建失败。
改变你安装的东西
已安装的 bricks 是只读的,否则更新会抹掉你的修改。取而代之的是一整套让 brick 变成你自己的阶梯,从最轻到最重:
| 步骤 | 方式 | 跟随 brick 的更新? |
|---|---|---|
| 原样使用 | 通过 id 引用它 | 是 |
| 做一个变体 | 预制体变体,或覆盖少量数值的数据资产变体 | 是,你覆盖的除外 |
| 改变导入方式 | ProjectSettings/PackageImportOverrides.json |
是 |
| 复制一个资源 | Assets 面板中的 Copy,或 brick copy |
否:副本获得新的 id |
| 整个 fork | brick embed |
用 brick rebase 合并 |
一个嵌入的 brick 是 Bricks/<id>/ 中的可写副本,优先于已安装的版本。brick diff 显示你改了什么,brick rebase 把原版的下一个版本逐文件合并进你的 fork,在你们双方都编辑过的地方标记冲突——就像 git 那样。
制作你自己的
turian-cli brick new user.you.inventory 创建一个带清单、程序集定义和第一个脚本的 brick。id 以反向 DNS 顺序书写,就像 Java 和 Android 的包名:使用你拥有的域名(com.acme.inventory),没有就用 user.<name>.…。brick verify 检查每个资源都附带唯一的 .meta,brick pack 写出一个带哈希的 .brick 文件。打包是可复现的:相同的源永远产生相同的字节,所以任何人都能验证发布的 brick 与其源代码一致。一个可复用的 GitHub workflow,MASS4ORG/Turian/.github/workflows/brick.yml,为你验证、打包并发布 brick。
三个示例项目展示了完整流程。example-06-plugin-producer 编写三个 bricks,其中一个带编辑器代码;example-05-plugins 安装它们;example-07-team-split 面向团队。当一支团队制作 brick 而另一支在其上制作游戏时,brick stub 生成一个具有相同资源和类型 id 的占位符,这样游戏从第一天起就引用真实的 id;当 stub 和真实 brick 出现偏差时,brick verify --against 会失败。
注册表
注册表是人们发布 bricks 供他人查找的地方。一个 Turian 注册表是一个带签名索引的静态网站,因此 GitHub Pages 就足以托管一个,无需运行任何服务器。用 turian-cli brick registry add studio https://… --scope com.studio --key studio.pub 为项目添加一个,像 "com.studio.inventory": "^1.0.0" 这样的简单版本范围就会解析到最新兼容的发布版本。
每个发布都经过签名。发布者用 ed25519 密钥签名(turian-cli brick publish --registry site/ --key registry-key),一种现代的椭圆曲线签名方案,与 GitHub 接受的 SSH 登录密钥是同一类,所以大多数开发者已经有一个了。项目只信任自己列出的密钥,锁文件记录版本、文件哈希和签名的密钥。即使某个注册表被攻破,被篡改的 brick 会安装失败,而不是悄无声息地进入你的游戏。user.<name>.… 命名属于最先以它发布的人。
把 Unity 包带过来(非常实验性)
turian-cli import unitypackage Crates.unitypackage --brick user.you.crates --out bricks/ 直接读取 .unitypackage,无需安装 Unity,并将其转换为 brick。纹理、模型和音频保留它们的 Unity id,简单的材质和预制体也会一并带来。把它当作实验,而不是迁移路径: 脚本、着色器和动画不会被转换,超出简单场景的内容需要手工处理。报告会列出被跳过的内容。还要检查你导入内容的许可证:这个工具只移动文件,不改变条款。
告别全局:依赖注入
Turian 2.0.0,工单 #44
Unity 在最初几年做了一个至今仍困扰着它的选择:它的 API 是全局的。Input.GetKey、Time.deltaTime、Camera.main、GameObject.Find 是任何人从任何地方都能调用的静态方法,而大多数 Unity 项目还在其上添加自己的 GameManager.Instance 单例。第一天无比方便。几年后,这正是 Unity 组件难以单独测试的原因,是为了加速 Play Mode 而关闭 domain reload 后需要追捕每个记住上一次运行的静态字段的原因,也是社区构建 Zenject 和 VContainer 来绕过它的原因。Unity 永远无法移除它的静态方法,因为世界上每个项目都依赖它们。
Turian 1.0 有同样的便利,藏在 Input 和 Localization 这样的静态类背后。2.0 在还没有多少项目可破坏的时候移除了它。依赖注入意味着组件不去寻找它需要的东西;它声明它们,引擎把它们递过来。每个会话(导出的游戏、每次 Play Mode 运行、每个测试)都有自己的服务容器,构建在 .NET 标准的 Microsoft.Extensions.DependencyInjection 之上:
public class Patrol : Component
{
[InjectService, JsonIgnore]
public IInputSource? Input { get; private set; }
public override void OnUpdate(float deltaTime)
{
if (Input?.IsKeyDown(Key.W) == true)
Node.Position += Vector3.UnitZ * deltaTime;
}
}
| Unity | Turian 2.0 | |
|---|---|---|
| 读取输入 | 任何地方调用 Input.GetKey(KeyCode.W) |
注入一个 IInputSource |
| 你自己的服务 | 单例,或 Zenject / VContainer | IEngineServiceModule,内置 |
| Play Mode 隔离 | 除非 domain 重载,静态字段会残留 | 每个会话有自己的服务 |
| 单独测试一个组件 | 手动替换全局 | 给它一个测试容器 |
| 设计师拥有的服务 | 惯例上的 ScriptableObject 单例 | 一个 DataAsset,按会话共享 |
[InjectService(Optional = true)] 让组件可以在服务缺失的地方运行,比如在 Studio 中预览、没有输入路由的场景。游戏和 bricks 通过实现 IEngineServiceModule 注册自己的服务,Turian 会在游戏加载的每个程序集中发现这些模块。SceneTicker.TimeScale 也一同到来:它缩放游戏时间,0 冻结它,而非缩放时间继续为菜单和效果运行。从 1.x 升级只需几处替换,列在迁移指南中。
表格的最后一行是同一故事的设计师一侧,值得单独一节。
DataAssets:设计师的支柱
Turian 1.0 至 2.0,工单 #145、#146、#147、#43、#197
DataAsset 是 Turian 对 Unity ScriptableObject 的诠释:一个 C# 类,其实例以文件形式存在于你的项目中,在 Inspector 里而不是代码里编辑。一个 WeaponData 资产保存一把剑的伤害和速度;一个 LevelData 保存一个关卡的敌波。程序员写一次类,设计师创建、调整、平衡任意数量的资产而无需触碰脚本。
2017 年,Ryan Hipple 在 Unite Austin 的一场名为 Game Architecture with Scriptable Objects 的演讲中展示了这个思路能走多远。不是在代码里硬连接系统,而是通过资产连接它们:玩家的生命值是一个 FloatVariable 资产,血条、音频和存档系统都读它;"玩家死亡"是一个任何人都能监听的 GameEvent 资产。设计师在 Inspector 里拖动资产就能重新布线游戏。这一方法被称为 SOAP,即 ScriptableObject Architecture Pattern,是最对设计师友好的游戏构建方式之一。Turian 正在加倍投入:DataAssets 将成为设计师工作的支柱,而九月打下了地基。
| Unity ScriptableObject | Turian DataAsset | |
|---|---|---|
| 每个资产一个实例 | 是 | 是,按会话共享 |
| Play Mode 中的修改 | Play Mode 结束后仍然存在 | 被丢弃;创作文件永远不会被触碰 |
| 变体 | 否,除非用第三方工具 | 是,类似预制体变体 |
| 项目设置 | ProjectSettings/ 中的特殊文件 |
DataAsset 本身 |
| 引用场景对象 | 保存时静默丢失 | 编译时拒绝,并给出建议 |
- 一个共享实例。 每个引用某 DataAsset 的字段、场景和服务都获得同一个对象,因此一个
GameManagerDataAsset 可以作为服务工作:在一个场景里改它的金币,下一个场景就能看到。另一个会话,比如另一次 Play Mode 运行或一个测试,获得自己的副本。 - 创作的数据保持创作状态。 Play Mode 每次都从保存的值开始,运行中的游戏所做的一切都不会写回文件。在 Unity 里,运行中的游戏对 ScriptableObject 做出的修改会在 Play Mode 结束后依然存在,这是损失一下午平衡工作的经典方式。
DataAsset.Instantiate在脚本需要时给它一份私有副本。 - 变体。 一个 DataAsset 可以是另一个的变体,只存储它覆盖的值,就像预制体变体一样。"火焰剑"可以是伤害更高的"剑"的变体,当你重新平衡"剑"时,每个变体都会跟随。可以在 Studio 中或用
turian-cli variant创建。 - 设置就是 DataAsset。 项目的 Input、Graphics、Player 和 Localization 设置都是 DataAsset,通过 New → Settings 创建,和其他一切一样在 Inspector 中编辑。这意味着设置可以是变体、可以存在于 bricks 中、并通过与你的数据相同的服务来读取。
- 加固。 序列化器在编译时生成,而不是在运行时通过反射发现,所以 DataAsset 加载更快,还可以在后台预加载。变体拒绝任何会悄悄改变它们本质的东西:覆盖不能替换资产的 id 或类型,变体必须保持其基类的类型,未知语法会大声报错而不是被忽略。试图持有场景对象引用的 DataAsset 会被编译器报告(
TUR0001),并附带改用什么代替的建议。
保持关联的预制体
预制体是场景中可复用的一块,比如一根路灯、一个敌人或一整栋房子,你构建一次,然后放置多次。在 1.0 中,放置的预制体只是一个副本:改了原件,副本毫无察觉。现在每个实例都是一个链接,工作流正是 Unity 用户熟悉的那一套:
- 跟踪覆盖。 实例记得它对预制体改了什么:数值、添加的组件和节点,以及移除的部分。场景树和 Inspector 会标记它们。
- Apply 和 revert 可按值、按组件或对整个实例生效。apply 写入拥有该值的预制体,最内层优先,因此对嵌套预制体的编辑会落到它该在的地方。
- 嵌套预制体和变体。 一个预制体可以包含其他预制体的实例:一栋满是路灯预制体的房子预制体。根节点是另一个预制体实例的预制体是它的变体,比如一个除了颜色以外从"房子"继承一切的"红房子"。
- 预制体模式。 从场景打开实例的预制体,编辑,然后顺着面包屑导航返回。开放场景中的实例在预制体变化的那一刻就会刷新。
- Unpack 断开链接并将层级保留为普通节点,也可以选择同时解包嵌套的实例。
这些操作全部可以撤销。场景文件也保持小巧:一个实例保存为指向其预制体的链接加上它覆盖、添加和移除的内容,而不是在每个使用它的场景里塞进预制体的完整副本。
撤销、重做,不丢任何东西
Turian 1.1.0,工单 #81
Studio 现在为每个编辑器操作提供撤销和重做,其工作方式和 Unity 的一样:整个项目一份历史,每一步归属于一个文档。文档是一个打开的场景或 Inspector 正在编辑的资产。撤销某一步会先把它的文档带到前台,所以你总能看到改变了什么。
撤销跟踪的是你的意图,而不是每一个微小事件。用 gizmo 拖动对象无论持续多少帧都是一步,拖动数值字段会合并为一步。保存会封存当前步骤,因此保存之后的编辑总是单独撤销。场景之外的修改也算数:在 Assets 面板中重命名或移动文件,或通过 Apply 重写预制体。Edit 菜单会说出它即将撤销或重做的步骤名。当 Play Mode 显示运行中的游戏时,历史被锁定,反正那些修改都会被丢弃。
现在,带着未保存的工作退出 Studio 或切换项目时,会先询问。
性能
下面每一张表格里都会出现一个词:分配。在 C# 中,堆上创建的每个对象都是垃圾回收器之后必须找到并释放的内存。每帧都分配的游戏最终会停下来做回收,玩家就会看到卡顿。每帧零分配意味着引擎不会带来卡顿。
不再分配的变换
它源于一个问题:Turian 是否应该像某些引擎那样,把 Node 拆分为特化的 2D、3D 和 UI 类型?那会波及组件、序列化、预制体和工具,所以我先测量再决定。
答案在别处。每个节点的 Transform(位置、旋转和缩放)是一个带变更通知的类:每次编辑都触发事件,每次读取世界位置都要遍历层级并分配。在 2.0 中,Transform 是一个不可变的 struct,一个存储在节点内部而非独立对象中的小值,每个节点缓存其世界变换,直到它自身或某个父级移动。
在 100,000 节点的树上,移动每个节点然后读取每个世界位置:
| 帧时间 | 每帧分配 | 每节点内存 | |
|---|---|---|---|
1.x:类 Transform |
75.9 ms | 10 MB | 556 字节 |
2.0:struct Transform |
6.5 ms | 0 | 342 字节 |
| 扁平 3D 存储原型 | 0.60 ms | 0 | 85 字节 |
| 扁平 2D 存储原型 | 0.36 ms | 0 | 53 字节 |
快了 11.7 倍,而且完全没有垃圾。最后两行是把每个节点的数据存进扁平数组的原型。它们表明 2D/3D 拆分真正划算的地方,因此保留为未来大版本的目标,而 Node 暂时保持为单一类型。升级写入变换的代码基本上是查找替换;见迁移指南。
不再重建的渲染列表
渲染器以前每帧遍历整个场景来重建要绘制的东西的列表。现在它保留列表,只更新变化的部分,并按材质排序,让 GPU 更少地切换状态。在 Bistro 示例(Amazon Lumberyard 的街景)上,超过 300 帧:
| Bistro,300 帧 | 中位渲染 | 每帧分配 |
|---|---|---|
| 之前 | 7.2 ms | 797 KiB |
| 之后 | 2.1 ms | 0.1 KiB |
度量那些不该采用的东西
ZLinq,一个流行的无分配 LINQ(C# 的集合查询语法)替代品,曾是场景查询的候选。在 50,000 个节点上的基准测试说了不:
| 游戏玩法查询,50,000 节点 | 标准 LINQ | ZLinq | 普通循环 |
|---|---|---|---|
| 统计玩家附近的敌人 | 1.8 ms | 3.3 ms | 1.7 ms |
| 按名称查找节点 | 1.5 ms | 1.9 ms | 0.35 ms |
| 最近的拾取物 | 1.9 ms | 4.5 ms | 1.7 ms |
| 最弱的 10 个敌人 | 2.0 ms,40 KiB | 4.6 ms | 1.6 ms,0 KiB |
一旦场景遍历本身不再分配,普通循环在所有查询中都获胜,还不用新依赖。作为替代,Turian 增加了 GetComponentsInChildren<T>(List<T>) 之类接受你传入的列表并填充的重载,使你的玩法代码可以每帧查询而不分配。Guinevere 的布局引擎也经历了同样的锻炼,它的数字在下面。
自己写出来的表单:Autoformers
Turian 1.2.0 与 2.0.0,工单 #28、#29、#194
写一个 C# 类,Studio 的 Inspector 就把它的字段显示成表单:[Range] 对应滑块,bool 对应复选框,颜色对应取色器。Turian 1.2 通过属性绘制器使这套机制可扩展,它可以改变一个类型或特性的绘制方式,还带来了 [Tooltip] 和 [InspectorOrder]。
然后我注意到这套机制里没有任何与游戏相关的东西。任何构建在 Gaya 之上的工具都想要从 C# 类生成的表单。所以在 2.0 中,表单生成器作为 Autoformers 移入了 Guinevere,特性则移入共享的 MASS4.Attributes 包。其他 GUI 应用现在可以使用它们,Gaya 已经在用了:它的 Settings 面板是从 C# 设置类自动生成的,包括自定义呈现和验证。Studio 的 Inspector 和 Bricks Inspector 也是 Autoformer 表单。Turian 只保留与游戏相关的特性。特性名称在此过程中略有变化;见迁移指南。
更少装饰的 Studio
Studio 的主菜单现在折叠成应用栏里的一个按钮,与项目切换器和播放控件共用这一条栏。这条栏还可以取代操作系统的标题栏:采用自定义装饰后,栏中的空白处可以拖动窗口,它自己的按钮最小化、最大化和关闭窗口,窗口可以从每条边和每个角调整大小。
Gaya 中新的外观页面把主题、文字大小、缩放和窗口装饰集中在一起,主题下拉菜单和 View → Themes 共享保存的选择。Studio 附带五个主题:Dark、Light、Dark Contrast、Darcula 和 Catppuccin。

贡献窗口装饰的自定义 Gaya 宿主和插件需要做两处小改动;见迁移指南。
引擎内部
bricks 让内容可组合,这也带来一个问题:当内容来自许多地方时,是什么让游戏保持稳定?2.0 用代码和测试把这些答案写了出来。
- 内容覆盖。 内容包按依赖顺序挂载。冲突、循环和缺失的依赖在游戏启动之前就会被报告,而运行中的游戏保留它启动时拥有的内容。这是 mod 们未来立足的地面。
- 运行时存档。 新的
RuntimeSave写出运行中游戏的带版本快照:它的 tick、随机状态和游戏状态。恢复之前,它会将快照与游戏内容对照检查,因此用另一组 mod 做出的存档会被发现,而不是被错误加载。旧存档的升级在副本上进行,失败也不会碰原存档。
Guinevere:更坚实的地基
Studio 由 GUI 库 Guinevere 绘制,所以你在 Studio 里看到的大部分都是 Guinevere 的工作,而 Guinevere 度过了疯狂的一个月:从 9 月 9 日的 1.6 到 10 月 2 日的 5.6。它的包现在以 MASS4.Guinevere.* 发布。亮点:
Excalibur 控件。 构建在核心之上的专用控件库,作为独立包发布:只绘制可见行的虚拟化树视图、滑块、Unity 风格的可拖动数值字段、标签页、toast、工具提示、菜单栏、上下文菜单、级联浮出、模态对话框和一个文件浏览器。颜色和尺寸可以逐值定制主题。
停靠。
gui.DockSpace支持标签组、嵌套拆分和浮动窗口、拖放停靠、标签重排和拖出,以及任意两个面板之间的分隔条。Studio 的整个布局都构建在它之上。Autoformers。 Turian 的表单生成器在 Guinevere 找到了新家;见上文。
更快的布局引擎。 尺寸现在是可组合的表达式:像素、百分比、比例、扩展和适应内容可以相加、缩放并在其间动画,因此一个面板可以从"200 像素"平滑地长到"窗口的 70%"。同时,布局不再分配,并在深层树上线性扩展:
1,000 节点场景 之前 之后 每次布局分配 宽幅换行 0.035 ms 0.011 ms 45 KB → 0 嵌套适应内容 0.132 ms 0.036 ms 55 KB → 0 文本测量 0.035 ms 0.011 ms 52 KB → 0 事件与光标。 鼠标和键盘事件像在网页浏览器中那样穿过界面,下行到目标再上行回来,任何处理程序都可以拦截它们。部件声明自己的光标形状,指针可以隐藏或锁定,这是 3D 视口实现鼠标视角所需要的。
平台能力。 各集成(OpenGL、Vulkan、OpenTK 和 Raylib)声明它们能做什么,比如光标形状、指针锁定、窗口移动或否决关闭。Gaya 和 Turian 使用 Vulkan,四者中最现代的一个。
Guinevere 的应用栏在此过程中变了形状;直接使用它的代码应遵循迁移指南。

展望未来
这里没有列出的修复、调整和增强还有很多。完整列表见变更日志。
发布页提供 Windows 和 Linux 的 Turian 2.0.0,迁移指南带领 1.x 项目完成升级,文档新增了关于 bricks的页面。如果什么东西坏了,请在 GitHub 上告诉我。如果你想资助下一个版本的开发,可以在 Patreon 或 Ko-fi 上找到我。
#release#engine#studio#dotnet