1 module prova.core.scene; 2 3 import derelict.openal.al, 4 prova, 5 std.algorithm, 6 std.math; 7 8 /// 9 class Scene 10 { 11 /// 12 public Camera camera; 13 package LinkedList!(AudioSource) audioSources; 14 package SpacialMap2D collider2DMap; 15 package Game _game; 16 package bool isSetup; 17 private LinkedList!(Entity) entities; 18 19 /// 20 this() 21 { 22 camera = new Camera(); 23 collider2DMap = new SpacialMap2D(); 24 entities = new LinkedList!(Entity); 25 audioSources = new LinkedList!(AudioSource); 26 } 27 28 /// 29 final @property Game game() 30 { 31 if(!_game) 32 throw new Exception("Scene is not attached to a game"); 33 return _game; 34 } 35 36 /// 37 final @property Input input() 38 { 39 return game.input; 40 } 41 42 /// 43 final Entity[] getEntities() 44 { 45 Entity[] clonedArray; 46 clonedArray.length = entities.length; 47 int i = 0; 48 49 foreach(Node!Entity node; entities) 50 clonedArray[i++] = node.value; 51 52 return clonedArray; 53 } 54 55 /// 56 final void addEntity(Entity entity) 57 { 58 if(entity._scene == this) 59 throw new Exception("Entity was already added to the scene"); 60 61 entities.insertBack(entity); 62 entity._scene = this; 63 64 foreach(Entity child; entity.children) 65 if(!entities.contains(entity)) 66 addEntity(entity); 67 68 foreach(AudioSource source; entity.audioSources) 69 audioSources.insertBack(source); 70 71 collider2DMap.add(entity.colliders2d); 72 73 if(!entity.isSetup) { 74 entity.setup(); 75 entity.isSetup = true; 76 } 77 78 entity.start(); 79 } 80 81 /// 82 final void removeEntity(Entity entity) 83 { 84 if(entity._scene != this) 85 throw new Exception("Entity was not added to the scene"); 86 87 disassociateEntity(entity); 88 89 // detach from parent to not be drawn 90 if(entity.parent) 91 entity.parent.detach(entity); 92 } 93 94 package void disassociateEntity(Entity entity) 95 { 96 foreach(Entity child; entity.children) 97 disassociateEntity(entity); 98 99 foreach(AudioSource source; entity.audioSources) 100 audioSources.remove(source); 101 102 collider2DMap.remove(entity.colliders2d); 103 104 entities.remove(entity); 105 entity._scene = null; 106 } 107 108 /// Finds the closest entity to this entity 109 Entity findClosestEntity(Entity entity) 110 { 111 return findClosestEntity(entity, 0, false); 112 } 113 114 /// Finds the closest entity with the matching tag 115 Entity findClosestEntity(Entity entity, int tag) 116 { 117 return findClosestEntity(entity, tag, true); 118 } 119 120 private Entity findClosestEntity(Entity entity, int tag, bool needsTag) 121 { 122 float closestDistance = -1; 123 Entity closestEntity = null; 124 125 foreach(Entity other; entities) 126 { 127 // make sure we aren't matching with self 128 if(other == entity) 129 continue; 130 131 // make sure tag matches 132 if(needsTag && !other.hasTag(tag)) 133 continue; 134 135 const float distance = other.position.distanceTo(entity.position); 136 137 if(distance < closestDistance || closestDistance == -1) { 138 closestDistance = distance; 139 closestEntity = other; 140 } 141 } 142 143 return closestEntity; 144 } 145 146 /// Called when attached to a scene for the first time 147 void setup() { } 148 /// Called when attached to a scene 149 void start() { } 150 151 /// Call super.update() to update entities if overridden 152 void update() 153 { 154 updateEntities(); 155 updateCollisions(); 156 } 157 158 /// Called by Scene.update() 159 void updateEntities() 160 { 161 foreach(Entity entity; entities) { 162 entity.update(); 163 entity.position += entity.velocity; 164 entity.velocity *= 1 - entity.friction; 165 } 166 } 167 168 /// Called by Scene.update() 169 void updateCollisions() 170 { 171 collider2DMap.mapColliders(); 172 collider2DMap.markCollisions(); 173 collider2DMap.resolveCollisions(); 174 } 175 176 package void updateAudio() 177 { 178 Vector3 position = camera.getWorldPosition() / Audio.scale; 179 Quaternion rotation = camera.getWorldRotation(); 180 181 Vector3[] orientation = [ 182 rotation * Vector3(0, 0, -1), // forward 183 rotation * Vector3(0, 1, 0) // up 184 ]; 185 186 alListener3f(AL_POSITION, position.x, position.y, position.z); 187 alListenerfv(AL_ORIENTATION, cast(float*) orientation.ptr); 188 189 foreach(AudioSource source; audioSources) 190 source.update(); 191 } 192 193 /** 194 * All draw operations performed here will be affected by the camera 195 * 196 * Call super.draw(renderTarget) to render entities if overridden 197 */ 198 void draw(RenderTarget renderTarget) 199 { 200 Entity[][float] distanceMappedEntities; 201 202 foreach(Entity entity; entities) 203 { 204 // skip entities with parents 205 // the parent entity will handle rendering 206 if(entity.parent) 207 continue; 208 209 float distance; 210 211 if(camera.sortingMethod == SortingMethod.Distance) 212 distance = entity.position.distanceTo(camera.position); 213 else 214 distance = camera.position.z - entity.position.z; 215 216 distanceMappedEntities[distance] ~= entity; 217 } 218 219 foreach_reverse(float key; sort(distanceMappedEntities.keys)) 220 foreach(Entity entity; distanceMappedEntities[key]) 221 entity.draw(renderTarget, entity.getLocalTransformMatrix()); 222 } 223 224 /** 225 * All draw operations here will be affected by a static orthographic perspective 226 * 227 * Origin is moved to the bottom left of the window 228 */ 229 void drawStatic(RenderTarget renderTarget) { } 230 }