Scene Management
Scene's are a really handy way to organise your game into distinct 'scenes' or 'screens', such as a menu screen or a loading screen or the main game scene. The benefit is that, although they do not stop you from having full access to everything, they allow you to minify and encapsulate each screens functionality and data to keep it small and easy to reason about.
Example Links
How to manage game scenes
In this example we'll set up a simple game with two scenes, where clicking anywhere moves from one scene to the next.
Scene's can either use the main game model and view model, or they can have their own. Custom models are made up of data stored in the main models. In this case, we'll define a simple model for each.
final case class SceneModelA(value: String)
final case class SceneModelB(value: String)
Scenes are simple objects then extend the Scene type.
Scenes are another instance of the TEA patterm themselves follow a very pattern to the main game
functions, defining their own updateModel
, updateViewModel
, and present
and so on.
object SceneA extends Scene[StartUpData, Model, ViewModel]:
The way that scene's get their own models and view models is via 'lenses'.
First We tell the scene what the type of it's own model and view models are. We then define lenses that tell the scene how to 'get' and 'set' those models from the main game models.
Scene models can be a piece of data that lives in a field on the main model, as in this example. They can also be ephemeral data types aggregated from various main model data points and pulled together each frame, as needed.
type SceneModel = SceneModelA
type SceneViewModel = ViewModel
val modelLens: Lens[Model, SceneModelA] =
Lens(
model => model.sceneA,
(model, newMessage) => model.copy(sceneA = newMessage)
)
val viewModelLens: Lens[ViewModel, ViewModel] =
Lens.keepLatest
There are a number of SceneEvent
s that you can experiment with, here we listen to the
SceneChange
event and use the JumpTo
event to go to the scene we want, but there are
others such as Next
and Previous
to explore.
def updateModel(
context: SceneContext[StartUpData],
sceneModel: SceneModelA
): GlobalEvent => Outcome[SceneModelA] =
case SceneEvent.SceneChange(from, to, at) =>
println(s"A: Changed scene from '${from}' to '${to}' at running time: ${at}")
Outcome(sceneModel)
case MouseEvent.Click(_) =>
Outcome(sceneModel)
.addGlobalEvents(SceneEvent.JumpTo(SceneB.name))
case _ =>
Outcome(sceneModel)
The main game functions of an IndigoGame
entry point are largely the same as you'd find in an
IndigoGame
, but there are two things to note:
- New methods called
scenes
andinitialScene
are declared. Scene's must be declared in order in the scenesNonEmptyList
, and there must be at least one scene. The initial scene is optional, ifNone
then the first scene loaded will be the first one in the scene's list. - The usual functions like
updateModel
are declared as normal, but you can now thing of them as 'global' functions. The scene's implementation ofupdateModel
will only be called when that scene is running, butupdateModel
in the main game runs all the time. This is helpful for dealing with any events that are not specific to any one scene, or presenting graphics that should appear all the time.
def scenes(bootData: BootData): NonEmptyList[Scene[StartUpData, Model, ViewModel]] =
NonEmptyList(SceneA, SceneB)
def initialScene(bootData: BootData): Option[SceneName] = Option(SceneA.name)
def updateModel(context: Context[StartUpData], model: Model): GlobalEvent => Outcome[Model] =
case _ => Outcome(model)