【ECS】实体,组件,系统的设计思路
前言
大家好我是IT侠来了!
上一篇文章带大家了解了下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: 「点赞」 「在看」 支持一下呗)
开发者实践游戏开发、副业挣钱,新的种子又开始发芽!