├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── index.ts ├── lib ├── ECSComponent.ts ├── ECSComponents.ts ├── ECSEntities.ts ├── ECSEntity.ts ├── ECSEnvironment.ts ├── ECSEvent.ts ├── ECSEvents.ts ├── ECSSystem.ts ├── ECSSystems.ts ├── __private.ts └── impl │ ├── ECSComponentsImpl.ts │ ├── ECSEntitiesImpl.ts │ ├── ECSEventsImpl.ts │ ├── ECSImpl.ts │ └── ECSSystemsImpl.ts ├── package.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iam-ecs-typescript 2 | 3 | 使用 TypeScript 实现的一个 ECS 系统。目前用于 Cocos Creator,不过因为没有依赖任何 Cocos Creator 的接口,所以也可以用在任何其他支持 TS/JS 的引擎中。 4 | 5 | ``` 6 | QQ 群号码: 367237484 7 | ``` 8 | 9 | ``` 10 | COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 11 | ``` 12 | 13 | 在线 DEMO 展示: [https://liaoyulei.cn/projects/iam-ecs-typescript-demo/](https://liaoyulei.cn/projects/iam-ecs-typescript-demo/) 14 | 15 | DEMO 源代码: [https://github.com/dualface/iam-ecs-typescript-demo](https://github.com/dualface/iam-ecs-typescript-demo) 16 | 17 | ECS 框架源代码: [https://github.com/dualface/iam-ecs-typescript](https://github.com/dualface/iam-ecs-typescript) 18 | 19 | ~ 20 | 21 | 22 | ## CHANGELOG 23 | 24 | - 版本 1.1.5 打断兼容性的改动: `ECSComponent.entityID` 改名为 `ECSComponent.entityId` 25 | - 2021/03/24: 按照 [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) 更新了代码和注释 26 | - 2021/03/19: 更新 README 和在线 DEMO 27 | - 2021/03/18: 更名为 `iam-ecs-typescript` 28 | - 2021/03/14: 增加 `ECSComponentInterface` 接口 29 | - 2021/03/10: 将 DEMO 迁移到单独的仓库 30 | - 2021/03/10: 发布为 NPM 包,使用 `ecsclass` 装饰器简化代码 31 | - 2021/02/08: 添加 Cocos Creator 3.0 示例项目 32 | - 2021/01/26: 初始发布 33 | 34 | ~ 35 | 36 | 37 | ## 目标 38 | 39 | `ECS` 是 `Entity-Component-System` 的缩写,代表了一种架构模式。 40 | 41 | `ECS` 并没有什么标准的实现。不过总的来讲,`ECS` 具有这些特点: 42 | 43 | - `Entity` 代表一个实体,例如游戏中的 NPC、子弹、物品等等。通常每一个 `Entity` 都有一个唯一 ID。 44 | - `Component` 代表构成一个实体的其中一部分,称为组件。例如 NPC 可能由 `Movable`(可以移动)、`Health`(具有健康度,也就是血量)、`RenderNode`(渲染节点)几个组件组成,每个组件只定义特定的一组属性或者状态。 45 | - `System` 代表处理某一个领域逻辑的系统。大部分系统都是处理特定的 `Component`,例如专门处理所有 `Movable` 组件的系统仅仅处理移动等操作。这样一来大部分 system 通常都只包含相对简单的逻辑。 46 | 47 | 不过 `ECS` 也并非万能钥匙。在 UI 交互等场景中,`ECS` 用起来就比较繁琐了。所以从实践的角度,我主要将 `ECS` 用于构建游戏世界的玩法实现。而涉及到 UI 交互等场景,仍然采用传统的面向对象架构。 48 | 49 | 在 `iam-ecs-typescript` 这个实现中,我主要追求以下目标: 50 | 51 | - 容易理解的设计 52 | - 简单明了的 API 53 | - 无性能损耗 54 | 55 | ~ 56 | 57 | 58 | ## DEMO 59 | 60 | 在线 DEMO 展示: [https://liaoyulei.cn/projects/iam-ecs-typescript-demo/](https://liaoyulei.cn/projects/iam-ecs-typescript-demo/) 61 | 62 | DEMO 源代码: [https://github.com/dualface/iam-ecs-typescript-demo](https://github.com/dualface/iam-ecs-typescript-demo) 63 | 64 | ~ 65 | 66 | 67 | ## ECS API 说明 68 | 69 | 所有 API 都在 `lib/` 目录中,并且有中文注释,文档此处省略。。。 70 | 71 | \-EOF\- 72 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSEnvironment } from "./lib/ECSEnvironment"; 6 | import { ECSImpl } from "./lib/impl/ECSImpl"; 7 | 8 | export * from "./lib/ECSComponent"; 9 | export * from "./lib/ECSEntity"; 10 | export * from "./lib/ECSEnvironment"; 11 | export * from "./lib/ECSEvent"; 12 | export * from "./lib/ECSSystem"; 13 | export { ecsclass } from "./lib/__private"; 14 | 15 | /** 16 | * 创建 ECS 环境 17 | */ 18 | export function createECSEnv(): ECSEnvironment { 19 | return new ECSImpl(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/ECSComponent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | /** 6 | * 组件接口 7 | */ 8 | export interface ECSComponentInterface { 9 | /** 10 | * 组件的类名 11 | */ 12 | name: string; 13 | 14 | /** 15 | * 组件的所有者实体 ID 16 | */ 17 | entityId: string; 18 | } 19 | 20 | /** 21 | * 组件 22 | */ 23 | export abstract class ECSComponent implements ECSComponentInterface { 24 | /** 25 | * 组件的类名 26 | */ 27 | get name(): string { 28 | return this.constructor.name; 29 | } 30 | 31 | /** 32 | * 组件的所有者实体 ID 33 | */ 34 | entityId = ""; 35 | } 36 | -------------------------------------------------------------------------------- /lib/ECSComponents.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSComponentInterface } from "./ECSComponent"; 6 | import { Constructor } from "./__private"; 7 | 8 | /** 9 | * 组件集合 10 | */ 11 | export interface ECSComponents { 12 | /** 13 | * 返回组件总数 14 | */ 15 | size(): number; 16 | 17 | /** 18 | * 查询特定名字的所有组件 19 | * 20 | * @param constructor 21 | */ 22 | all(constructor: Constructor): T[]; 23 | 24 | /** 25 | * 查询特定名字的第一个组件 26 | * 27 | * @param constructor 28 | */ 29 | get(constructor: Constructor): T; 30 | 31 | /** 32 | * 添加组件 33 | * 34 | * @param component 35 | */ 36 | add(component: ECSComponentInterface): void; 37 | 38 | /** 39 | * 移除组件 40 | * 41 | * @param component 42 | */ 43 | delete(component: ECSComponentInterface): void; 44 | } 45 | -------------------------------------------------------------------------------- /lib/ECSEntities.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSEntity } from "./ECSEntity"; 6 | 7 | /** 8 | * 实体集合 9 | */ 10 | export interface ECSEntities { 11 | /** 12 | * 返回实体总数 13 | */ 14 | size(): number; 15 | 16 | /** 17 | * 检查指定实体是否存在 18 | * 19 | * @param id 20 | */ 21 | has(id: string): boolean; 22 | 23 | /** 24 | * 获取指定实体 25 | * 26 | * @param id 27 | */ 28 | get(id: string): ECSEntity; 29 | 30 | /** 31 | * 添加实体 32 | * 33 | * @param entity 34 | */ 35 | add(entity: ECSEntity): void; 36 | 37 | /** 38 | * 按照 ID 移除指定实体 39 | * 40 | * @param id 41 | */ 42 | delete(id: string): void; 43 | 44 | /** 45 | * 移除实体 46 | * 47 | * @param entity 48 | */ 49 | delete(entity: ECSEntity): void; 50 | 51 | /** 52 | * 移除所有实体 53 | */ 54 | clear(): void; 55 | } 56 | -------------------------------------------------------------------------------- /lib/ECSEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSComponentInterface } from "./ECSComponent"; 6 | import { ECSComponents } from "./ECSComponents"; 7 | import { Constructor } from "./__private"; 8 | 9 | /** 10 | * 用于生成实体唯一 ID 11 | */ 12 | let nextEntityId: number = 0; 13 | 14 | /** 15 | * 实体基础类 16 | */ 17 | export class ECSEntity { 18 | /** 19 | * 实体 ID 20 | */ 21 | readonly id: string; 22 | 23 | /** 24 | * 实体的所有组件 25 | */ 26 | readonly components = new Map(); 27 | 28 | /** 29 | * 实体当前是否可用 30 | */ 31 | private enabled = false; 32 | 33 | /** 34 | * 全局组件集合,由 ECS 指定,让 Entity 可以直接将组件添加到 ECS 中 35 | */ 36 | globalComponentsRef: ECSComponents | undefined = undefined; 37 | 38 | /** 39 | * 构造函数 40 | */ 41 | constructor() { 42 | nextEntityId++; 43 | this.id = nextEntityId.toString(); 44 | } 45 | 46 | /** 47 | * 返回实体可用状态 48 | */ 49 | isEnabled(): boolean { 50 | return this.enabled; 51 | } 52 | 53 | /** 54 | * 设置实体的所有组件是否可用 55 | * 56 | * @param enabled 57 | */ 58 | setEnabled(enabled: boolean): ECSEntity { 59 | if (this.enabled === enabled || !this.globalComponentsRef) { 60 | // 状态未改变,或者还未添加到 ECS 中的实体,设置 enabled 不起作用 61 | return this; 62 | } 63 | 64 | this.enabled = enabled; 65 | const ref = this.globalComponentsRef; 66 | if (enabled) { 67 | // 把组件添加到全局组件列表 68 | this.components.forEach((component) => ref.add(component)); 69 | } else { 70 | // 从全局组件列表移除组件 71 | this.components.forEach((component) => ref.delete(component)); 72 | } 73 | return this; 74 | } 75 | 76 | /** 77 | * 添加组件到实体 78 | * 79 | * @param component 80 | */ 81 | addComponent(component: ECSComponentInterface): ECSEntity { 82 | const name = component.name; 83 | if (this.components.has(name)) { 84 | throw new RangeError( 85 | `[ECS] component '${name}' already exists in entity '${this.id}'` 86 | ); 87 | } 88 | component.entityId = this.id; 89 | if (this.globalComponentsRef) { 90 | this.globalComponentsRef.add(component); 91 | } 92 | this.components.set(name, component); 93 | return this; 94 | } 95 | 96 | /** 97 | * 检查指定的组件是否存在 98 | * 99 | * @param constructor 100 | */ 101 | hasComponent( 102 | constructor: Constructor 103 | ): boolean { 104 | return this.components.has(constructor.name); 105 | } 106 | 107 | /** 108 | * 取得指定名字的组件 109 | * 110 | * @param constructor 111 | */ 112 | getComponent( 113 | constructor: Constructor 114 | ): T { 115 | const name = constructor.name; 116 | const component = this.components.get(name); 117 | if (!component) { 118 | throw new RangeError( 119 | `[ECS] not found component '${name}' in entity '${this.id}'` 120 | ); 121 | } 122 | return component as T; 123 | } 124 | 125 | /** 126 | * 移除组件 127 | * 128 | * @param constructor 129 | */ 130 | removeComponent( 131 | constructor: Constructor 132 | ): ECSEntity { 133 | const name = constructor.name; 134 | const component = this.components.get(name); 135 | if (!component) { 136 | throw new RangeError( 137 | `[ECS] not found component '${name}' in entity '${this.id}'` 138 | ); 139 | } 140 | if (this.globalComponentsRef) { 141 | this.globalComponentsRef.delete(component); 142 | } 143 | this.components.delete(name); 144 | return this; 145 | } 146 | 147 | /** 148 | * 移除所有组件 149 | */ 150 | removeAllComponents(): ECSEntity { 151 | if (this.globalComponentsRef) { 152 | const ref = this.globalComponentsRef; 153 | this.components.forEach((component) => ref.delete(component)); 154 | } 155 | this.components.clear(); 156 | return this; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/ECSEnvironment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSComponents } from "./ECSComponents"; 6 | import { ECSEntities } from "./ECSEntities"; 7 | import { ECSEvents } from "./ECSEvents"; 8 | import { ECSSystems } from "./ECSSystems"; 9 | 10 | /** 11 | * ECS 运行环境 12 | * 13 | * ECSEnvironment 会确保加入的 system 一定是在 system.load() 方法结束后, 14 | * 才会调用 system.start() 方法。 15 | * 16 | * 当系统需要异步加载资源时,需要将 system.load() 17 | * 方法定义为 async function,并且使用 await 操作。 18 | * 19 | * 如果是在调用 ECSEnvironment.start() 之前加入的 system。 20 | * ECSEnvironment 会保证所有 system.load() 方法结束后, 21 | * 才会依次调用这些系统的 system.start() 方法。 22 | * 23 | * ---- 24 | * 25 | * ECSEnvironment 运作流程: 26 | * 27 | * - 按照优先级执行所有 system 28 | * - 如果某个 system 生成了事件,会立即放入事件队列 29 | * - 每个 system 执行时都可以查询事件队列 30 | * - 清理事件队列 31 | */ 32 | export interface ECSEnvironment { 33 | /** 34 | * 事件集合 35 | */ 36 | readonly events: ECSEvents; 37 | 38 | /** 39 | * 系统集合 40 | */ 41 | readonly systems: ECSSystems; 42 | 43 | /** 44 | * 实体集合 45 | */ 46 | readonly entities: ECSEntities; 47 | 48 | /** 49 | * 组件集合 50 | */ 51 | readonly components: ECSComponents; 52 | 53 | /** 54 | * 启动环境 55 | */ 56 | start(): void; 57 | 58 | /** 59 | * 停止环境 60 | */ 61 | stop(): void; 62 | 63 | /** 64 | * 更新状态 65 | * 66 | * @param dt 67 | */ 68 | update(dt: number): void; 69 | 70 | /** 71 | * 更新状态并保留事件 72 | * 73 | * @param dt 74 | * @param keepEvents 75 | */ 76 | update(dt: number, keepEvents: boolean): void; 77 | } 78 | -------------------------------------------------------------------------------- /lib/ECSEvent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | /** 6 | * 事件 7 | */ 8 | export abstract class ECSEvent { 9 | /** 10 | * 返回类名 11 | */ 12 | get name(): string { 13 | return this.constructor.name; 14 | } 15 | 16 | /** 17 | * 构造函数 18 | * 19 | * 如果 `unique = true`,则同名事件即便多次添加到队列,也只会保留一个。 20 | * 21 | * @param unique 同名事件是否保持唯一性 22 | */ 23 | constructor(readonly unique: boolean = false) {} 24 | } 25 | -------------------------------------------------------------------------------- /lib/ECSEvents.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSEvent } from "./ECSEvent"; 6 | import { Constructor } from "./__private"; 7 | 8 | /** 9 | * 事件队列 10 | */ 11 | export interface ECSEvents { 12 | /** 13 | * 追加事件到队列 14 | * 15 | * @param event 16 | */ 17 | push(event: ECSEvent): void; 18 | 19 | /** 20 | * 取得指定事件的列表 21 | * 22 | * @param constructor 23 | */ 24 | fetch(constructor: Constructor): T[]; 25 | 26 | /** 27 | * 检查是否存在指定事件 28 | * 29 | * @param constructor 30 | */ 31 | has(constructor: Constructor): boolean; 32 | 33 | /** 34 | * 清理所有事件 35 | */ 36 | clear(): void; 37 | } 38 | -------------------------------------------------------------------------------- /lib/ECSSystem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSEnvironment } from "./ECSEnvironment"; 6 | 7 | /** 8 | * 系统接口 9 | */ 10 | export abstract class ECSSystem { 11 | /** 12 | * 返回类名 13 | */ 14 | get name(): string { 15 | return this.constructor.name; 16 | } 17 | 18 | /** 19 | * 系统是否处于允许状态 20 | */ 21 | enabled = true; 22 | 23 | /** 24 | * 执行时的优先级,数字更小的系统会更优先执行 25 | */ 26 | priority = 0; 27 | 28 | /** 29 | * 系统所属的 ECS,由 ECS 设置 30 | */ 31 | private ecsInstance: ECSEnvironment | undefined; 32 | 33 | /** 34 | * 系统所在的 ECS 环境 35 | */ 36 | get ecs(): ECSEnvironment { 37 | if (!this.ecsInstance) { 38 | throw new TypeError("[ECS] System.ecs is undefined"); 39 | } 40 | return this.ecsInstance; 41 | } 42 | 43 | /** 44 | * 设置系统所属 ECS 环境 45 | */ 46 | setEnvironment(env: ECSEnvironment | undefined) { 47 | this.ecsInstance = env; 48 | } 49 | 50 | /** 51 | * 系统载入 52 | */ 53 | async load() {} 54 | 55 | /** 56 | * 系统卸载 57 | */ 58 | unload(): void {} 59 | 60 | /** 61 | * 启动系统 62 | */ 63 | start(): void {} 64 | 65 | /** 66 | * 停止系统 67 | */ 68 | stop(): void {} 69 | 70 | /** 71 | * 更新状态 72 | * 73 | * @param dt 74 | */ 75 | update(dt: number): void {} 76 | } 77 | -------------------------------------------------------------------------------- /lib/ECSSystems.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSSystem } from "./ECSSystem"; 6 | import { Constructor } from "./__private"; 7 | 8 | /** 9 | * 系统集合 10 | */ 11 | export interface ECSSystems { 12 | /** 13 | * 返回指定名字的系统 14 | * 15 | * @param constructor 16 | */ 17 | get(constructor: Constructor): T; 18 | 19 | /** 20 | * 添加系统 21 | * 22 | * @param system 23 | * @param priority 24 | */ 25 | add(system: ECSSystem, priority?: number): ECSSystems; 26 | 27 | /** 28 | * 移除系统 29 | * 30 | * @param system 31 | */ 32 | delete(system: ECSSystem): ECSSystems; 33 | 34 | /** 35 | * 移除所有系统 36 | */ 37 | clear(): ECSSystems; 38 | 39 | /** 40 | * 按照优先级对所有系统排序,数值小的优先执行 41 | */ 42 | sort(): ECSSystems; 43 | } 44 | -------------------------------------------------------------------------------- /lib/__private.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | /** 6 | * 构造函数类型 7 | */ 8 | export type Constructor = new (...args: any[]) => T; 9 | 10 | /** 11 | * ecsclass() 装饰器为 class 添加有效的 name 属性 12 | * 13 | * @param name 14 | */ 15 | export function ecsclass(name: string) { 16 | return (ctor: Function) => { 17 | Object.defineProperty(ctor, "name", { 18 | value: name, 19 | writable: false, 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /lib/impl/ECSComponentsImpl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSComponentInterface } from "../ECSComponent"; 6 | import { ECSComponents } from "../ECSComponents"; 7 | import { Constructor } from "../__private"; 8 | 9 | /** 10 | * 组件集合的实现 11 | */ 12 | export class ECSComponentsImpl implements ECSComponents { 13 | /** 14 | * 按照类型分组的所有组件 15 | */ 16 | private components = new Map(); 17 | 18 | /** 19 | * 返回组件总数 20 | */ 21 | size(): number { 22 | return this.components.size; 23 | } 24 | 25 | /** 26 | * 返回包含特定类型组件的数组 27 | * 28 | * @param constructor 29 | */ 30 | all(constructor: Constructor): T[] { 31 | const components = this.components.get(constructor.name); 32 | return components ? (components as T[]) : (EMPTY as T[]); 33 | } 34 | 35 | /** 36 | * 取得指定类型组件中的第一个 37 | * 38 | * @param constructor 39 | */ 40 | get(constructor: Constructor): T { 41 | const components = this.all(constructor); 42 | if (components.length === 0) { 43 | throw new RangeError( 44 | `[ECS] not found component '${constructor.name}'` 45 | ); 46 | } 47 | return components[0]; 48 | } 49 | 50 | /** 51 | * 添加组件 52 | * 53 | * @param component 54 | */ 55 | add(component: ECSComponentInterface): void { 56 | const name = component.name; 57 | if (typeof name !== "string" || name.length === 0) { 58 | throw new RangeError( 59 | `[ECS] component '${component}' not set name by @ecsclass` 60 | ); 61 | } 62 | 63 | let components = this.components.get(name); 64 | if (!components) { 65 | components = []; 66 | this.components.set(name, components); 67 | } 68 | components.push(component); 69 | } 70 | 71 | /** 72 | * 删除特定组件 73 | * 74 | * @param component 75 | */ 76 | delete(component: ECSComponentInterface): void { 77 | const components = this.components.get(component.name); 78 | if (components) { 79 | const i = components.indexOf(component); 80 | if (i >= 0) components.splice(i, 1); 81 | } 82 | } 83 | } 84 | 85 | //// private 86 | 87 | /** 88 | * 预定义的空组件集合 89 | */ 90 | const EMPTY: ECSComponentInterface[] = []; 91 | -------------------------------------------------------------------------------- /lib/impl/ECSEntitiesImpl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSEntities } from "../ECSEntities"; 6 | import { ECSEntity } from "../ECSEntity"; 7 | import { ECSComponentsImpl } from "./ECSComponentsImpl"; 8 | 9 | /** 10 | * 实体集合的实现 11 | */ 12 | export class ECSEntitiesImpl implements ECSEntities { 13 | /** 14 | * 跟踪所有组件 15 | */ 16 | readonly components = new ECSComponentsImpl(); 17 | 18 | /** 19 | * 跟踪所有实体 20 | */ 21 | private entities = new Set(); 22 | 23 | /** 24 | * 按照实体 ID 跟踪所有实体 25 | */ 26 | private entitiesById = new Map(); 27 | 28 | /** 29 | * 所有实体的总数 30 | */ 31 | size(): number { 32 | return this.entities.size; 33 | } 34 | 35 | /** 36 | * 检查指定 ID 的实体是否存在 37 | * 38 | * @param id 39 | */ 40 | has(id: string): boolean { 41 | return this.entitiesById.has(id); 42 | } 43 | 44 | /** 45 | * 取得指定 ID 的实体 46 | * 47 | * @param id 48 | */ 49 | get(id: string): ECSEntity { 50 | const entity = this.entitiesById.get(id); 51 | if (!entity) { 52 | throw new RangeError(`[ECS] not found entity '${id}'`); 53 | } 54 | return entity; 55 | } 56 | 57 | /** 58 | * 添加实体 59 | * 60 | * @param entity 61 | */ 62 | add(entity: ECSEntity): void { 63 | entity.globalComponentsRef = this.components; 64 | entity.setEnabled(true); 65 | this.entities.add(entity); 66 | this.entitiesById.set(entity.id, entity); 67 | } 68 | 69 | /** 70 | * 删除指定 ID 的实体 71 | * 72 | * @param id 73 | */ 74 | delete(id: string): void; 75 | 76 | /** 77 | * 删除指定实体 78 | * 79 | * @param entity 80 | */ 81 | delete(entity: ECSEntity): void; 82 | 83 | delete(entity: string | ECSEntity): void { 84 | if (typeof entity === "string") { 85 | entity = this.get(entity); 86 | } 87 | entity.setEnabled(false); 88 | entity.globalComponentsRef = undefined; 89 | this.entities.delete(entity); 90 | this.entitiesById.delete(entity.id); 91 | } 92 | 93 | /** 94 | * 删除所有实体 95 | */ 96 | clear(): void { 97 | this.entities.forEach((entity) => this.delete(entity)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/impl/ECSEventsImpl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSEvent } from "../ECSEvent"; 6 | import { ECSEvents } from "../ECSEvents"; 7 | import { Constructor } from "../__private"; 8 | 9 | /** 10 | * 事件集合的实现 11 | */ 12 | export class ECSEventsImpl implements ECSEvents { 13 | /** 14 | * 所有事件 15 | */ 16 | private events = new Map(); 17 | 18 | /** 19 | * 追加事件到队列 20 | * 21 | * @param event 22 | */ 23 | push(event: ECSEvent): void { 24 | const name = event.name; 25 | if (typeof name !== "string" || name.length === 0) { 26 | throw new RangeError( 27 | `[ECS] event '${event}' not set name by @ecsclass` 28 | ); 29 | } 30 | 31 | let list = this.events.get(name); 32 | if (!list) { 33 | list = []; 34 | this.events.set(name, list); 35 | } 36 | if (event.unique) { 37 | list.length = 0; 38 | } 39 | list.push(event); 40 | } 41 | 42 | /** 43 | * 取得指定事件的列表 44 | * 45 | * @param constructor 46 | */ 47 | fetch(constructor: Constructor): T[] { 48 | const events = this.events.get(constructor.name); 49 | return events ? (events as T[]) : (EMPTY as T[]); 50 | } 51 | 52 | /** 53 | * 检查是否存在指定事件 54 | * 55 | * @param constructor 56 | */ 57 | has(constructor: Constructor): boolean { 58 | const events = this.events.get(constructor.name); 59 | return events ? events.length > 0 : false; 60 | } 61 | 62 | /** 63 | * 清理所有事件 64 | */ 65 | clear(): void { 66 | this.events.clear(); 67 | } 68 | } 69 | 70 | /// private 71 | 72 | /** 73 | * 预定义的空事件集合 74 | */ 75 | const EMPTY: ECSEvent[] = []; 76 | -------------------------------------------------------------------------------- /lib/impl/ECSImpl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSComponents } from "../ECSComponents"; 6 | import { ECSEntities } from "../ECSEntities"; 7 | import { ECSEnvironment } from "../ECSEnvironment"; 8 | import { ECSEvents } from "../ECSEvents"; 9 | import { ECSSystems } from "../ECSSystems"; 10 | import { ECSEntitiesImpl } from "./ECSEntitiesImpl"; 11 | import { ECSEventsImpl } from "./ECSEventsImpl"; 12 | import { ECSSystemsImpl } from "./ECSSystemsImpl"; 13 | 14 | /** 15 | * ECS 实现 16 | */ 17 | export class ECSImpl implements ECSEnvironment { 18 | /** 19 | * 所有事件 20 | */ 21 | readonly events: ECSEvents; 22 | 23 | /** 24 | * 所有系统 25 | */ 26 | readonly systems: ECSSystems; 27 | 28 | /** 29 | * 所有实体 30 | */ 31 | readonly entities: ECSEntities; 32 | 33 | /** 34 | * 所有组件 35 | */ 36 | readonly components: ECSComponents; 37 | 38 | /** 39 | * 所有系统的内部实现 40 | */ 41 | private systemsImpl: ECSSystemsImpl; 42 | 43 | /** 44 | * 所有实体的内部实现 45 | */ 46 | private entitiesImpl: ECSEntitiesImpl; 47 | 48 | /** 49 | * 构造函数 50 | */ 51 | constructor() { 52 | this.events = new ECSEventsImpl(); 53 | this.systems = this.systemsImpl = new ECSSystemsImpl(this); 54 | this.entities = this.entitiesImpl = new ECSEntitiesImpl(); 55 | this.components = this.entitiesImpl.components; 56 | } 57 | 58 | /** 59 | * 启动所有系统 60 | */ 61 | start(): void { 62 | this.systemsImpl.start(); 63 | } 64 | 65 | /** 66 | * 停止所有系统 67 | */ 68 | stop(): void { 69 | this.systemsImpl.stop(); 70 | } 71 | 72 | /** 73 | * 更新所有系统并清理事件,如果 keepEvents = true,则保留事件 74 | * 75 | * @param dt 76 | * @param keepEvents 是否保留事件 77 | */ 78 | update(dt: number, keepEvents: boolean = false): void { 79 | this.systemsImpl.update(dt); 80 | if (keepEvents !== true) { 81 | // 由于输入系统产生的事件可能在 update() 之前, 82 | // 所以只能在 update 之后再清理事件 83 | this.events.clear(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/impl/ECSSystemsImpl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * COPYRIGHT 2021 ALL RESERVED. (C) liaoyulei, https://github.com/dualface 3 | */ 4 | 5 | import { ECSSystem } from "../ECSSystem"; 6 | import { ECSSystems } from "../ECSSystems"; 7 | import { Constructor } from "../__private"; 8 | import { ECSImpl } from "./ECSImpl"; 9 | 10 | /** 11 | * ECS 系统集合的实现 12 | */ 13 | export class ECSSystemsImpl implements ECSSystems { 14 | /** 15 | * 所有已经载入完成的系统,按照优先级排序 16 | */ 17 | private readonly loaded: ECSSystem[] = []; 18 | 19 | /** 20 | * 按照名字查找所有已经载入完成的系统 21 | */ 22 | private readonly loadedByName = new Map(); 23 | 24 | /** 25 | * 保存正在载入中的系统,当 ECSSystem.load() 方法完成后移动到 loaded 队列 26 | */ 27 | private readonly loading = new Map(); 28 | 29 | /** 30 | * 系统的运行状态 31 | */ 32 | private readonly systemsRunning = new Map(); 33 | 34 | /** 35 | * 当前运行状态 36 | */ 37 | private running = false; 38 | 39 | /** 40 | * 下一个优先级值,后添加的系统会自动使用更大的优先级数值 41 | */ 42 | private nextPriority = 0; 43 | 44 | /** 45 | * 构造函数 46 | * 47 | * @param ecs 所属 ECS 48 | */ 49 | constructor(readonly ecs: ECSImpl) { 50 | this.ecs = ecs; 51 | } 52 | 53 | /** 54 | * 启动所有系统 55 | */ 56 | start(): void { 57 | if (this.running) { 58 | throw new RangeError("[ECS] already is running"); 59 | } 60 | this.running = true; 61 | this.checkLoading(); 62 | } 63 | 64 | /** 65 | * 停止所有系统 66 | */ 67 | stop(): void { 68 | if (!this.running) { 69 | throw new RangeError("[ECS] not running"); 70 | } 71 | 72 | // 停止所有正在运行的系统 73 | this.loadedByName.forEach((system, name) => { 74 | if (this.systemsRunning.get(name) === true) { 75 | system.stop(); 76 | } 77 | this.systemsRunning.set(name, false); 78 | }); 79 | 80 | this.running = false; 81 | } 82 | 83 | /** 84 | * 更新所有系统 85 | * 86 | * @param dt 87 | */ 88 | update(dt: number): void { 89 | for (const system of this.loaded) { 90 | if (system.enabled) { 91 | system.update(dt); 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * 取得指定的系统 98 | * 99 | * @param constructor 100 | */ 101 | get(constructor: Constructor): T { 102 | const name = constructor.name; 103 | const system = this.loadedByName.get(name); 104 | if (!system) { 105 | throw new RangeError(`[ECS] not found system '${name}'`); 106 | } 107 | return system as T; 108 | } 109 | 110 | /** 111 | * 添加系统 112 | * 113 | * @param system 114 | * @param priority 115 | */ 116 | add(system: ECSSystem, priority?: number): ECSSystems { 117 | const name = system.name; 118 | if (typeof name !== "string" || name.length === 0) { 119 | throw new RangeError( 120 | `[ECS] system '${system}' not set name by @ecsclass` 121 | ); 122 | } 123 | if (this.loadedByName.has(name)) { 124 | throw new RangeError(`[ECS] system '${name}' already exists`); 125 | } 126 | if (this.loading.has(name)) { 127 | throw new RangeError(`[ECS] system '${name}' already in loading`); 128 | } 129 | 130 | // 设置系统运行初始状态 131 | system.setEnvironment(this.ecs); 132 | if (typeof priority === "number") { 133 | system.priority = priority; 134 | if (priority > this.nextPriority) { 135 | this.nextPriority = priority + 1; 136 | } 137 | } else { 138 | system.priority = this.nextPriority; 139 | this.nextPriority++; 140 | } 141 | this.systemsRunning.set(system.name, false); 142 | 143 | // 加入 loading 列表 144 | this.loading.set(name, [system, false]); 145 | system 146 | .load() 147 | .then(() => { 148 | // 更新 loading 列表状态 149 | this.loading.set(name, [system, true]); 150 | // 检查 loading 列表 151 | this.checkLoading(); 152 | }) 153 | .catch((e) => { 154 | throw e; 155 | }); 156 | 157 | return this; 158 | } 159 | 160 | /** 161 | * 删除指定系统 162 | * 163 | * @param system 164 | */ 165 | delete(system: ECSSystem): ECSSystems { 166 | const name = system.name; 167 | if (this.loadedByName.has(name)) { 168 | // 从载入完成的列表中删除指定 system 169 | for (let i = 0, l = this.loaded.length; i < l; i++) { 170 | if (this.loaded[i].name === name) { 171 | this.loaded.splice(i, 1); 172 | break; 173 | } 174 | } 175 | this.loadedByName.delete(name); 176 | } else if (this.loading.has(name)) { 177 | // 从正在载入的列表中删除指定 system 178 | this.loading.delete(name); 179 | } else { 180 | throw new RangeError(`[ECS] not found system '${name}`); 181 | } 182 | 183 | // 检查是否需要停止系统 184 | if (this.systemsRunning.get(name)) { 185 | system.stop(); 186 | } 187 | this.systemsRunning.delete(name); 188 | 189 | // 卸载系统 190 | system.unload(); 191 | system.setEnvironment(undefined); 192 | 193 | // 检查载入中的系统 194 | this.checkLoading(); 195 | 196 | return this; 197 | } 198 | 199 | /** 200 | * 删除所有系统 201 | */ 202 | clear(): ECSSystems { 203 | this.loadedByName.forEach((system) => this.delete(system)); 204 | this.loading.forEach(([system]) => this.delete(system)); 205 | return this; 206 | } 207 | 208 | /** 209 | * 按照优先级对所有系统排序,数值小的优先执行 210 | */ 211 | sort(): ECSSystems { 212 | this.loaded.sort((a: ECSSystem, b: ECSSystem): number => { 213 | if (a.priority > b.priority) { 214 | return 1; 215 | } else if (a.priority < b.priority) { 216 | return -1; 217 | } else { 218 | return 0; 219 | } 220 | }); 221 | return this; 222 | } 223 | 224 | //// private 225 | 226 | /** 227 | * 检查 loading 列表中的所有系统是否都已经载入完成 228 | */ 229 | private checkLoading(): void { 230 | // 已经载入完成的系统移动到 loaded 列表 231 | // 未载入完成的保持在 loading 列表中 232 | const removes: string[] = []; 233 | this.loading.forEach(([system, loaded], name) => { 234 | if (loaded) { 235 | this.loaded.push(system); 236 | this.loadedByName.set(name, system); 237 | removes.push(name); 238 | } 239 | }); 240 | // 从正在载入列表清理已经完成载入的系统 241 | removes.forEach((name) => this.loading.delete(name)); 242 | 243 | // 重新排序 244 | this.sort(); 245 | 246 | if (this.loading.size > 0) { 247 | // 如果还有系统未加载完成,则继续等待 248 | return; 249 | } 250 | 251 | if (!this.running) return; 252 | 253 | // 启动所有还未启动的系统 254 | for (const system of this.loaded) { 255 | if (this.systemsRunning.get(system.name) !== true) { 256 | system.start(); 257 | this.systemsRunning.set(system.name, true); 258 | } 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iam-ecs-typescript", 3 | "version": "1.1.5", 4 | "description": "", 5 | "main": "index.ts", 6 | "dependencies": {}, 7 | "devDependencies": {}, 8 | "scripts": {}, 9 | "keywords": [ 10 | "ecs", 11 | "game", 12 | "framework" 13 | ], 14 | "author": "liaoyulei", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/dualface/iam-ecs-typescript.git" 18 | }, 19 | "homepage": "https://github.com/dualface/iam-ecs-typescript", 20 | "license": "Apache-2.0" 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "module": "ES2015", 5 | "moduleResolution": "node", 6 | 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "noEmit": true, 10 | "noImplicitAny": true, 11 | "skipLibCheck": true, 12 | 13 | "declaration": true, 14 | "declarationMap": true, 15 | "experimentalDecorators": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "isolatedModules": true 18 | }, 19 | "exclude": ["node_modules", "library", "local", "temp", "build", "settings"] 20 | } 21 | --------------------------------------------------------------------------------