← 博客

2026年9月发布

2026年9月发布

2026-10-01
本页面为自动翻译,可能包含错误。如果发现问题,请通过“编辑此页面”链接帮助修正。

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

    https://turian.mass4.org/blog/2026-september-release/


一两年前,我决定只用自己搭建的技术来做一款游戏。这意味着我需要一个游戏引擎。我想按自己习惯的方式工作——在一个类似 Unity 的编辑器里,场景周围有设计师工具——所以引擎需要一个 studio。studio 需要一个 GUI,于是有了 Guinevere。在这个过程中我意识到,与其再写一个单一用途的编辑器,不如构建在一个像 VSCode 那样已经提供了大量基础工作的平台上。于是 studio 变成了一个通用的插件宿主,叫做 Gaya,而 Turian 成为它的一个插件。再加上游戏本身,这套技术栈就是 MEGA4。

几个月前我卡住了,去探索了 Zig。我取得了很大的进展,那个引擎以 TurianZ 的身份延续着,回归 C# 之家 讲述了我回来的原因。这次回归花掉了最近两个月。

在 1.0 的文章里我开玩笑说,几个月内看到 v23 也不会让我惊讶。达到 v2 经历了六个破坏性变更。那篇文章里列出的两个缺口——Studio 没有撤销功能、预制体副本不与源保持关联——现在都已补上。

Turian Studio 2.0 with the main menu expanded across the application bar
The same Studio frame with the main menu collapsed into a single button

为什么是 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:开放的包格式

Turian 2.0.0,工单 #79、#74

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.inventory brick 在安装的那一刻就为 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 的字段、场景和服务都获得同一个对象,因此一个 GameManager DataAsset 可以作为服务工作:在一个场景里改它的金币,下一个场景就能看到。另一个会话,比如另一次 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),并附带改用什么代替的建议。

保持关联的预制体

Turian 1.1.0,工单 #84、#185

预制体是场景中可复用的一块,比如一根路灯、一个敌人或一整栋房子,你构建一次,然后放置多次。在 1.0 中,放置的预制体只是一个副本:改了原件,副本毫无察觉。现在每个实例都是一个链接,工作流正是 Unity 用户熟悉的那一套:

  • 跟踪覆盖。 实例记得它对预制体改了什么:数值、添加的组件和节点,以及移除的部分。场景树和 Inspector 会标记它们。
  • Apply 和 revert 可按值、按组件或对整个实例生效。apply 写入拥有该值的预制体,最内层优先,因此对嵌套预制体的编辑会落到它该在的地方。
  • 嵌套预制体和变体。 一个预制体可以包含其他预制体的实例:一栋满是路灯预制体的房子预制体。根节点是另一个预制体实例的预制体是它的变体,比如一个除了颜色以外从"房子"继承一切的"红房子"。
  • 预制体模式。 从场景打开实例的预制体,编辑,然后顺着面包屑导航返回。开放场景中的实例在预制体变化的那一刻就会刷新。
  • Unpack 断开链接并将层级保留为普通节点,也可以选择同时解包嵌套的实例。

这些操作全部可以撤销。场景文件也保持小巧:一个实例保存为指向其预制体的链接加上它覆盖、添加和移除的内容,而不是在每个使用它的场景里塞进预制体的完整副本。

撤销、重做,不丢任何东西

Turian 1.1.0,工单 #81

Studio 现在为每个编辑器操作提供撤销和重做,其工作方式和 Unity 的一样:整个项目一份历史,每一步归属于一个文档。文档是一个打开的场景或 Inspector 正在编辑的资产。撤销某一步会先把它的文档带到前台,所以你总能看到改变了什么。

撤销跟踪的是你的意图,而不是每一个微小事件。用 gizmo 拖动对象无论持续多少帧都是一步,拖动数值字段会合并为一步。保存会封存当前步骤,因此保存之后的编辑总是单独撤销。场景之外的修改也算数:在 Assets 面板中重命名或移动文件,或通过 Apply 重写预制体。Edit 菜单会说出它即将撤销或重做的步骤名。当 Play Mode 显示运行中的游戏时,历史被锁定,反正那些修改都会被丢弃。

现在,带着未保存的工作退出 Studio 或切换项目时,会先询问。

性能

Turian 2.0.0,工单 #32、#132、#58

下面每一张表格里都会出现一个词:分配。在 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。

Studio's Appearance settings and View, Themes menu in the Catppuccin theme
The same Studio frame in the Light theme

贡献窗口装饰的自定义 Gaya 宿主和插件需要做两处小改动;见迁移指南。

引擎内部

Turian 2.0.0,工单 #63、#197

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 的应用栏在此过程中变了形状;直接使用它的代码应遵循迁移指南。

Studio's Appearance settings and View, Themes menu in the Catppuccin theme
The same Studio frame in the Light theme

展望未来

这里没有列出的修复、调整和增强还有很多。完整列表见变更日志。

发布页提供 Windows 和 Linux 的 Turian 2.0.0,迁移指南带领 1.x 项目完成升级,文档新增了关于 bricks的页面。如果什么东西坏了,请在 GitHub 上告诉我。如果你想资助下一个版本的开发,可以在 Patreon 或 Ko-fi 上找到我。

#release#engine#studio#dotnet