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