Scene Graph (Metal Part 8)

z4gon
z4gon

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.

Cover Image for Scene Graph (Metal Part 8)

Source Code

See Project in GitHub 👩‍💻

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.

Picture