Tutorial ‐ Entitas‐lang(已停止维护) - OneYoungMean/Entitas-CSharp-OYM GitHub Wiki

Introduction

本教程将简要介绍如何将Entitas-lang与Entitas一起使用。在本教程中,我将使用一个小例子来创建一个简单的战斗系统,玩家可以通过按空格键来攻击游戏中的敌人。

译者注:这玩意相当的奇怪,翻译起来很多地方谷歌翻译都败了,所以翻译不过去的我只能保留原文了(摊手)。

Why use Entitas-lang?

使用Entitas-lang的一个优点是,它允许您在Entitas中创建新组件和系统时,一遍又一遍地消除编写相​​同代码的许多冗余。这意味着您必须编写更少的代码来完成相同的工作量,并且每次添加新的组件和系统时它都会自动生成新文件。

另一个优点是,它允许您生成新文件,即使您遇到编译错误。如果没有Entitas-lang,Unity中的“Entitas”菜单将会消失,从而使您无法生成新文件(如果您不使用Entitas-lang,这是生成文件的唯一方法).

一旦习惯了Entitas-lang的语法,就可以很容易地创建新的组件和系统,从而为游戏添加新功能.

Step 1 - Install Entitas-lang

首先你得安装一个Entitas,这里我就不重复叙述了。 其次,你可以使用两种方法可以使用Entitas-lang;使用Visual Studio Code或Eclipse。请注意,Visual Studio Code有一些限制(例如不支持多个.entitas文件)。但是,它确实支持自动完成,这在开始学习Entitas-lang时可以提供很多帮助。在本教程中,我将使用Visual Studio Code。

1. 使用 Visual Studio Code

  1. 下载 Visual Studio Code.(注意不是visual studio!)
  2. 确保您的计算机上安装了 Java 1.8
  3. 下载 Entitas Extension.(度盘炸掉发issue补档)
  4. 打开VS Code的命令提示符 (Ctrl+Shift+P)输入 "install from VSIX" 然后在弹出的对话框中选择vsix文件。
  5. File > Open Folder... > Go to your Unity project folder > Select the Assets folder // 文件 > 打开文件夹... > 你的 Unity project 文件夹> Assets 文件夹

2. Using Eclipse(有需要的自己看一下,OYM用的vscode)

  1. Download the latest version of Eclipse.
  2. Download Xtext.

Install the eclipse plugin from the update site by following this step-by-step guide:

  1. Open Eclipse > Go to Help > Install New Software...
  2. Paste "http://bomzhi.de/entitas_lang/site.xml" into "Work with" (top input bar).
  3. Tick the item "Uncategorized" in the box.
  4. Click Next and let it install.
  5. File > Open Projects from file system > Click on Directory > Select your Unity projects Assets folder
  6. If eclipse prompts you with a dialog about converting the project to an Xtext project; click yes.

Alternatively you can download the plugin as a zip file if you want to add it to eclipse manually.

Project structure(项目结构)

在你安装完Entitas之后。你的项目应该看起来像是这个样子:

新建一个 "Sources"文件夹. 这是你创建操纵游戏实体的体统的地方,然后在Assets文件夹下创建一个叫做"compAndSys.entitas" (记住结尾一定要是.entitas,这很重要)(没看到格式选项就自己写一下),这个文件将会包含所有的component与系统那些Entitas-lang将会生成的。

最后项目结构看起来是这个样子的:

Step 2 - Setting Entitas-lang to C#(设置Entitas-lang转换为C#)

为了让Entitas-lang在C#中生成文件,请在"compAndSys.entitas"第一行写上:

target entitas_csharp

执行此操作后,您可以看到Entitas-lang创建了一个名为“src-gen”的文件夹。这是放置所有生成的文件的位置。

Step 3 - Generating contexts(生成上下文)

在这里我们将会只使用两个上下文————一个game和一个input,game上下文将会拥有所有的游戏逻辑,input上下文将会处理所有来自user的输入。(在这个例子中,我们使用的是空格键)

要生成game与input的上下文,只需要在 "compAndSys.entitas" 中写:

context Game(default), Input

另一个标记(default标记)已添加到上面的代码中。正如我们编写所需要的component格式那样,所有未指定其上下文的新component(即没有注明[Input]的component)都将被置于默认上下文中。

现在我们的"compAndSys.entitas"应该是这个样子:

target entitas_csharp

context Game(default), Input

Step 4 - Generating components(生成组件)

游戏将由玩家和玩家可以攻击的敌人组成,这可以分为几个区间:生命值,伤害,玩家,敌人,与来自空格的输入。

要生成这些组件,我们需要引入一个别名:

alias int : "int"


Why use an alias?(如何使用别名(alias?))

每当您想在组件中使用C#中的类型时,最佳做法是指定一个新别名。请看Entitas-lang中的以下代码:

comp CompA
    ListOfInts : "System.Collections.Generic.List"
comp CompB
    ListOfInts : "System.Collections.Generic.List"
comp CompC
    ListOfInts : "System.Collections.Generic.List"

因为Entitas-lang不了解C#中的类型,所以必须指定完整的命名空间。但是,使用别名这会变得更容易. 请区分下面代码与上面的区别 (两者所希望表达的含义是一样的):

alias IntList : "System.Collections.Generic.List"

comp CompA
    ListOfInts : IntList 
comp CompB
    ListOfInts : IntList 
comp CompC
    ListOfInts : IntList 

如上所示,生成组件;使用“comp”关键字。也就是说,要生成运行状况和损坏组件,请编写代码:

comp Health
    Value : int

comp Damage
    Value : int

这是Entitas-lang的力量;能够快速编写和生成组件。通常你会写下面的C#代码:

public class HealthComponent : IComponent{
    public int Value { get; set; }
}

public class DamageComponent : IComponent{
    public int Value { get; set; }
}

然后在Unity中按生成。 Entitas-lang会自动为您完成此操作.

为了区分玩家实体和敌人实体,我们将创建两个将用作标志的组件.

comp Player (unique)

comp Enemy

此外,还添加了"(unique)"来指定整个游戏中只有一个玩家。

(简体) 应该指出的是,到目前为止,我们尚未指定组件的上下文。那是因为我们之前设置的默认上下文。在输入组件中,我们必须使用“in”关键字显式编写上下文。以下代码演示了这一点:

comp SpacebarInput in Input

现在你的 "compAndSys.entitas"看起来应该是这样子的:

target entitas_csharp

context Game(default), Input

alias int : "int"
comp Health
    Value : int
comp Damage
    Value : int
comp Player (unique)
comp Enemy
comp SpacebarInput in Input

如果你顺手打开了"src-gen"文件夹,你会看到所有你所希望的组件此刻已经被Entitas-lang生成了:

Step 5 - Generating systems(生成组件)

在我们可以开始使用刚刚生成的component之前,我们需要生成一个系统,在这里我们需要四个系统来支持:

  • SpawnSystem (initialize system)
  • InputSystem (execute and cleanup system - to gather inputs from the keyboard)(键盘输入系统:execute and cleanup system)
  • ProcessSpacebarInputSystem (reactive system - 处理所有有关于实体的伤害)
  • PrintHealthSystem (reactive system - 把生命值传递给entitas

SpawnSystem (initialize system)

() 我们将使用初始化系统来创建游戏中的玩家和敌人。以下代码执行此操作:

sys SpawnSystem (init)
    access:
        _gameContext : Game

为了生成系统,我们需要使用关键词“sys”,当我们处理一个初始化系统,我们需要指定它才能给他命名。这在上面用“(init)”显示。另一个关键词“访问”被引入了。这允许我们访问我们将创建玩家和敌方实体的上下文。 “_gameContext”是将在C#中生成的字段的名称,“Game”是上下文。

上面的代码生成了一个名为“AbstractSpawnSystem”的类,我们将在创建spawn系统时继承它。以下C#代码段显示了这一点:

public class SpawnSystem : AbstractSpawnSystem
{
    public SpawnSystem(Contexts contexts) : base(contexts)
    { }

    public override void Execute()
    {
        // leave this as empty
    }

    public override void Initialize()
    {
        // access the game context and create a player
        var player = _gameContext.CreateEntity();
        player.isPlayer = true;
        player.AddDamage(2);
        player.AddHealth(10);

        // create two enemies
        var enemy1 = _gameContext.CreateEntity();
        enemy1.isEnemy = true;
        enemy1.AddDamage(2);
        enemy1.AddHealth(10);

        var enemy2 = _gameContext.CreateEntity();
        enemy2.isEnemy = true;
        enemy2.AddDamage(2);
        enemy2.AddHealth(10);
    }
}

在上面的代码中,我展示了如何使用我们创建的上下文访问器“_gameContext”。上下文字段用于创建玩家和敌方实体。

InputSystem (execute and cleanup system)(输入系统)

将Entitas与Unity一起使用时,通常将所有外部逻辑同步到Entitas是一种很好的做法。这意味着,如果您有来自Unity的输入,您应该制作可以操作该输入的组件和系统。例如,这可能是:来自Unity的碰撞系统的碰撞,在Unity中创建游戏对象和/或键盘和鼠标输入。

在这里,我将展示如何将键盘输入同步到Entitas,以便它可以被我们游戏中的不同系统流畅地使用。以下代码显示了如何创建输入系统:

sys InputSystem (cleanup)
    access:
        _inputContext : Input

请注意,我们现在使用访问器作为输入上下文。此外,还增加了"(cleanup)",使其成为一个cleanup system。

与SpawnSystem一样,Entitas-lang创建了一个名为“AbstractInputSystem”的类,我们将使用SpacebarInputComponent创建一个实体:

using UnityEngine;

public class InputSystem : AbstractInputSystem
{
    public InputSystem(Contexts contexts) : base(contexts)
    { }

    public override void Execute()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            var spaceBarInput = _inputContext.CreateEntity();
            spaceBarInput.isSpacebarInput = true;
        }
    }

    public override void Cleanup()
    {
        var spacebarInputs = _inputContext.GetGroup(InputMatcher.SpacebarInput);
        foreach (InputEntity inputEntity in spacebarInputs.GetEntities())
        {
            _inputContext.DestroyEntity(inputEntity);
        }
    }
}

如果我们不这么做的话,在这个系统中,我们每次玩家按空格键时都会创建一个新实体,实体永远不会被清理干净!这就是.entitas文件中添加“(cleanup)”的原因。每个帧我们清理所有输入实体,因为它们将不再被任何系统使用。

ProcessSpacebarInputSystem (reactive system)(空格输入系统)

们在InputSystem中创建的实体将具有SpacebarInputComponent。当添加到实体时,我们希望在ProcessSpacebarInputSystem中对它做出反应。反过来,这会对游戏中的所有敌人造成伤害。

要创建一个被动系统,我们将在定义系统时使用“trigger”关键字。我们只想在玩家击中空格键时伤害我们的敌人。也就是说,将SpacebarInputComponent添加到实体时。因此我们使用关键字“添加”。我们还添加了“过滤allOf(SpacebarInput”以确保我们获得的所有实体实际上都有这个组件。参考下面的代码:

sys ProcessSpacebarInputSystem
    trigger:
        added(SpacebarInput)
        filter allOf(SpacebarInput)
    access:
        _gameContext : Game

在C#中,我们希望获得游戏中的所有敌人,并根据玩家可以处理多少伤害来降低他们的健康状况。以下代码演示了这一点:

using System.Collections.Generic;

class ProcessSpacebarInputSystem : AbstractProcessSpacebarInputSystem
{
    public ProcessSpacebarInputSystem(Contexts contexts) : base(contexts)
    {
    }

    protected override void Execute(List<InputEntity> spacebarInputs)
    {
        // get all enemies
        var enemies = _gameContext.GetGroup(GameMatcher.Enemy).GetEntities();
        // get the player
        var player = _gameContext.player;
        
        foreach (var spacebarInput in spacebarInputs)
        {
            foreach (var enemy in enemies)
            {
                enemy.ReplaceHealth(enemy.health.Value - player.damage.Value);
            }
        }
    }
}

PrintHealthSystem (reactive system)

PrintHealthSystem将在其健康状况发生变化时打印出实体的健康状况。以下代码创建此系统:

sys PrintHealthSystem
    trigger:
        added(Health)
        filter allOf(Health)

And the C# code looks like this:

using System.Collections.Generic;
using UnityEngine;

class PrintHealthSystem : AbstractPrintHealthSystem
{
    public PrintHealthSystem(Contexts contexts) : base(contexts)
    {
    }

    protected override void Execute(List<GameEntity> entities)
    {
        // iterate over all entities where their health has changed
        foreach (var entity in entities)
        {
            Debug.Log(entity + " has " + entity.health.Value + " health left");
        }
    }
}

The final code for the systems in "compAndSys.entitas" looks like this:

sys SpawnSystem (init)
    access:
        _gameContext : Game
        
sys InputSystem (cleanup)
    access:
        _inputContext : Input

sys ProcessSpacebarInputSystem
    trigger:
        added(SpacebarInput)
        filter allOf(SpacebarInput)
    access:
        _gameContext : Game

sys PrintHealthSystem
    trigger:
        added(Health)
        filter allOf(Health)

Step 6 - GameController (running the systems)

有一件事丢失了!这些代码都没有运行。为了使系统执行他们的代码,在Unity中创建一个名为“GameController”的空游戏对象,其脚本名为“GameController.cs”,如下所示:

In GameController.cs write the following code:

using UnityEngine;
using Entitas;

class GameController : MonoBehaviour
{
    private Systems _systems;

    private void Start()
    {
        var contexts = Contexts.sharedInstance;
        contexts.SetAllContexts();

        _systems = CreateSystems(contexts);
        _systems.Initialize();
    }

    private void Update()
    {
        _systems.Execute();
        _systems.Cleanup();
    }

    private void OnDestroy()
    {
        _systems.TearDown();
    }

    private Systems CreateSystems(Contexts contexts)
    {
        return new Feature("Systems")
            .Add(new SpawnSystem(contexts))
            .Add(new InputSystem(contexts))
            .Add(new ProcessSpacebarInputSystem(contexts))
            .Add(new PrintHealthSystem(contexts))
            ;
    }
}

在运行游戏时,您将首先在控制台中看到三个打印件;一个为玩家,两个为敌人:

每一次你按下空格,都会对敌人造成两点的伤害 Every time you press the space bar, it will deal 2 damage to the enemies:

The End - Where to go from here(然后我该做些什么呢?)

这就对了!这就是使用Entitas时如何使用Entitas-lang的优势。正如我所示,它允许您快速为游戏创建新的组件和系统。

在这个小例子中做了很多,但游戏仍然需要所有的视觉效果!尝试查看[Match-One示例](https://github.com/sschmid/Match-One)并查看是否可以在Unity中添加代表游戏中敌人和玩家的游戏对象。

我向您展示了Entitas-lang的一些功能。如果您想查看完整游戏,可以找到Match-One示例项目的.entitas文件here (converted by mazrks)

如果你知道如何阅读语言语法图( language grammars),你可能会对阅读语法感兴趣,可以在这里找到它

⚠️ **GitHub.com Fallback** ⚠️