1 module re.ng.scene; 2 3 import re; 4 import std.string; 5 import re.ecs; 6 import re.gfx; 7 import re.math; 8 import re.ng.manager; 9 import std.typecons; 10 import std.range; 11 static import raylib; 12 13 public { 14 import re.time; 15 import re.ng.scene2d; 16 import re.ng.scene3d; 17 } 18 19 /// represents a collection of entities that draw to a texture 20 abstract class Scene { 21 /// the cleared background color 22 public raylib.Color clear_color = Colors.WHITE; 23 /// the entity manager 24 public EntityManager ecs; 25 /// the render target 26 public RenderTarget render_target; 27 private Vector2 _resolution; 28 /// the mode of compositing 29 public CompositeMode composite_mode; 30 /// postprocessors effects 31 public PostProcessor[] postprocessors; 32 /// updatable managers 33 public Manager[] managers; 34 35 /// the mode for compositing a scene onto the display buffer 36 public struct CompositeMode { 37 /// the texture render tint color 38 raylib.Color color = raylib.Colors.WHITE; 39 } 40 41 /// creates a new scene 42 this() { 43 } 44 45 /// gets the render resolution. initialized to Core.default_resolution 46 @property Vector2 resolution() { 47 return _resolution; 48 } 49 50 /// sets the render resolution and updates the render target 51 @property Vector2 resolution(Vector2 value) { 52 _resolution = value; 53 update_render_target(); 54 return value; 55 } 56 57 /// called at the start of the scene 58 protected void on_start() { 59 60 } 61 62 /// called right before cleanup 63 protected void unload() { 64 65 } 66 67 /// called internally to update ecs. can be overridden, but super.update() must be called. 68 public void update() { 69 // update ecs 70 ecs.update(); 71 72 // update managers 73 foreach (manager; managers) { 74 manager.update(); 75 } 76 77 // update components 78 foreach (component; ecs.storage.updatable_components) { 79 auto updatable = cast(Updatable) component; 80 updatable.update(); 81 } 82 } 83 84 /// called internally to render ecs 85 public void render() { 86 raylib.BeginTextureMode(render_target); 87 raylib.ClearBackground(clear_color); 88 89 render_scene(); 90 91 raylib.EndTextureMode(); 92 } 93 94 /// run postprocessors 95 public void post_render() { 96 import std.algorithm : filter; 97 import std.array : array; 98 99 auto pipeline = postprocessors.filter!(x => x.enabled).array; 100 // skip if no postprocessors 101 if (pipeline.length == 0) 102 return; 103 104 pipeline[0].process(render_target); 105 auto last_buf = pipeline[0].buffer; 106 for (auto i = 1; i < pipeline.length; i++) { 107 auto postprocessor = pipeline[i]; 108 postprocessor.process(last_buf); 109 last_buf = postprocessor.buffer; 110 } 111 // draw the last buf in the chain to the main texture 112 RenderExt.draw_render_target_from(last_buf, render_target); 113 } 114 115 protected abstract void render_scene(); 116 117 /// may optionally be used to render global things from a scene 118 protected void render_hook() { 119 } 120 121 private void update_render_target() { 122 if (Core.headless) 123 return; 124 // free any old render target 125 if (render_target == raylib.RenderTexture2D.init) { 126 raylib.UnloadRenderTexture(render_target); 127 } 128 // create render target 129 // TODO: use scene resolution instead of window resolution 130 render_target = raylib.LoadRenderTexture(cast(int) resolution.x, cast(int) resolution.y); 131 // apply texture filter 132 raylib.SetTextureFilter(render_target.texture, Core.default_filter_mode); 133 } 134 135 /// called internally on scene creation 136 public void begin() { 137 setup(); 138 139 on_start(); 140 } 141 142 /// setup that hapostprocessorsens after begin, but before the child scene starts 143 protected void setup() { 144 // set up ecs 145 ecs = new EntityManager; 146 147 resolution = Core.default_resolution; 148 } 149 150 /// called internally on scene destruction 151 public void end() { 152 unload(); 153 154 ecs.destroy(); 155 ecs = null; 156 157 foreach (postprocessor; postprocessors) { 158 postprocessor.destroy(); 159 } 160 postprocessors = []; 161 162 foreach (manager; managers) { 163 manager.destroy(); 164 } 165 166 if (!Core.headless) { 167 // free render target 168 raylib.UnloadRenderTexture(render_target); 169 } 170 } 171 172 public Nullable!T get_manager(T)() { 173 import std.algorithm.searching : find; 174 175 // find a manager matching the type 176 auto matches = managers.find!(x => (cast(T) x) !is null); 177 if (matches.length > 0) { 178 return Nullable!T(cast(T) matches.front); 179 } 180 return Nullable!T.init; 181 } 182 183 public T add_manager(T)(T manager) { 184 managers ~= manager; 185 manager.scene = this; 186 return manager; 187 } 188 189 // - ecs 190 191 /// create an entity given a name 192 public Entity create_entity(string name) { 193 auto nt = ecs.create_entity(); 194 nt.name = name; 195 nt.scene = this; 196 return nt; 197 } 198 199 /// create an entity given a name and a 2d position 200 public Entity create_entity(string name, Vector2 pos = Vector2(0, 0)) { 201 auto nt = create_entity(name); 202 nt.position2 = pos; 203 return nt; 204 } 205 206 /// create an entity given a name and a 3d position 207 public Entity create_entity(string name, Vector3 pos = Vector3(0, 0, 0)) { 208 auto nt = create_entity(name); 209 nt.position = pos; 210 return nt; 211 } 212 213 public Entity get_entity(string name) { 214 return ecs.get_entity(name); 215 } 216 } 217 218 @("scene-lifecycle") 219 unittest { 220 class TestScene : Scene2D { 221 override void on_start() { 222 auto apple = create_entity("apple"); 223 assert(get_entity("apple") == apple, "could not get entity by name"); 224 } 225 } 226 227 Core.headless = true; 228 auto scene = new TestScene(); 229 scene.begin(); 230 scene.update(); 231 scene.end(); 232 } 233 234 @("scene-load") 235 unittest { 236 class TestScene : Scene2D { 237 } 238 239 Core.headless = true; 240 241 auto scene = new TestScene(); 242 Core.load_scenes([scene]); 243 assert(Core.get_scene!TestScene == scene); 244 } 245 246 /// create a test game, with a test scene, and update it 247 @("scene-full") 248 unittest { 249 import re.util.test : TestGame; 250 251 static class TestScene : Scene2D { 252 class Plant : Component, Updatable { 253 public int height = 0; 254 255 void update() { 256 height++; 257 } 258 } 259 260 override void on_start() { 261 // create a basic entity 262 auto nt = create_entity("apple"); 263 // add a basic component 264 nt.add_component(new Plant()); 265 } 266 } 267 268 auto my_scene = new TestScene(); 269 270 class Game : TestGame { 271 override void initialize() { 272 load_scenes([my_scene]); 273 } 274 } 275 276 auto game = new Game(); 277 game.run(); 278 279 // make sure scene is accessible 280 assert(game.primary_scene == my_scene, "primary scene does not match loaded scene"); 281 282 // make sure components worked 283 assert(my_scene.get_entity("apple").get_component!(TestScene.Plant)() 284 .height > 0, "test Updatable was not updated"); 285 286 game.destroy(); // clean up 287 288 // make sure scene is cleaned up 289 assert(my_scene.ecs is null, "scene was not cleaned up"); 290 }