【ECS】实体,组件,系统的设计思路

Creator星球游戏开发社区

共 4547字,需浏览 10分钟

 ·

2020-10-18 00:49

点击蓝色字关注一下哦

前言

大家好我是IT侠来了!

上一篇文章带大家了解了下ECS框架的一些基本概念,不知道ECS是什么可以点击下面的传送门过去了解下,再回来继续看本篇。

上一篇:[ECS系列] 欢迎来到ECS的世界

今天这篇文章我们就来具体说说ECS框架中「Entity」 「Component」「System」 的设计思路。我的ECS框架是用「TypeScript」编写的,所以我假定小伙伴是有「TypeScript」语言基础的。因为篇幅有限,所以我主要讲比较核心的地方。如果对「TypeScript」不是很了解,可以去其官网或者其他文档学习一下基础。

*官网地址:https://www.typescriptlang.org/

Entity

上一篇文章也说了「实体」其实只有一个必须要定义的属性「id」

*实体 需要 「Signal」类支持 因为篇幅问题,这个类我将放在后面的文章详细讲解。

import Signal from "./signal";
import Component from "./component";
export default class Entity {
   // 实体类上都静态属性
   static _id: number;
   id: number;
   removed: boolean;
   _components: { [key: string]: Component } = {};
   onComponentAdded: Signal;
   onComponentRemoved: Signal; 
   constructor() {
    // 因为在世界中实体必须是唯一的,所以需要一个自增的静态属性
    // 来确保每一个实体对象的id是唯一的
    this.id = Entity._id++;
    // 标记一个实体是被从该世界移除
    this.removed = false;
    // 存放该实体上的组件
    this._components = {};
    //signal 是一个事件收发器 
    // 当有组件增加到实体上时候就会发射一个事件告诉监听者们 
    this.onComponentAdded = new Signal();
     // 当有组件从实体上删除时候就会发射一个事件告诉监听者们 
    this.onComponentRemoved = new Signal();
  }
  

实体有4个成员方法:

「addComponent」  给实体增加组件的时候调用

「removeComponent」 从实体上删除组件的时候调用

「getComponent」  从实体上获取指定组件

「hasComponent」  检查实体上是否有指定组件

具体定义代码如下:

  
  /**
   * 检查实体上是否有指定的组件
   * @param componentName 组件名
   * @return boolean  true是有指定组件 false是没有
   */

  hasComponent(componentName: string): boolean {
    return !!this._components[componentName];
  }

  /**
   * 获取实体上指定组件
   * @param componentName 组件名
   * @return Component|any  返回组件信息
   */

  getComponent(componentName: string): Component | any {
    return this._components[componentName];
  }

  /**
   * 向实体新增组件
   * 告诉增加组件事件的监听函数有组件加到实体上
   * @param component 组件实例
   * @return Component|any  返回组件信息
   */

  addComponent(component: Component) {
    this._components[component.name] = component;
    this.onComponentAdded.emit(this, component.name);
  }

  /**
   * 从实体删除指定组件
   * 告诉删除组件事件的监听函数有组件从实体上删除
   * @param componentName 组件名
   * @return Component|any  返回组件信息
   */

  removeComponent(componentName: string) {
    this._components[componentName] = null;
    this.onComponentRemoved.emit(this, componentName);
  }

从上面的代码不难看出来「实体」 类的功能提供成员方法都是管理「组件」的。因为「实体」就是组件的容器。

「addComponent」「removeComponent」方法都有相关的信号器发送信号(可以理解为事件)。监听这2个信号的其实就是后面我们要说的「系统」的工作。

「系统」定义好后,需要的就是注册自己所关心的组件的信号监听器。如果「实体」上的「组件」发生变化的时候,「系统」就可以捕获到,执行对应的业务逻辑。

「实体」部分就是这些,下面我们来说说 「组件」

Component

「组件」就更加简单只有一个属性:「name」 因为「组件」上的属性应该需要灵活定义。所以只能在实例化一个组件的时候定义属性。

//组件上的属性可以灵活的定义

interface IComponent{
    [key:string]:any
}

export default  class Component implements IComponent{
    //组件名是只读的 只允许在声明的时候赋值一次
    readonly name:string = "";

    constructor(name:string){
        this.name = name;
    }
}

上面的代码就是「组件」类所有代码,是不是很简单。没有任何成员方法,因为「组件」 只是各种自定义属性的容器,其他的事情它不需要关心。

「组件」上面定义的是「自定义」的属性和属性对应的数据,这就需要「组件」实例来完成。

我把「组件」设计的很灵活,「IComponent」接口接受任何key为string类型,值为any类型的键值对。方便实例自定义属性来描述实体的某一个特征。

「组件」说完了,最后来说说 「系统」

System

「系统」的成员变量也只有一个:「world」 标记该系统是属于哪个世界的。


import World from "./world";
import Entity from "./entity";

export default  class System{
    //标记该系统是属于哪个世界
    world:World = null;
    constructor(){
        this.world = null;
    }
  

成员方法如下:

  
    /**
     * 把系统加到世界中
     * @param world  world实例
     */

    addToWorld(world:World){
        this.world = world;
    }

    removedFromWorld(world:World){
        this.world = null;
    }
    /**
     * 每一帧更新实体
     * @param dt 每帧刷新时经过的时间秒
     */

    update(dt){
        throw new Error('子类应该重写改方法来实现自己的业务逻辑');
    }
     /**
     * 监听增加指定组件的实体
     * @param components 
     * @param callback 
     */

    on(components:string|string[], callback:(ent:Entity)=>any) {
        throw new Error('子类应该重写改方法来实现自己的业务逻辑');
    }

     /**
     * 增加一个监听组件从实体删除行为 
     * @param components 监听的组件列表
     * @param callback 触发监听行为
     */

    onRemove(components:string|string[], callback:(ent:Entity)=>any) {
        throw new Error('子类应该重写改方法来实现自己的业务逻辑');
    }

    /**
     * 增加每一帧执行的行为 如果监听的组件都在实体上 
     * @param components 监听的组件列表
     * @param callback 触发监听行为
     */

    onUpdate(components:string|string[], callback:(dt:number,es:Entity[])=>any) {
        throw new Error('子类应该重写改方法来实现自己的业务逻辑');
    }
  

系统中定义了如下成员方法:

「addToWorld」增加到指定的世界中

「removedFromWorld」从指定的世界中移除

「update」接受一个「dt」形参 用于每一帧都执行业务逻辑。

「on」监听一个或多个组件,返回匹配的实体 该方法只会在组件增加的时候执行一次。

「onRemove」监听一个或多个组件,返回匹配的实体该方法只会在组件从实体上移除的时候执行一次。

「onUpdate」监听一个或多个组件,每一帧都检查 返回匹配的实体数组。

*「update」 「on」 「onRemove」 「onUpdate」 都需要子类完成重写实现自己的逻辑。

总结

本篇介绍了ECS框架中 「Entity」 「Component」 「System」,3个核心的类,下一篇文章将会聊聊 「World」是如何管理它们的。因为「World」 东西比较多,所以单独用一篇文章来介绍。

谢谢看到最后(ps: 「点赞」 「在看」 支持一下呗)


开发者实践游戏开发、副业挣钱,新的种子又开始发芽!

浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报