跳至正文
Gabriel • SyedMuhammad
lead developer™

nanos world Lead Developer.

查看所有作者

3 月:文件传输、地图包!

· 12 分钟读完
Gabriel • SyedMuhammad
lead developer™

文件传输、地图包、Esc 菜单及更多!

Esc 菜单信息​

游戏中的 Esc 菜单现在显示服务器信息,如名称、描述、地图、游戏模式、已连接玩家,以及一个可以使用新方法 Client.SetEscapeMenuText() 自定义的特殊区域。

它支持使用富文本标签如 \<strong>、\<h1>、\<h2> 和 \<h3> 进行格式化!

<h1>标题 1!</>
<h2>标题 2!</>
<h3>标题 3!</>
<strong>粗体文本</>
普通文本

新包类型:地图​

引入了一种新的包类型“地图”,以完全取代所有旧的地图加载方式(以及地图包)!它现在是一个像其他包一样的包,能够包含脚本并运行 Lua 代码。

提示

现在地图资产路径配置在其 Package.toml 中,因此当加载此地图包时,它将加载其内部配置的地图资产!

到目前为止,地图包是通过在资产包内创建一个与地图同名的文件夹来配置的,与其他 .uassets 和烘焙文件混在一起,这不可持续且有若干缺点。

有了这个新系统,我们将能够进行改进,消除服务器在本地下载资产包的需要,因为所有地图包将不再包含在资产内!

由于它像其他包一样是一个包,也可以上传到资源库,所以所有现有地图都可以在新游戏屏幕中选择,即使你尚未下载它们!而且你不需要事先下载它们,因为通过新游戏启动服务器将自动下载其所有依赖项!

与其他包类型一样,地图包的 Package.toml 略有不同,它包含新属性:map_asset(此地图实际使用的资产)、spawn_points(出生点列表)和 custom_data 部分(可通过 Server.GetMapConfig() 访问的通用数据):

Package.toml
loading...

在包指南页面阅读更多。

资源库改进​

菜单中的资源库标签页经历了多项改进!

新标签页​

现在我们有多个标签页分隔每种包类型和资产包,使得在不同类别之间导航和搜索更容易。

此外,类别下拉筛选器现在仅在资产包标签页中可见。

创建本地资源​

现在可以通过 UI 创建新的本地资产包和包!

我们新增了一个按钮,打开表单以创建资源!

未来我们将扩展此功能,允许创建并上传到商店!全部通过 UI 完成!

图像加载​

现在所有资源库图像都以延迟方式加载,避免了主菜单加载时的一些初始卡顿,因为标题/团队图像现在将在屏幕上出现时按需加载,更不用说现在更快更可靠了。

返回按钮​

返回按钮从左下角移到了右上角,使其对整体 UI 使用更直观:

新文件传输系统​

我们彻底改造了文件传输系统!这个新系统更加健壮,旨在解决我们之前下载文件和加载屏幕遇到的所有问题!

子系统​

这个新系统通过将工作分为三个子系统/步骤来运作:套接字、HTTP 和 资源库资产 .zip。

每个子系统/步骤有不同的文件下载方式,针对不同场合:

HTTP​

HTTP 文件传输系统是最常规的文件发送方式。服务器启动一个 HTTP 服务器,客户端通过 HTTP 请求下载文件。它在内联网络系统之外并行工作。

这对于发送大文件非常好。但在 P2P 服务器中不起作用,因为通常主机没有在路由器中转发端口,而这对于让 HTTP 服务器公开是必需的。

当文件大于 512 KB 时使用此系统,客户端一次只向服务器请求 16 个文件(值可通过设置高级标签页配置)。

套接字​

由于 HTTP 请求每次请求文件时都需要连接到服务器(建立连接、握手、校验等),这在每次文件下载之间增加了大量时间,如果我们有数千个文件,这很糟糕。因此为了加速整个下载时间,我们还有套接字系统。

套接字子系统使用内联网络系统发送文件,它更快,因为它将所有文件数据附加到网络线路中并全部刷新到客户端。缺点是内联网络系统并非用于发送文件,因为它会瓶颈网络,并且根据连接质量,可能发生大量丢包,导致整个传输变慢。

但这对于发送大量小文件非常好,因为它将它们全部附加到线路中并以单一流发送给客户端。

此系统用于两种情况:当文件小于 512 KB 时(可通过设置高级标签页配置)或当服务器是 P2P 时。

信息

P2P 服务器使用 Steam 数据报中继服务器来处理玩家之间的连接,无需他们在路由器中转发端口。

因此,所有连接和网络传输都通过 Steam 服务器进行,其似乎有 1 MB/s 的硬编码速度限制,使得整个文件传输系统在 P2P 服务器上*“硬编码地”*变慢。

资源库资产​

最后一个子系统使用资源库直接从我们的 CDN 服务器下载所需的资产包,无需通过游戏服务器。

当服务器加载的资产包存在于资源库且版本相同时使用此方式。然后客户端可以直接从中下载资产包,而不是从游戏服务器!

已修复的问题​

除了所有重做的系统外,我们特别关注了过去存在的问题和错误,并为此添加了多个新的校验层以确保一切顺利。因此如果任何失败,或者下载文件耗时过长,玩家将被断开连接,以防止由此引起任何进一步问题。

下载速度​

在旧系统中,我们一次只下载 8 个套接字文件和 1 个 HTTP 文件。但现在我们一次下载多达 128 个套接字文件和 32 个 HTTP 文件!因此我们在连接服务器时的下载速度获得了巨大提升!

提示

以下基准测试是在官方服务器上连接沙盒游戏模式,仅使用 HTTP 传输,仅用于测试 HTTP 文件传输系统。由于它没有充分利用套接字优化,实际时间可能会大不相同。

文件时间提升
102:35N/A
201:1898%
400:40287%
800:24545%
1600:19715%
3200:15903%

此表显示了使用新文件传输系统同时下载的 HTTP 文件数量及其对最终连接时间(分钟)的影响。

服务器异步处理​

此外,现在游戏服务器异步处理文件传输,意味着每个文件传输请求都排队并从单独的线程处理,减少了玩家加入服务器时的卡顿和高 tick 率。

加载屏幕改进​

加载屏幕也经历了几项改进!

详细进度​

现在它显示更多关于下载进度的信息,包括:

  • 正在下载的总大小(汇总所有子系统,包括资源库)
  • 当前已下载的大小
  • 当前下载速度
  • 正在下载的文件数量
  • 当前步骤的进度(下载中、安装中……)

服务器启动进度​

从菜单启动新游戏时,现在显示更多关于本地服务器的信息,包括服务器下载资源库项目时的进度!

它准确告诉服务器初始化的每一步和进度,从菜单启动游戏时提供更好的体验。

此外,整个新游戏启动过程也通过更好的校验得到改进,以防启动新游戏时出现问题!

载具翻转复位系统​

翻转载具的复位现在更加确定,物理漏洞更少。

现在翻转载具必须使用转向输入(例如 A 或 S)来指向旋转哪一侧,而不是像以前那样仅加速。

实体网络大小​

对实体与客户端同步时通过网络发送的内部结构进行了进一步优化。通过优化向量等大型结构体和其他不必要过大的数值数据的使用,实现了大幅缩减。

我们将其大小缩减了高达 30%!

Entity之前之后减少量
Blueprint192 B136 B29.17%
Cable464 B312 B32.76%
Character392 B336 B14.29%
CharacterSimple416 B360 B13.46%
Light240 B184 B23.33%
Melee256 B184 B28.13%
Particle200 B144 B28.00%
Prop208 B152 B26.92%
StaticMesh192 B136 B29.17%
TextRender248 B192 B22.58%
Trigger240 B168 B30.00%
载具360 B288 B20.00%
Weapon416 B312 B25.00%
备注

这些是静态大小,但实体也有动态大小,如字符串和列表,会动态增加它们的整体大小!

此外,这些值代表实体序列化时发送给客户端的网络数据,而非内存占用。

修复冻结行为​

连接服务器时冻结的行为已修复!“等待”步骤已移至加载屏幕末尾,现在以异步方式处理!这将可用性提升了 1000 倍! 🥳

改进与漏洞修复​

主菜单已更新,首次出现时(游戏启动或从服务器断开连接)具有新的淡入效果,大大改善了其使用的流畅度。

还值得一提的是,我们成功修复了主菜单中遇到的几个问题和崩溃。

现在用户体验流畅多了!

设置标签页:高级​

游戏现在有一个名为“高级”的新设置标签页,玩家可以在其中调整文件传输设置以帮助调试问题。

每个设置都有特殊含义,悬停时会进行说明!

近战改进​

近战获得了社区要求的重大改进 (咳咳 Timmy 咳咳)!

多重命中​

近战现在每次“动作”可命中多个角色。这意味着一次挥砍现在可以同时砍中多个敌人!

附加静态网格体​

现在近战在考虑碰撞基元计算伤害时会利用附加的静态网格体!为命中检测的边界做出贡献!

杂项​

其他杂项改进。

蓝图参数​

此外,游戏现在支持将几种新结构体传递给蓝图函数,包括 FQuat、FMargin、FVector4、nil。

它还支持传递实体 Canvas、WebUI 和 SceneCapture,这些在蓝图中接收时会转换为材质接口。

完整列表请查看蓝图页面。

--logo 命令行​

现在服务器命令行 --logo 将 URL 下载到内存中以显示在服务器列表中,不再下载并覆盖本地的 Server.jpg 文件。修复了使用相同 .exe 启动多个服务器时的问题。

文件/文件夹大小写​

此外,现在创建 Server、Shared、Client 文件夹或 Index.lua 时如果大小写不正确(小写或全大写)将抛出警告。

这是我们在脚本编写者在 Windows 上开发包,然后在 Linux 上加载时无法工作时遇到的常见问题。我们最终发现这是因为他们创建的文件的大小写敏感性问题。因为在 Linux 上所有文件都是区分大小写的。

结语​

上个月是 nanos world “框架”整体稳定性和健壮性取得出色进展的一个月。加载屏幕、文件传输系统和菜单现在流畅多了。

这些改进和修复使游戏看起来专业多了。我们在文件传输方面遇到的难以复现、只有在大规模测试时才能目睹的问题终于解决了!

地图包是另一个巨大的改进!虽然从技术角度来说不是什么大事,但这解决了我们必须处理的几个问题,例如服务器上不必要下载大型资产的需求(很快™),或者我们之前糟糕且丑陋的旧地图包实现,它导致了很多漏洞和问题。

我一直致力于实现至少一个来自我们反馈中心的投票最多和请求最多的项目。所以请务必投票支持或建议你想要的功能!

感谢又一个月的陪伴! 🙏🙏🙏

2 月:控件与通知!

· 7 分钟读完
Gabriel • SyedMuhammad
lead developer™

控件与通知!

以下是 2 月最新更新的汇总。

控件​

经过漫长的期待和大量请求,我们很高兴地宣布 nanos world 引入了一个新类:Widget!这个新类允许我们从 Lua 生成 UserWidgets 或任何 Widget 子类,作为游戏中的 UI 使用,类似于 WebUI。

就像蓝图一样,可以从 Lua 调用函数并监听调度器,从而实现两种环境之间的无缝集成。

看看创建一个原生按钮并订阅其事件有多么简单:

local my_button = Widget(NativeWidget.Button)

-- 在其中放入文本
local my_text = Widget(NativeWidget.Text)
my_text:CallBlueprintEvent("SetText", "按我!")
my_button:AddChild(my_text)

-- 绑定原生 OnClicked 调度器
my_button:BindBlueprintEventDispatcher("OnClicked", function()
Console.Log("已点击!")
end)

-- 将按钮添加到视口
my_button:AddToViewport()

-- 调整布局,使其居中
my_button:SetCanvasLayout(Vector2D(0, 0), Vector2D(0, 0), Vector2D(0.5, 0.5), Vector2D(0.5, 0.5), Vector2D(0.5, 0.5))

Widget 的所有原生函数都可以通过 CallBlueprintEvent() 调用。就像事件/调度器也可以通过 BindBlueprintEventDispatcher() 订阅一样!在控件页面中,我们添加了一个章节,链接了所有 虚幻原生 Widget 文档页面,你可以在那里找到它们所有可用的方法和事件!

除了可以生成原生 Widget 外,你还可以从资产包中生成自定义 UserWidget!只需使用另一个构造函数:

local my_custom_widget = Widget("my-asset-pack::WBP_CustomWidget")

我们计划继续扩展 Widget 的功能,并在未来使其能够在 3D 世界中绘制。

现在构建 UI 简直起飞了!

更好的警告​

我们改进了警告系统,为弃用警告添加了行号。这使开发者更容易识别和修复脚本中的弃用代码。

SceneCapture 编码​

我们为 SceneCapture 添加了一个新方法:EncodeToBase64()!

这项新的 SceneCapture 编码功能允许你拍摄快照并生成 Base64 图像字符串,用于 WebUI。我们提供同步和异步两种方法。

聊天中的玩家头像​

我们在聊天中添加了玩家头像,使识别谁在说话变得更加容易。

新的原生服务器命令​

我们添加了新的服务器命令,包括踢出玩家和列出已连接玩家的功能:

  • players - 列出所有已连接的玩家。
  • kick - 将玩家从服务器中踢出。

CharacterSimple 可受伤​

我们为 CharacterSimple 添加了受伤和死亡的支持,就像现有的 Character 类一样。

此外,我们还为 BaseDamageable 添加了新的文档页面,包含所有 Damageable 的方法和事件!它在 Character 和 CharacterSimple 之间共享!

新的自定义设置类型:选择​

我们添加了一种新的自定义设置类型:选择。这个新选项在主菜单中显示为下拉菜单,允许玩家在游戏模式 Package.toml 的 custom_settings 设置中选择一个预定义选项。

致命崩溃报告​

我们现在会自动将所有致命错误发送到我们的崩溃报告系统。由于致命错误通常是“不应发生的已处理错误”,这有助于我们快速识别和修复问题,提高游戏的整体稳定性。

蓝图材质作为参数​

我们扩展了蓝图支持,以包含来自 WebUI、Canvas 和 SceneCapture 实体的材质参数。这意味着蓝图现在可以自动从这些实体接收 MaterialInterface 参数。

提示

新的 Widget 类也从中受益!

你可以在 Blueprint 文档页面中查看所有可能的参数类型列表。

资源库改进​

标签​

我们为资源库显示添加了新图标,用于指示资源是否为官方资源,或者你是否拥有其所有权(属于其团队)。

所有权管理​

此外,我们还添加了一个用于管理资源的新标签页,目前仅包含一个跳转到商店的按钮。

本地文件夹​

我们还添加了一个新按钮用于打开本地安装文件夹,使管理已下载的资源更加容易。

无网络连接​

即使玩家没有网络连接,资源库现在也能正常工作并加载本地资源。

我们在主菜单中添加了通知,用于显示一般错误和资源库消息。这意味着当资源安装完成或发生错误时,通知将会出现。

当前会话的所有通知都可以在通知 🔔 按钮下的新窗口中找到。

Assets.toml 文件校验器​

我们添加了一个新系统,可根据 Assets.toml 自动校验资产包文件。该系统会检查 Assets.toml 中列出的文件是否在磁盘上找不到,帮助快速识别和调试资产问题。

它仅在服务器加载资产包时运行,并会在控制台显示警告。

新的追踪方法​

我们为 Trace 添加了新的追踪方法,包括 SphereSingle()、SphereMulti()、BoxSingle()、BoxMulti()、CapsuleSingle() 和 CapsuleMulti()。为游戏世界提供了更多追踪可能性!

新的 Sky 方法​

我们添加了多个新的 Sky 方法,允许自定义 Moon 以及 Ultra Dynamic Sky 的其他新属性。

网络通道​

我们对游戏框架的网络层做了重大改动。 VOIP 和文件传输现在使用新的低优先级网络通道来传输数据。这意味着当玩家连接(并下载文件)或使用 VOIP(会使用大量网络数据)时,对游戏玩法性能的影响降低了。

Steam 是否已启动​

我们添加了一个更好的校验器,用于在启动游戏时检查 Steam 是否已启动。现在会弹出一个提示框,而不是让游戏崩溃。

千万别按“否”。 😉

结语​

上个月我们为 nanos world 带来了令人兴奋的新功能和改进!

最受期待的新增内容之一是新类 Widget 的引入!这确实令人兴奋,因为它将大大增加在游戏内创建 UI 的可能性!

菜单通知也为玩家带来了很多便利,我们经常遇到后台发生问题却不知道的情况。有了新的通知系统,我们现在会收到问题警报,也能知道资源库正在做什么!

CharacterSimple 现在更加完整了!随着伤害系统的加入,它现在可以更好地用作敌人或简单的 NPC!

所有这些更新和改进都旨在提升游戏的整体稳定性和体验!非常感谢你们的建议、想法和漏洞报告!

下个月见! Nogitsu 终于可以安息了。 😁

1 月:角色、天气、控制台!

· 9 分钟读完
Gabriel • SyedMuhammad
lead developer™

新 Character、天气系统、控制台命令/布局以及更多!

新 Character 类​

这个月我们努力重构了内部的 Player/Character 实现及其交互的所有内容(Weapon、Prop、Vehicle)。我们的内部代码仍然与主 Character 实体紧密相连,一切都直接引用它。因此我们努力将它们模块化,从而允许添加新的“子”Character 类。

作为初始版本,我们创建了新的 CharacterSimple 类。一个“简单”但强大的新类,除了基本移动(基于虚幻引擎基础 Character)外没有特殊功能,但它允许通过传入自定义动画蓝图来构建!它还支持传入 StaticMesh 作为网格体,我们会根据网格体大小自动调整其胶囊体尺寸,非常适合道具躲猫猫!

这个新的 Character 实体可以被任何东西使用,自定义 NPC、动物、Prop 等。而且它们都可以被玩家附身!

信息

我们将研究为此类添加更多功能的能力(例如驾驶载具或操作武器)。由于这个类旨在保持简单,因此到目前为止尚未添加这些功能。

在 CharacterSimple 类页面中了解更多。

文档改进​

文档中重要改动的摘要。

沙盒章节​

我为沙盒游戏模式添加了一个新的章节。其中包含关于生成菜单、上下文菜单和工具枪如何工作的示例,并教你如何创建脚本来扩展或创建与之集成的新功能!这是我们很棒的新快速入门!

更新日志​

我们新增了一个更新日志标签页,包含 nanos world 的最新更新!

兼容性版本​

我将兼容性版本页面移到了 核心概念 / 包 章节下的一个新单页中。现在所有兼容性版本的变更及其指南都将集中在那里。

改进的 CSS​

我们对文档布局和 CSS 做了一些改进。移动端显示得到了改进,我们在 API 定义之间添加了新的分隔线,让一切看起来整洁!

资产疑难解答​

我添加了一个新的资产疑难解答章节,包含最常见的问题及其修复方法。

ADK 已上架 Steam​

现在可以直接通过 Steam 下载并使用资产开发套件了!这个新的 nanos world Steam 工具条目使 ADK 始终保持更新!它会自动启动虚幻编辑器。

新的控制台颜色​

我们重做了控制台布局,现在它更加色彩丰富和漂亮!它变得更加健壮和现代!希望你喜欢!

控制台命令​

我们推出了官方的控制台命令系统。这个新系统允许向服务器和客户端控制台添加自定义控制台命令,并在输入 help 时将其集成到可用命令列表中。

这是一个简单的初始实现,你可以定义参数的名称及其描述,未来我们可能会扩展它以定义期望的类型、默认值等:

Console.RegisterCommand("myawesomecommand",
function(parameter_one, parameter_two)
-- 对 parameter_one 和 parameter_two 执行某些操作
end,
"此命令执行超棒的代码", -- 描述
{ "参数一", "参数二" } -- 参数
)

在 Console.RegisterCommand 方法页面中了解更多。

Steam Avatar​

现在可以获取玩家的 Steam 头像了!我们添加了一个新方法 Player:GetPlayerIconURL(),它返回一个特殊 URL,可在 WebUI 中加载 Steam 头像:

天空与天气​

我们将 Ultra Dynamic Sky 的天气系统集成到了 Sky 类中!现在可以通过 Sky.ChangeWeather() 更改当前天气,如果你拥有 UDS,甚至可以使用其资产内容中的 Ultra Dynamic Sky 材质函数将其集成到你的地图中!

我们在上下文菜单中添加了一个新的下拉菜单,允许你从中更改天气:

此外,Sandbox 上下文菜单有一个专门用于控制天气和天空的新章节,以及一个自动天气变化系统,可将当前天气同步给所有已连接的玩家!

新的 Config.toml 格式​

服务器配置文件被完全重做。现在我们有了多个不同的章节,以及更多可通过其设置的选项。几乎所有可通过命令行参数使用的设置现在都可以在那里持久定义。

在此处查看完整的配置文件架构。

压缩​

新增的一个设置是 compression,它允许你配置应用于某些网络操作的压缩量(0 为无,9 为极致)。目前我们正在测试这个新的压缩系统用于小文件传输和服务器需求清单,在启用它时,我们看到了这些文件大小最多减少 90%!

提示

使用高压缩级别会影响服务器和客户端性能,因为我们为 CPU 增加了一层计算处理。

关卡实体设置​

现在可以在 Package.toml 中切换是否加载关卡实体,也就是将关卡 StaticMesh 作为实体加载。此设置作为全局 OR 设置,如果我们至少有一个包启用了它,该设置将在全局启用。

如果我们不需要通过脚本使用或引用关卡的 StaticMesh,此设置允许我们跳过大量处理和运行时性能损失。

Map.toml 的新设置​

我们为 Map.toml 添加了两个新设置:package_requirements 和 compatibility_version,使地图包能够更好地独立工作。

Level Streaming 支持​

我们实现了对 Level Streaming 和 World Partition 的完全支持。之前它们没有正确集成到我们的 Level 类中,但现在自动流送以及 World Partition 区块的加载/卸载也会触发事件!它现在还与关卡实体集成,使关卡 StaticMesh 也能按需作为实体加载/卸载!

大世界坐标​

我们的 Vector 结构体经过了重构,现在支持双精度 Vector 结构体!这意味着 nanos world 现在可以实现巨大的关卡!在虚幻引擎官方文档页面中了解更多。

WebUI 布局​

我们为 WebUI 添加了一个新方法,虽然看起来简单,但它极大地扩展了 UI 的可能性和模块化。现在可以通过 WebUI:SetLayout 更改 WebUI 的 Canvas 布局。

此方法允许更改其 Canvas 布局(锚点、偏移、对齐),这意味着我们可以让 WebUI 像小弹窗一样工作,允许你在屏幕前方有小的 WebUI 时点击背景中的 WebUI!

WebUI ExecuteJavaScript​

我们为 WebUI 添加了一个新的 实验性 方法:WebUI:ExecuteJavaScript。这允许在你的 WebUI 中执行任意 JavaScript 代码。

备注

此方法仅对本地 WebUI 有效。

上个月我们从 Config、Package 和 Asset Pack 中移除了 logo 设置,因为现在它们都必须是本地放置的 jpg 图片。但现在我们为服务器重新添加了 --logo 命令行参数,允许它自动下载为 Server.jpg 图片!允许你轻松设置远程服务器并为其添加徽标。

新的图形设置​

我们添加了一个新设置,允许将阴影贴图方法更改为使用新的实验性虚拟阴影贴图方法!

CEF 帧率​

我们通过动态配置 CEF 的渲染/更新率改进了其实现,现在它以与游戏相同的速率刷新,大大提升了流畅感。

HTTP 与 Database 重命名​

我们重命名了 HTTP 和 Database 方法,以遵循正确的命名标准。

例如我们将 HTTP:Select 改为同步方法,并添加了新的 HTTP:SelectAsync 方法作为新的异步方法。在官方文档页面中查看更多关于这个新兼容性版本的信息。

结语​

几个月来,我们在代码库内部进行了重大的显著改动(到目前为止这些改动只是给游戏添加了随机的漏洞 😜),但终于我们可以开始看到好的结果,能够通过推出新类来扩展 API,比如新的 CharacterSimple!

我喜欢做这些改进,比如控制台布局或 Config.toml 的改动,因为它们极大地提升了我们作为开发者的生活质量,并给事物带来清新的气息。

我很惊讶 Ultra Dynamic Sky,特别是天气集成做起来竟然如此容易。真的就是把蓝图拖放到地图里而已!这为玩家增添了更多的沉浸感!

在这次更新中,我们解决了迄今为止的几个僵局,例如大世界坐标或 Level Streaming/World Partition,这些之前都不确定是否能工作。更不用说新添加的 Character 类,这是备受请求的东西,将为游戏增加更多可能性!而这仅仅是开始!

感谢又一个月的支持!希望你喜欢这篇新闻博客!下次见!

12 月:一台虚拟机统领所有!

· 9 分钟读完
Gabriel • SyedMuhammad
lead developer™

一统天下的 Lua 虚拟机!

单一 Lua 虚拟机​

经过大量分析、研究和重构。我高兴地介绍包的新工作方式!这主要是由于缺乏以简单方式创建扩展或自定义项目的可能性。

除了上次新的实体系统更新之外,这一变化将大大增加 nanos world 的模组和脚本能力的可能性!

信息

这些更改旨在产生尽可能小的影响和破坏性更改,到目前为止我做的所有测试都确保一切仍然向后兼容!

到目前为止,我们为每个包创建了一个 VM(虚拟机 - 或 lua_State)。这导致每个包彼此完全隔离。因此有必要为包之间的通信创建机制,例如 Events 和 Package.Export/Package.Call 方法,它们允许(以有限的方式)包之间相互通信。

但如前所述,这带来了许多限制,例如包无法直接访问其他包的变量或调用函数,甚至更糟(从上次实体系统更新中变得更加明显):无法在包之间共享自定义类!

但现在所有这一切都将得到解决!我重做了整个内部脚本系统,现在不再为每个包创建新的虚拟机,而是将拥有一个虚拟化的沙盒环境(使用 Lua 环境 _ENV 特性),分离每个包的作用域,但允许它们访问全局共享环境!

这将允许包全局定义类、暴露函数和共享库!这样其他所有人都可以使用,而无需变通机制!

现在,例如你可以只加载额外的脚本与 sandbox 游戏模式一起,加载自定义的工具或武器类,它们将自动出现在生成菜单中,不再需要手动添加它们!

每个包仍将拥有自己的子全局环境,因此定义全局变量或函数不会立即使它们对其他包可用。

调用与导出​

Package.Call 和 Package.Export 不再以传统方式必要。由于现在我们可以共享全局环境,我们可以定义方法并暴露它们以直接调用,而无需 Package.Call 它们。

Package.Export 仍将存在并以类似方式工作。但现在可以将任何值“暴露”到全局作用域!示例:

local my_table = { 123, 456 }

function DoSomething(var1)
return var1 + 123
end

Package.Export("MyTable", my_table)
Package.Export("DoSomething", DoSomething)
Console.Log(MyTable[1])
-- 123

Console.Log(DoSomething(456))
-- 579

继承类​

现在所有继承类将对所有包可用,因为它们将被自动创建并暴露到全局作用域。

它的第一个参数现在将用作在全局作用域中设置类的变量名。此外,它刚获得一个新的可选第二参数,用于定义包含自定义值的自定义表,以在创建类表之前设置在其中:

-- 注意我们将 Inherit 的返回值赋值给一个名为
-- MyPropClassLocal 的变量,但 MyPropClass 也将在全局定义
-- 作为我们的类,两个变量都指向同一个类表
MyPropClassLocal = Prop.Inherit("MyPropClass", {
"my_custom_value" = 123,
"my_another_value" = "hello"
})

local val = MyPropClass.my_custom_value
-- val == 123

我们还添加了一个新的静态事件,用于在注册新继承类时获取:ClassRegister:

Prop.Subscribe("ClassRegister", function(class)
-- 对新类做一些事情
end)

库包类型​

有了这种新的包工作方式,不再需要 library 类型。由于现在我们可以通过暴露的全局方法直接与其他包通信,我们只需定义要使用 Package.Export 导出的方法,并随时使用它们。

这样我们将只有两种“Lua 脚本”包类型:game-mode 和 script。所有现有的 library 包在加载时将自动转换为 script。

RequirePackage​

因此,Package.RequirePackage 也将变得不必要。由于我们现在可以全局重用相同的脚本,此方法将被弃用,改为将你的包添加到包的 Package.toml 中的 package_dependencies 列表中。

需要包的默认 nanos-world-vehicles 或 nanos-world-weapons 将作为独立包加载,因为它们现在全局导出 NanosWorldWeapons 和 NanosWorldVehicles 表。

游戏模式设置​

我们正在引入游戏模式自定义设置的概念!这些设置可以在 Package.toml 中这样定义:

# 游戏模式自定义设置配置
[custom_settings]
my_toggle = { label = "启用 PVP", type = "boolean", description = "是否启用 PVP", default = true }
my_text_input = { label = "输入任何内容", type = "text", description = "自定义文本!", default = "hello world!" }

这些设置可以通过服务器参数 --custom_settings "my_toggle = true, my_text_input = 'awesome text!', ..." 配置,或通过新游戏屏幕配置,该屏幕在创建新游戏时动态解析并显示配置:

定义的值可以通过新方法 Server.GetCustomSettings() 访问。

请参阅更新的文档页面以获取更多信息。

配置文件​

正如之前博客中所宣布的,配置文件的格式将在下一次更新中更改!

提示

所有旧格式的 Package.toml 和 Assets.toml 在首次加载时将自动转换为新格式。

元数据块​

现在我们在所有 Assets.toml 和 Package.toml 之间有了一个新的通用头部:[meta]。

它将具有以下格式,用于定义主要由商店使用的一般配置:

_meta.toml
loading...

Package​

对于包,我们将有一个新格式来区分它们的类型。每种包类型将拥有一个独立且不同的配置块。现在包类型将通过定义块 [game_mode]、[script] 或 [loading-screen] 来确定。

这是游戏模式 Package.toml 的样子:

_game_mode.toml
loading...

配置图片​

你可能已经注意到,Package.toml 中不再有 image 设置,Assets.toml 以及 Config.toml 中的服务器图像也是如此。

现在,图像应物理放置在 .toml 文件旁边,名称为 Package.jpg、Assets.jpg 或 Server.jpg。

这是一种标准化,用于解决商店和配置文件之间的图像不一致问题,并避免放置外部和恶意链接。但这带来了一个缺点,从现在起只有专用服务器才能在服务器列表中显示图像。

新的静态类​

我们终于发布了包含所有静态类重构的更新!这将为我们在文档中查找内容提供一个很好的新组织,因为现在特定方法将被分组到具有合理名称的类中!

所有新静态类列表:

此外,🕹️ 输入 也从 Client 获得了许多新方法和事件。

来自 ⌨️ Client 和旧 🌍 World 的许多方法已迁移到这些类中!查看它们的页面以获取准确信息!

重命名的事件​

根据我们上次的投票,我们开始更改所有名称错误的事件的名称。我们正在将它们标准化为现在时,例如像 CharacterEntered 这样的事件将被重命名为 CharacterEnter。

所有更改将保持旧事件作为弃用继续工作。请注意新的警告,将它们更新到较新版本!

资产插件内容​

现在可以从插件内容创建资产包,这将成为从现在起创建内容的首选方式,因为它可以防止资产路径与其他资产冲突。

为此,我们在 Assets.toml 中添加了一个新设置来定义资产包是否为插件内容:is_plugin_content。

请参阅更新的文档页面以获取更多信息。

商店改进​

上个月 MegaThorx 发布了商店更新,大大改善了新版本的上传流程。现在我们有了进度条,以及用于验证语法错误和潜在问题的特殊脚本。将来还会添加更多功能!

结语​

我对 nanos world 处理模组和脚本可能性的方式不太满意。脚本编写者必须让包之间相互通信的方式感觉非常有限和不自然,它不允许太多的自定义或扩展性。

随着上个月的实体系统以及现在包工作方式的改变,我感觉一扇巨大的可能性之门进一步打开了,这将为新事物的添加提供良好的呼吸空间!

现在可以实现的一个很好的例子是创建一个空白的 script 包,其中包含一个自定义的特殊继承武器,只需将沙盒游戏模式与你的脚本一起加载即可使其显示在生成菜单中,无需任何特殊调用或变通方法!这是巨大的,并且将所有剩余的结都系好了! 🥳

这种向单一 VM 的转变是一个巨大的变化,起初我以为它不会成功,但完成并进行测试后,我对结果感到非常惊讶和高兴!现在我们必须进一步细化和改进它!

模组制作起飞!!感谢支持!下个月见! 💪💪

11 月:实体系统、UDS 与 UE5.1!

· 9 分钟读完
Gabriel • SyedMuhammad
lead developer™

实体系统、UDS 与 UE5.1!

重构发布​

正如上次博客新闻中所宣布的,我们正在对 API 进行一系列重构,以更好地标准化它。我们有一长串的更改,我正在逐步进行。

使我们能够在不断兼容性的情况下进行更多更改的主要一项是上次更新中发布的 compatibility_version。 Package.toml 中的此配置将确保包在发生破坏性更改的未来更新中继续工作。

此外,我们已经有第一个功能来尝试 compatibility_version 系统:新的 Events.SubscribeRemote 方法!此方法将仅订阅远程调用的方法,完全分离了订阅本地和远程事件的行为。此方法的更改导致了一个破坏性更改,因为所有现有的 Events.Subscribe 将不再触发远程事件。但 compatibility_version 确保它们继续工作,直到你将值更新到版本 1.22 或更新版本。

我也在着手进行几个静态类的重构,并更改一些方法的名称(例如 HTTP.RequestSync 将被标准化为仅 HTTP.Request,异步版本将变为 HTTP.RequestAsync)。

我再次强调,如果你对事件或方法名称等其他更改有建议,请不要犹豫,说出来!像这样的大规模重构机会很少见!

实体与继承系统​

这是一个非常特别的更新,我在过去几个月中一直在努力! 🥳

新的继承系统是一个完全改变创建脚本范式的补充!该系统允许你继承现有类来创建自己的自定义类,并像原生类一样生成它们!

-- 创建一个名为 "MyNewClass" 的新类,继承自 Prop
MyNewClass = Prop.Inherit("MyNewClass")

-- 使用默认构造函数生成它
local ent = MyNewClass(Vector(), Rotator(), "nanos-world::SM_Cube")

内置事件​

原生事件将返回你的类而不是原生类。例如:

-- 这将为所有 Prop 及其后代触发,
-- 并且 'prop' 的值将是子类本身
Prop.Subscribe("Destroy", function(prop)
-- 你可以检查实体是否来自继承的类类型!
-- IsA 递归检查是否有任何父类是来自该类型
if (NanosUtils.IsA(prop, MyCube)) then
-- prop 是 MyCube 类型
end
end)

自定义方法​

轻松向你的类添加新方法!

MyNewClass = Prop.Inherit("MyNewClass")

function MyNewClass:MyMethod()
-- 做一些事情
end

自定义构造函数​

该系统还允许创建个性化构造函数:

-- 创建一个名为 "MyCube" 的新类,继承自 Prop
MyCube = Prop.Inherit("MyCube")

-- 定义我的构造函数,参数随意
function MyCube:Constructor(loc, rot)
-- 在这里做任何逻辑
loc = loc + Vector(0, 0, 100)

-- 调用父类构造函数完成构建
self.Super:Constructor(loc, rot, "nanos-world::SM_Cube")
end

-- 使用你的自定义构造函数生成它
local cube = MyCube(Vector(123, 456, 100), Rotator())

多重继承​

我们也可以从其他继承类继承!

MyCube = Prop.Inherit("MyCube")
MyMiniCube = MyCube.Inherit("MyMiniCube")
MySuperMiniCube = MyMiniCube.Inherit("MySuperMiniCube")

自定义实体事件​

现在我们可以在自定义类上触发远程事件,以实现轻松的网络通信:

Client/Index.lua
function MyCube:OnMyAnotherEvent(my_param)
Console.Log(my_param)
end

MyCube.SubscribeRemote("MyAnotherEvent", MyCube.OnMyAnotherEvent)

local my_cube = MyCube()
my_cube:CallRemoteEvent("MyCustomEvent", 123)
Server/Index.lua
function MyCube:OnMyCustomEvent(player, my_param)
self:BroadcastRemoteEvent("MyAnotherCustomEvent", "hello")
end

MyCube.SubscribeRemote("MyCustomEvent", MyCube.OnMyCustomEvent)

全局注册表​

通过父类,我们可以获取该类的所有子类列表,从而获得所有现有类的全局注册表!

local children_classes = ToolGun.GetInheritedClasses()
for _, class in pairs(children_classes) do
-- 'class' 是自定义的继承类!我们可以生成它
local p = class()
end

进一步阅读​

还有更多内容!比如实体事件或调用父类方法!在专门文档页面阅读所有内容。

沙盒改进​

借助这个新的实体系统,我正在花时间对沙盒游戏模式进行一些改进,因为现在该系统允许更好地组织事物之间的交互方式。

实体​

我正在将几乎所有内容转换为这个实体系统,例如火炬(生成菜单中的近战武器)现在变成了一个继承自 Melee 的 Torch 实体:

Torch = Melee.Inherit("Torch")

function Torch:Constructor(location, rotation)
self.Super:Constructor(location or Vector(), rotation or Rotator(), "nanos-world::SM_Torch")
self:AddAnimationCharacterUse("nanos-world::AM_Mannequin_Torch_Attack")
self:SetImpactSound(SurfaceType.Flesh, "nanos-world::A_Punch_Cue")
self:SetDamageSettings(0.5, 0.25)
self:SetCooldown(1.5)
self:SetBaseDamage(25)

local light = Light(Vector(), Rotator(), Color(1, 0.7, 0.4), LightType.Point, 100, 1000)
light:AttachTo(self, AttachmentRule.SnapToTarget, "", 0)
light:SetRelativeLocation(Vector(0, 0, 75))

local particle = Particle(Vector(), Rotator(), "nanos-world::P_Fire", false)
particle:AttachTo(self, AttachmentRule.SnapToTarget, "", 0)
particle:SetRelativeLocation(Vector(0, 0, 50))
end

我们可以这样生成它:

local my_torch = Torch(Vector(), Rotator())

生成菜单​

我正在尝试摆脱 "AddSpawnMenuItem" 函数。并取而代之,用继承注册系统替换所有内容,你只需继承一个类,它就会像魔法一样出现在生成菜单中!

例如,我们能够 Melee.GetInheritedClasses() 并获取此包中定义的所有自定义近战类(如 Torch),并自动在生成菜单中显示它们!

工具​

现在我们将有一个名为 ToolGun 的工具基类。这样我们只需继承这个类来创建新工具,它们将自动显示在生成菜单中并作为工具枪工作!

看看气球枪现在是如何定义的:

BalloonGun = ToolGun.Inherit("BalloonGun")

function BalloonGun:Constructor(location, rotation)
-- 调用父类 ToolGun 构造函数
ToolGun.Constructor(self, location, rotation, Color.VIOLET)
end

function BalloonGun:OnSpawnBalloon(player, location, ...)
-- 在这里做原始逻辑...
...

-- 生成一个气球实体!
local balloon = Balloon(location, rotation, ...)
end

BalloonGun.SubscribeRemote("SpawnBalloon", BalloonGun.OnSpawnBalloon)

在客户端,我们只需定义它并设置一些属性,这些属性将从工具枪和生成菜单中读取:

BalloonGun = ToolGun.Inherit("BalloonGun")

-- 定义 ToolGun 和 SpawnMenu 使用的一些属性
BalloonGun.name = "气球"
BalloonGun.image = "package://sandbox/Client/Tools/BalloonGun.webp"
BalloonGun.tutorials = {
{ key = "左键点击", text = "生成气球" },
{ key = "撤销", text = "撤销生成" },
{ key = "上下文菜单", text = "气球设置" },
}

function BalloonGun:OnFire(shooter)
-- 在这里做原始逻辑...
...

-- 调用远程以生成气球
self:CallRemoteEvent("SpawnBalloon", location, rotation, ...)
end

BalloonGun.Subscribe("Fire", BalloonGun.OnFire)

大功告成!不再需要调用任何 "AddToSpawnMenu" 函数了!我们只需继承它的一个已知类!

提示

注意我们定义了一个名为 Balloon 的类,这是气球的新实现。 😉

上下文菜单​

此外,我正在对上下文菜单进行一些改进,使其更加模块化。现在它允许动态添加新条目,我将把它与 ToolGun 系统集成以允许工具自定义,看看这个使用气球枪的示例:

这个新的上下文菜单允许通过 Lua 100% 自定义它,无需在其上添加新的硬编码 HTML/JS 条目。

虚幻引擎 5.1​

本月,Epic Games 发布了虚幻引擎 5.1。这对我们来说是一个期待已久的版本,特别是预期的修正。

升级进展顺利,这些升级中最困难的部分是迫使每个人也升级他们的资产和项目。

物理与缆绳​

自 5.0 以来,缆绳渲染和物理约束完全损坏,我甚至向 Epic 报告了这个错误,他们修复了这两个问题,但将发布推迟到了 5.1。

但终于我们可以再次为气球物理完美工作而高兴了! 😎

Nanite 与 Lumen​

两个系统自 5.0 以来都获得了巨大的升级,但使用最新技术的一个缺点是,我们需要强制游戏客户端使用 DirectX 12,因为这是 Nanite 和某些 Lumen 功能工作的要求。

我仍会研究是否应该强制使用 DX12,我不想通过做出这样的决定而在未来产生不可预见的后果。

Ultra Dynamic Sky​

Ultra Dynamic Sky 是虚幻引擎市场上最好的资产包之一,它为游戏提供了动态天空和天气解决方案!

我决定将这个天空系统原生地包含在 nanos world 中。它将用一个新的静态类:Sky 替换我们现有的 DefaultSun(在 World 类上),该类将很快™ 与虚幻引擎 5.1 更新一起发布!

如果你还不了解它,请观看这个产品功能视频:

结语​

新实体系统的更新是巨大的!我对结果非常满意,它的可用性变得非常简单,这开启了太多新的可能性!

这一变化将使 nanos world 在脚本能力方面提升到另一个水平。我正在研究更多方法,以提供更简单的方法来创建自定义脚本扩展,例如一种创建实体并在包上动态加载的方法,而无需在原始包中通过 Package.Require 手动加载它们。

仅凭现在我们可以简单地定义一个继承自另一个类的新类,并且这足以让它在沙盒的生成菜单中显示,就已经很棒了!

我希望完成仍在进行的静态类重构,并开始重命名一些非标准化的类方法。兼容模式功能将帮助我们很多人在这些更改中保持事物正常工作。并尽快实现我向所有人发布 UE 5.1 更新的愿望,这最后一个将是最痛苦的,因为它需要我们重新烘焙和更新商店中的所有资产。 😜

此外,我希望在 Discord 上开始关于扩展类未来的讨论,它目前无论新的继承系统如何都能工作,但它有几个问题,我想听听你们的意见来决定它的未来。

希望你喜欢!感谢反馈!下个月见!