65.9K
CodeProject 正在变化。 阅读更多。
Home

Stride Community Toolkit 预览版入门:纯代码基础

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2024年9月25日

MIT

5分钟阅读

viewsIcon

5817

探索 Stride Community Toolkit 的纯代码功能,该功能旨在帮助 C#/.NET 开发者使用强大的开源 (FOSS) C# 游戏引擎 Stride 轻松创建沉浸式 2D/3D 游戏和可视化效果。

介绍🌱

欢迎使用 Stride Community Toolkit 的预览版。这是一个为 Stride C# 游戏引擎提供的扩展和助手集合,作为社区驱动的开源项目开发。该工具包使开发者能够使用 Stride 的纯代码方法创建 2D/3D 游戏和可视化效果,而无需使用 Game Studio

在这篇简短的文章中,我们将探讨 纯代码 功能,我发现它对于 C#/.NET 开发者来说特别有用。我们将使用一个 .NET 8 控制台应用程序,通过添加几个 NuGet 包来创建一个简单的项目。如果您喜欢这篇文章,请务必查看文末链接的更全面的文章,以进行深入了解。

目录

  • 引言
  • 背景
  • 必备组件
  • 使用代码
  • 步骤 1:创建新的 C# .NET 8 控制台应用程序
  • 步骤 2:添加代码
  • 步骤 3:构建游戏
  • 步骤 4:运行游戏
  • 步骤 5:理解代码
  • 步骤 6:轮到你了
  • 参考文献
  • 结论

背景🌄

几年前我偶然发现了 Stride,它引起了我的注意,因为 Stride 使用 .NET 6 和 C# 10,而其他 C#/.NET 游戏引擎则没有。我开始探索它,但不想通过 Game Studio 来学习 Stride。相反,我想运行一个控制台应用程序,并逐步添加功能和逻辑,一步一步地学习,“.NET 的方式”。

当时,纯代码方法不像现在这样可用,所以我开始深入研究引擎,与社区联系,并贡献了一些想法和 PR。快进到今天,有了 Stride Community Toolkit,我可以通过添加常规的 NuGet 包从一个简单的控制台中运行 Stride 游戏引擎。

先决条件🏠

要遵循本教程,需要 C# 和 .NET 的基本知识。

这些先决条件已在干净的 Windows 11 安装上进行了测试。

  1. 安装 Microsoft Visual C++ 2015-2022 可再发行版 (25MB),并在出现提示时重启系统。
  2. 安装 .NET 8 SDK x64 (200MB)。
  3. 安装您选择的 IDE。我将使用 Visual Studio 2022,但您也可以使用 Visual Studio Code、Rider 或任何其他支持 .NET 开发的 IDE。

使用代码

您可以将代码复制并粘贴到您的 Program.cs 文件中,然后运行应用程序以查看结果。

步骤 1:创建新的 C# .NET 8 控制台应用程序

  1. 在您的 IDE 中创建一个新的 C# .NET 8 控制台应用程序。
  2. 添加以下 NuGet 包📦
dotnet add package Stride.CommunityToolkit.Windows --prerelease

步骤 2:添加代码

将以下代码粘贴到您的 Program.cs 文件中:💻

using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering.ProceduralModels;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Games;
using Stride.Input;
using Stride.Physics;
 
float movementSpeed = 1f;
float force = 3f;
Entity? sphere1 = null;
Entity? sphere2 = null;
 
// Create an instance of the game
using var game = new Game();
 
// Start the game loop and provide the Start and Update methods as callbacks
// This method initializes the game, begins running the game loop,
// and starts processing events.
game.Run(start: Start, update: Update);
 
// Define the Start method to set up the scene
void Start(Scene rootScene)
{
    // Add the default graphics compositor to handle rendering
    game.AddGraphicsCompositor();
 
    // Add a 3D camera and a controller for basic camera movement
    game.Add3DCamera().Add3DCameraController();
 
    // Add a directional light to illuminate the scene
    game.AddDirectionalLight();
 
    // Add a 3D ground plane to catch the cone
    game.Add3DGround();
 
    // Add a ground gizmo to visualize axis directions
    game.AddGroundGizmo(position: new Vector3(-5, 0.1f, -5), showAxisName: true);
 
    // Create a 3D primitive cone and store it in an entity
    var entity = game.Create3DPrimitive(PrimitiveModelType.Cone);
 
    // Reposition the cone 8 units above the origin in the scene
    entity.Transform.Position = new Vector3(0, 8, 0);
 
    // Add the entity to the root scene so it becomes part of the scene graph
    entity.Scene = rootScene;
 
    // Create a sphere with material, disable its collider, and add it to the scene
    // The sphere is hanging in the default position Vector(0,0,0) in the air,
    // well intersecting the ground plane as it is not aware of the ground
    sphere1 = game.Create3DPrimitive(PrimitiveModelType.Sphere, new()
    {
        Material = game.CreateMaterial(Color.Gold),
        IncludeCollider = false // No collider for simple movement
    });
    sphere1.Scene = rootScene;
 
    // This was added
    // Create a second sphere with a collider for physics-based interaction
    sphere2 = game.Create3DPrimitive(PrimitiveModelType.Sphere, new()
    {
        Material = game.CreateMaterial(Color.Orange)
    });
    sphere2.Transform.Position = new Vector3(-3, 5, 0);  // Reposition the sphere above the ground
    sphere2.Scene = rootScene;
}
 
// Define the Update method, called every frame to update the game state
void Update(Scene scene, GameTime time)
{
    // Calculate the time elapsed since the last frame for consistent movement
    var deltaTime = (float)time.Elapsed.TotalSeconds;
 
    // Handle non-physical movement for sphere1
    if (sphere1 != null)
    {
        // Move the first sphere along the negative X-axis when the Z key is held down
        if (game.Input.IsKeyDown(Keys.Z))
        {
            sphere1.Transform.Position -= new Vector3(movementSpeed * deltaTime, 0, 0);
        }
        // Move the first sphere along the positive X-axis when the X key is held down
        else if (game.Input.IsKeyDown(Keys.X))
        {
            sphere1.Transform.Position += new Vector3(movementSpeed * deltaTime, 0, 0);
        }
    }
 
    // Handle physics-based movement for sphere2
    if (sphere2 != null)
    {
        // Retrieve the RigidbodyComponent, which handles physics interactions
        var rigidBody = sphere2.Get<RigidbodyComponent>();
 
        // We use KeyPressed instead of KeyDown to apply impulses only once per key press.
        // This means the player needs to press and release the key to apply an impulse,
        // preventing multiple impulses from being applied while the key is held down.
 
        // Apply an impulse to the left when the C key is pressed (and released)
        if (game.Input.IsKeyPressed(Keys.C))
        {
            rigidBody.ApplyImpulse(new Vector3(-force, 0, 0));
        }
        // Apply an impulse to the right when the V key is pressed (and released)
        else if (game.Input.IsKeyPressed(Keys.V))
        {
            rigidBody.ApplyImpulse(new Vector3(force, 0, 0));
        }
    }
}

步骤 3:构建游戏

从命令行构建项目,或使用您的 IDE 来构建它

dotnet build

步骤 4:运行游戏

从您的 IDE 运行应用程序。您应该会看到下面的内容。

步骤 5:理解代码

让我们仔细看看代码。

有了这段代码,我们就可以运行游戏了,但当循环运行时,目前还看不到任何东西。

using Stride.Engine;

// Create an instance of the game
using var game = new Game();

// Start the game loop
// This method initializes the game, begins running the game loop,
// and starts processing events.
game.Run();

我们需要向游戏循环添加基本组件,以便在 3D 空间中显示内容。这些都是包装了基本功能的扩展,但如果这些扩展太有限,您可以实现自己的版本来进一步自定义它们。

我们将向 game.Run() 添加一个 Start() 回调方法,并使用它来使用必要的组件设置场景。

using Stride.CommunityToolkit.Engine;
using Stride.Core.Mathematics;
using Stride.Engine;

// Create an instance of the game
using var game = new Game();

// Start the game loop and provide the Start method as a callback
// This method initializes the game, begins running the game loop,
// and starts processing events.
game.Run(start: Start);

// Define the Start method to set up the scene
void Start(Scene rootScene)
{
    // Add the default graphics compositor to handle rendering
    game.AddGraphicsCompositor();

    // Add a 3D camera and a controller for basic camera movement
    game.Add3DCamera().Add3DCameraController();

    // Add a directional light to illuminate the scene
    game.AddDirectionalLight();

    // Add a 3D ground plane to catch the capsule
    game.Add3DGround();

    // Add a ground gizmo to visualize axis directions
    game.AddGroundGizmo(position: new Vector3(-5, 0.1f, -5), showAxisName: true);
}
太棒了!现在我们有了一个带有地面平面的空白空间。🤷‍♂️
 
 
接下来,让我们添加一些 3D 原始对象,它们将被封装在实体中。请记住为每个实体分配 rootScene,否则它们将不会出现在场景中。
 
这部分代码在 Start() 方法中初始化并添加 3D 原始对象。
// Create a 3D primitive cone and store it in an entity
var entity = game.Create3DPrimitive(PrimitiveModelType.Cone);
 
// Reposition the cone 8 units above the origin in the scene
entity.Transform.Position = new Vector3(0, 8, 0);
 
// Add the entity to the root scene so it becomes part of the scene graph
entity.Scene = rootScene;
 
// Create a sphere with material, disable its collider, and add it to the scene
// The sphere is hanging in the default position Vector(0,0,0) in the air,
// well intersecting the ground plane as it is not aware of the ground
var sphere1 = game.Create3DPrimitive(PrimitiveModelType.Sphere, new()
{
    Material = game.CreateMaterial(Color.Gold),
    IncludeCollider = false // No collider for simple movement
});
sphere1.Scene = rootScene;
 
// This was added
// Create a second sphere with a collider for physics-based interaction
var sphere2 = game.Create3DPrimitive(PrimitiveModelType.Sphere, new()
{
    Material = game.CreateMaterial(Color.Orange)
});
sphere2.Transform.Position = new Vector3(-3, 5, 0);  // Reposition the sphere above the ground
sphere2.Scene = rootScene;    

我们添加了三个 3D 原始对象,其中一些带有碰撞体,一些不带。

现在,让我们深入探讨键盘交互和运动。

键盘交互非常简单。 Update() 方法在游戏循环中被调用,我们使用 Input.IsKeyDown() 和 Input.IsKeyPressed() 方法来检测玩家输入,以根据按下的键触发操作。

// Define the Update method, called every frame to update the game state
void Update(Scene scene, GameTime time)
{
    // Calculate the time elapsed since the last frame for consistent movement
    var deltaTime = (float)time.Elapsed.TotalSeconds;
 
    // Handle non-physical movement for sphere1
    if (sphere1 != null)
    {
        // Move the first sphere along the negative X-axis when the Z key is held down
        if (game.Input.IsKeyDown(Keys.Z))
        {
            sphere1.Transform.Position -= new Vector3(movementSpeed * deltaTime, 0, 0);
        }
        // Move the first sphere along the positive X-axis when the X key is held down
        else if (game.Input.IsKeyDown(Keys.X))
        {
            sphere1.Transform.Position += new Vector3(movementSpeed * deltaTime, 0, 0);
        }
    }
 
    // Handle physics-based movement for sphere2
    if (sphere2 != null)
    {
        // Retrieve the RigidbodyComponent, which handles physics interactions
        var rigidBody = sphere2.Get<RigidbodyComponent>();
 
        // We use KeyPressed instead of KeyDown to apply impulses only once per key press.
        // This means the player needs to press and release the key to apply an impulse,
        // preventing multiple impulses from being applied while the key is held down.
 
        // Apply an impulse to the left when the C key is pressed (and released)
        if (game.Input.IsKeyPressed(Keys.C))
        {
            rigidBody.ApplyImpulse(new Vector3(-force, 0, 0));
        }
        // Apply an impulse to the right when the V key is pressed (and released)
        else if (game.Input.IsKeyPressed(Keys.V))
        {
            rigidBody.ApplyImpulse(new Vector3(force, 0, 0));
        }
    }
}

运动说明

我们有两种移动方式:一种用于没有碰撞体的原始对象,另一种用于带有碰撞体的原始对象。

  • 没有碰撞体的原始对象:我们通过直接修改它们的 Transform.Position 来移动它们。例如, sphere1 可以在场景中自由移动,但由于它没有碰撞体,它不会与其他实体交互——它只会漂浮在虚空中。

  • 带有碰撞体的原始对象:必须使用 RigidbodyComponent 来移动带有碰撞体的原始对象。这就是为什么我们引用 RigidbodyComponent 并应用一个冲量来使对象移动,模拟现实世界的物理。

代码本身就相当直观,我已经添加了注释来指导您完成。希望您玩得开心!

步骤 6:轮到你了

我希望您能看到,仅用几行代码就可以实现纯 C# 和 .NET 的游戏开发学习。

编码不仅能增强创造力和解决问题的能力,还能通过可视化将创造力提升到新的水平,无论是游戏、模拟还是仅仅在虚拟空间中试验 3D 模型。

让我们看看您将如何发展 🙂。

参考文献

我引起您的注意了吗?本教程的完整、全面的版本可在 Suchong:Stride Community Toolkit 预览 - C# 中的纯代码功能基础

或者,如果您对 F# 感兴趣,本文的精简版可在 Suchong:Stride Community Toolkit 预览 - F# 中的纯代码功能基础。是的,您也可以在 F# 中运行它👀!

您问 Visual Basic?👀 嗯,技术上来说,您可以,但这真的不是我的菜。不过,如果您好奇,这里有一个非常简单的例子:Visual Basic 中的带刚体的胶囊体

结论

在本文中,您学习了如何开始使用 Stride Community Toolkit,重点是其纯代码功能,用于完全通过 C# 和 .NET 开发游戏项目。我们通过设置一个简单的 .NET 8 控制台应用程序、向场景添加实体和运动以及使用键盘实现用户交互进行了讲解。通过采用这种代码优先的方法,您已经看到了在不依赖图形化 Game Studio 的情况下,游戏开发可以多么强大和灵活。

无论您是构建游戏、模拟还是试验 3D 模型,Stride 引擎及其工具包都为开发者开辟了新的可能性。现在轮到您将这些基础技能应用到您自己的创意项目中了。继续试验,继续学习,并不断突破您的创造极限!🚀

历史

- 2024/09/23 初始发布

© . All rights reserved.