├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docs ├── assets │ ├── css │ │ ├── main.css │ │ └── main.css.map │ ├── images │ │ ├── icons.png │ │ ├── icons@2x.png │ │ ├── widgets.png │ │ └── widgets@2x.png │ └── js │ │ ├── main.js │ │ └── search.js ├── classes │ ├── _engine_.engine.html │ ├── _entity_.entity.html │ ├── _family_.abstractfamily.html │ ├── _family_.cachedfamily.html │ ├── _family_.familybuilder.html │ ├── _family_.noncachedfamily.html │ └── _system_.system.html ├── globals.html ├── index.html ├── interfaces │ ├── _component_.component.html │ ├── _component_.componentclass.html │ ├── _engine_.engineentitylistener.html │ └── _family_.family.html └── modules │ ├── _component_.html │ ├── _engine_.html │ ├── _entity_.html │ ├── _family_.html │ ├── _index_.html │ └── _system_.html ├── index.d.ts ├── index.js ├── lib ├── Component.d.ts ├── Component.js ├── Component.js.map ├── Engine.d.ts ├── Engine.js ├── Engine.js.map ├── Entity.d.ts ├── Entity.js ├── Entity.js.map ├── Family.d.ts ├── Family.js ├── Family.js.map ├── System.d.ts ├── System.js ├── System.js.map ├── index.d.ts ├── index.js └── index.js.map ├── package-lock.json ├── package.json ├── src ├── Component.ts ├── Engine.ts ├── Entity.spec.ts ├── Entity.ts ├── Family.spec.ts ├── Family.ts ├── System.spec.ts ├── System.ts ├── index.spec.ts └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | package-lock.json 3 | .gitignore 4 | node_modules -------------------------------------------------------------------------------- /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 | # Nova Engine - Entity-Component-System 2 | 3 | An entity component system made with typescript for usage in the Nova Engine. 4 | 5 | ## Installing 6 | 7 | ```sh 8 | npm i --save @nova-engine/ecs 9 | ``` 10 | 11 | ## Basic Usage 12 | 13 | To use the entity component system, you first must create your Engine to store your systems and entities. 14 | 15 | You can create as many as you wish, but usually it's only one per application. 16 | 17 | ```ts 18 | import { Engine } from "@nova-engine/ecs"; 19 | const engine = new Engine(); 20 | ``` 21 | 22 | Now you just define your components: 23 | 24 | ```ts 25 | import { Component } from "@nova-engine/ecs"; 26 | 27 | // Components can have custom constructors, but they must be able to be initialized 28 | // with no arguments, because entities creates the instances for you. 29 | // Try not to save complex data types in yout components 30 | class PositionComponent implements Component { 31 | x = 0; 32 | y = 0; 33 | } 34 | 35 | class VelocityComponent implements Component { 36 | x = 0; 37 | y = 0; 38 | } 39 | 40 | // If you are making a component library, and want to avoid collitions 41 | // You can add a tag to your component implementations 42 | 43 | class MyLibraryComponent implements Component { 44 | // This will ensure your component won't collide with other "MyLibraryComponent" 45 | static readonly tag = "my-library/MyLibraryComponent"; 46 | } 47 | ``` 48 | 49 | You can also define your systems: 50 | 51 | ```ts 52 | import { Component, Family, System, FamilyBuilder } from "@nova-engine/ecs"; 53 | class GravitySystem extends System { 54 | static readonly DEFAULT_ACCELERATION = 0.98; 55 | family?: Family; 56 | acceleration: number; 57 | 58 | // Constructors are free for your own implementation 59 | constructor(acceleration = GravitySystem.DEFAULT_ACCELERATION) { 60 | super(); 61 | this.acceleration = acceleration; 62 | // higher priorities means the system runs before others with lower priority 63 | this.priority = 300; 64 | } 65 | // This is called when a system is added to an engine, you may want to 66 | // startup your families here. 67 | onAttach(engine: Engine) { 68 | // Needed to work properly 69 | super.onAttach(engine); 70 | // Families are an easy way to have groups of entities with some criteria. 71 | this.family = new FamilyBuilder(engine).include(VelocityComponent).build(); 72 | } 73 | 74 | // This, in reality is the only method your system must implement 75 | // but using onAttach to prepare your families is useful. 76 | update(engine: Engine, delta: number) { 77 | for (let entity of this.family.entities) { 78 | // Easy to get a component by class 79 | // Be warned, if the entity lacks this component, an error *will* be thrown. 80 | // But families ensures than we will always have the required components. 81 | const velocity = entity.getComponent(VelocityComponent); 82 | velocity.y += this.acceleration; 83 | // if the family doesn't require that component 84 | // you can always check for it 85 | if (entity.hasComponent(PositionComponent)) { 86 | const position = entity.getComponent(PositionComponent); 87 | } else { 88 | // You can create components on an entity easily. 89 | const position = entity.putComponent(PositionComponent); 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | ## Limitations 97 | 98 | You can only have one instance of a component per entity. 99 | entity IDs are not generated by default, if you need them to have IDs, set them up yourself: 100 | 101 | ```ts 102 | entity.id = myGeneratedId(); 103 | ``` 104 | 105 | ## LICENSE 106 | 107 | The license is Apache-2.0, so use it as you please without worries. 108 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nova-engine/ecs/092352e32e88b3525369578c2d8a60d551b0071c/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nova-engine/ecs/092352e32e88b3525369578c2d8a60d551b0071c/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nova-engine/ecs/092352e32e88b3525369578c2d8a60d551b0071c/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nova-engine/ecs/092352e32e88b3525369578c2d8a60d551b0071c/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /docs/globals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 59 |

@nova-engine/ecs

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |

Index

68 |
69 |
70 |
71 |

External modules

72 | 80 |
81 |
82 |
83 |
84 |
85 | 116 |
117 |
118 | 177 |
178 |

Generated using TypeDoc

179 |
180 |
181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 59 |

@nova-engine/ecs

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |

Nova Engine - Entity-Component-System

68 |

An entity component system made with typescript for usage in the Nova Engine.

69 |

Installing

70 |
npm i --save @nova-engine/ecs
 71 | 
72 |

Basic Usage

73 |

To use the entity component system, you first must create your Engine to store your systems and entities.

74 |

You can create as many as you wish, but usually it's only one per application.

75 |
import { Engine } from "@nova-engine/ecs";
 76 | const engine = new Engine();
 77 | 
78 |

Now you just define your components:

79 |
import { Component } from "@nova-engine/ecs";
 80 | 
 81 | // Components can have custom constructors, but they must be able to be initialized
 82 | // with no arguments, because entities creates the instances for you.
 83 | // Try not to save complex data types in yout components
 84 | class PositionComponent implements Component {
 85 |   x = 0;
 86 |   y = 0;
 87 | }
 88 | 
 89 | class VelocityComponent implements Component {
 90 |   x = 0;
 91 |   y = 0;
 92 | }
 93 | 
 94 | // If you are making a component library, and want to avoid collitions
 95 | // You can add a tag to your component implementations
 96 | 
 97 | class MyLibraryComponent implements Component {
 98 |   // This will ensure your component won't collide with other "MyLibraryComponent"
 99 |   static readonly tag = "my-library/MyLibraryComponent";
100 | }
101 | 
102 |

You can also define your systems:

103 |
import { Component, Family, System, FamilyBuilder } from "@nova-engine/ecs";
104 | class GravitySystem extends System {
105 |   static readonly DEFAULT_ACCELERATION = 0.98;
106 |   family?: Family;
107 |   acceleration: number;
108 | 
109 |   // Constructors are free for your own implementation
110 |   constructor(acceleration = GravitySystem.DEFAULT_ACCELERATION) {
111 |     super();
112 |     this.acceleration = acceleration;
113 |     // higher priorities means the system runs before others with lower priority
114 |     this.priority = 300;
115 |   }
116 |   // This is called when a system is added to an engine, you may want to
117 |   // startup your families here.
118 |   onAttach(engine: Engine) {
119 |     // Needed to work properly
120 |     super.onAttach(engine);
121 |     // Families are an easy way to have groups of entities with some criteria.
122 |     this.family = new FamilyBuilder(engine).include(VelocityComponent).build();
123 |   }
124 | 
125 |   // This, in reality is the only method your system must implement
126 |   // but using onAttach to prepare your families is useful.
127 |   update(engine: Engine, delta: number) {
128 |     for (let entity of this.family.entities) {
129 |       // Easy to get a component by class
130 |       // Be warned, if the entity lacks this component, an error *will* be thrown.
131 |       // But families ensures than we will always have the required components.
132 |       const velocity = entity.getComponent(VelocityComponent);
133 |       velocity.y += this.acceleration;
134 |       // if the family doesn't require that component
135 |       // you can always check for it
136 |       if (entity.hasComponent(PositionComponent)) {
137 |         const position = entity.getComponent(PositionComponent);
138 |       } else {
139 |         // You can create components on an entity easily.
140 |         const position = entity.putComponent(PositionComponent);
141 |       }
142 |     }
143 |   }
144 | }
145 | 
146 |

Limitations

147 |

You can only have one instance of a component per entity. 148 | entity IDs are not generated by default, if you need them to have IDs, set them up yourself:

149 |
entity.id = myGeneratedId();
150 | 
151 |

LICENSE

152 |

The license is Apache-2.0, so use it as you please without worries.

153 |
154 |
155 | 186 |
187 |
188 | 247 |
248 |

Generated using TypeDoc

249 |
250 |
251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /docs/interfaces/_component_.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Component | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface Component

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | Component 77 |
  • 78 |
79 |
80 |
81 | 122 |
123 |
124 | 183 |
184 |

Generated using TypeDoc

185 |
186 |
187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docs/interfaces/_component_.componentclass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ComponentClass | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface ComponentClass<T>

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Type parameters

74 | 79 |
80 |
81 |

Hierarchy

82 |
    83 |
  • 84 | ComponentClass 85 |
  • 86 |
87 |
88 |
89 |

Index

90 |
91 |
92 |
93 |

Constructors

94 | 97 |
98 |
99 |

Properties

100 | 104 |
105 |
106 |
107 |
108 |
109 |

Constructors

110 |
111 | 112 |

constructor

113 |
    114 |
  • new ComponentClass(): T
  • 115 |
116 |
    117 |
  • 118 | 123 |

    Returns T

    124 |
  • 125 |
126 |
127 |
128 |
129 |

Properties

130 |
131 | 132 |

name

133 |
name: string
134 | 139 |
140 |
141 | 142 |

Optional tag

143 |
tag: undefined | string
144 | 149 |
150 |
151 |
152 | 204 |
205 |
206 | 265 |
266 |

Generated using TypeDoc

267 |
268 |
269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /docs/interfaces/_engine_.engineentitylistener.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EngineEntityListener | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface EngineEntityListener

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |

Hierarchy

74 |
    75 |
  • 76 | EngineEntityListener 77 |
  • 78 |
79 |
80 |
81 |

Index

82 |
83 |
84 |
85 |

Methods

86 | 90 |
91 |
92 |
93 |
94 |
95 |

Methods

96 |
97 | 98 |

onEntityAdded

99 |
    100 |
  • onEntityAdded(entity: Entity): void
  • 101 |
102 |
    103 |
  • 104 | 109 |

    Parameters

    110 |
      111 |
    • 112 |
      entity: Entity
      113 |
    • 114 |
    115 |

    Returns void

    116 |
  • 117 |
118 |
119 |
120 | 121 |

onEntityRemoved

122 |
    123 |
  • onEntityRemoved(entity: Entity): void
  • 124 |
125 |
    126 |
  • 127 | 132 |

    Parameters

    133 |
      134 |
    • 135 |
      entity: Entity
      136 |
    • 137 |
    138 |

    Returns void

    139 |
  • 140 |
141 |
142 |
143 |
144 | 193 |
194 |
195 | 254 |
255 |

Generated using TypeDoc

256 |
257 |
258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /docs/interfaces/_family_.family.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Family | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 65 |

Interface Family

66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |

A family is a criteria to separate your entities. 76 | You can have families on wich entities must have a component, 77 | entities cannot have some components or a mix of both. 78 | Families also cache the entities of the engine by default, 79 | so you won't have to worry about filtering entities every time.

80 |
81 |
82 |
83 |
84 |

Hierarchy

85 |
    86 |
  • 87 | Family 88 |
  • 89 |
90 |
91 |
92 |

Implemented by

93 | 98 |
99 |
100 |

Index

101 |
102 |
103 |
104 |

Properties

105 | 108 |
109 |
110 |

Methods

111 | 114 |
115 |
116 |
117 |
118 |
119 |

Properties

120 |
121 | 122 |

entities

123 |
entities: ReadonlyArray<Entity>
124 | 129 |
130 |
131 |

Computes a list of entities on the family. 132 | The list may or may not be cached, depending of implementation.

133 |
134 |
135 |
136 |
137 |
138 |

Methods

139 |
140 | 141 |

includesEntity

142 |
    143 |
  • includesEntity(entity: Entity): boolean
  • 144 |
145 |
    146 |
  • 147 | 152 |

    Parameters

    153 |
      154 |
    • 155 |
      entity: Entity
      156 |
    • 157 |
    158 |

    Returns boolean

    159 |
  • 160 |
161 |
162 |
163 |
164 | 222 |
223 |
224 | 283 |
284 |

Generated using TypeDoc

285 |
286 |
287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /docs/modules/_component_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Component" | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

External module "Component"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Interfaces

75 | 79 |
80 |
81 |
82 |
83 |
84 | 121 |
122 |
123 | 182 |
183 |

Generated using TypeDoc

184 |
185 |
186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /docs/modules/_engine_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Engine" | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

External module "Engine"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |

Interfaces

81 | 84 |
85 |
86 |
87 |
88 |
89 | 126 |
127 |
128 | 187 |
188 |

Generated using TypeDoc

189 |
190 |
191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /docs/modules/_entity_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Entity" | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

External module "Entity"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |

Type aliases

81 | 84 |
85 |
86 |
87 |
88 |
89 |

Type aliases

90 |
91 | 92 |

EntityChangeListener

93 |
EntityChangeListener: function
94 | 99 |
100 |

Type declaration

101 |
    102 |
  • 103 |
      104 |
    • (entity: Entity): any
    • 105 |
    106 |
      107 |
    • 108 |

      Parameters

      109 |
        110 |
      • 111 |
        entity: Entity
        112 |
      • 113 |
      114 |

      Returns any

      115 |
    • 116 |
    117 |
  • 118 |
119 |
120 |
121 |
122 |
123 | 160 |
161 |
162 | 221 |
222 |

Generated using TypeDoc

223 |
224 |
225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /docs/modules/_family_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "Family" | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

External module "Family"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 81 |
82 |
83 |

Interfaces

84 | 87 |
88 |
89 |
90 |
91 |
92 | 138 |
139 |
140 | 199 |
200 |

Generated using TypeDoc

201 |
202 |
203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /docs/modules/_index_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "index" | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

External module "index"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 101 |
102 |
103 | 162 |
163 |

Generated using TypeDoc

164 |
165 |
166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/modules/_system_.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | "System" | @nova-engine/ecs 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

External module "System"

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Classes

75 | 78 |
79 |
80 |
81 |
82 |
83 | 117 |
118 |
119 | 178 |
179 |

Generated using TypeDoc

180 |
181 |
182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./lib/index"; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./lib/index"); -------------------------------------------------------------------------------- /lib/Component.d.ts: -------------------------------------------------------------------------------- 1 | interface Component { 2 | } 3 | interface ComponentClass { 4 | readonly name: string; 5 | readonly tag?: string; 6 | new (): T; 7 | } 8 | export { Component, ComponentClass }; 9 | -------------------------------------------------------------------------------- /lib/Component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=Component.js.map -------------------------------------------------------------------------------- /lib/Component.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Component.js","sourceRoot":"","sources":["../src/Component.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /lib/Engine.d.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from "./Entity"; 2 | import { System } from "./System"; 3 | interface EngineEntityListener { 4 | onEntityAdded(entity: Entity): void; 5 | onEntityRemoved(entity: Entity): void; 6 | } 7 | declare class Engine { 8 | private _entities; 9 | private readonly _entityListeners; 10 | private readonly _systems; 11 | private _systemsNeedSorting; 12 | readonly entities: ReadonlyArray; 13 | notifyPriorityChange(system: System): void; 14 | addEntityListener(listener: EngineEntityListener): this; 15 | removeEntityListener(listener: EngineEntityListener): this; 16 | addEntity(entity: Entity): this; 17 | addEntities(...entities: Entity[]): this; 18 | removeEntity(entity: Entity): void; 19 | removeEntities(...entities: Entity[]): this; 20 | addSystem(system: System): this; 21 | addSystems(...systems: System[]): void; 22 | removeSystem(system: System): this; 23 | removeSystems(...systems: System[]): void; 24 | update(delta: number): void; 25 | } 26 | export { Engine, EngineEntityListener }; 27 | -------------------------------------------------------------------------------- /lib/Engine.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Engine = (function () { 4 | function Engine() { 5 | this._entities = []; 6 | this._entityListeners = []; 7 | this._systems = []; 8 | this._systemsNeedSorting = false; 9 | } 10 | Object.defineProperty(Engine.prototype, "entities", { 11 | get: function () { 12 | return Object.freeze(this._entities.slice(0)); 13 | }, 14 | enumerable: true, 15 | configurable: true 16 | }); 17 | Engine.prototype.notifyPriorityChange = function (system) { 18 | this._systemsNeedSorting = true; 19 | }; 20 | Engine.prototype.addEntityListener = function (listener) { 21 | if (this._entityListeners.indexOf(listener) === -1) { 22 | this._entityListeners.push(listener); 23 | } 24 | return this; 25 | }; 26 | Engine.prototype.removeEntityListener = function (listener) { 27 | var index = this._entityListeners.indexOf(listener); 28 | if (index !== -1) { 29 | this._entityListeners.splice(index, 1); 30 | } 31 | return this; 32 | }; 33 | Engine.prototype.addEntity = function (entity) { 34 | if (this._entities.indexOf(entity) === -1) { 35 | this._entities.push(entity); 36 | for (var _i = 0, _a = this._entityListeners; _i < _a.length; _i++) { 37 | var listener = _a[_i]; 38 | listener.onEntityAdded(entity); 39 | } 40 | } 41 | return this; 42 | }; 43 | Engine.prototype.addEntities = function () { 44 | var entities = []; 45 | for (var _i = 0; _i < arguments.length; _i++) { 46 | entities[_i] = arguments[_i]; 47 | } 48 | for (var _a = 0, entities_1 = entities; _a < entities_1.length; _a++) { 49 | var entity = entities_1[_a]; 50 | this.addEntity(entity); 51 | } 52 | return this; 53 | }; 54 | Engine.prototype.removeEntity = function (entity) { 55 | var index = this._entities.indexOf(entity); 56 | if (index !== -1) { 57 | this._entities.splice(index, 1); 58 | for (var _i = 0, _a = this._entityListeners; _i < _a.length; _i++) { 59 | var listener = _a[_i]; 60 | listener.onEntityRemoved(entity); 61 | } 62 | } 63 | }; 64 | Engine.prototype.removeEntities = function () { 65 | var entities = []; 66 | for (var _i = 0; _i < arguments.length; _i++) { 67 | entities[_i] = arguments[_i]; 68 | } 69 | for (var _a = 0, entities_2 = entities; _a < entities_2.length; _a++) { 70 | var entity = entities_2[_a]; 71 | this.removeEntity(entity); 72 | } 73 | return this; 74 | }; 75 | Engine.prototype.addSystem = function (system) { 76 | var index = this._systems.indexOf(system); 77 | if (index === -1) { 78 | this._systems.push(system); 79 | system.onAttach(this); 80 | this._systemsNeedSorting = true; 81 | } 82 | return this; 83 | }; 84 | Engine.prototype.addSystems = function () { 85 | var systems = []; 86 | for (var _i = 0; _i < arguments.length; _i++) { 87 | systems[_i] = arguments[_i]; 88 | } 89 | for (var _a = 0, systems_1 = systems; _a < systems_1.length; _a++) { 90 | var system = systems_1[_a]; 91 | this.addSystem(system); 92 | } 93 | }; 94 | Engine.prototype.removeSystem = function (system) { 95 | var index = this._systems.indexOf(system); 96 | if (index !== -1) { 97 | this._systems.splice(index, 1); 98 | system.onDetach(this); 99 | } 100 | return this; 101 | }; 102 | Engine.prototype.removeSystems = function () { 103 | var systems = []; 104 | for (var _i = 0; _i < arguments.length; _i++) { 105 | systems[_i] = arguments[_i]; 106 | } 107 | for (var _a = 0, systems_2 = systems; _a < systems_2.length; _a++) { 108 | var system = systems_2[_a]; 109 | this.removeSystem(system); 110 | } 111 | }; 112 | Engine.prototype.update = function (delta) { 113 | if (this._systemsNeedSorting) { 114 | this._systemsNeedSorting = false; 115 | this._systems.sort(function (a, b) { return a.priority - b.priority; }); 116 | } 117 | for (var _i = 0, _a = this._systems; _i < _a.length; _i++) { 118 | var system = _a[_i]; 119 | system.update(this, delta); 120 | } 121 | }; 122 | return Engine; 123 | }()); 124 | exports.Engine = Engine; 125 | //# sourceMappingURL=Engine.js.map -------------------------------------------------------------------------------- /lib/Engine.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Engine.js","sourceRoot":"","sources":["../src/Engine.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/Entity.d.ts: -------------------------------------------------------------------------------- 1 | import { ComponentClass, Component } from "./Component"; 2 | declare type EntityChangeListener = (entity: Entity) => any; 3 | declare class Entity { 4 | private _id; 5 | private readonly _components; 6 | private readonly _listeners; 7 | private readonly _componentClasses; 8 | id: string | number; 9 | isNew(): boolean; 10 | listComponents(): Component[]; 11 | listComponentsWithTypes(): { 12 | component: Component; 13 | type: ComponentClass; 14 | }[]; 15 | listComponentsWithTags(): Readonly<{ 16 | tag: string; 17 | component: Component; 18 | }>[]; 19 | hasComponent(componentClass: ComponentClass): boolean; 20 | getComponent(componentClass: ComponentClass): T; 21 | putComponent(componentClass: ComponentClass): T; 22 | removeComponent(componentClass: ComponentClass): void; 23 | cast(component: Component | undefined | null, componentClass: ComponentClass): component is T; 24 | addListener(listener: EntityChangeListener): this; 25 | removeListener(listener: EntityChangeListener): this; 26 | } 27 | export { Entity, EntityChangeListener }; 28 | -------------------------------------------------------------------------------- /lib/Entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Entity = (function () { 4 | function Entity() { 5 | this._id = null; 6 | this._components = {}; 7 | this._listeners = []; 8 | this._componentClasses = {}; 9 | } 10 | Object.defineProperty(Entity.prototype, "id", { 11 | get: function () { 12 | if (this._id === null) { 13 | throw new Error("Cannot retrieve an ID when is null."); 14 | } 15 | return this._id; 16 | }, 17 | set: function (value) { 18 | if (value === null || value === undefined) { 19 | throw new Error("Must set a non null value when setting an entity id."); 20 | } 21 | if (this._id !== null) { 22 | throw new Error("Entity id is already set as \"" + this._id + "\"."); 23 | } 24 | this._id = value; 25 | }, 26 | enumerable: true, 27 | configurable: true 28 | }); 29 | Entity.prototype.isNew = function () { 30 | return this._id === null; 31 | }; 32 | Entity.prototype.listComponents = function () { 33 | var _this = this; 34 | return Object.keys(this._components).map(function (i) { return _this._components[i]; }); 35 | }; 36 | Entity.prototype.listComponentsWithTypes = function () { 37 | var _this = this; 38 | return Object.keys(this._components).map(function (i) { return ({ 39 | component: _this._components[i], 40 | type: _this._componentClasses[i] 41 | }); }); 42 | }; 43 | Entity.prototype.listComponentsWithTags = function () { 44 | var _this = this; 45 | return Object.keys(this._components).map(function (tag) { 46 | return Object.freeze({ 47 | tag: tag, 48 | component: _this._components[tag] 49 | }); 50 | }); 51 | }; 52 | Entity.prototype.hasComponent = function (componentClass) { 53 | var tag = componentClass.tag || componentClass.name; 54 | var component = this._components[tag]; 55 | if (!component) 56 | return false; 57 | if (!this.cast(component, componentClass)) { 58 | throw new Error("There are multiple classes with the same tag or name \"" + tag + "\".\nAdd a different property \"tag\" to one of them."); 59 | } 60 | return true; 61 | }; 62 | Entity.prototype.getComponent = function (componentClass) { 63 | var tag = componentClass.tag || componentClass.name; 64 | var component = this._components[tag]; 65 | if (!component) { 66 | throw new Error("Cannot get component \"" + tag + "\" from entity."); 67 | } 68 | if (!this.cast(component, componentClass)) { 69 | throw new Error("There are multiple classes with the same tag or name \"" + tag + "\".\nAdd a different property \"tag\" to one of them."); 70 | } 71 | return component; 72 | }; 73 | Entity.prototype.putComponent = function (componentClass) { 74 | var tag = componentClass.tag || componentClass.name; 75 | var component = this._components[tag]; 76 | if (component) { 77 | if (!this.cast(component, componentClass)) { 78 | throw new Error("There are multiple classes with the same tag or name \"" + tag + "\".\nAdd a different property \"tag\" to one of them."); 79 | } 80 | delete this._components[tag]; 81 | delete this._componentClasses[tag]; 82 | } 83 | var newComponent = new componentClass(); 84 | this._components[tag] = newComponent; 85 | this._componentClasses[tag] = componentClass; 86 | for (var _i = 0, _a = this._listeners; _i < _a.length; _i++) { 87 | var listener = _a[_i]; 88 | listener(this); 89 | } 90 | return newComponent; 91 | }; 92 | Entity.prototype.removeComponent = function (componentClass) { 93 | var tag = componentClass.tag || componentClass.name; 94 | var component = this._components[tag]; 95 | if (!component) { 96 | throw new Error("Component of tag \"" + tag + "\".\nDoes not exists."); 97 | } 98 | if (!this.cast(component, componentClass)) { 99 | throw new Error("There are multiple classes with the same tag or name \"" + tag + "\".\nAdd a different property \"tag\" to one of them."); 100 | } 101 | delete this._components[tag]; 102 | for (var _i = 0, _a = this._listeners; _i < _a.length; _i++) { 103 | var listener = _a[_i]; 104 | listener(this); 105 | } 106 | }; 107 | Entity.prototype.cast = function (component, componentClass) { 108 | return !!(component && component instanceof componentClass); 109 | }; 110 | Entity.prototype.addListener = function (listener) { 111 | var index = this._listeners.indexOf(listener); 112 | if (index === -1) { 113 | this._listeners.push(listener); 114 | } 115 | return this; 116 | }; 117 | Entity.prototype.removeListener = function (listener) { 118 | var index = this._listeners.indexOf(listener); 119 | if (index !== -1) { 120 | this._listeners.splice(index, 1); 121 | } 122 | return this; 123 | }; 124 | return Entity; 125 | }()); 126 | exports.Entity = Entity; 127 | //# sourceMappingURL=Entity.js.map -------------------------------------------------------------------------------- /lib/Entity.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Entity.js","sourceRoot":"","sources":["../src/Entity.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/Family.d.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentClass } from "./Component"; 2 | import { Engine } from "./Engine"; 3 | import { Entity } from "./Entity"; 4 | interface Family { 5 | readonly entities: ReadonlyArray; 6 | includesEntity(entity: Entity): boolean; 7 | } 8 | declare class FamilyBuilder { 9 | private _engine; 10 | private _cached; 11 | private readonly _include; 12 | private readonly _exclude; 13 | constructor(engine?: Engine); 14 | include(...classes: ComponentClass[]): this; 15 | exclude(...classes: ComponentClass[]): this; 16 | changeEngine(engine: Engine): this; 17 | setCached(cached: boolean): void; 18 | build(): Family; 19 | } 20 | export { Family, FamilyBuilder }; 21 | -------------------------------------------------------------------------------- /lib/Family.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = Object.setPrototypeOf || 4 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 5 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 6 | return function (d, b) { 7 | extendStatics(d, b); 8 | function __() { this.constructor = d; } 9 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 10 | }; 11 | })(); 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | var AbstractFamily = (function () { 14 | function AbstractFamily(engine, include, exclude) { 15 | var _this = this; 16 | this.includesEntity = function (entity) { 17 | for (var _i = 0, _a = _this._include; _i < _a.length; _i++) { 18 | var include = _a[_i]; 19 | if (!entity.hasComponent(include)) { 20 | return false; 21 | } 22 | } 23 | for (var _b = 0, _c = _this._exclude; _b < _c.length; _b++) { 24 | var exclude = _c[_b]; 25 | if (entity.hasComponent(exclude)) { 26 | return false; 27 | } 28 | } 29 | return true; 30 | }; 31 | this._engine = engine; 32 | this._include = Object.freeze(include.slice(0)); 33 | this._exclude = Object.freeze(exclude.slice(0)); 34 | } 35 | Object.defineProperty(AbstractFamily.prototype, "engine", { 36 | get: function () { 37 | return this._engine; 38 | }, 39 | enumerable: true, 40 | configurable: true 41 | }); 42 | return AbstractFamily; 43 | }()); 44 | var CachedFamily = (function (_super) { 45 | __extends(CachedFamily, _super); 46 | function CachedFamily(engine, include, exclude) { 47 | var _this = _super.call(this, engine, include, exclude) || this; 48 | _this.onEntityChanged = function (entity) { 49 | var index = _this._entities.indexOf(entity); 50 | if (index === -1) { 51 | _this._entities.push(entity); 52 | entity.addListener(_this.onEntityChanged); 53 | } 54 | _this._needEntityRefresh = true; 55 | }; 56 | var allEntities = _this.engine.entities; 57 | _this._entities = allEntities.filter(_this.includesEntity); 58 | _this.engine.addEntityListener(_this); 59 | for (var _i = 0, allEntities_1 = allEntities; _i < allEntities_1.length; _i++) { 60 | var entity = allEntities_1[_i]; 61 | entity.addListener(_this.onEntityAdded); 62 | } 63 | _this._needEntityRefresh = false; 64 | return _this; 65 | } 66 | Object.defineProperty(CachedFamily.prototype, "entities", { 67 | get: function () { 68 | if (this._needEntityRefresh) { 69 | this._needEntityRefresh = false; 70 | this._entities = this._entities.filter(this.includesEntity); 71 | } 72 | return Object.freeze(this._entities.slice(0)); 73 | }, 74 | enumerable: true, 75 | configurable: true 76 | }); 77 | CachedFamily.prototype.onEntityAdded = function (entity) { 78 | var index = this._entities.indexOf(entity); 79 | if (index === -1) { 80 | this._entities.push(entity); 81 | this._needEntityRefresh = true; 82 | entity.addListener(this.onEntityChanged); 83 | } 84 | }; 85 | CachedFamily.prototype.onEntityRemoved = function (entity) { 86 | var index = this._entities.indexOf(entity); 87 | if (index !== -1) { 88 | var entity_1 = this._entities[index]; 89 | this._entities.splice(index, 1); 90 | entity_1.removeListener(this.onEntityChanged); 91 | } 92 | }; 93 | return CachedFamily; 94 | }(AbstractFamily)); 95 | var NonCachedFamily = (function (_super) { 96 | __extends(NonCachedFamily, _super); 97 | function NonCachedFamily() { 98 | return _super !== null && _super.apply(this, arguments) || this; 99 | } 100 | Object.defineProperty(NonCachedFamily.prototype, "entities", { 101 | get: function () { 102 | return this.engine.entities.filter(this.includesEntity); 103 | }, 104 | enumerable: true, 105 | configurable: true 106 | }); 107 | return NonCachedFamily; 108 | }(AbstractFamily)); 109 | var FamilyBuilder = (function () { 110 | function FamilyBuilder(engine) { 111 | this._engine = engine || null; 112 | this._include = []; 113 | this._exclude = []; 114 | this._cached = true; 115 | } 116 | FamilyBuilder.prototype.include = function () { 117 | var classes = []; 118 | for (var _i = 0; _i < arguments.length; _i++) { 119 | classes[_i] = arguments[_i]; 120 | } 121 | (_a = this._include).push.apply(_a, classes); 122 | return this; 123 | var _a; 124 | }; 125 | FamilyBuilder.prototype.exclude = function () { 126 | var classes = []; 127 | for (var _i = 0; _i < arguments.length; _i++) { 128 | classes[_i] = arguments[_i]; 129 | } 130 | (_a = this._exclude).push.apply(_a, classes); 131 | return this; 132 | var _a; 133 | }; 134 | FamilyBuilder.prototype.changeEngine = function (engine) { 135 | this._engine = engine; 136 | return this; 137 | }; 138 | FamilyBuilder.prototype.setCached = function (cached) { 139 | this._cached = cached; 140 | }; 141 | FamilyBuilder.prototype.build = function () { 142 | if (!this._engine) { 143 | throw new Error("Family should always belong to an engine."); 144 | } 145 | if (!this._cached) { 146 | return new NonCachedFamily(this._engine, this._include, this._exclude); 147 | } 148 | return new CachedFamily(this._engine, this._include, this._exclude); 149 | }; 150 | return FamilyBuilder; 151 | }()); 152 | exports.FamilyBuilder = FamilyBuilder; 153 | //# sourceMappingURL=Family.js.map -------------------------------------------------------------------------------- /lib/Family.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Family.js","sourceRoot":"","sources":["../src/Family.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/System.d.ts: -------------------------------------------------------------------------------- 1 | import { Engine } from "./Engine"; 2 | declare abstract class System { 3 | private _priority; 4 | private readonly _engines; 5 | constructor(); 6 | priority: number; 7 | readonly engines: ReadonlyArray; 8 | onAttach(engine: Engine): void; 9 | onDetach(engine: Engine): void; 10 | abstract update(engine: Engine, delta: number): void; 11 | } 12 | export { System }; 13 | -------------------------------------------------------------------------------- /lib/System.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var System = (function () { 4 | function System() { 5 | this._priority = 0; 6 | this._engines = []; 7 | } 8 | Object.defineProperty(System.prototype, "priority", { 9 | get: function () { 10 | return this._priority; 11 | }, 12 | set: function (value) { 13 | this._priority = value; 14 | for (var _i = 0, _a = this._engines; _i < _a.length; _i++) { 15 | var engine = _a[_i]; 16 | engine.notifyPriorityChange(this); 17 | } 18 | }, 19 | enumerable: true, 20 | configurable: true 21 | }); 22 | Object.defineProperty(System.prototype, "engines", { 23 | get: function () { 24 | return Object.freeze(this._engines.slice(0)); 25 | }, 26 | enumerable: true, 27 | configurable: true 28 | }); 29 | System.prototype.onAttach = function (engine) { 30 | var index = this._engines.indexOf(engine); 31 | if (index === -1) { 32 | this._engines.push(engine); 33 | } 34 | }; 35 | System.prototype.onDetach = function (engine) { 36 | var index = this._engines.indexOf(engine); 37 | if (index !== -1) { 38 | this._engines.splice(index, 1); 39 | } 40 | }; 41 | return System; 42 | }()); 43 | exports.System = System; 44 | //# sourceMappingURL=System.js.map -------------------------------------------------------------------------------- /lib/System.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"System.js","sourceRoot":"","sources":["../src/System.ts"],"names":[],"mappings":";;AAEA;IAIE;QACE,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACrB,CAAC;IAED,sBAAI,4BAAQ;aAAZ;YACE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;aAMD,UAAa,KAAa;YACxB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,GAAG,CAAC,CAAe,UAAa,EAAb,KAAA,IAAI,CAAC,QAAQ,EAAb,cAAa,EAAb,IAAa;gBAA3B,IAAI,MAAM,SAAA;gBACb,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;aACnC;QACH,CAAC;;;OAXA;IAED,sBAAI,2BAAO;aAAX;YACE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;;;OAAA;IASD,yBAAQ,GAAR,UAAS,MAAc;QACrB,IAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5C,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,yBAAQ,GAAR,UAAS,MAAc;QACrB,IAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5C,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAGH,aAAC;AAAD,CAAC,AAvCD,IAuCC;AAEQ,wBAAM"} -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./Component"; 2 | export * from "./Engine"; 3 | export * from "./Entity"; 4 | export * from "./Family"; 5 | export * from "./System"; 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | __export(require("./Engine")); 7 | __export(require("./Entity")); 8 | __export(require("./Family")); 9 | __export(require("./System")); 10 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AACA,8BAAyB;AACzB,8BAAyB;AACzB,8BAAyB;AACzB,8BAAyB"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nova-engine/ecs", 3 | "description": "An Entity-Component-System part of the Nova MMO Engine.", 4 | "private": false, 5 | "version": "1.1.1", 6 | "main": "index.js", 7 | "author": { 8 | "name": "Ramiro Rojo", 9 | "email": "ramiro.rojo.cretta@gmail.com" 10 | }, 11 | "scripts": { 12 | "precommit": "npm run build && npm run docs && npm test", 13 | "prepush": "npm test", 14 | "test": "mocha -r ts-node/register src/**/*.spec.ts", 15 | "build": "./node_modules/.bin/tsc", 16 | "docs": "typedoc --out docs --mode modules --module commonjs --target es5" 17 | }, 18 | "devDependencies": { 19 | "@types/chai": "^4.1.2", 20 | "@types/mocha": "^2.2.48", 21 | "chai": "^4.1.2", 22 | "husky": "^0.14.3", 23 | "mocha": "^5.0.4", 24 | "ts-node": "^5.0.1", 25 | "typedoc": "^0.11.1", 26 | "typescript": "^2.7.2" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/nova-engine/ecs.git" 31 | }, 32 | "license": "Apache-2.0", 33 | "bugs": { 34 | "url": "https://github.com/nova-engine/ecs/issues" 35 | }, 36 | "homepage": "https://github.com/nova-engine/ecs#readme", 37 | "keywords": [ 38 | "ecs", 39 | "nova-engine" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/Component.ts: -------------------------------------------------------------------------------- 1 | interface Component {} 2 | 3 | interface ComponentClass { 4 | readonly name: string; 5 | readonly tag?: string; 6 | new (): T; 7 | } 8 | 9 | export { Component, ComponentClass }; 10 | -------------------------------------------------------------------------------- /src/Engine.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from "./Entity"; 2 | import { System } from "./System"; 3 | 4 | interface EngineEntityListener { 5 | onEntityAdded(entity: Entity): void; 6 | onEntityRemoved(entity: Entity): void; 7 | } 8 | 9 | /** 10 | * An engine is the class than combines systems and entities. 11 | * You may have one Engine in your application, but you can make as many as 12 | * you want. 13 | */ 14 | class Engine { 15 | /** Private array containing the current list of added entities. */ 16 | private _entities: Entity[] = []; 17 | /** Private list of entity listeners */ 18 | private readonly _entityListeners: EngineEntityListener[] = []; 19 | /** Private list of added systems. */ 20 | private readonly _systems: System[] = []; 21 | /** Checks if the system needs sorting of some sort */ 22 | private _systemsNeedSorting: boolean = false; 23 | /** 24 | * Computes an immutable list of entities added to the engine. 25 | */ 26 | get entities() { 27 | return Object.freeze(this._entities.slice(0)); 28 | } 29 | /** 30 | * Alerts the engine to sort systems by priority. 31 | * @param system The system than changed priority 32 | */ 33 | notifyPriorityChange(system: System) { 34 | this._systemsNeedSorting = true; 35 | } 36 | 37 | /** 38 | * Adds a listener for when entities are added or removed. 39 | * @param listener The listener waiting to add 40 | */ 41 | addEntityListener(listener: EngineEntityListener) { 42 | if (this._entityListeners.indexOf(listener) === -1) { 43 | this._entityListeners.push(listener); 44 | } 45 | return this; 46 | } 47 | 48 | /** 49 | * Removes a listener from the entity listener list. 50 | * @param listener The listener to remove 51 | */ 52 | removeEntityListener(listener: EngineEntityListener) { 53 | const index = this._entityListeners.indexOf(listener); 54 | if (index !== -1) { 55 | this._entityListeners.splice(index, 1); 56 | } 57 | return this; 58 | } 59 | 60 | /** 61 | * Add an entity to the engine. 62 | * The listeners will be notified. 63 | * @param entity The entity to add 64 | */ 65 | addEntity(entity: Entity) { 66 | if (this._entities.indexOf(entity) === -1) { 67 | this._entities.push(entity); 68 | for (let listener of this._entityListeners) { 69 | listener.onEntityAdded(entity); 70 | } 71 | } 72 | return this; 73 | } 74 | 75 | /** 76 | * Add a list of entities to the engine. 77 | * The listeners will be notified once per entity. 78 | * @param entities The list of entities to add 79 | */ 80 | addEntities(...entities: Entity[]) { 81 | for (let entity of entities) { 82 | this.addEntity(entity); 83 | } 84 | return this; 85 | } 86 | 87 | /** 88 | * Removes an entity to the engine. 89 | * The listeners will be notified. 90 | * @param entity The entity to remove 91 | */ 92 | removeEntity(entity: Entity) { 93 | const index = this._entities.indexOf(entity); 94 | if (index !== -1) { 95 | this._entities.splice(index, 1); 96 | for (let listener of this._entityListeners) { 97 | listener.onEntityRemoved(entity); 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * Removes a list of entities to the engine. 104 | * The listeners will be notified once per entity. 105 | * @param entities The list of entities to remove 106 | */ 107 | removeEntities(...entities: Entity[]) { 108 | for (let entity of entities) { 109 | this.removeEntity(entity); 110 | } 111 | return this; 112 | } 113 | 114 | /** 115 | * Adds a system to the engine. 116 | * @param system The system to add. 117 | */ 118 | addSystem(system: System) { 119 | const index = this._systems.indexOf(system); 120 | if (index === -1) { 121 | this._systems.push(system); 122 | system.onAttach(this); 123 | this._systemsNeedSorting = true; 124 | } 125 | return this; 126 | } 127 | 128 | /** 129 | * Adds a list of systems to the engine. 130 | * @param systems The list of systems to add. 131 | */ 132 | addSystems(...systems: System[]) { 133 | for (let system of systems) { 134 | this.addSystem(system); 135 | } 136 | } 137 | 138 | /** 139 | * Removes a system to the engine. 140 | * @param system The system to remove. 141 | */ 142 | removeSystem(system: System) { 143 | const index = this._systems.indexOf(system); 144 | if (index !== -1) { 145 | this._systems.splice(index, 1); 146 | system.onDetach(this); 147 | } 148 | return this; 149 | } 150 | 151 | /** 152 | * Removes a list of systems to the engine. 153 | * @param systems The list of systems to remove. 154 | */ 155 | removeSystems(...systems: System[]) { 156 | for (let system of systems) { 157 | this.removeSystem(system); 158 | } 159 | } 160 | 161 | /** 162 | * Updates all systems added to the engine. 163 | * @param delta Time elapsed (in milliseconds) since the last update. 164 | */ 165 | update(delta: number) { 166 | if (this._systemsNeedSorting) { 167 | this._systemsNeedSorting = false; 168 | this._systems.sort((a, b) => a.priority - b.priority); 169 | } 170 | for (let system of this._systems) { 171 | system.update(this, delta); 172 | } 173 | } 174 | } 175 | 176 | export { Engine, EngineEntityListener }; 177 | -------------------------------------------------------------------------------- /src/Entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import "mocha"; 3 | 4 | import { Component } from "./Component"; 5 | 6 | import { Entity } from "./Entity"; 7 | 8 | class MyComponent implements Component {} 9 | 10 | class MyBadTagComponent implements Component { 11 | static readonly tag = "MyComponent"; 12 | } 13 | 14 | describe("Entities work", function() { 15 | it("Can only set id once", function() { 16 | const entity = new Entity(); 17 | expect(() => entity.id).to.throw(); 18 | expect(() => (entity.id = "testing id")).to.not.throw(); 19 | expect(() => (entity.id = "other id")).to.throw(); 20 | expect(entity.id).to.not.be.equals("other id"); 21 | }); 22 | it("Can retrieve id when set for the first time", function() { 23 | const entity = new Entity(); 24 | expect(() => entity.id).to.throw(); 25 | expect(() => (entity.id = "testing id")).to.not.throw(); 26 | expect(() => entity.id).to.not.throw(); 27 | expect(entity.id).to.be.equals("testing id"); 28 | }); 29 | it("Can add a component.", function() { 30 | const entity = new Entity(); 31 | expect(entity.putComponent(MyComponent)).to.be.instanceof(MyComponent); 32 | expect(() => entity.hasComponent(MyComponent)).to.not.throw(); 33 | }); 34 | it("Throw error when bad class tags override component types.", function() { 35 | const entity = new Entity(); 36 | expect(entity.putComponent(MyComponent)).to.be.instanceof(MyComponent); 37 | expect(() => entity.putComponent(MyBadTagComponent)).to.throw(); 38 | }); 39 | it("Remove a component.", function() { 40 | const entity = new Entity(); 41 | expect(entity.putComponent(MyComponent)).to.be.instanceof(MyComponent); 42 | expect(() => entity.removeComponent(MyComponent)).to.not.throw(); 43 | }); 44 | it("Throw an error when a bad class tag tries to remove a component.", function() { 45 | const entity = new Entity(); 46 | expect(entity.putComponent(MyComponent)).to.be.instanceof(MyComponent); 47 | expect(() => entity.removeComponent(MyBadTagComponent)).to.throw(); 48 | }); 49 | it("Throw an error when getting a non added component", function() { 50 | const entity = new Entity(); 51 | expect(() => entity.getComponent(MyComponent)).to.throw(); 52 | }); 53 | it("Throw an error when removing a non added component", function() { 54 | const entity = new Entity(); 55 | expect(() => entity.removeComponent(MyComponent)).to.throw(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/Entity.ts: -------------------------------------------------------------------------------- 1 | import { ComponentClass, Component } from "./Component"; 2 | 3 | type EntityChangeListener = (entity: Entity) => any; 4 | 5 | /** 6 | * An Entity is every object you may have on your system. 7 | * A character, a weapon, an skill, a map. 8 | * Everything is an Entity. 9 | * Entities are bag of Components, and Components describe how the data exists on 10 | * the entities. 11 | * Entities have an id field that can only be set once and 12 | * will throw when you try to get one when no id is set. 13 | * This set can be used to persist the entity on a database. 14 | */ 15 | class Entity { 16 | private _id: string | number | null = null; 17 | private readonly _components: { [tag: string]: Component } = {}; 18 | private readonly _listeners: EntityChangeListener[] = []; 19 | private readonly _componentClasses: { 20 | [tag: string]: ComponentClass; 21 | } = {}; 22 | 23 | /** 24 | * Gets the id of the entity. 25 | * @throws when the id is null. 26 | */ 27 | get id(): string | number { 28 | if (this._id === null) { 29 | throw new Error("Cannot retrieve an ID when is null."); 30 | } 31 | return this._id; 32 | } 33 | 34 | /** 35 | * Sets the id of the entity to a new value. 36 | * @throws when the new value is null or undefined or the id is already set. 37 | */ 38 | set id(value: string | number) { 39 | if (value === null || value === undefined) { 40 | throw new Error(`Must set a non null value when setting an entity id.`); 41 | } 42 | if (this._id !== null) { 43 | throw new Error(`Entity id is already set as "${this._id}".`); 44 | } 45 | this._id = value; 46 | } 47 | 48 | /** 49 | * Checks if the entity is newly created. 50 | * An entity is considered new when the id is null. 51 | */ 52 | isNew() { 53 | return this._id === null; 54 | } 55 | 56 | /** 57 | * Generates a read only list of components of the entity. 58 | * @returns a list of all components of the entity. 59 | */ 60 | listComponents() { 61 | return Object.keys(this._components).map(i => this._components[i]); 62 | } 63 | 64 | /** 65 | * Generates a read only list of components of the entity 66 | * with it's corresponding types. 67 | * @returns a list of all components with types of the entity. 68 | */ 69 | 70 | listComponentsWithTypes() { 71 | return Object.keys(this._components).map(i => ({ 72 | component: this._components[i], 73 | type: this._componentClasses[i] 74 | })); 75 | } 76 | 77 | /** 78 | * Generates a read only list of components of the entity with it's corresponding tags. 79 | * @returns a list of all components with tags of the entity. 80 | */ 81 | listComponentsWithTags() { 82 | return Object.keys(this._components).map(tag => 83 | Object.freeze({ 84 | tag, 85 | component: this._components[tag] 86 | }) 87 | ); 88 | } 89 | 90 | /** 91 | * Checks if the entity has a component of the specified class. 92 | * @throws if the class than exists on the entity with that tag is different than the asked one. 93 | * @param componentClass The class of the component. 94 | */ 95 | hasComponent(componentClass: ComponentClass) { 96 | const tag = componentClass.tag || componentClass.name; 97 | const component = this._components[tag]; 98 | if (!component) return false; 99 | if (!this.cast(component, componentClass)) { 100 | throw new Error( 101 | `There are multiple classes with the same tag or name "${tag}".\nAdd a different property "tag" to one of them.` 102 | ); 103 | } 104 | return true; 105 | } 106 | 107 | /** 108 | * Returns the component of the specified class. 109 | * @throws if the class than exists on the entity with that tag is different than the asked one. 110 | * @throws if the component is not on the entity. 111 | * @param componentClass The class of the component. 112 | */ 113 | getComponent(componentClass: ComponentClass): T { 114 | const tag = componentClass.tag || componentClass.name; 115 | const component = this._components[tag]; 116 | if (!component) { 117 | throw new Error(`Cannot get component "${tag}" from entity.`); 118 | } 119 | if (!this.cast(component, componentClass)) { 120 | throw new Error( 121 | `There are multiple classes with the same tag or name "${tag}".\nAdd a different property "tag" to one of them.` 122 | ); 123 | } 124 | return component; 125 | } 126 | 127 | /** 128 | * Creates a component of the specified class and adds it to the entity. 129 | * @throws if the class than exists on the entity with that tag is different than the asked one. 130 | * @param componentClass The class of the component. 131 | * @returns The newly created component. 132 | */ 133 | putComponent(componentClass: ComponentClass): T { 134 | const tag = componentClass.tag || componentClass.name; 135 | const component = this._components[tag]; 136 | if (component) { 137 | if (!this.cast(component, componentClass)) { 138 | throw new Error( 139 | `There are multiple classes with the same tag or name "${tag}".\nAdd a different property "tag" to one of them.` 140 | ); 141 | } 142 | delete this._components[tag]; 143 | delete this._componentClasses[tag]; 144 | } 145 | const newComponent = new componentClass(); 146 | this._components[tag] = newComponent; 147 | this._componentClasses[tag] = componentClass; 148 | for (let listener of this._listeners) { 149 | listener(this); 150 | } 151 | return newComponent; 152 | } 153 | 154 | /** 155 | * Removes a component from the entity. 156 | * @throws If the component of that class don't exists on the entity. 157 | * @throws if the class than exists on the entity with that tag is different than the asked one. 158 | * @param componentClass The class of the component. 159 | */ 160 | removeComponent(componentClass: ComponentClass) { 161 | const tag = componentClass.tag || componentClass.name; 162 | const component = this._components[tag]; 163 | if (!component) { 164 | throw new Error(`Component of tag "${tag}".\nDoes not exists.`); 165 | } 166 | if (!this.cast(component, componentClass)) { 167 | throw new Error( 168 | `There are multiple classes with the same tag or name "${tag}".\nAdd a different property "tag" to one of them.` 169 | ); 170 | } 171 | delete this._components[tag]; 172 | for (let listener of this._listeners) { 173 | listener(this); 174 | } 175 | } 176 | 177 | /** 178 | * Checks if the component is an instance of the class 179 | * @param component The component to check 180 | * @param componentClass The class to cast into 181 | */ 182 | cast( 183 | component: Component | undefined | null, 184 | componentClass: ComponentClass 185 | ): component is T { 186 | return !!(component && component instanceof componentClass); 187 | } 188 | 189 | /** 190 | * Adds a listener to the entity when components are added or removed. 191 | * @param listener The listener to add 192 | */ 193 | addListener(listener: EntityChangeListener) { 194 | const index = this._listeners.indexOf(listener); 195 | if (index === -1) { 196 | this._listeners.push(listener); 197 | } 198 | return this; 199 | } 200 | 201 | /** 202 | * Removes a listener from the entity. 203 | * @param listener The listener to remove. 204 | */ 205 | removeListener(listener: EntityChangeListener) { 206 | const index = this._listeners.indexOf(listener); 207 | if (index !== -1) { 208 | this._listeners.splice(index, 1); 209 | } 210 | return this; 211 | } 212 | } 213 | 214 | export { Entity, EntityChangeListener }; 215 | -------------------------------------------------------------------------------- /src/Family.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import "mocha"; 3 | 4 | import { FamilyBuilder } from "./Family"; 5 | import { Engine } from "./Engine"; 6 | import { Component } from "./Component"; 7 | import { Entity } from "./Entity"; 8 | 9 | class MyComponent implements Component {} 10 | 11 | class MyOtherComponent implements Component {} 12 | 13 | describe("Families work", function() { 14 | it("Empty family returns all entities", function() { 15 | const engine = new Engine(); 16 | engine.addEntities(new Entity(), new Entity()); 17 | const builder = new FamilyBuilder(engine); 18 | const family = builder.build(); 19 | expect(family.entities.length).to.be.equals(engine.entities.length); 20 | }); 21 | it("Families must always have an Engine attached", function() { 22 | const builder = new FamilyBuilder(); 23 | expect(() => builder.build()).to.throw(); 24 | }); 25 | it("Family includes the corresponding entity for inclusion", function() { 26 | const engine = new Engine(); 27 | const entity = new Entity(); 28 | entity.putComponent(MyComponent); 29 | entity.putComponent(MyOtherComponent); 30 | engine.addEntities(entity, new Entity()); 31 | const builder = new FamilyBuilder(engine); 32 | builder.include(MyComponent, MyOtherComponent); 33 | const family = builder.build(); 34 | expect(family.entities.indexOf(entity)).to.not.be.equals(-1); 35 | expect(family.entities.length).to.not.be.equals(engine.entities.length); 36 | expect(family.entities.length).to.not.be.equals(0); 37 | }); 38 | it("Family includes the corresponding entity for exclusion", function() { 39 | const engine = new Engine(); 40 | const entity = new Entity(); 41 | entity.putComponent(MyComponent); 42 | engine.addEntities(entity, new Entity()); 43 | const builder = new FamilyBuilder(engine); 44 | builder.exclude(MyComponent); 45 | const family = builder.build(); 46 | expect(family.entities.indexOf(entity)).to.be.equals(-1); 47 | expect(family.entities.length).to.not.be.equals(engine.entities.length); 48 | expect(family.entities.length).to.not.be.equals(0); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/Family.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentClass } from "./Component"; 2 | import { Engine, EngineEntityListener } from "./Engine"; 3 | import { Entity } from "./Entity"; 4 | 5 | /** 6 | * A family is a criteria to separate your entities. 7 | * You can have families on wich entities must have a component, 8 | * entities cannot have some components or a mix of both. 9 | * Families also cache the entities of the engine by default, 10 | * so you won't have to worry about filtering entities every time. 11 | */ 12 | interface Family { 13 | /** 14 | * Computes a list of entities on the family. 15 | * The list may or may not be cached, depending of implementation. 16 | */ 17 | readonly entities: ReadonlyArray; 18 | includesEntity(entity: Entity): boolean; 19 | } 20 | 21 | /** 22 | * An abstract family is the base implementation of a family interface. 23 | * This class is private to this module. 24 | * @private 25 | */ 26 | abstract class AbstractFamily implements Family { 27 | private readonly _engine: Engine; 28 | private readonly _include: ReadonlyArray>; 29 | private readonly _exclude: ReadonlyArray>; 30 | 31 | constructor( 32 | engine: Engine, 33 | include: ComponentClass[], 34 | exclude: ComponentClass[] 35 | ) { 36 | this._engine = engine; 37 | this._include = Object.freeze(include.slice(0)); 38 | this._exclude = Object.freeze(exclude.slice(0)); 39 | } 40 | 41 | get engine() { 42 | return this._engine; 43 | } 44 | 45 | abstract readonly entities: ReadonlyArray; 46 | 47 | includesEntity = (entity: Entity) => { 48 | for (let include of this._include) { 49 | if (!entity.hasComponent(include)) { 50 | return false; 51 | } 52 | } 53 | for (let exclude of this._exclude) { 54 | if (entity.hasComponent(exclude)) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | }; 60 | } 61 | 62 | /** 63 | * A CachedFamily is a family than caches it's results and alters it only 64 | * when an entity changes. 65 | * 66 | */ 67 | class CachedFamily extends AbstractFamily { 68 | private _needEntityRefresh: boolean; 69 | private _entities: Entity[]; 70 | 71 | constructor( 72 | engine: Engine, 73 | include: ComponentClass[], 74 | exclude: ComponentClass[] 75 | ) { 76 | super(engine, include, exclude); 77 | const allEntities = this.engine.entities; 78 | this._entities = allEntities.filter(this.includesEntity); 79 | this.engine.addEntityListener(this); 80 | for (let entity of allEntities) { 81 | entity.addListener(this.onEntityAdded); 82 | } 83 | this._needEntityRefresh = false; 84 | } 85 | 86 | get entities() { 87 | if (this._needEntityRefresh) { 88 | this._needEntityRefresh = false; 89 | this._entities = this._entities.filter(this.includesEntity); 90 | } 91 | return Object.freeze(this._entities.slice(0)); 92 | } 93 | 94 | onEntityAdded(entity: Entity) { 95 | const index = this._entities.indexOf(entity); 96 | if (index === -1) { 97 | this._entities.push(entity); 98 | this._needEntityRefresh = true; 99 | entity.addListener(this.onEntityChanged); 100 | } 101 | } 102 | 103 | onEntityRemoved(entity: Entity) { 104 | const index = this._entities.indexOf(entity); 105 | if (index !== -1) { 106 | const entity = this._entities[index]; 107 | this._entities.splice(index, 1); 108 | entity.removeListener(this.onEntityChanged); 109 | } 110 | } 111 | 112 | onEntityChanged = (entity: Entity) => { 113 | const index = this._entities.indexOf(entity); 114 | if (index === -1) { 115 | this._entities.push(entity); 116 | entity.addListener(this.onEntityChanged); 117 | } 118 | this._needEntityRefresh = true; 119 | }; 120 | } 121 | 122 | /** 123 | * A NonCacheFamily always computes the members of it. 124 | * If you find than the performance from cached families is not decent. 125 | * You can use this instead. 126 | * @private 127 | */ 128 | class NonCachedFamily extends AbstractFamily { 129 | get entities() { 130 | return this.engine.entities.filter(this.includesEntity); 131 | } 132 | } 133 | 134 | /** 135 | * Utility class to build Families. 136 | * It's the only way to create the implementations of CachedFamily and NonCachedFamily. 137 | */ 138 | class FamilyBuilder { 139 | private _engine: Engine | null; 140 | private _cached: boolean; 141 | private readonly _include: ComponentClass[]; 142 | private readonly _exclude: ComponentClass[]; 143 | 144 | constructor(engine?: Engine) { 145 | this._engine = engine || null; 146 | this._include = []; 147 | this._exclude = []; 148 | this._cached = true; 149 | } 150 | 151 | /** 152 | * Indicates than entities than are members of this class MUST 153 | * HAVE this components. 154 | * @param classes A list of component classes. 155 | */ 156 | include(...classes: ComponentClass[]) { 157 | this._include.push(...classes); 158 | return this; 159 | } 160 | /** 161 | * Indicates than entities than are members of this class MUST NOT 162 | * HAVE this components. 163 | * @param classes A list of component classes. 164 | */ 165 | exclude(...classes: ComponentClass[]) { 166 | this._exclude.push(...classes); 167 | return this; 168 | } 169 | 170 | /** 171 | * Changes the engine of the builder. 172 | * Useful to create multiple instances of the same family for different 173 | * engines. 174 | * @param engine 175 | */ 176 | changeEngine(engine: Engine) { 177 | this._engine = engine; 178 | return this; 179 | } 180 | 181 | /** 182 | * Changes if the family should use cached values or not. 183 | * @param cached If the family must use or not a cache. 184 | */ 185 | setCached(cached: boolean) { 186 | this._cached = cached; 187 | } 188 | 189 | /** 190 | * Builds the family, using the information provided. 191 | * @returns a new family to retrieve the entities. 192 | */ 193 | build(): Family { 194 | if (!this._engine) { 195 | throw new Error("Family should always belong to an engine."); 196 | } 197 | if (!this._cached) { 198 | return new NonCachedFamily(this._engine, this._include, this._exclude); 199 | } 200 | return new CachedFamily(this._engine, this._include, this._exclude); 201 | } 202 | } 203 | 204 | export { Family, FamilyBuilder }; 205 | -------------------------------------------------------------------------------- /src/System.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import "mocha"; 3 | 4 | import { System } from "./System"; 5 | import { Engine } from "./Engine"; 6 | import { Family, FamilyBuilder } from "./Family"; 7 | 8 | class MySystem extends System { 9 | public family: Family | null = null; 10 | 11 | onAttach(engine: Engine) { 12 | super.onAttach(engine); 13 | this.family = new FamilyBuilder(engine).build(); 14 | } 15 | 16 | onDetach(engine: Engine) { 17 | super.onDetach(engine); 18 | this.family = null; 19 | } 20 | 21 | update(engine: Engine, delta: number) {} 22 | } 23 | 24 | describe("Systems works", function() { 25 | it("Can be extended", function() { 26 | expect(new MySystem()).to.be.instanceof(System); 27 | expect(new MySystem()).to.be.instanceof(MySystem); 28 | }); 29 | it("Attached systems should call the onAttach method", () => { 30 | const engine = new Engine(); 31 | const system = new MySystem(); 32 | engine.addSystem(system); 33 | expect(system.family).to.not.be.equals(null); 34 | }); 35 | it("Detached systems should call the onDetach method", () => { 36 | const engine = new Engine(); 37 | const system = new MySystem(); 38 | engine.addSystem(system); 39 | engine.removeSystem(system); 40 | expect(system.family).to.be.equals(null); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/System.ts: -------------------------------------------------------------------------------- 1 | import { Engine } from "./Engine"; 2 | 3 | abstract class System { 4 | private _priority: number; 5 | private readonly _engines: Engine[]; 6 | 7 | constructor() { 8 | this._priority = 0; 9 | this._engines = []; 10 | } 11 | 12 | get priority() { 13 | return this._priority; 14 | } 15 | 16 | get engines() { 17 | return Object.freeze(this._engines.slice(0)); 18 | } 19 | 20 | set priority(value: number) { 21 | this._priority = value; 22 | for (let engine of this._engines) { 23 | engine.notifyPriorityChange(this); 24 | } 25 | } 26 | 27 | onAttach(engine: Engine) { 28 | const index = this._engines.indexOf(engine); 29 | if (index === -1) { 30 | this._engines.push(engine); 31 | } 32 | } 33 | 34 | onDetach(engine: Engine) { 35 | const index = this._engines.indexOf(engine); 36 | if (index !== -1) { 37 | this._engines.splice(index, 1); 38 | } 39 | } 40 | 41 | abstract update(engine: Engine, delta: number): void; 42 | } 43 | 44 | export { System }; 45 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import "mocha"; 3 | 4 | import * as lib from "./index"; 5 | 6 | import { Engine } from "./Engine"; 7 | import { Entity } from "./Entity"; 8 | import { FamilyBuilder } from "./Family"; 9 | import { System } from "./System"; 10 | 11 | describe("Modules are exported", function() { 12 | it("Engine is exported", function() { 13 | expect(lib.Engine).to.equal(Engine); 14 | expect(lib.Engine).to.not.be.null; 15 | expect(lib.Engine).to.not.be.undefined; 16 | }); 17 | it("Entity is exported", function() { 18 | expect(lib.Entity).to.equal(Entity); 19 | expect(lib.Entity).to.not.be.null; 20 | expect(lib.Entity).to.not.be.undefined; 21 | }); 22 | it("FamilyBuilder is exported", function() { 23 | expect(lib.FamilyBuilder).to.equal(FamilyBuilder); 24 | expect(lib.FamilyBuilder).to.not.be.null; 25 | expect(lib.FamilyBuilder).to.not.be.undefined; 26 | }); 27 | it("System is exported", function() { 28 | expect(lib.System).to.equal(System); 29 | expect(lib.System).to.not.be.null; 30 | expect(lib.System).to.not.be.undefined; 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Component"; 2 | export * from "./Engine"; 3 | export * from "./Entity"; 4 | export * from "./Family"; 5 | export * from "./System"; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "CommonJS", 5 | "noImplicitAny": true, 6 | "strictNullChecks": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "outDir": "lib", 10 | "sourceMap": true, 11 | "declaration": true, 12 | "lib": ["es5", "es2015.promise", "dom"] 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "**/*.spec.ts"] 16 | } 17 | --------------------------------------------------------------------------------