如何在实体系统中实现功能?


31

询问实体系统(两个问题后12),并阅读一些文章在他们身上,我想我理解他们比以前好多了。我仍然有一些不确定性,主要是关于构建粒子发射器,输入系统和相机。我显然在理解实体系统方面仍然存在一些问题,它们可能适用于其他所有对象,但是我选择这三个对象是因为它们是非常不同的概念,应该涵盖相当广泛的领域,并有助于我理解实体系统以及如何理解实体系统。我自己会在遇到这些问题时处理这些问题。

我正在用JavaScript构建引擎,并且已经实现了大多数核心功能,其中包括:输入处理,灵活的动画系统,粒子发射器,数学类和函数,场景处理,相机和渲染以及一大堆引擎通常支持的其他功能。我阅读了Byte56的回答,这使我对将引擎制作为实体系统感兴趣。它仍将是具有基本场景概念的HTML5游戏引擎,但它应支持从组件动态创建实体。


我现在遇到的问题是将我的旧引擎概念适合这种新的编程范例。这些是以前的问题中的一些定义,这些定义已更新:

  • 一个实体是一个标识符。它没有任何数据,它不是对象,它是一个简单的ID,代表所有实体的场景列表中的索引(我实际上计划将其实现为组件矩阵)。

  • 组件是一个数据保持器,但是具有可以对数据进行操作的方法。最好的示例是Vector2D或“位置”组件。它具有data:xy,还有一些使对数据进行操作更容易的方法:add()normalize()等。

  • 一个系统的东西,可以对一组满足某些要求的实体的操作; 通常,实体需要具有一组特定的组件才能进行操作。系统是“逻辑”部分,是“算法”部分,组件提供的所有功能纯粹是为了简化数据管理。


相机

相机具有Vector2Dposition属性,rotation属性以及一些将其围绕点定中心的方法。每帧都将其与场景一起馈入渲染器,并且所有对象均根据其位置进行平移。然后渲染场景。

我如何在实体系统中表示这种对象?摄像机是实体,组件还是组合(按照我的回答)?

粒子发射器

我的粒子发射器遇到的问题还是,应该是什么。我非常确定粒子本身不应该是实体,因为我想支持超过10,000个粒子,而且我相信创建这么多实体将对我的性能造成沉重打击。

我如何在实体系统中表示这种对象?

输入管理员

我要谈的最后一个是如何处理输入。在我当前的引擎版本中,有一个名为的类Input。它是一个处理程序,订阅浏览器的事件,例如按键和鼠标位置更改,并且还维护内部状态。然后,播放器类具有一个react()方法,该方法接受输入对象作为参数。这样做的好处是可以将输入对象序列化为.JSON,然后在网络上共享,从而实现流畅的多人模拟。

这如何转化为实体系统?

Answers:


26
  • 相机:将其作为组件将非常整洁。它只会有一个isRendering国旗和深度范围如肖恩所说。除了“视野”(我想您可能称其为2D缩放比例?)和一个输出区域。输出区域可以定义此摄像机渲染到的游戏窗口部分。它不会像您提到的那样有单独的位置/旋转。您创建的具有摄影机组件的实体将使用该实体的位置和旋转组件。然后,您将拥有一个摄像头系统,该系统将查找具有摄像头,位置和旋转组件的实体。系统采用该实体并将其可以“看到”的所有实体从其位置,旋转,景深和视场绘制到屏幕的指定部分。这为您提供了许多用于模拟多个视图端口,“角色视图”窗口,本地多人游戏,

  • 粒子发射器:这也应该只是一个组件。粒子系统将查找具有位置,旋转和粒子发射器的实体。发射器具有再现当前发射器所需的所有属性,我不确定所有这些属性是什么,例如:速率,初始速度,衰减时间等。您无需多次通过。粒子系统知道哪些实体具有该组件。我想您可以重用大量现有代码。

  • 输入:考虑到我上面的建议,我不得不说把它变成一个组件是最有意义的。你的input system将使用当前输入事件在每一帧进行更新。然后,当它遍历具有输入组件的所有实体时,它将应用这些事件。输入组件将具有所有相关方法回调的键盘和鼠标事件的列表。我不太确定方法回调将在哪里存在。也许一些输入控制器类?引擎用户以后进行修改时最有意义的选择。但这将使您能够轻松地将输入控制应用于摄像机实体,播放器实体或任何您需要的功能。是否想通过键盘同步一堆实体的运动?只要给他们所有响应相同输入的输入组件,输入系统就会将这些移动事件应用于要求它们的所有组件。

因此,大多数操作都离我远去,因此,如果没有进一步的解释,这可能是没有意义的。因此,请让我知道您不清楚的内容。基本上,我给了您很多工作:)


另一个很好的答案!谢谢!现在,我唯一的问题是快速存储和检索实体,因此用户实际上可以实现游戏循环/逻辑……我将尝试自己弄清楚它,但我必须首先学习Javascript如何处理数组,对象和内存中未定义的值可以很好地猜测...这将是一个问题,因为不同的浏览器可能会以不同的方式实现它。
jcora 2012年

这在结构上感觉很纯正,但是渲染系统如何确定活动相机,而不需要遍历所有实体?
Pace

@Pace因为我希望很快找到活动的摄像机,所以我可能会允许摄像机系统保留对具有活动摄像机的实体的引用。
MichaelHouse

您将逻辑控制多个摄像机放在何处(查看,旋转,移动等)?您如何控制多台摄像机?
plasmacel

@plasmacel如果您有多个共享控件的对象,则控制系统有责任确定哪个对象接收输入。
MichaelHouse

13

这是我的处理方法:

相机

我的相机是一个像其他实体一样的实体,它具有以下组成部分:

  1. Transform具有TranslationRotationScale属性,以及速度等其他属性。

  2. Pov(视点)已FieldOfViewAspectRatioNearFar,和其他任何需要产生的投影矩阵,除了一个IsOrtho透视图和正投影之间用于切换标志。Pov还提供了ProjectionMatrix渲染系统使用的延迟加载属性,该属性在读取时在内部进行计算,并进行缓存,直到修改了其他任何属性。

没有专用的摄像头系统。渲染系统维护的列表,Pov并包含用于确定渲染时使用哪个逻辑的逻辑。

输入值

InputReceiver部件可以连接到任何实体。它具有一个附加的事件处理程序(如果您的语言支持,则为lambda),该事件处理程序用于保存特定于实体的输入处理,该处理程序将获取当前和先前的键状态,当前和先前的鼠标位置以及按钮状态等参数。(实际上,有用于鼠标和键盘的单独处理程序)。

例如,在我习惯实体/组件时创建的类似Asteroids的测试游戏中,我有两个输入lambda方法。通过处理箭头键和空格键(用于发射)来处理船舶导航。另一个处理一般的键盘输入-退出,暂停等键,重新启动级别等键。我创建了两个组件,将每个lambda附加到其自己的组件,然后将导航接收器组件分配给船实体,另一个分配给船体。不可见的命令处理器实体。

这是事件处理程序,用于处理在附接到飞船InputReceiver组件(C#)的框架之间保持的关键点:

  void ship_input_Hold(object sender, InputEventArgs args)
    {
        var k = args.Keys;
        var e = args.Entity;

        var dt = (float)args.GameTime.ElapsedGameTime.TotalSeconds;

        var verlet = e.As<VerletMotion>();
        var transform = e.As<Transform>();

        if (verlet != null)
        {

        /// calculate applied force 
            var force = Vector3.Zero;
            var forward = transform.RotationMatrix.Up * Settings.ShipSpeedMax;

            if (k.Contains(Keys.W))
                force += forward;

            if (k.Contains(Keys.S))
                force -= forward;

            verlet.Force += force * dt;
        }

        if (transform != null)
        {
            var theta = Vector3.Zero;

            if (k.Contains(Keys.A))
                theta.Z += Settings.TurnRate;

            if (k.Contains(Keys.D))
                theta.Z -= Settings.TurnRate;

            transform.Rotation += theta * dt;
        }

        if (k.Contains(Keys.Space))
        {
            var time = (float)args.GameTime.TotalGameTime.TotalSeconds - _rapidFireLast;

            if (time >= _rapidFireDelay)
            {
                Fire();
                _rapidFireLast = (float)args.GameTime.TotalGameTime.TotalSeconds;
            }
        }
    }

如果您的相机手机,给它自己InputReceiverTransform组件,连接lambda和处理程序实现什么样的控制,你想,你就大功告成了。

这样做很巧妙,您可以将InputReceiver带有导航处理程序的组件从船上移到小行星或其他任何东西上,然后绕开它。或者,通过将Pov组件分配给场景中的任何其他对象(小行星,路灯等),您可以从该实体的角度查看场景。

InputSystem维护键盘,鼠标等内部状态的类,将InputSystem其内部实体集合过滤为具有InputReceiver组件的实体。在其Update()方法中,它循环访问该集合,并以与呈现系统使用一个Renderable组件绘制每个实体相同的方式调用附加到每个组件的输入处理程序。

粒子

这实际上取决于您如何计划与粒子交互。如果您只需要一个行为像一个对象的粒子系统(例如,烟花显示玩家无法触摸或击打),那么我将创建一个实体,并创建一个ParticleRenderGroup包含粒子所需信息的组件-衰减等- Renderable组件未涵盖。渲染时,渲染系统将查看实体是否具有RenderParticleGroup附件并相应地进行处理。

如果您需要单个粒子参与碰撞检测,响应输入等,但是您只想将它​​们渲染为批处理,则可以创建一个Particle包含每个粒子的信息的组件,并将其创建为单独的实体。渲染系统仍可以批处理它们,但其他系统会将它们视为单独的对象。(这对于实例化非常有效。)

然后,在您MotionSystem(或您用于处理更新实体位置等的所有操作中)或专用中ParticleSystem,对每帧每个粒子执行所需的任何处理。该RenderSystem会负责建设/配料和他们创建和销毁缓存颗粒的集合,并根据需要使它们。

这种方法的优点在于,您不必为粒子的碰撞,剔除等任何特殊情况。您为其他所有类型的实体编写的代码仍然可以使用。

结论

如果您考虑跨平台使用-不适用于JavaScript,则所有特定于平台的代码(即渲染和输入)都被隔离为两个系统。您的游戏逻辑保留在与平台无关的类(运动,碰撞等)中,因此在移植时不必触摸它们。

我理解并同意Sean的立场,即严格遵守该模式而不是对模式进行调整以满足您的应用程序需求,将鞋拔成规则是很不好的。我只是在输入,相机或粒子中看不到任何需要这种处理的东西。


您将逻辑控制多个摄像机放在何处(查看,旋转,移动等)?
plasmacel

7

输入和游戏逻辑可能会在实体组件系统外部的专用代码块中处理。从技术上讲,可以将其推入设计中,但是却没有什么好处-游戏逻辑和用户界面都很笨拙,无论您做什么,都充满了泄漏的抽象,而仅仅为了建筑的纯正性而将方形钉强制插入圆孔是浪费的时间。

同样,粒子发射器是特殊的野兽,尤其是如果您完全关心性能的话。发射器组件很有意义,但是图形将对那些组件做一些特殊的魔术,并在剩下的渲染中将其与魔术混合在一起。

关于您的相机,只需给相机一个活动的标志,或者给它一个“深度”索引,然后让图形系统呈现所有已启用的相机。实际上,这对于很多技巧都是很方便的,包括GUI(想让您的GUI在游戏世界的顶部以正交方式呈现吗?没问题,它们只是两个具有不同对象蒙版的摄像机,并且GUI设置在更高的层)。对于特效图层等也很有用。


4

摄像机将是实体还是仅仅是组件?

我不确定这个问题到底在问什么。鉴于您在游戏中仅有的东西是实体,所以相机必须是实体。摄像机功能是通过某种摄像机组件实现的。没有单独的“位置”和“旋转”组件-太低了。它们应该组合成某种适用于世界上任何实体的WorldPosition组件。至于要使用哪一个……您必须以某种方式使逻辑进入系统。您可以将其硬编码到相机处理系统中,或者附加脚本等。如果有帮助,您可以在相机组件上启用/禁用标记。

我很确定粒子本身不应该是实体

我也是。粒子发射器将是一个实体,粒子系统将跟踪与给定实体关联的粒子。这样的事情使您意识到“一切都是实体”是不切实际的。实际上,只有实体是相对复杂的对象,这些对象得益于组件的组合。

至于输入:输入在游戏世界中是不存在的,因此由系统处理。不一定是“组件系统”,因为游戏中的所有内容都不会围绕组件展开。但是会有一个输入系统。您可能想用某种Player组件标记响应输入的实体,但是输入将是复杂的并且完全是特定于游戏的,因此尝试为此制作组件没有什么意义。


1

这是我解决这些问题的一些想法。他们可能会遇到问题,并且可能会有更好的方法,所以请指导我给您回答的那些人!

相机

有一个“相机”组件,可以将其添加到任何实体。但是,我真的不知道应该在该组件中放入哪些数据:我可以有单独的“位置”和“旋转”组件!follow不需要实现该方法,因为它已经在它所附加的实体之后!而且我可以随意移动它。该系统的问题将是许多不同的相机对象:如何RendererSystem知道要使用哪些相机对象?而且,我过去只是传递相机对象,但是现在看来,RendererSystem需要对所有实体进行两次迭代:首先找到像相机一样的实体,其次,真正地渲染所有物体。

粒子发射器

将有一个ParticleSystem将更新所有具有“发射器”组件的实体。粒子是该组件内部相对坐标空间中的哑对象。这里存在一个渲染问题:我要么需要创建一个ParticleRenderer系统,要么扩展现有系统的功能。

输入系统

我这里主要关心的是逻辑或react()方法。我想出的唯一解决方案是为此使用一个单独的系统,以及每个系统的组件,该组件将指示要使用的系统。这似乎太过分了,我不知道该如何处理。一件事是,就我而言,Input可以将其作为一个类实现,但是我看不到如何将其集成到游戏的其余部分中。


RendererSystem并没有真正遍历所有实体的理由-它应该已经有一个可绘制对象列表(以及摄影机和灯光(除非灯光是可绘制对象)),或者知道这些列表在哪里。另外,您可能希望对要渲染的摄像机进行剔除,因此,您的摄像机可能包含可见的可绘制实体ID列表。您可能有很多摄像头和一个活动的摄像头,或者一个连接到不同POV的摄像头,这两个摄像头都可以由许多东西来控制,例如脚本,触发器和输入

@ melak47,是的,我也曾想过,但我想以Aremis的方式来实现它。但是这种“系统存储对相关实体的引用”似乎越来越有缺陷……
jcora 2012年

Artemis不会将每种组件类型都存储在自己的列表中吗?所以您难道没有确切列出那些可绘制组件,相机组件,灯光以及什么不在列表中吗?
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.