1 module prova.entities.entity;
2 
3 import prova;
4 
5 ///
6 class Entity
7 {
8   ///
9   Vector3 position;
10   ///
11   Quaternion rotation;
12   ///
13   Vector3 scale = Vector3(1, 1, 1);
14   ///
15   Vector3 velocity;
16   /// Velocity is multiplied by (1 - friction) every tick
17   float friction = 0;
18   package(prova) bool isSetup = false;
19   package(prova) Scene _scene;
20   package(prova) Entity[] children;
21   package(prova) Collider2D[] colliders2d;
22   package(prova) AudioSource[] audioSources;
23   private Renderable[] renderables;
24   private Entity _parent;
25   private int[] tags;
26 
27   ///
28   final @property Scene scene()
29   {
30     assert(_scene, "Entity should be attached to a scene");
31 
32     return _scene;
33   }
34 
35   /// Shortcut for scene.game
36   final @property Game game()
37   {
38     return scene.game;
39   }
40 
41   ///
42   final @property Entity parent()
43   {
44     return _parent;
45   }
46 
47   package(prova) final @property void parent(Entity parent)
48   {
49     _parent = parent;
50   }
51 
52   /// Shortcut for scene.game.input
53   final @property Input input()
54   {
55     return scene.game.input;
56   }
57 
58   ///
59   final void addTag(int tag)
60   {
61     tags ~= tag;
62   }
63 
64   ///
65   final void removeTag(int tag)
66   {
67     tags = tags.removeElement(tag);
68   }
69 
70   ///
71   final bool hasTag(int tag)
72   {
73     import std.algorithm : canFind;
74 
75     return tags.canFind(tag);
76   }
77 
78   /// Makes the passed entity a child of this entity
79   final void attach(Entity entity)
80   {
81     assert(!entity.parent, "Entity should not already be attached to an entity");
82 
83     // marked as parent before Scene.addEntity, scene needs to find
84     // entities without parents for it's root entities list
85     entity.parent = this;
86     children ~= entity;
87 
88     if(_scene)
89       _scene.addEntity(entity);
90   }
91 
92   /// Detach a child entity or the parent entity
93   final void detach(Entity entity)
94   {
95     if(parent == entity) {
96       parent.detach(this);
97       return;
98     }
99 
100     assert(entity.parent == this, "Entity should be attached to this entity");
101 
102     if(_scene && entity._scene)
103       _scene.removeEntity(entity);
104 
105     // abandon child after Scene.removeEntity, so that the scene
106     // knows that the entity removed is not a root entity
107     entity.parent = null;
108     children = children.removeElement(entity);
109   }
110 
111   ///
112   final void attach(Collider2D collider)
113   {
114     assert(!collider.entity, "Collider should not already be attached to an entity");
115 
116     if(_scene)
117       _scene.collider2DMap.add(collider);
118 
119     colliders2d ~= collider;
120     collider.entity = this;
121   }
122 
123   ///
124   final void detach(Collider2D collider)
125   {
126     assert(collider.entity == this, "Collider should be attached to this entity");
127 
128     if(_scene)
129       _scene.collider2DMap.remove(collider);
130 
131     colliders2d = colliders2d.removeElement(collider);
132     collider.entity = null;
133   }
134 
135   ///
136   final void attach(AudioSource source)
137   {
138     assert(!source.entity, "AudioSource should not already be attached to an entity");
139 
140     assert(source.channels == 1, "Source must use a mono format");
141 
142     if(_scene)
143       _scene.audioSources ~= source;
144 
145     audioSources ~= source;
146     source.entity = this;
147   }
148 
149   ///
150   final void detach(AudioSource source)
151   {
152     assert(source.entity == this, "AudioSource should be attached to this entity");
153 
154     if(_scene)
155       _scene.audioSources = _scene.audioSources.removeElement(source);
156 
157     audioSources = audioSources.removeElement(source);
158     source.entity = null;
159   }
160 
161   ///
162   final void attach(Renderable renderable)
163   {
164     renderables ~= renderable;
165   }
166 
167   ///
168   final void detach(Renderable renderable)
169   {
170     import std.algorithm : canFind;
171 
172     assert(renderables.canFind(renderable), "Renderable should be attached to this entity");
173 
174     renderables = renderables.removeElement(renderable);
175   }
176 
177   ///
178   final void lookAt(Entity entity)
179   {
180     lookAt(entity.getWorldPosition());
181   }
182 
183   ///
184   final void lookAt(Vector3 position)
185   {
186     Vector3 difference = position - getWorldPosition();
187     rotation = difference.getDirection();
188   }
189 
190   ///
191   final Matrix getLocalTransformMatrix()
192   {
193     Matrix transform = Matrix.identity;
194     transform = transform.scale(scale);
195     transform = transform.rotate(rotation);
196     transform = transform.translate(position);
197 
198     return transform;
199   }
200 
201   ///
202   final Matrix getWorldTransformMatrix()
203   {
204     Matrix transform = getLocalTransformMatrix();
205 
206     if(parent)
207       transform = parent.getWorldTransformMatrix() * transform;
208 
209     return transform;
210   }
211 
212   ///
213   final Vector3 getWorldPosition()
214   {
215     Matrix transform = getWorldTransformMatrix();
216 
217     return transform * Vector3();
218   }
219 
220   ///
221   final Quaternion getWorldRotation()
222   {
223     Quaternion worldRotation;
224 
225     if(parent)
226       worldRotation = parent.getWorldRotation();
227 
228     return rotation * worldRotation;
229   }
230 
231   ///
232   final Vector3 getWorldScale()
233   {
234     Vector3 worldScale = Vector3(1, 1, 1);
235 
236     if(parent)
237       worldScale = parent.getWorldScale();
238 
239     return scale * worldScale;
240   }
241 
242   ///
243   final Vector2 getScreenPosition()
244   {
245     return scene.camera.getScreenPosition(position);
246   }
247 
248   /// Called every draw tick (skipped if update loop is behind)
249   void draw(RenderTarget renderTarget, Matrix transform)
250   {
251     import std.algorithm : sort;
252 
253     const auto sortDelegate = scene.camera.getSortDelegate();
254     auto sortedChildren = children.sort!(sortDelegate);
255 
256     foreach(Entity child; sortedChildren)
257       child.draw(renderTarget, transform * child.getLocalTransformMatrix());
258 
259     foreach(Renderable renderable; renderables)
260       renderable.draw(renderTarget, transform);
261   }
262 
263   /// Called when first attached to a scene
264   void setup(){}
265   /// Called when attached to a scene
266   void start(){}
267   /// Called every update tick
268   void update(){}
269 
270   ///
271   void onCollisionEnter2D(Collider2D collider, Collider2D other){}
272   ///
273   void onCollision2D(Collider2D collider, Collider2D other){}
274   ///
275   void onCollisionExit2D(Collider2D collider, Collider2D other){}
276 }