Table of Contents

面向 Godot 开发者的 Stride

编辑器

Stride 的编辑器是 Game Studio。它相当于 Godot Editor。

Godot 布局

Stride Game Studio 布局

你可以通过拖动选项卡来自定义 Game Studio 的布局,与 Visual Studio 类似。

有关 Game Studio 的更多信息,请参阅 Game Studio 页面。

代码编辑器

Godot

Godot 提供了一个内置代码编辑器,支持其自身的脚本语言 GDScript,以及 C# 和 VisualScript。Godot 编辑器与引擎的集成更紧密,并且通常会随着新功能保持更新。

Stride

Stride 在 Game Studio 中附带了一个集成的 C# 代码编辑器。虽然可用,但该编辑器并非高优先级功能,可能不会频繁更新。因此,通常建议使用专用的 IDE 进行代码编辑。一些常见的选择包括:

  • Visual Studio Code:免费、开源且高度可扩展。
  • Rider:付费,但提供了一套针对 .NET 开发的强大功能。
  • Visual Studio Community:对小团队和个人开发者免费。
  • Visual Studio Professional 和 Enterprise:付费版本,附带额外的功能和服务。

总之,虽然 Stride 和 Godot 都提供集成的代码编辑器,但 Stride 的编辑器最好被视为补充工具,而不是完整的 IDE。建议在 Stride 中使用专门的 IDE 来处理更复杂的开发任务。另一方面,Godot 的编辑器足够强大,如果你使用 GDScript 或 C#,可以进行完整的开发。

术语

Godot Stride
场景(PackedScene) 预制体 / 场景资产
Inspector(检查器) Property Grid(属性网格)
FileSystem(文件系统) 解决方案/资产视图(Asset View)
2D/3D Viewport(视口) 场景编辑器(Scene Editor)
节点(Node) Entity(实体)
附加到节点的脚本 脚本组件(SyncScript、AsyncScript、StartupScript)
[Export] [DataMember]
[GlobalClass] 可序列化类上的 [DataContract](用于在编辑器中公开自定义类型)

文件夹和文件

  • Assets
    • 在 Godot 中,你可以将资产存储在任何位置。
    • 在 Stride 中,资产存储在 Assets 文件夹中。
  • Stride 和 Godot 使用标准的 C# 解决方案结构。一个关键区别是 Stride 使用多项目架构,包含以下项目:
    • MyPackage.Game 包含你的源代码。
    • MyPackage.Platform 包含项目所支持平台的其他代码。Game Studio 会为每个平台创建文件夹(例如 MyPackage.Windows、MyPackage.Linux 等)。这些文件夹通常很小,只包含程序的入口点。
    • 任何其他子项目。Stride 扫描子项目的方式与扫描主项目的方式相同,以查找 DataContract 类和编辑器、游戏的特性。
  • Bin 包含编译后的二进制文件和数据。Stride 会在你构建项目时创建此文件夹,并为每个平台创建一个子目录。
  • obj 包含缓存文件。Game Studio 会在你构建项目时创建此文件夹。要强制完全重新构建资产和代码,请删除此文件夹并重新构建项目。
  • Resources 是存储资产所用图像和音频等文件的建议位置。Godot 和 Stride 使用不同的资源系统,因此请将其视为项目组织文件夹,而不是 Godot 风格的 Resource 类型。

从 Game Studio 打开项目目录

你可以在 Game Studio 中通过 Project > Show in explorer 打开项目目录。

从 Game Studio 打开项目目录

游戏设置

Godot 将全局设置保存在 Project Settings 中。

Stride 将全局设置保存在一个资产中,即 Game Settings 资产。你可以配置:

  • 默认场景(default scene)
  • 渲染设置(Rendering settings)
  • 编辑器设置(Editor settings)
  • 纹理设置(Texture settings)
  • 物理设置(Physics settings)
  • 覆盖(Overrides)

要使用 Game Settings 资产,请在资产视图中选择 GameSettings,并在属性网格中查看其属性。

游戏设置

场景

与 Godot 一样,在 Stride 中,你将所有对象放置在场景中。Game Studio 将场景作为独立的 .sdscene 资产存储在项目目录中。

设置默认场景

你的项目中可以有多个场景。游戏一启动就加载的场景称为默认场景(Default Scene)。

要设置默认场景:

  1. 在 GameSettings 属性中,Default Scene 旁边,单击 手形图标(选择资产)。

    设置默认场景

    **选择资产(Select an asset)**窗口随即打开。

  2. 选择默认场景,然后单击确定。

有关场景的更多信息,请参阅场景。

实体与节点

在 Godot 中,场景中的对象称为节点(Node)(组织在场景树中)。在 Stride 中,它们称为实体(entity)。

Stride 中的实体

与节点一样,实体是"行为"和数据的载体。在 Godot 中,这通常通过使用不同的节点类型(例如 Node3D、Sprite2D、AudioStreamPlayer)并附加脚本来实现。在 Stride 中,则通过添加组件(变换组件、模型组件、音频组件等)来实现。如果你习惯在 Godot 中使用节点,那么在 Game Studio 中使用实体应该不会有问题。

实体组件

在 Stride 中,你在编辑器中向实体添加组件的方式,与在 Godot 编辑器中附加子节点或配置节点属性的方式大致相同。

要在 Game Studio 中向实体添加组件:

  1. 选择要添加组件的实体。

  2. 在属性网格(默认位于右侧)中,单击添加组件并从下拉列表中选择组件。

    添加组件

变换组件

与 Godot 中的 Node3D / Node2D 一样,Stride 中的每个实体都有一个变换组件,用于设置其在世界中的位置、旋转和缩放。

变换组件

所有实体创建时都默认带有 Transform 组件。

在 Stride 中,Transform 组件包含一个 LocalMatrix 和一个 WorldMatrix,它们会在每个 Update 帧更新。如果你需要强制提前更新,可以使用 TransformComponent.UpdateLocalMatrix()、Transform.UpdateWorldMatrix() 或 Transform.UpdateLocalFromWorld(),具体取决于你需要如何更新矩阵。

局部位置/旋转/缩放

Stride 使用 position、rotation 和 scale 来表示局部位置、旋转和缩放。

Godot(典型 3D) Stride
position Transform.Position
quaternion / basis Transform.Rotation
rotation_degrees Transform.RotationEulerXYZ
scale Transform.Scale
Tip

在 Godot 中,Node3D.rotation 是以弧度为单位的欧拉角,Godot 还提供了 rotation_degrees 之类的便捷属性。

要以度为单位获取和设置旋转,请将实用方法 MathUtil.DegreesToRadians 和 MathUtil.RadiansToDegrees 与 Transform.RotationEulerXYZ 配合使用。

世界位置/旋转/缩放

在 Godot 中,世界空间的变换值通常通过 global_* 属性或 global_transform 访问。与 Godot 相比,Transform 组件中与世界位置相关的许多属性通过 Stride 的 WorldMatrix 访问。

Godot(4.x, Node3D) Stride
global_position / global_transform.origin Transform.WorldMatrix.TranslationVector
global_rotation(欧拉角,弧度) 从 Transform.WorldMatrix 分解
global_scale 从 Transform.WorldMatrix 分解
global_transform Transform.WorldMatrix
global_transform.basis(全局空间中的旋转/缩放/错切) 使用方向向量并在 Transform.WorldMatrix 上进行分解
从 global_transform / global_transform.basis 分解(旋转/缩放等) Transform.WorldMatrix.DecomposeXYZ(out Vector3 rotation)
分解平移/缩放 Transform.WorldMatrix.Decompose(out Vector3 scale, out Vector3 translation)
分解平移/旋转/缩放 Transform.WorldMatrix.Decompose(out Vector3 scale, out Quaternion rotation, out Vector3 translation)
Note

WorldMatrix 仅在整个 Update 循环运行完毕后才更新,这意味着如果该对象或其父对象的位置在上一帧和现在之间发生了变化,你可能会读取到过时的数据。 为确保读取到最新的位置和旋转,你应该在读取之前通过调用 Transform.UpdateWorldMatrix() 强制更新矩阵。

变换方向

在 Godot 中,方向向量通常从变换基(basis)派生(例如使用 global_transform.basis)。在 Stride 中,WorldMatrix 直接公开方向向量。请注意,这些是矩阵属性,因此仅设置其中之一不足以正确旋转矩阵。

Godot(典型 3D) Stride
-global_transform.basis.z(3D 中的前方) Transform.WorldMatrix.Forward
global_transform.basis.z(3D 中的后方) Transform.WorldMatrix.Backward
global_transform.basis.x(右方) Transform.WorldMatrix.Right
-global_transform.basis.x(左方) Transform.WorldMatrix.Left
global_transform.basis.y(上方) Transform.WorldMatrix.Up
-global_transform.basis.y(下方) Transform.WorldMatrix.Down
Note

参见世界位置/旋转/缩放中的注释

资产

资产在**资产视图(Asset View)**中导入和管理。

资源

Godot 使用专用的 Resource 对象。Stride 不使用相同的资源对象模型。在 Stride 中,等效数据通常用资产、预制体和可序列化类([DataContract] / [DataMember])表示,你可以从实体和脚本中引用它们。

更多详细信息,请参阅 Godot Resource 和 Godot PackedScene。

支持的文件格式

与 Godot 一样,Stride 支持的文件格式包括:

资产类型 支持的格式
模型、动画、骨骼 .dae, .3ds, .obj, .blend, .x, .md2, .md3, .dxf, .fbx
精灵、纹理、天空盒 .dds, .jpg, .jpeg, .png, .gif, .bmp, .tga, .psd, .tif, .tiff
音频 .wav, .mp3, .ogg, .aac, .aiff, .flac, .m4a, .wma, .mpc
字体 .ttf, .otf
视频 .mp4

有关资产的更多信息,请参阅资产。

预制体继承

相当于 Godot 继承场景的是原型(archetype)。原型是控制从中派生的资产属性的主资产。派生资产适用于你想要创建资产"混音版"版本的情况。这与预制体类似。

例如,假设我们有三个共享名为 Metal 材质资产的球体实体。现在假设我们只想更改其中一个球体的颜色,同时保持其其他属性不变。我们可以复制材质资产,更改其颜色,然后将新资产仅应用于一个球体。但如果以后我们想更改所有球体的另一个属性,就必须同时修改两个资产。这既费时又容易出错。

更好的方法是从原型派生新资产。派生资产会继承原型的属性,并允许你在需要时覆盖各个属性。例如,我们可以派生球体的材质资产并覆盖其颜色。然后,如果我们更改原型的光泽度,所有三个球体的光泽度都会改变。

对象生命周期

在 Godot 中,节点通常使用 queue_free() / free() 移除。在 Stride 中,实体和组件会从场景/实体中移除,然后在不再被引用时由 .NET 垃圾回收器释放。

这使得诸如将实体从一个场景移除并稍后添加到另一个场景之类的模式成为可能,只要保留引用即可。

输入

在 Stride 中,你可以像在 Godot 中一样通过按键来处理输入,也可以通过虚拟按钮处理输入,后者类似于 Godot 的输入映射。

public override void Update()
{
    // 在按下空格键的那一帧为 true
    if (Input.IsKeyDown(Keys.Space))
    {
        // 做一些事情。
    }

    // 当该操纵杆按钮按下期间为 true
    if (Input.GameControllers[0].IsButtonDown(0))
    {
        // 做一些事情。
    }

    float Horiz = (Input.IsKeyDown(Keys.Left) ? -1f : 0) + (Input.IsKeyDown(Keys.Right) ? 1f : 0);
    float Vert = (Input.IsKeyDown(Keys.Down) ? -1f : 0) + (Input.IsKeyDown(Keys.Up) ? 1f : 0);
    // 做别的事情。
}

时间

Godot Stride
_Process(double delta) 中的 delta Game.UpdateTime.WarpElapsed.TotalSeconds
_PhysicsProcess(double delta) 中的 delta SimulationUpdate(BepuSimulation simulation, float simTimeStep) 中的 simTimeStep
Engine.time_scale Game.UpdateTime.Factor

物理

Stride 和 Godot 都提供全面的物理引擎,但它们处理碰撞和基于物理的交互的方式不同。以下是它们功能特性的对比。

Stride

在 Stride 中,主要有三种类型的碰撞体:

  • 静态碰撞体(Static Colliders): 固定在原地且不会移动,通常用于墙壁或地板等环境元素。
  • 刚体(Rigidbodies): 受物理模拟(如重力或力)影响的动态碰撞体。
  • 角色(Characters): 为与角色控制器配合使用而设计的特殊碰撞体。

在 Stride 中,碰撞处理通过处理碰撞事件和触发器的物理组件和脚本逻辑完成。详情请参阅物理、触发器和物理查询。

Godot

在 Godot 中,你可以使用基于信号的系统来响应碰撞。当特定事件(例如两个对象碰撞)发生时会发出信号,你可以将这些信号连接到自定义方法来执行自己的逻辑。

脚本

不同的脚本方法

在 Stride 中,有三种类型的脚本,提供了与 Godot 不同的范式。Godot 要求你继承特定的类来创建该类型的节点,而 Stride 允许你通过添加脚本来扩展实体,然后搜索要交互的特定实体。

在 Stride 中扩展实体

例如,你不必在 Godot 中继承 CharacterBody3D,而是在 Stride 中将 CharacterComponent 附加到实体上。别忘了还要附加一个碰撞形状,使其可交互。然后,你可以在脚本中搜索这些组件来操作它们。

Stride 示例

// 在 Stride 中搜索 CharacterComponent 的示例
public class MyScript : StartupScript
{
    public override void Start()
    {
        var characterComponent = Entity.Get<CharacterComponent>();

        if (characterComponent != null)
        {
            // 对 characterComponent 执行操作
        }
    }
}

委托优于继承

Stride 中的这种方式体现了"委托优于继承(Delegation over Inheritance)"的原则,为你在设计游戏架构时提供了更大的灵活性。

StartupScript

就初始化流程而言,StartupScript.Start() 最接近 Godot 的 _Ready(),但它们在引擎的每个细节上并非生命周期完全相同。在 Godot 中,_Ready() 在节点进入场景树时运行,父节点通常先于其子节点就绪。在 Stride 中,StartupScript.Start() 在场景或实体初始化之后脚本启动时运行,确切的父子启动顺序可能与 Godot 不匹配。在实践中,使用 StartupScript 进行初始化和设置任务。

Stride 示例

public class BasicMethods : StartupScript
{
    // 将在 Game Studio 中可见的公共成员字段和属性
    public override void Start()
    {
        // 脚本的初始化代码
    }

    public override void Cancel()
    {
        // 脚本的清理代码
    }     
}

Godot 示例

public class BasicMethods : Node
{
    // 此方法相当于 Stride StartupScript 中的 Start
    public override void _Ready()
    {
        // 脚本的初始化代码
    }

    // Godot 没有与 Stride 的 Cancel 直接对应的方法,
    // 但你可以使用 _ExitTree 进行清理
    public override void _ExitTree()
    {
        // 脚本的清理代码
    }
}

SyncScript

Stride 和 Godot 都提供了为游戏更新重复调用的方法。在 Stride 中,该方法称为 Update(),是 SyncScript 类的一部分。在 Godot 中,对应的方法是 _Process(double delta)。

主要区别

  1. 增量时间(Delta Time): Stride 的 Update() 不包含 delta time 参数。相比之下,Godot 在 _Process(double delta) 中以参数(delta)的形式提供自上一帧以来的时间。
  2. 访问增量时间: 在 Stride 中,你仍然可以通过 Game 属性访问增量时间,即 using Game.UpdateTime.Elapsed.TotalSeconds。

Stride 示例

public class BasicMethods : SyncScript
{
    public override void Start() { }
    public override void Cancel() { }        
    public override void Update()
    {
        // 在 Stride 中访问增量时间
        double deltaTime = Game.UpdateTime.Elapsed.TotalSeconds;

        // 基于 deltaTime 执行操作
    }
}

Godot 示例

public class BasicMethods : Node
{
    public override void _Ready() { }
    public override void _ExitTree() { }        
    public override void _Process(double delta)
    {
        // 基于 delta 执行操作
    }
}

AsyncScript

Stride 和 Godot 都提供了异步运行代码的方式,但它们采用的方法不同。

Stride 示例

Stride 提供了一个专门的 AsyncScript 类,允许你使用 C# 的 async/await 语法异步执行代码。Execute() 方法可以被 await,使你的代码能够在不阻塞主游戏循环的情况下运行。

public class BasicMethods : AsyncScript
{
    // 公共成员字段和属性将在 Game Studio 中可见
    public override async Task Execute()
    {
        // 如有需要,初始化代码应放在这里

        // 循环直到游戏结束(是否需要取决于脚本)
        while (Game.IsRunning)
        {
            await MyEvent;

            // 做一些事情

            // 等待下一帧(是否需要取决于脚本)
            await Script.NextFrame();
        }
    }

    public override void Cancel()
    {
        // 脚本的清理代码
    }     
}

Godot 示例

Godot 不提供像 Stride 那样的专用 AsyncScript 类。不过,你仍然可以使用标准的 async/await 语法在 C# 中编写异步代码。

public class BasicMethods : Node
{
    public async override void _Ready()
    {
        await ToSignal(GetTree().CreateTimer(1.0f), "timeout");
        // 在 1 秒计时器到期后执行代码
    }

    // Godot 没有与 Stride 的 Cancel 方法直接对应的方法
    public override void _ExitTree()
    {
        // 脚本的清理代码
    }
}

总之,Stride 和 Godot 都提供了异步运行代码的机制,但它们实现的方式不同。Stride 提供了内置的 AsyncScript 类,而 Godot 则允许通过标准 C# 机制实现异步代码。

脚本组件

在 Stride 和 Godot 中,脚本都用于为游戏实体定义行为和逻辑。但是,两个引擎附加和管理这些脚本的方式有所不同。

创建脚本

Stride

要创建脚本,请单击添加资产按钮并选择 Scripts。

在 Stride 中创建脚本

Stride 有一个 SyncScript 类,附带以下方法:

如果你想让脚本是启动脚本或异步脚本,请使用相应的脚本类型:

Godot

在 Godot 中,你可以从编辑器创建脚本,也可以通过 Inspector 将现有脚本附加到节点。

在 Godot 中,你使用 _Ready() 等方法进行初始化,使用 _Process(delta) 进行逐帧更新。Godot 还支持 C# 中的 async/await 语法。

重新加载程序集

Stride

创建脚本后,你可能需要手动重新加载程序集。为此,请在 Game Studio 工具栏中单击 Reload assemblies。

重新加载程序集

Godot

Godot 会在脚本保存时自动重新加载脚本,无需手动重新加载。

将脚本添加到实体

Stride

  1. 在实体树(默认位于左侧)中或在场景中,选择要添加脚本的实体。

    选择实体

  2. 在属性网格(默认位于右侧)中,单击添加组件并选择要添加的脚本。

    添加脚本组件

Godot

  1. 在场景树中选择节点。
  2. 在 Inspector 中,单击 Attach Script 按钮或附加现有脚本。

在 Stride 中,脚本与其他组件一起按字母顺序列出。在 Godot 中,脚本直接附加到节点上,并作为子资源显示在 Inspector 中。

有关在 Stride 中添加脚本的更多信息,请参阅使用脚本。

实例化预制体

在 Godot 中,常见的等效方式是实例化 PackedScene。

var node = packedScene.Instantiate<Node3D>();
AddChild(node);

在 Stride 中,你可以使用预制体实例化实体。但是,与 Godot 不同,Stride 中的预制体在其根部可以包含多个实体,这意味着实例化将返回多个实体实例。

在 Stride 中,可以像下面这样实例化实体:

// 显示在 Game Studio 属性网格中的公共成员字段和属性
public Prefab CarPrefab;
public Vector3 SpawnPosition;
public Quaternion SpawnRotation;

public override void Start()
{
    // 脚本的初始化
    List<Entity> carEntities = CarPrefab.Instantiate();
    
    // 将实例化的实体添加到根场景
    SceneSystem.SceneInstance.RootScene.Entities.AddRange(carEntities);
    
    // 为列表中的第一个实体设置位置和旋转
    carEntities[0].Transform.Position = SpawnPosition;
    carEntities[0].Transform.Rotation = SpawnRotation;
    
    // 你还可以选择为实体设置名称
    carEntities[0].Name = "MyNewEntity";
}

序列化

Godot

在 Godot C# 中,暴露给检查器的成员通常使用 [Export],脚本通常附加到某个节点类型上。导出支持与引擎可以序列化的类型相关联。

Stride

Stride 采用了不同的方法,旨在与 C# 更紧密地集成。

DataContract 特性

要使你的类可在 Game Studio 中序列化,请在类上添加 [DataContract] 特性。默认情况下,所有公共成员都会被序列化。

[DataContract]
public class MyClass
{
    public int MyProperty { get; set; }
}

DataMember 特性

如果你想明确指定要序列化的内容,可以使用 [DataMember] 特性。这与 Godot 的 [Export] 特性类似。

[DataContract]
public class MyClass
{
    [DataMember]
    public int MyProperty { get; set; }
}

排除成员

要将成员排除在序列化之外,请使用 [DataMemberIgnore] 特性。

[DataContract]
public class MyClass
{
    [DataMemberIgnore]
    public int MyProperty { get; set; }
}

集合和字典

Stride 支持 ICollection 和 IDictionary 类的序列化。请注意,只有基元和枚举可以用作字典的键。

在 Godot 中,你需要导出 Godot 集合才能在编辑器中显示它们。

嵌套序列化

你可以将任何标记了 [DataContract] 的类(包括抽象类或接口)序列化到编辑器中。Stride Editor 会搜索与这些接口或抽象类匹配的类型,使其符合序列化条件。

日志输出

在 Godot 中,你可以使用 GD.Print 打印消息。

要查看日志输出,请转到 Game Studio 工具栏并单击 View,然后启用 Output 选项。

启用输出

启用后,Output 选项卡将会出现,通常位于 Game Studio 界面的底部。

Output 选项卡

打印调试消息

从 ScriptComponent 进行日志记录:

public override void Start()
{
    // 启用日志记录。如果没有附加调试器,它还会生成一个控制台窗口。
    // 该参数决定了将过滤掉哪些类型的消息,在本例中,优先级低于警告的消息都不会显示
    Log.ActivateLog(LogMessageType.Warning);
    // 将此消息记录到你的控制台或 IDE 输出窗口
    Log.Warning("hello");
}
System.Diagnostics.Debug.WriteLine("hello");
Note

要打印调试消息,你必须从 IDE 而不是 Game Studio 运行游戏。正在运行的游戏无法打印到 Game Studio 的输出窗口。

另请参阅