[翻译]Godot创始人对从Unity迁移的答疑解惑

自从9月12日Unity公布了新的收费模式后,一部分开发者开始转向免费开源的Godot引擎。为了让开发者少走一些弯路,Godot创始人Juan Linietsky这几天发布了一系列推文,针对引擎各方面做了一些答疑,很多东西是官方文档没有提到的,更偏向于“在引擎作者眼中这个引擎应该怎么用”,值得翻译记录一下。

包含以下内容:

  • 概念上的对应关系
  • 场景树理念与Unity的区别
  • GDScript还是C#
  • 如何应对高性能需求
  • Servers API的使用(绕过场景系统直接使用底层API以达到极致性能)

概念上的对应关系

实体:节点
组件:节点
场景设置:节点
导航:节点
光照贴图:节点
视口:节点
行为:节点+脚本
预制体:场景
场景组合:场景
ScriptableObject:资源

几乎所有内容都是节点、场景或资源...Unity中很多非常复杂的子系统,在Godot中它们表达得更加自然和直观。

因此,我建议新用户首先熟悉 Godot 的工作原理及其背后的价值是什么。我理解(想尽快迁移到Godot的)这种冲动,但仅仅是把Godot当成Unity一样来转换游戏工程,很可能会导致很多痛点。

原文

对于场景树的建议

我能给那些从 Unity 转向 Godot 的人的最好建议是:
你必须将Godot的“场景树”想象为“一棵不包含任何实体的组件树”。这有两个特殊点:

  • 场景一目了然,更加清晰
  • 组合更加灵活

你不需要在每个节点上挂多个脚本来实现某种行为,只需要添加更多子节点,子节点上再挂脚本即可。
此外,预制体或场景组合,在概念上是并不存在的。你可以简单地在其他地方实例化或继承任何场景。你还可以让它们的实例变得可编辑并做一些本地更改。

译注:Godot中,一个节点上只能挂一个脚本,相比Unity中脚本是作为组件挂载到物体上,Godot中的脚本更像是节点功能的扩展

最后,要理解每个场景没有“全局设置”的概念,场景只是节点。 Unity 中属于场景的事物,例如光照贴图、导航、环境等,在Godot 中仍然只是节点,允许根据需要混合和匹配任何内容。

即使你在 Godot 中加载场景,引擎也会将其放置在一个“根”节点下。什么是根节点?一个窗口节点!没错,如果你想在游戏中使用多个窗口,请实例化更多窗口并将子节点放入其中。

个人认为这个是Godot非常有特色的地方,甚至有人做出了可以在多个窗口之间跳跃的2D平台类游戏

这些事情需要一些时间才能理解,但最终发生的事情是 Godot 让你彻底颠覆游戏开发的过程:

  • 在 Unity 中,你用代码设计游戏,并使用编辑器作为一个工具
  • 在 Godot 中,你在编辑器中设计游戏,然后添加代码..

由于 GDScript 实际上是编辑器紧密集成的一部分(并且该语言是深度集成的),因此使用它进行开发的体验甚至比使用在单独的 IDE 中编辑代码的引擎更加流畅。这就是大多数 Godot 用户更喜欢它的原因。

这些概念上的差异有助于大多数 Godot 用户提高工作效率。你很可能注意到一些 Godot 项目,甚至是那些没有太多使用该引擎经验的用户制作的项目,在今年过去的大型游戏大赛(GMTK、Ludum Dare 等)中取得了不错的成绩。

所以再次强调,在尝试从 Unity 1:1 转换你的游戏之前,在移植项目之前,请花点时间采取行动并充分理解 Godot 的设计哲学。我最不希望的是 Unity 用户在尝试过程中受到伤害并获得糟糕的体验。

原文

关于脚本语言GDScript

为什么 GDScript 存在?在 Godot 的背景下,有两个主要原因是用户的首选:

  • 快速迭代
  • 深度融合

快速迭代: 当你忙于开发游戏时,代码部分不会妨碍你:

  • 无构建时间,即时运行
  • 集成编辑器,快速打开附加到节点的脚本
  • 立即重新加载正在运行的游戏中的更改(热重载)
  • 同时调试游戏数据和代码..
  • 没有GC..

GDScript使用引用计数,而不是垃圾回收器:内存管理 。根据官方文档介绍,是为了避免GC工作时引起的卡顿和不必要的大量内存占用。

深度集成:

  • 大量与引擎紧密相关的特性:节点路径语法、onready、IDE中的可视化连接、预加载关键字等等
  • 与引擎共享数据模型,允许零消耗的查看变量、序列化、网络化等
  • 将变量暴露给编辑器无需胶水层

节点路径语法:
例如有一个这样的场景:

使用$加上路径即可获取子节点,也可以将节点拖入代码编辑器中自动生成代码:

var col = $CharacterBody2D/CollisionShape2D

集成甚至更加深入:

  • 代码自动完成可以自动提示游戏数据(节点路径,文件路径,动画名称,对象中的实时数据等)
  • 可以从编辑器拖入各种东西到代码中,自动生成相关代码(节点路径、文件路径、属性等)
  • 内置的代码编辑器和检查器

总而言之,Godot 用户更喜欢使用 GDScript,因为这种针对引擎量身定制的深层次集成。就像虚幻中的蓝图。 所以,Godot 并不是打算让 C# 成为二等公民,C# 已经尽可能地集成了(但没办法达到GDScript的高集成度)。

原文

个人看法和建议:

虽然个人更熟悉C#,但在上手Godot的过程中依然是先使用了GDScript,包括参加Game Jam、制作一些个人项目,都是使用GDScript。如果你之前使用过Python或Lua,那么GDScript是非常容易上手的,给它一些尝试,说不定它是你的菜。

正如上面所说,GDScript与引擎的集成度是最高的,对开发效率有不错的提升,相比C#在开发中没法享受到高集成度带来的便利(例如拖拽生成代码、可视化信号连接等等)。

性能上根据社区做的测评,C#是比GDScript快的,如果非常注重性能,C#是更好的选择;如果想在GDScript中提升性能,那么使用静态类型,避免使用动态类型。

如果对强类型有要求,或者希望代码尽可能与引擎解耦,这种情况下GDScript可能不是好的选择,虽然支持静态类型,但它本质上还是动态类型语言,不支持接口(只有鸭子类型),没有很强的类型检查;与引擎集成度高也导致它更难与引擎解耦。

Godot 3.x LTS 版本下,C#支持全平台打包,在目前最新版Godot 4.1中,C#不支持移动端和WebGL打包。这是由于3.x 使用的是Mono,而现在Mono已废弃,4.x版本改用.Net,没有IL2CPP那样的魔法加持,需要等待微软官方做移动端和WebGL的支持,预计时间是今年底。

多种开发语言问题

我从 Unity 开发人员那里看到的一个持续的担忧是,由于 Godot 设计为支持多种语言(不仅仅是 C#),这将导致插件碎片化。 Godot 使用通用语言适配器 API,因此目标是你可以使用任何语言的任何插件。

虽然这仍然不是 100% 完善(现在 GDScript 可以使用 C# 和 C++ 插件,C# 只能使用 C++ 插件,但两者都不能使用 GDScript),在架构上已经可以实现这一点, Godot 中的所有语言都使用相同的引擎 API。

老实说,我希望大多数复杂的附加组件都用 GDExtension (C++/Rust) 或 C# 编写。 借助他们都已经使用的通用 Godot 语言 API 适配器,应该很容易实现互通。

好奇这是如何运作的?基本上,Godot 4 使用 C 中公开的 API 适配器:

https://github.com/godotengine/godot/blob/master/core/extension/gdextension_interface.h

C 非常高效,这个星球上的每种语言编程都可以与它交互。此外,C 是 ABI 稳定的,确保当前和未来的互操作。

顺便说一下,在编写或使用插件时,这对你来说是完全透明的,你将在文档中看到 API,并且只需从你喜欢的语言中使用它即可。

原文

性能问题

我从迁移到Godot的Unity用户那里看到的另一个常见主题是,Godot是如何处理类似用Burst/ECS编写的代码的? 这是以不同的哲学方式处理的,节点没问题,但是如果处理数万个节点,性能就会受到影响,那么该怎么办?

在 Godot 中,有两种方法可以获得“更快”的性能,特别是对于大量实体:

  • 用底层语言重写脚本(C++/Rust/等)
  • 使用Servers API

正如我之前提到的,Godot 使用通用语言适配器 API。这意味着,如果你有一个带有脚本的节点并且需要对其进行优化,你可以简单地用更快的语言(C++、Rust,甚至 C#)重写它,并且对于其余代码来说它将是透明的。

一般来说,需要优化的部分很小(比如永远不会超过整个游戏的 5%),其余部分 GDScript 可以很好地处理。 但如前所述,如果使用节点,在处理数以万计的实体时可能会遇到问题,这种情况下应该使用Servers API。

Godot 提供了两个抽象层:场景层(Scene)和服务层(Servers)。场景及其节点是一个高级的、非常灵活的抽象。但Godot中所有的底层操作都是在服务层完成的。在Godot中,你可以轻松绕过场景层,直接使用服务层。

使用底层语言和Servers API,你可以获得最大性能,并且仍然保留使用 Godot 的所有可移植性和易用性优势,同时该代码可以与游戏的所有高级代码顺利交互。

原文

Godot 4.x支持Compute Shader,但这里没有提到太多

下面是对Servers API文档的部分翻译

使用Servers优化性能

官方文档-使用Servers优化性能

就像大多数引擎那样,Godot的场景系统使用节点与资源来简化项目内容的组织和资产的管理,以此制作复杂的游戏。但是显然,这样做有以下缺点:

  • 这又会导致一层额外的复杂性
  • 性能比直接使用简单 API 时要低
  • 不可能使用多个线程来控制它们
  • 需要更多的内存

大部分情况下,这并不是问题(Godot 进行了非常多的优化,大多数操作都使用信号处理,因此不需要做轮询)。尽管如此,有些情况还是不能满足要求,例如,每帧需要处理数以万计的实体可能会达到性能瓶颈。

Godot最有趣的设计决策之一是整个场景系统是可选的。虽然目前还不能将其单独提出来,但是在运行时可以完全绕过它。

与Unity类比,就像是绕过Renderer组件,直接调用底层API渲染

在核心部分,Godot 使用了服务层的概念。它们是用于控制渲染、物理、声音等的非常底层的 API。场景系统是建立在他们之上,并直接使用他们。最常见的服务层有:

  • RenderingServer: 处理图形相关
  • PhysicsServer3D: 处理3D物理相关
  • PhysicsServer2D: 处理2D物理相关
  • AudioServer: 处理音频相关

查看它们的API可以发现,提供的函数都是Godot允许你做的所有事情的底层实现。

使用服务层的关键是理解资源 ID (RID)对象。这些是服务器实现的不透明句柄。它们是手动分配和释放的。服务器中的几乎每个函数都需要 RID 来访问实际资源。

大多数 Godot 节点和资源都在内部包含了来自服务层的这些 RID,可以通过不同的函数获得它们。事实上,继承 Resource 的任何内容都可以直接强制转换为 RID,然后可以将资源作为 RID 传递给服务层 API。但是,并非所有资源都包含 RID(在这种情况下,RID 将是空的)。

下面是一些使用Servers API的示例:

创建Sprite

extends Node2D

# RenderingServer需要维持一个纹理引用
var texture

func _ready():
    # 创建一个CanvasItem
    var ci_rid = RenderingServer.canvas_item_create()
    # 设置当前节点为父节点
    RenderingServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    # 将纹理画在CanvasItem上
    texture = load("res://my_texture.png")
    # 使用RenderingServer添加到渲染
    RenderingServer.canvas_item_add_texture_rect(ci_rid, Rect2(texture.get_size() / 2, texture.get_size()), texture)
    # 旋转45°,变换位置
    var xform = Transform2D().rotated(deg_to_rad(45)).translated(Vector2(20, 30))
    RenderingServer.canvas_item_set_transform(ci_rid, xform)

Canvas Item API允许你往Canvas上画东西,一旦添加,它们便无法修改,需要清除然后再次添加(变换位置、旋转不需要清除)。

通过这个函数来清除:

RenderingServer.canvas_item_clear(ci_rid)

用过SFML可能会对这种方式比较熟悉(但又有一些不同),SFML中几乎每帧都需要调用draw和clear

创建Mesh

extends Node3D

# RenderingServer需要维持一个网格引用
var mesh

func _ready():
    # 创建一个3D实例.
    var instance = RenderingServer.instance_create()
    # 设置scenario,这样这个实例才会出现当前世界中
    var scenario = get_world_3d().scenario
    RenderingServer.instance_set_scenario(instance, scenario)
    # 添加网格
    mesh = load("res://mymesh.obj")
    RenderingServer.instance_set_base(instance, mesh)
    # 移动网格
    var xform = Transform3D(Basis(), Vector3(20, 100, 0))
    RenderingServer.instance_set_transform(instance, xform)

你可能感兴趣的:([翻译]Godot创始人对从Unity迁移的答疑解惑)