精华内容
下载资源
问答
  • 游戏开发中的ECS 架构概述

    万次阅读 多人点赞 2018-08-06 11:12:02
    0x00 何为ECS架构 ECS,即 Entity-Component-System(实体-组件-系统) 的缩写,其模式遵循组合优于继承原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的...

    0x00 何为ECS架构

    ECS,即 Entity-Component-System(实体-组件-系统) 的缩写,其模式遵循组合优于继承原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法),例如:移动相关的组件MoveComponent包含速度、位置、朝向等属性,一旦一个实体拥有了MoveComponent组件便可以认为它拥有了移动的能力,系统便是来处理拥有一个或多个相同组件实体集合的工具,其只拥有行为(即在系统中没有任何数据),在这个例子中,处理移动的系统仅仅关心拥有移动能力的实体,它会遍历所有拥有MoveComponent组件实体,并根据相关的数据(速度、位置、朝向等),更新实体的位置。

    实体组件是一个一对多的关系,实体拥有怎样的能力,完全是取决于其拥有哪些组件,通过动态添加或删除组件,可以在(游戏)运行时改变实体的行为。

    0x01 ECS基本结构

    一个使用ECS架构开发的游戏基本结构如下图所示:

     

     

    先有一个World,它是系统实体的集合,而实体就是一个ID,这个ID对应了组件的集合。组件用来存储游戏状态并且没有任何行为,系统拥有处理实体的行为但是没有状态。

    0x02 详解ECS中实体、组件与系统

    1. 实体

    实体只是一个概念上的定义,指的是存在你游戏世界中的一个独特物体,是一系列组件的集合。为了方便区分不同的实体,在代码层面上一般用一个ID来进行表示。所有组成这个实体的组件将会被这个ID标记,从而明确哪些组件属于该实体。由于其是一系列组件的集合,因此完全可以在运行时动态地为实体增加一个新的组件或是将组件从实体中移除。比如,玩家实体因为某些原因(可能陷入昏迷)而丧失了移动能力,只需简单地将移动组件从该实体身上移除,便可以达到无法移动的效果了。

    样例

    • Player(Position, Sprite, Velocity, Health)
    • Enemy(Position, Sprite, Velocity, Health, AI)
    • Tree(Position, Sprite)

    注:括号前为实体名,括号内为该实体拥有的组件

    2. 组件

    一个组件是一堆数据的集合,可以使用C语言中的结构体来进行实现。它没有方法,即不存在任何的行为,只用来存储状态。一个经典的实现是:每一个组件都继承(或实现)同一个基类(或接口),通过这样的方法,我们能够非常方便地在运行时动态添加、识别、移除组件。每一个组件的意义在于描述实体的某一个特性。例如,PositionComponent(位置组件),其拥有xy两个数据,用来描述实体的位置信息,拥有PositionComponent的实体便可以说在游戏世界中拥有了一席之地。当组件们单独存在的时候,实际上是没有什么意义的,但是当多个组件通过系统的方式组织在一起,才能发挥出真正的力量。同时,我们还可以用空组件(不含任何数据的组件)对实体进行标记,从而在运行时动态地识别它。如,EnemyComponent这个组件可以不含有任何数据,拥有该组件的实体被标记为“敌人”。

    根据实际开发需求,这里还会存在一种特殊的组件,名为 Singleton Component (单例组件),顾名思义,单例组件在一个上下文中有且只有一个。具体在什么情况下使用下文系统一节中会提到。

    样例

    • PositionComponent(x, y)
    • VelocityComponent(X, y)
    • HealthComponent(value)
    • PlayerComponent()
    • EnemyComponent()

    注:括号前为组件名,括号内为该组件拥有的数据

    3. 系统

    理解了实体和组件便会发现,至此还未曾提到过游戏逻辑相关的话题。系统便是ECS架构中用来处理游戏逻辑的部分。何为系统,一个系统就是对拥有一个或多个相同组件的实体集合进行操作的工具,它只有行为,没有状态,即不应该存放任何数据。举个例子,游戏中玩家要操作对应的角色进行移动,由上面两部分可知,角色是一个实体,其拥有位置和速度组件,那么怎么根据实体拥有的速度去刷新其位置呢,MoveSystem(移动系统)登场,它可以得到所有拥有位置和速度组件的实体集合,遍历这个集合,根据每一个实体拥有的速度值和物理引擎去计算该实体应该所处的位置,并刷新该实体位置组件的值,至此,完成了玩家操控的角色移动了。

    注意,我强调了移动系统可以得到所有拥有位置和速度组件的实体集合,因为一个实体同时拥有位置和速度组件,我们便认为该实体拥有移动的能力,因此移动系统可以去刷新每一个符合要求的实体的位置。这样做的好处在于,当我们玩家操控的角色因为某种原因不能移动时,我们只需要将速度组件从该实体中移除,移动系统就得不到角色的引用了,同样的,如果我们希望游戏场景中的某一个物件动起来,只需要为其添加一个速度组件就万事大吉。

    一个系统关心实体拥有哪些组件是由我们决定的,通过一些手段,我们可以在系统中很快地得到对应实体集合。

    上文提到的 Singleton Component (单例组件) ,明白了系统的概念更容易说明,还是玩家操作角色的例子,该实体速度组件的值从何而来,一般情况下是根据玩家的操作输入去赋予对应的数值。这里就涉及到一个新组件InputComponent(输入组件)和一个新系统ChangePlayerVelocitySystem(改变玩家速度系统),改变玩家速度系统会根据输入组件的值去改变玩家速度,假设还有一个系统FireSystem(开火系统),它会根据玩家是否输入开火键进行开火操作,那么就有 2 个系统同时依赖输入组件,真实游戏情况可能比这还要复杂,有无数个系统都要依赖于输入组件,同时拥有输入组件的实体在游戏中仅仅需要有一个,每帧去刷新它的值就可以了,这时很容易让人想到单例模式(便捷地访问、只有一个引用),同样的,单例组件也是指整个游戏世界中有且只有一个实体拥有该组件,并且希望各系统能够便捷的访问到它,经过一些处理,在任何系统中都能通过类似world->GetSingletonInput()的方法来获得该组件引用。

    系统这里比较麻烦,还存在一个常见问题:由于代码逻辑分布于各个系统中,各个系统之间为了解耦又不能互相访问,那么如果有多个系统希望运行同样的逻辑,该如何解决,总不能把代码复制 N 份,放到各个系统之中。UtilityFunction(实用函数) 便是用来解决这一问题的,它将被多个系统调用的方法单独提取出来,放到统一的地方,各个系统通过 UtilityFunction 调用想执行的方法,同系统一样, UtilityFunction 中不能存放状态,它应该是拥有各个方法的纯净集合。

    样例

    • MoveSystem(Position, Velocity)
    • RenderSystem(Position, Sprite)

    注:括号前为系统名,括号内为该系统关心的组件集合

    0x03 ECS架构实战

    接下来终于到了实战环节,这里笔者使用 Unity3d 游戏引擎(5.6.3p4),配合现成的 Entitas 框架来实现一个小 Demo。由于 Unity3d 游戏引擎已经为我们提供了输入类和物理引擎,因此 Demo 中有部分内容可能与上文不太一致,主要以展示整体架构为主,请读者忽略这些细节。

    1. Entitas介绍

    Entitas is a super fast Entity Component System Framework (ECS) specifically made for C# and Unity. Internal caching and blazing fast component access makes it second to none. Several design decisions have been made to work optimal in a garbage collected environment and to go easy on the garbage collector. Entitas comes with an optional code generator which radically reduces the amount of code you have to write and  makes your code read like well written prose.

    以上是 Entitas 官方介绍,简单来说该框架提供了代码生成器,只需要按照它的规范实现组件和系统,便可以一键生成我们需要的属性和方法,同时为了方便我们在系统中获得感兴趣的组件,它还提供了强大的分组、匹配功能。多说无益,直接开始实战吧。

    2. 实战

    下载Unity3d游戏引擎的步骤这里就省略了,我们先从 Github 上下载 Entitas,笔者这里使用的是 Entitas 0.42.4 。下载好解压后,将其 CodeGenerator 和 Entitas 目录导入到一个新的 Unity 工程(这里一切从简,创建了一个空的 2D 项目),如下图所示。

     

     

    接着,在工具栏找到 Tools -> Entitas ->Preference 对 Entitas 进行配置,由于这只是一个演示 ECS架构的小 Demo,就不对各种配置项进行解释了,对这些感兴趣的同学可以去官网查看文档,配置如下:

     

     

    点击绿色按钮 Generate,如果没有任何报错,则配置没有问题。接下来就可以开始写代码了。

    我们 Demo 的目标是控制一个矩形进行上下左右移动。由上文可知,我们至少需要 2 个组件:PositionComponentVelocityComponent。在 Scripts/Components 目录下分别新建这两个脚本:

    // PositionComponent.cs
    using Entitas;
    using UnityEngine;
    
    public class PositionComponent : IComponent
    {
        public Vector2 Value;
    }
    // VelocityComponent.cs
    using Entitas;
    using UnityEngine;
    
    public class VelocityComponent : IComponent {
        public Vector2 Value;
    }
    

    由于在我们 Demo 中,玩家只能操控一个矩形,我们需要对其进行标记,告诉系统这个实体是玩家的代表,于是我们还要加上一个PlayerComponent来进行标记。

    // PlayerComponent.cs
    using Entitas;
    
    public class PlayerComponent : IComponent { }
    

    它不需要任何数据,仅仅用自身就可以实现标记的效果,拥有该组件的实体便是我们玩家控制的代表了。

    实现完这 3 个组件后,我们需要利用 Entitas 框架提供的代码生成器,生成一下相应的代码,Tools -> Entitas -> Generate 或者快捷键control + shift + g

     

     

    没有看到任何报错,很好我们继续。

    接着我们要实现ChangePlayerVelocitySystem,它每一帧都会运行,根据玩家是否输入wasd来改变矩形的速度。

    // ChangePlayerVelocitySystem.cs
    using Entitas;
    using UnityEngine;
    
    public class ChangePlayerVelocitySystem : IExecuteSystem
    {
        // 每一帧都会执行
        public void Execute()
        {
            // 得到拥有 Player、Position、Velocity 组件的实体集合
            var playerCollection = Contexts.sharedInstance.game.GetGroup(
                GameMatcher.AllOf(
                    GameMatcher.Player,
                    GameMatcher.Position,
                    GameMatcher.Velocity));
    
            var velocity = Vector2.zero;
            if (Input.GetKey(KeyCode.W))
            {
                velocity.y += 1;
            }
    
            if (Input.GetKey(KeyCode.S))
            {
                velocity.y -= 1;
            }
    
            if (Input.GetKey(KeyCode.A))
            {
                velocity.x -= 1;
            }
    
            if (Input.GetKey(KeyCode.D))
            {
                velocity.x += 1;
            }
    
            foreach (var player in playerCollection)
            {
                player.ReplaceVelocity(velocity);
            }
        }
    }
    

    这里实现了IExecuteSystem接口,每一帧其Execute方法都会执行。

    至此,我们每一帧都会根据用户的输入去改变矩形的速度,还需要一个ChangePositionSystem,它会根据实体身上速度组件的值,去改变位置组件的值。

    // ChangePositionSystem.cs
    using System.Collections.Generic;
    using Entitas;
    using UnityEngine;
    
    public class ChangePositionSystem : ReactiveSystem<GameEntity>
    {
        public ChangePositionSystem(Contexts contexts) : base(contexts.game)
        {
        }
    
        protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
        {
            return context.CreateCollector(GameMatcher.AllOf(GameMatcher.Position, GameMatcher.Velocity));
        }
    
        protected override bool Filter(GameEntity entity)
        {
            return entity.hasPosition && entity.hasVelocity;
        }
    
        protected override void Execute(List<GameEntity> entities)
        {
            foreach (var entity in entities)
            {
                var velocity = entity.velocity.Value;
                var newPosition = entity.position.Value + velocity * Time.deltaTime;
    
                entity.ReplacePosition(newPosition);
            }
        }
    }
    

    这里我们用到了ReactiveSystem<GameEntity>基类,稍微讲解一下,它应该算是一种特殊的IExecuteSystem接口实现,它也会每一帧都执行,但它会帮助我们监听我们感兴趣的组件,只有当这些组件发生变化时,它的Execute方法才会被调用,GetTriggerFilter两个方法相当于过滤器,具体就不细讲了,可以去官网查看一下文档。

    由于使用了 Unity3d 游戏引擎,我们的框架需要由引擎来驱动,因此我们还要添加一个继承自MonoBehaviourGameController脚本,在其中的Start方法里实例化各个系统,Update方法里调用Excute

    // GameController.cs
    using UnityEngine;
    using Entitas;
    
    public class GameController : MonoBehaviour
    {
    
        private Systems _systems;
    
        private void Start()
        {
            Contexts contexts = Contexts.sharedInstance;
    
            // 创建系统
            _systems = CreateSystems(contexts);
    
            // 创建我们的玩家实体
            var player = contexts.game.CreateEntity();
            // 为其添加相应的组件
            player.isPlayer = true;
            player.AddPosition(Vector2.zero);
            player.AddVelocity(Vector2.zero);
    
            // 初始化系统
            _systems.Initialize();
        }
    
        private void Update()
        {
            _systems.Execute();
            _systems.Cleanup();
        }
    
        private void OnDestroy()
        {
            _systems.TearDown();
        }
    
        private Systems CreateSystems(Contexts contexts)
        {
            // Feature 是 Entitas 框架提供的在 Editor 下进行调试的类
            return new Feature("Game")
                .Add(new ChangePlayerVelocitySystem())
                .Add(new ChangePositionSystem(contexts));
        }
    }
    

    在场景中新建一个名为“GameController”的空物体,将该脚本添加上去,运行游戏,在“Hierarchy”页签下就可以看到我们创建的系统和实体了,如下图:

    当我们按下wasd时,可以看到左侧 Position 下面的数值和 Velocity 下面的数值都根据我们的输入产生了对应的变化,这说明功能实现的没有问题。

    至此,虽然还没有图形显示在场景中,但一个可操控的 Demo 已经完成了。

    为了节省篇幅,SpriteComponent(精灵组件)和RenderSystem(渲染系统),这里就不再展示了,完整项目可以在我的 Github 里查看。

    0x04 后记

    到此,整篇文章也进入了尾声,不知读者是否对 ECS 架构有了自己的理解,其实笔者也是最近这段时间才开始使用该架构编写一些小项目,还未在商业项目中使用过,因此有些地方的理解可能存在一定的偏差,欢迎大家讨论与指正,感谢大家的阅读。

    参考

    展开全文
  • Entitas是德国Wooga公司开源的一款ECS架构,不只是C#,还有其他多种语言版本,基本涵盖了目前主流的计算机语言

    Entitas是德国Wooga公司开源的一款ECS架构,不只是C#,还有其他多种语言版本,基本涵盖了目前主流的计算机语言
    Entitas开源项目地址:https://github.com/sschmid/Entitas-CSharp

    我自己还写了个Entitas框架代码生成工具,可以自动初始化系统部分,传送门:Entitas代码生成工具

    Unity本身就是EC(Entity Component )模式的引擎,只不过在Unity中Entity 叫 GameObject,在GameObject上的脚本也能通过开放字段,修改数据,就和ECS的思路是差不多的,但是这样的缺点也很明显,它的逻辑和数据是耦合的,可能会出现后期的行为函数膨胀,多个组件之间的耦合过高的情况,所以进一步逻辑分离就是ECS的写法了。

    一、ECS基本概念

    Entity:代表游戏中的实体,是 Component 的容器。本身并 无数据和逻辑
    Component:代表实体“有什么”,一个或多个 Component 组成了游戏中的逻辑实体。 只有数据 ,不涉及逻辑。
    System:对 Component 集中进行 逻辑操作 的部分。一个 System 可以操作一类或多类 Component。同一个 Component 在不同的 System 中,可以关联不同的逻辑。

    也就是组件(Component)只携带数据,它没有函数,也就不涉及逻辑,而系统(System)则负责逻辑部分,所以它没有字段,不需要携带状态,实体(Entity)就是组件的载体,它并不携带数据和逻辑

    举个例子:
    我们实现一个Human类,他有Run,Jump,Eat三种状态,保存状态的字段叫做State
    面向对象编程
    我们通常的实现思路是,有一个名为Human的类,带有这三个行为的函数,然后有一个状态机,根据State的改变用switch调用对应的行为函数
    面向数据编程
    也就是在ECS中的思路是有一个携带了State的组件,而每一个行为对应了一个system,每个system只关心自己的行为,所以不需要携带状态
    这样就完成了解耦,而且清晰了逻辑,面向对象的思维是我当前是什么,而面向数据的思维是我当前有什么

    Entity Component System (ECS) 本身是逻辑层面的框架,它并不提供渲染引擎或是物理引擎的一类东西,所以不要误会说,它会提供物理接口或是资源加载接口啥的,它负责的是游戏的逻辑,处理游戏对象之间的状态更新问题。

    二、ECS优缺点

    (1)ECS 框架优点:

    1)高复用性:组件之间容易组合
    2)扩展性强:面向对象的思想是对象本身是什么,而面向数据的思想是对象有什么,增加新的功能就是添加新的组件,无需修改之前的逻辑
    3)性能方面提供了更大的优化空间
    4)降低了代码之间的耦合度

    (2)ECS 框架缺点:

    1)面向数据的编程模式,势必造成大量的组件及系统组合的形式,对于代码重构会形成很大的困难
    2)默写模块与系统的耦合过高,造成一些功能的重用要有整个ECS环境才能运行
    3)全部是数据都是开放的,数据上设计不好会造成很大影响

    三、Entitas基本构成

    (1)Component(组件)

    携带数据,也就是状态

    [Event(EventTarget.Self)]
    public sealed class MoveComponent : IComponent {
    
        [EntityIndex]
        public IntVector2 target;
    }
    

    组件是由我们自己定义的,所以它有几个需要注意的地方

    1. 组件需要实现自IComponent接口
    2. 组件的名字就作为关键字生成一系列类及方法,如 MoveComponent 它会自动剔除Component把Move作为关键字,生成如:添加组件的方法AddMove,移除的方法RemoveMove等等
    3. 它需要添加特性,作为自动生成代码的标记,如上的[Event(EventTarget.Self)]和[EntityIndex]还有很多其他的,下面介绍

    (2)Entity(实体)

    它是组件的容器,可以在实体上添加,删除,替换这些组件,但是实体本身并没有数据和行为,数据都是组件的

      		var entity = _contexts.game.CreateEntity();
      		//添加组件
            entity.AddMove(new IntVector2(x, y));
            //移除组件
            entity.RemoveMove();
            //替换组件
            entity.ReplaceMove(new IntVector2(x, y));
    

    (3)System(系统)

    编写游戏逻辑的地方,这里需要注意,之前也说过,系统内不能包含状态
    Entitas中包含以下几个系统

    1. ICleanupSystem:实现接口用来创建一个执行后清理逻辑的系统
    2. IExecuteSystem:实现接口用来创建一个每帧执行的系统
    3. IInitializeSystem:实现接口用来创建一个开始时执行一次的初始化系统
    4. ITearDownSystem:实现接口用来实现一个结束时执行一次的拆除系统
    5. ReactiveSystem:继承这个类实现一个响应系统(后面的帖子会详细介绍)
    6. MultiReactiveSystem:继承这个类实现一个多上下文的响应系统(后面的帖子会详细介绍)
    7. JobSystem:Entitas的多线程系统

    (4)Context(上下文)

    这里的上下文规定了实体的逻辑边界,在上下文内,管理实体和组件的声明周期,类似于一个工厂模式一样
    这部分的代码是自动生成的,不用自己编写

    (5)Matcher(匹配器)

    这部分是自动生成的代码,它代表的实际就是你感兴趣的一类实体的标签
    如:你要获取含有Move组件的实体,那么它会自动生成这样一个属性

       public static Entitas.IMatcher<GameEntity> Move {
            get {
                if (_matcherMoveComplete == null) {
                    var matcher = (Entitas.Matcher<GameEntity>)Entitas.Matcher<GameEntity>.AllOf(GameComponentsLookup.Move);
                    matcher.componentNames = GameComponentsLookup.componentNames;
                    _matcherMove = matcher;
                }
    
                return _matcherMove;
            }
        }
    

    其中GameComponentsLookup.Move是组件的ID,是自动生成的,在框架内,类型为int的组件的标识

    实际这个Matcher就代表了一类实体的条件,也就是同时含有规定的组件,才会匹配上

    (6)Group(组)

    组是由上下文(Context)管理的,并且始终保持最新,它会自动添加与匹配器匹配的实体,或当实体与匹配器不匹配时删除实体

    也就是你可以在框架内,使用context.GetGroup(matcher) 来获取与指定Matcher(匹配器)匹配的一组实体
    

    (7)Collector(搜集器)

    顾名思义,就是用来收集实体的类,它会关注一个或多个组(Group),并根据指定的事件,收集更改的实体

    结尾

    基础部分就这些,之后我会写些应用方面以及源码讲解的文章,欢迎大家关注,不对的地方,也欢迎指正

    我会在我的公众号上推送新的博文,也可以帮大家解答问题
    微信公众号 Andy and Unity 搜索名称或扫描二维码
    在这里插入图片描述
    希望我们能共同成长,共同进步

    展开全文
  • 一般工作流程:1. 客户遇到问题,需要协助,技术支持提供协助。2. 技术支持搞不定问题,需要开发支持。Level 2 Support提供协助。3. Level 2 Support需要具体的开发协助,向上提交给Dev。解决方案最后由技术支持...

    一般工作流程:

    1.      客户遇到问题,需要协助,技术支持提供协助。

    2.      技术支持搞不定问题,需要开发支持。Level 2 Support提供协助。

    3.      Level 2 Support需要具体的开发协助,向上提交给Dev。解决方案最后由技术支持转给客户。

    一般工作流可以用之前的一张图来表示。


    除了一般工作流程,特殊情况下,还会有异常工作流程。

    异常工作流程:

    1. Level2 Support没及时响应,Support直接找Dev,或是直接找上级领导。要避免这情况出现。

    2. 节假日没人处理请求,会直接找上级领导,尽量避免这种情况出现。

    另外,由于客户分布在不同区域,所以对应的,开发支持也会分布在不同区域,以覆盖到所有时区的紧急客户请求。比如,如果所有的人都在亚太区,那么当北美那边是工作区,有客户需要协助时,亚太区还是在休息时间,这样可能就没人响应了。

    人员分布在不同区域了,内部之间也会有协作的。比如,一个请求,先在亚太区的同事手上处理,当北美区的同事上班了之后,可以继续处理,通过这样的协作,能够实现24小时的连续服务。


    展开全文
  • 基于STM32的C语言编写的ESC32电机驱动(电调),本文档重点介绍整个程序架构,流程图,以及ad采样滤波过零换向,速度生力拟合,启动算法,恒力输出模式等。
  • ECS框架初识

    2020-09-18 16:18:22
    // HPSystem,CrashSystem 类比MoveSystem } } ECS的优势 将数据更有效的组织,提高CPU cache利用率 关于cache的利用率的好处,更多信息大家可以参考ECS 真的是「未来主流」的架构吗?的各位大牛的探讨。 逻辑更彻底...

    ECS框架

    • Entity(实体)
    • Component(组件)
    • System(系统)

    ECS框架是一个为了迎合游戏开发,在进几年开始慢慢被推荐熟知的框架,最有代表性的作品就是《守望先锋》 ,其对传统的面向对象设计,组件化思维做了进一步的拆解,分成了Entity,Component,System3个部分,三者关系如图:
    在这里插入图片描述

    下面具体说一下这3个部分的功能和特性。

    Entity

    Entity就是我们的游戏世界的各个实体,如人物、枪支、建筑等等最直接的物体原型。无数个Entity构成勒我们整个游戏世界,同样,我们的游戏世界,也就是安装各个规则去运营各个Entity。在传统的面向对象中,一个实体一般是有各种属性,各种功能函数,各种继承、联合等关联的繁杂存在,在ECS框架中,Entity的作品相对简单,ECS会把各种数据拆分对应到各个Component中,Entity根据需求仅需挂载对应属性的Component,而不用关系具体的数据,且各种Component的逻辑处理拆分到对应各个System中,这样,Entity仅是一个包含多个Component的载体。

    举个例子,游戏世界中一辆车,它是一个Entity,它能移动、碰撞、买卖,我们只需挂载对应moveComponent、crashComponent、tradeComponent组件即可,至于各个Component到底存了什么数据,怎么维护的,这都不是Entity需要考虑的,我们只要指定其能移动、碰撞、交易就好了,是不是很简单。

    Component

    做游戏开发的同学肯定对组件比较熟悉,Component是指具有某一个特定功能特性的组件,传统框架中,我们给一个对象挂载一个组件,通常是对对象附加对应的逻辑处理,从而来让对象具有对应的功能,而在ECS中,则把特定的功能特性划分得更彻底,更加强调解耦。对应这个思路,ECS中的Component可理解为专门存储对应特性的数据集合,而对应特性的逻辑,则放到对应的System中去处理。

    仍然以上面的汽车的例子来讨论,汽车要移动,移动的这个特性对应游戏的实现其实是坐标的变化,那对应的数据就有position(x, y),这里我们以二维坐标为例,moveComponent则存放position数据,具体的position的坐标变化逻辑,则放到对应的moveSystem中去处理,我们给汽车挂载moveComponent,这样汽车就能移动勒。

    这里另外要强调一点,在ECS的框架中,特定的Component是要整合到一起的,Component中通常会有一个所有component实例化对象的集合,这样处理的好处就是能达到功能解耦,也能做到利用内存cache,达到更好的性能。后文会再去强调这一点。

    System

    游戏世界中,肯定会有各种各样的规则,我们把各种规则,再细分成各个系统,划分依据是系统最好是功能专一,与其它系统不能耦合,ECS中把这些成为System,且与Component能对应,System主要处理对应Component的逻辑。

    如上面汽车的例子,可能游戏世界,不止汽车会移动,玩家也能奔跑,其也能移动,总之,游戏世界中,所有挂载勒moveComponent的Entity都具有移动的功能,所有的moveComponent的移动逻辑,都归到moveSystem中去处理。

    代码实例

    有了以上的一些初步了解,那对比传统的面向对象,我们剖析一个案例,然后给出大致的代码框架,来更直接对比。
    案例:一个简单的竞速比赛,参数的载具有汽车、摩托车,且每个选手之间能发生碰撞,并掉血,若血量为0,则直接出局。

    传统OOB设计

    class Racer {
      hp: number; // 血量
      crashDecHp: number; // 碰撞扣除血量
      maxSpeed: number; // 最大速度
      accSpeed: number; // 加速度
      posX: number; // 横坐标
      posY: number; // 纵坐标
      // 处理碰撞逻辑
      checkCrash(){ ...}
      // 处理血量逻辑
      updateHp() { ...}
        // 处理移动逻辑
      updatePos() { ...}
    }
    
    class GameManager {
      allRacer: Racer[];
    
      updateGameLogic() {
        allRacer.forEach((racer) => {
          racer.updatePos();
          racer.checkCrash();
          racer.updateHp();
        });
      }
    }
    

    ECS

    // entity和component的映射关系,可以根据自己需求去设计,这里只给出最简单的设计,仅供参考
    class Racer {
    	id: number
    }
    
    class MoveComponent: {
      static allMoveCom: MoveComponent[]; // 所有移动组件集合
      // 在entity上挂载component
      static registerCom(entity: Racer) {
        let newCom = new MoveComponent();
        this.allMoveCom.push(newCom);
      }
      // 销毁整个Component
      static destory();
    
      target: Racer; // 挂载的Entity对象,可根据自己需求设计entity和component的映射关系
      posX: number; // 横坐标
      posY: number; // 纵坐标
      speed: number; // 当前速度
    
      // 注销组件
      cancelComponent();
    }
    
    class MoveSystem {
      static Instance(): MoveSystem;
      updatePos(dt) {
        MoveComponent.allMoveCom.forEach((com) => {
          com.posX += com.speed * dt;
          // ...
        });
      }
    }
    
    class GameManager {
      // 注册System
      registerSystem();
      // 注销System
      cancelSystem();
    
      updateGameLogic() {
        MoveSystem.Instance.updatePos();
        // HPSystem,CrashSystem 类比MoveSystem
      }
    }
    

    ECS的优势

    • 将数据更有效的组织,提高CPU cache利用率
      关于cache的利用率的好处,更多信息大家可以参考ECS 真的是「未来主流」的架构吗?的各位大牛的探讨。
    • 逻辑更彻底的解耦,便于并行、扩展、整合
      逻辑解耦是所以框架都希望的解决的。传统的框架也是力求将游戏能拆分更好更干净的模块。

    总结

    ECS的并不是一个突如其来的框架,其思想在传统的游戏开发中多少都会运用到,比如“输入指令”,“网络模块”,各种单例的Manager类等等,其思想很接近ECS,只不过他们更多的吧ECS整合到一个类中,即保存数据,又处理逻辑,能完成单一独立的逻辑。ECS的思想则是看到这种设计的好处,把其严格的扩展到项目的所有逻辑处理中,所有的逻辑都拆分成System,需要参与其中的Entity挂载对应Component,这样项目的逻辑就非常解耦,便于扩充,维护。

    一个东西出现,很容易被其亮眼的地方所吸引,但同时我们也要多思考其是否真的那么完美,这里推荐大家阅读下《ECS 真的是「未来主流」的架构吗?》,不一定要为了ECS就强行套用,等自己发现项目的System逻辑臃肿,自己也分不清怎么去解耦时,给自己带来的反而是困扰。

    ECS本人并没有实际的项目经验,对其的理解可能也是停留在很肤浅的表面,本文的论述仅供参考,欢迎探讨指正。

    参考资料

    ECS 真的是「未来主流」的架构吗?
    浅谈《守望先锋》中的 ECS 构架
    游戏开发中的ECS 架构概述

    展开全文
  • 一种适合创业公司的技术架构方案 创业公司,觉得很重要的一点,是快速的以市场行动去验证业务方向的可行性。对于在创业公司的技术团队,一支作为服务支撑的团队,应该遵循的设计原则是: 在此前提下,老夫基于阿里...
  • Unity2018 ECS框架简明教程

    万次阅读 2018-09-25 09:15:12
    简介 全新的Unity官版ECS在Unite2017 Austin就已经show过了,主要特征如下: 数据和行为分离 在通常的Unity开发中,我们会将Monobehavior组件挂载到一个Gameobjec上,而ECS中,则将设计为将组建附加到Entity上 ...
  • 说起来比较汗颜,自从掌握了PureMVC并基于此思想搭建了一个自己的PureMVC框架后就再没碰过其他框架了,后来听说StrangeIOC框架用的比较多一直打算学习一下,结果后面忙于各种事一直没有时间,最终unity推出了ECS框架...
  • Linux总结

    千次阅读 多人点赞 2020-01-14 20:36:45
    在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤: vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表...
  • Docker

    千次阅读 2020-02-09 13:20:52
    Docker的相关架构 镜像(images) : 类似于的安装软件包 / 类 容器(container):类似于安装软件按住成功后的可运行的软件 / 对象 一个镜像可以对应多个容器 远程仓库: Docker官方提供的下载镜像的地址,国外网站,...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
    C#基础教程-c#实例教程,适合初学者。 第一章 C#语言基础 本章介绍C#语言的基础知识,希望具有C语言的读者能够基本掌握C#语言,并以此为基础,能够进一步学习用C#语言编写window应用程序和Web应用程序。...
  • OSS+ESC+SLB 安全隔离 采用混合云模式,通过专有网络VPC和医院的数据中心组成一个按需定制、强隔离的网络环境。 通过专线/VPN构建安全的线路传输环境。 通过WAF、安骑士和态势感知等高级安全服务,抵御来自...
  • C++ ECS架构ForEach实现

    2020-12-14 17:56:57
    最近参考unity的ecs架构,想写一个C++版本给自己的demo使用。在写到ForEach的时候,发现要想实现类似unity的ForEach还是要花点功夫的,因此写一篇文章记录一下。先看一下unity版本这个接口怎么用。 Entities ....
  • 基于改进ESC算法的语音端点检测及FPGA实现.pdf
  • 在unity里做打包或者帮策划美术做工具的时候经常会需要把Prefab拉出来或者场景打开做检验工作 其实这个在上一篇在ui打包的文章里有提到,不过重点不同,上篇重点是打包,这篇的重点是把里面的一个小知识点拉出来讲一...
  • autoquad是一个出色的开源 软件,了解了ukf的姿态解算流程,分析了ukf姿态解算代码,同时分析了autoquad的姿态控制,最后角速率环autoquad使用了两种可供选择的pid控制方式,并行pid和串行pid
  • 游戏设计模式——ECS架构

    千次阅读 多人点赞 2018-10-26 21:33:13
    1.ECS概述  ECS全称Entity-Component-System(实体-组件-系统),是基于组合优于继承(将不变的部分使用继承以方便复用, 将多变的部分用组合来方便拓展)的原则的一种模式,游戏中的每一个单元(怪物、相机等)...
  • WebRTC 架构

    千次阅读 多人点赞 2020-04-30 17:04:12
    WebRTC还是比较庞大的,咋一看无从下手,本篇以WebRTC自带的例子,阐述WebRTC Native核心的音频、视频和信令三个部分,WebRTC本身架构是P2P的,信令的部分也是围绕P2P展开的,好了,废话不多,直接上正文了。...
  • 1. 什么是云服务器ESC 云服务器(Elastic Compute ervice,简称ESC)是一种简单高效、处理能力可伸缩的计算服务,帮助您快速构建更稳定、安全的应用、提升运维效率,降低IT成本,是您更专注于核心业务创新。 2. ESC...
  • 如果执行ESB服务编排的节点只有一个,往往就达不到ESB中间件的设计要求甚至会使ESB中间件服务成为整个软件架构的性能瓶颈点。所以在我们设计的ESB中间件中,真正执行ESB服务的节点会有多个这种ESB-Broker Server节点...
  • Visio绘制架构图(一)

    万次阅读 2020-03-12 23:03:08
    2、ESC:清除对形状的选择或形状上的焦点; 3、Shift+箭头键:对所选图形位置进行微调; 4、Delete:删除所选主控形状; 5、Ctrl+1:切换到鼠标。 三、常见问题 (一)解决Visio图形粘贴到word中虚线变实线的问题 1...
  • 我是廖志伟,一名Java开发工程师,CSDN博客专家,多年一线研发经验,曾就职多家互联网公司,参与并主导多个百万级并发的互联网产品研发与系统架构搭建,对大型分布式,高并发及微服务架构,缓存框架有非常深入的研究...
  • 终于把阿里ESC服务器搞到手了 购买并搭建服务器的过程 怎样白嫖六个月阿里云服务器
  • 十张图看懂华为业务与组织架构

    千次阅读 2020-04-14 17:39:13
    近十年来,华为的业务和组织架构发生了比较大的变化。特别是2017年成立了Cloud BU,到2019年又成立了Cloud & AI BG,前不久华为高管侯金龙以云与计算(Cloud & AI)BG总裁身份亮相,以至于外界对于华为的...
  • 准备 SpringBoot项目 其中,使用了Maven项目管理工具 Linux 我的Linux发行版是CentOS 7 ,是阿里云服务器 学生“在线实践”计划,学生可以免费领6个月服务器—其实学生购买服务器也不贵,建议弄一个玩玩 ...
  • 为什么80%的码农都做不了架构师?>>> ...
  • ESC2012源科力推芯片级固态硬盘rSSD.pdf
  • 自动驾驶硬件系统架构概述

    万次阅读 多人点赞 2018-07-11 19:13:28
    自动驾驶汽车硬件系统概述硬件系统的基础:一、自动驾驶系统的硬件架构二、自动驾驶的传感器三、自动驾驶传感器的产品定义四、自动驾驶的大脑五、自动驾驶汽车的线控系统根据美国国家运输安全委员会的调查报告,当时...
  • Javascript监听 ESC按下事件

    千次阅读 2013-11-13 16:17:00
    为什么80%的码农都做不了架构师?>>> ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,049
精华内容 3,619
关键字:

esc架构