Scene Graph (Metal Part 8)
Implementing a Scene Object and a Scene Manager, to be able to render many objects and decouple the state out of the Game View Renderer.
Source Code
References
Table of Content
Scene
The Scene will also be a Transform object, it will be the root transform of the scene.
1class Scene : Transform { 2 3 override init(){ 4 super.init() 5 buildScene() 6 } 7 8 func buildScene() {} 9}
Scene Manager
The Scene Manager will be in charge of holding the current scene, in the future it can implement transitions between scenes, and handling loading and unloading of resources.
It also offers a public function to update and render the current scene.
1enum SceneType { 2 case Sandbox 3} 4 5class SceneManager { 6 7 private static var _currentScene: Scene! 8 9 public static func initialize(_ sceneType: SceneType){ 10 setScene(sceneType) 11 } 12 13 public static func setScene(_ sceneType: SceneType){ 14 switch sceneType { 15 case .Sandbox: 16 _currentScene = SandboxScene() 17 } 18 } 19 20 public static func tickScene(deltaTime: Float, renderCommandEncoder: MTLRenderCommandEncoder) { 21 _currentScene.update(deltaTime: deltaTime) 22 _currentScene.render(renderCommandEncoder: renderCommandEncoder) 23 } 24}
This can later be used in the Game View Renderer class, to decouple it from scene initialization.
1extension GameViewRenderer: MTKViewDelegate { 2 3 func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 4 // when the window is resized 5 } 6 7 func draw(in view: MTKView){ 8 ... 9 10 SceneManager.tickScene(deltaTime: 1.0 / Float(view.preferredFramesPerSecond), renderCommandEncoder: renderCommandEncoder!) 11 12 renderCommandEncoder?.endEncoding() 13 commandBuffer?.present(drawable) 14 commandBuffer?.commit() 15 } 16}
Testing Objects
To setup our testing scene, we can make use of all the testing classes we have created which inherit from our base classes.
1class MoveComponent : Component, Updatable { 2 3 var time: Float = 0 4 5 func doUpdate(deltaTime: Float) { 6 time += deltaTime 7 8// gameObject.position = float3(cos(time), gameObject.position.y, gameObject.position.z) 9// gameObject.scale = float3(repeating: sin(time)) 10 gameObject.rotation = float3(gameObject.rotation.x, gameObject.rotation.y, sin(time)) 11 } 12} 13 14class QuadGameObject : GameObject { 15 16 override init() { 17 super.init() 18 19 let meshRenderer = MeshRenderer(mesh: MeshCache.getMesh(.Quad)) 20 self.addComponent(component: meshRenderer) 21 22 let moveComponent = MoveComponent() 23 self.addComponent(component: moveComponent) 24 } 25} 26 27class SandboxScene : Scene { 28 29 override func buildScene() { 30 31 for y in -5..<5 { 32 for x in -5..<5 { 33 let gameObject = QuadGameObject() 34 35 gameObject.position.y = Float(Float(y) + 0.5) / 5 36 gameObject.position.x = Float(Float(x) + 0.5) / 5 37 gameObject.scale = float3(repeating: 0.15) 38 39 addChild(transform: gameObject) 40 } 41 } 42 } 43}
The Game Engine can then initialize itself with a sandbox scene by default.
1class Engine { 2 3 public static func initialize(device: MTLDevice){ 4 5 ... 6 7 SceneManager.initialize(Preferences.InitialScene) 8 } 9}
Result
Now the scene contains lots of game objects and all update and render accordingly.