在基于组件的实体系统中处理脚本化和“本机”组件


11

我目前正在尝试实现一个基于组件的实体系统,该实体基本上只是一个ID和一些将一组组件捆绑在一起以形成游戏对象的辅助方法。其中的一些目标包括:

  1. 组件仅包含状态(例如位置,健康状况,弹药计数)=>逻辑进入“系统”,该系统处理这些组件及其状态(例如PhysicsSystem,RenderSystem等)
  2. 我想用纯C#和脚本(Lua)实现组件和系统。基本上,我希望能够直接在Lua中定义全新的组件和系统,而不必重新编译C#源代码。

现在,我正在寻找有关如何高效且一致地处理此问题的想法,因此,例如,无需使用其他语法来访问C#组件或Lua组件。

我当前的方法是使用常规的公共属性实现C#组件,这些属性可能装饰有一些属性,这些属性可以告诉编辑器默认值和内容。然后,我将得到一个C#类“ ScriptComponent”,该类仅在内部包装Lua表,该表由脚本创建并保存该特定组件类型的所有状态。我真的不希望从C#端访问该状态,因为我在编译时不知道可以使用哪些ScriptComponents和哪些属性。尽管如此,编辑器仍需要访问它,但是像下面这样的简单界面就足够了:

public ScriptComponent : IComponent
{
    public T GetProperty<T>(string propertyName) {..}
    public void SetProperty<T>(string propertyName, T value) {..}
}

这将简单地访问Lua表并从Lua设置和检索那些属性,并且也可以轻松地将其包含在纯C#组件中(但随后可以通过反射或其他方式使用常规C#属性)。这只能在编辑器中使用,不能用常规的游戏代码使用,因此性能在这里并不重要。这将有必要生成或手写一些组件描述,以记录某些组件类型实际提供的属性,但这不是一个大问题,并且可以实现足够的自动化。

但是,如何从Lua端访问组件?如果我调用类似的东西,getComponentsFromEntity(ENTITY_ID)我可能只会获得一堆本地C#组件(包括“ ScriptComponent”)作为用户数据。从包装的Lua表访问值将导致我调用该GetProperty<T>(..)方法,而不是像其他C#组件那样直接访问属性。

也许编写一个getComponentsFromEntity()只能从Lua调用的特殊方法,该方法会将所有本机C#组件作为用户数据返回,但“ ScriptComponent”除外,它将返回包装后的表。但是会有其他与组件相关的方法,我真的不想复制所有这些方法,无论是从C#代码还是从Lua脚本调用。

最终目标将是处理所有类型的组件相同,而在本机组件和Lua组件之间(尤其是从Lua方面)不区分特殊情况的语法。例如,我希望能够编写如下的Lua脚本:

entity = getEntity(1);
nativeComponent = getComponent(entity, "SomeNativeComponent")
scriptComponent = getComponent(entity, "SomeScriptComponent")

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

该脚本不必关心它实际上获得了哪种组件,我想使用与C#端相同的方法来检索,添加或删除组件。

也许有一些将脚本与实体系统集成在一起的示例实现?


1
您是否坚持使用Lua?我已经做过类似的事情,编写了C#应用程序脚本,并在运行时解析了其他CLR语言(以我的情况为Boo)。因为您已经在受管环境中运行了高级代码,并且所有这些都在后台,所以很容易从“脚本”侧对现有类进行子类化,然后将这些类的实例传递回主机应用程序。就我而言,我什至会缓存已编译的脚本程序集以提高加载速度,并在脚本更改时重新构建它。
justinian 2012年

不,我不被Lua困扰。我个人非常想使用它,因为它简单,体积小,速度快且受欢迎(因此人们已经熟悉它的机会似乎更高),但是我愿意接受其他选择。
马里奥(Mario)

我可能会问,为什么您希望在C#和脚本环境之间具有相等的定义功能?常规设计是使用C#定义组件,然后使用脚本语言进行“对象组装”。我想知道这是什么解决方案?
詹姆斯

1
目的是使组件系统可从C#源代码外部扩展。不仅通过在XML或脚本文件中设置属性值,还可以通过定义具有全新属性的全新组件以及处理它们的新系统。这在很大程度上受到了Scott Bilas在Dungeon Siege中对对象系统的描述的启发,那里有“本机” C ++组件以及“ GoSkritComponent”,后者本身只是脚本的包装:scottbilas.com/games/dungeon -siege
Mario

当心不要陷入构建脚本或插件界面的陷阱,因为它是如此复杂,以至于它会重写原始工具(在这种情况下为C#或Visual Studio)。
3Dave 2012年

Answers:


6

FxIII答案的一个必然结果是,您应该首先(或者至少在其相当不错的一部分)以脚本语言设计一切(可修改的)东西,以确保您的集成逻辑真正为改装做好准备。当您确定脚本集成是通用的时,就可以用C#重写所需的位。

我个人讨厌C#4.0中的dynamic功能(我是一个静态语言迷),但这无疑是一个应该使用它并且真正发挥作用的方案。您的C#组件将仅仅是强大/扩展的行为对象

public class Villian : ExpandoComponent
{
    public void Sound() { Console.WriteLine("Cackle!"); }
}

//...

dynamic villian = new Villian();
villian.Position = new Vector2(10, 10); // Add a property.
villian.Sound(); // Use a method that was defined in a strong context.

您的Lua组件将是完全动态的(上面的同一篇文章应该使您了解如何实现此功能)。例如:

// LuaSharp is my weapon of choice.
public class LuaObject : DynamicObject
{
    private LuaTable _table;

    public LuaObject(LuaTable table)
    {
        _table = table;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var methodName = binder.Name;
        var function = (LuaFunction)_table[methodName];
        if (function == null)
        {
            return base.TryInvokeMember(binder, args, out result);
        }
        else
        {
            var resultArray = function.Call(args);
            if (resultArray == null)
                result = null;
            else if (resultArray.Length == 1)
                result = resultArray[0];
            else
                result = resultArray;
            return true;
        }
    }

    // Other methods for properties etc.
}

现在,因为您正在使用dynamic,所以可以像使用C#中的真实类一样使用Lua对象。

dynamic cSharpVillian = new Villian();
dynamic luaVillian = new LuaObject(_script["teh", "villian"]);
cSharpVillian.Sound();
luaVillian.Sound(); // This will call into the Lua function.

我完全同意第一段,我经常以此方式工作。编写代码时,您可能必须使用较低级别的语言来重新实现它,就像想知道需要支付利息的贷款一样。IT设计师经常借贷:当他们知道自己做到了并且可以控制债务时,这很好。
FxIII 2012年

2

我宁愿相反:使用动态语言编写所有内容,然后跟踪瓶颈(如果有)。找到一个后,您可以使用C#或(而不是)C / C ++选择性地重新实现所涉及的功能。

我之所以这样告诉你,主要是因为我们试图在C#部分中限制系统的灵活性。随着系统变得复杂,您的C#“引擎”将开始看起来像是动态解释器,可能不是最好的解释器。问题是:您是否愿意将大部分时间用于编写通用C#引擎或扩展游戏?

正如我所相信的,如果您的目标是第二个目标,那么您可能会把最好的时间集中在游戏机制上,让经验丰富的解释器来运行动态代码。


是的,如果我将通过脚本来完成所有工作,那么对于某些核心系统的性能仍然会有令人担忧的担忧。在那种情况下,我必须将该功能移回C#(或其他任何方式)...因此,无论如何,我需要一种很好的方法来从C#端与组件系统接口。这就是这里的核心问题:创建一个组件系统,我可以同时使用C#和脚本。
马里奥(Mario)

我以为C#用作C ++或C之类的“加速脚本式”环境?无论如何,如果不确定使用哪种语言,只需开始尝试用Lua和C#编写一些逻辑即可。最后,我敢打赌,由于先进的编辑器工具和调试功能,大多数情况下,您将最终在C#中更快。
Imi 2012年

4
+1我出于另一个原因同意这一点-如果您希望游戏可以扩展,那么您应该吃自己的狗粮:您可能会集成脚本引擎,只是发现您潜在的改装社区实际上无法做出任何贡献它。
乔纳森·迪金森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.