What is a Scene
system?
A Scene is an abstraction of akara.System
, a special system that contains a scene graph and one (or many) rendering viewports. Scenes manage their own list of entities and render those entities to the viewports. Please, refer to d2systems.BaseScene
.
A Scene system is a special type of system that has dependencies on several other systems. In particular, all scene systems require the following systems be added to the world in order for the scene to initialize:
d2systems.RenderSystem
- responsible for initializing the rendererd2systems.InputSystem
- responsible for handling keyboard/mouse/gamepad inputd2systems.GameObjectFactory
- responsible for creating game objects (which may be composites of many components)
The GameObjectFactory
deserves special attention. At the time of writing, this is what it looks like in full:
type GameObjectFactory struct {
akara.BaseSystem
*d2util.Logger
Sprites *SpriteFactory
Shapes *ShapeSystem
UI *UIWidgetSystem
}
func (t *GameObjectFactory) Init(world *akara.World) {
t.World = world
t.setupLogger()
t.Debug("initializing ...")
t.injectSubSystems()
}
func (t *GameObjectFactory) setupLogger() {
t.Logger = d2util.NewLogger()
t.SetPrefix(logPrefixGameObjectFactory)
}
func (t *GameObjectFactory) injectSubSystems() {
t.Sprites = NewSpriteFactory(t.BaseSystem, t.Logger)
t.Shapes = NewShapeSystem(t.BaseSystem, t.Logger)
t.UI = NewUIWidgetFactory(t.BaseSystem, t.Logger, t.Sprites, t.Shapes)
}
func (t *GameObjectFactory) Update() {
t.Sprites.Update()
t.Shapes.Update()
t.UI.Update()
}
As you can see, the GameObjectFactory
is really just a wrapper for several other subordinate systems, like the SpriteFactory
. The SpriteFactory
is like any other system, except it provides these two very important methods:
func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID
func (t *SpriteFactory) SegmentedSprite(x, y float64, imgPath, palPath string, xseg, yseg, frame int) akara.EID
Without going into too much gory detail, it should be easy to see that these methods are responsible for creating Sprites
and SegmentedSprites
. This leads into the next section...
How does a scene know which entities it owns?
each scene creates a local sceneObjectFactory
. This literally has all of the same method signatures as the global GameObjectFactory
system, but this local one handles adding entities to the scene's render list.
type sceneObjectFactory struct {
*BaseScene
*d2util.Logger
}
func (s *sceneObjectFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID {
s.Debugf("creating sprite: %s, %s", filepath.Base(imgPath), palPath)
eid := s.sceneSystems.Sprites.Sprite(x, y, imgPath, palPath)
s.SceneObjects = append(s.SceneObjects, eid)
s.addBasicComponents(eid)
return eid
}
The sceneObjectFactory
is embedded in the BaseScene
with the field name Add
:
eid := scene.Add.Sprite(0, 0, spritePath, spritePalette)
How do i create a scene?
First of all, you need to embed a *d2systems.BaseScene
inside of your scene struct:
type TestScene struct {
*BaseScene
booted bool
}
Next, you need to make sure to handle BaseScene
boot and update inside of the scene Update
method:
func (s *TestScene) boot() {
if !s.BaseScene.booted {
s.BaseScene.boot()
return
}
// do scene bootup stuff here...
s.booted = true
}
func (s *TestScene) Update() {
for _, id := range s.Viewports {
s.Components.Priority.Add(id).Priority = scenePriorityTestScene
}
if s.Paused() {
return
}
if !s.booted {
s.boot()
return
}
// add actual scene logic here
s.BaseScene.Update()
}
Optionally, you may want to make a provider function for creating an instance of your scene:
func NewTestScene() *TestScene {
scene := &TestScene{
BaseScene: d2systems.NewBaseScene("Test Scene"),
}
return scene
}
CAVEATS
At the time of writing, we need to manually manage the following:
- the render priority of viewports (in relation to other scenes!)
- We need to boot the
BaseScene
and return early in our scene'sUpdate
method if the base scene has not booted! - We need to manually call
BaseScene.Update
at the end of our scene'sUpdate
method
func (s *MouseCursorScene) Update() {
for _, id := range s.Viewports {
s.Components.Priority.Add(id).Priority = scenePriorityMouseCursor
}
if s.Paused() {
return
}
if !s.booted {
s.boot()
return
}
s.updateCursorTransform()
s.handleCursorFade()
s.BaseScene.Update()
}
func (s *MouseCursorScene) boot() {
if !s.BaseScene.booted {
s.BaseScene.boot()
return
}
s.registerTerminalCommands()
s.createMouseCursor()
s.booted = true
}
IT WOULD BE REALLY COOL IF WE DIDN'T HAVE TO DO THIS, PLEASE HELP FIX THIS!