We're back again, and as we start to wrap up for wider release we've got plenty to talk about - this is part 3 exploring what it feels like and looks like to use luxe to make games, in concrete detail. Get yet another cozy seat and see how Scenes, Prototypes and Templates work in luxe.
We'll begin with the basics, what is a scene exactly?
A container of entities!
Now that we know what modifiers look like, we need a way to store them in a scene we can load later, like a level file, or a chunk of the world. This is what a scene is for - it stores related entities together in a container that can be loaded, and unloaded, in one call.
A scene is just that - a container of entities. In luxe, we call this container an "Entity Context", e.g context when talking about the world stuff.
Entity assets
In a luxe scene, an entity is represented as a single file per entity. There's a dozen reasons for this - not least of which is working in teams where we avoid conflicts on big files everyone needs to touch - but it also affords a lot of other benefits.
This post isn't about all those details, but this is what an entity file looks like inside. Aha - we have found our modifiers!
A big goal with luxe is that all content is human friendly even if generated or editor created. Very often I tweak content without opening the editor as it's really easy to do so. It's easy to search and replace and so on.
This allows a lot of flexibility and custom scripting to happen as well, all using the intrinsic tools available in luxe. SO many times we've generated entities from blender, automated conversions, and plenty more because the format is really easy to do that kinda thing with. This is key!
Loading a scene
This is what it currently looks like to load a scene in code (an alias, Scene.load
exists for clarity btw):
What does the variable scene
contain?
The return value is actually an Entity
!
More specifically, it returns the scene root. The scene root is an entity typically with a transform attached. This means you can attach modifiers to the scene itself, which is often handy for systems that manage the world.
When loading a scene, it also looks at any entities with a Transform, that aren't linked to anything else, and links them to the scene. This creates the expected behavior that we'd want - when you move the scene root using regular Transform
- everything under it that is attached will follow.
This property is especially important for Prototypes, as we treat the contents as a single unit more often than not.
Scene Concepts
Scenes have some important properties worth mentioning as they exist for clarity of workflow, and clear expectations - these properties are intentional!
- A scene can only be loaded into the same world, once
- Multiple scenes can be loaded at any time, making them modular
- Scenes have a script that follows the lifetime of the scene
- The scene root is an entity, so destroying that root will unload the rest
Bonus image!
Scene are a tool
like most things in luxe...
Scene script
Mentioned above, the lifetime of the scene is typically a really useful boundary for when certain things should be running... And often times we'll have scene specific code that JUST needs to exist in this ONE area.
This is a big part of why scene scripts exist, they are a really good way to decouple one-off code cases from main systems and keep nuance separated. e.g We'll never have if(area == "area1")
inside Door
modifier, instead we handle the specific checks local to the scene inside the scene script.
A menu? Just a scene would work. Load it into the ui world, keep it resident there. Implement the menu code in the scene script. Hook up the ui buttons to the scene script via wires. This is how we do it in our game.
Like with modifiers, if you make a blank scene.wren
it'll be populated for you with the following template:
Another reason this is useful, is for storing shared systems or logic. We can layer scenes like this because we have ...
Scene stacking
Scenes are a versatile tool, there's no one way to use them. There are however many cases where they make sense for the task, like if you're making a game with distinct levels like a puzzle game or platformer, they're a perfect fit.
But since they're just a tool, we can use them for other workflows.
If you have a tutorial inside an area of the game you'll spend time in or return to - we have scene/areas/area1
and scene/areas/area1.tutorial
. We load both of those up the first time you play, and if you opt out of the tutorial, just don't load the second part. Or if you're returning, skip it.
Another concrete example is when making our game Mossfield Archives, we don't have to load in a really heavy scene just to tweak some camera triggers. I don't have to wait for long load times just to iterate on my corner of the world.
In game, we can load in any scenes on demand, like if the player enters a trigger, load the next part only if relevant. Here's an example from the intro area in archives, but only the first time through, opened in editor.
And one other example mentioned above - shared systems. If I'm making a content gym or test area for the game to iterate on game design, I don't need the entire world. I can make a tiny room, and load the foundations of the game into it, and then spawn just what I need in my own space.
This also is true for "persistent systems", like manager systems, or even the player. Often I'll load the player from a scene once off at the start of the game, and just teleport them around when changing levels. The player state is always resident that way and not caught up in unloading the world.
Needless to say there's no shortage of helpful ways to use them as a tool. If we're often dealing with multiple scenes, we'd also often want to open a group of scenes as one right?
Stage assets
For now we call this concept a stage. It is a very simple asset, it's just an array of scene references! When you load a stage, it loads all the scenes asked to. This is true in the editor as well, so you can make your own stages for your own iteration for common needs.
Scene contents
So a scene contains entity assets, one per entity, what does it look like on disk? It's just a folder!
If we have a scene called level1, on disk it is known as scene/level1.scene/
. Inside this folder we keep the root entity definition, the scene script, and entities within that scene. You can also have folders inside of them (shown below in editor), which is how you'd group and organize the entities within it.
Here's what the entity assets look like inside a scene:
And here's a complete simple example scene from our game that manages fade in/out. It is resident and will draw a black screen sized rectangle over everything. Here's what it looks like on disk. We can see the scene root entity (.scene.lx
) and we can see the scene.wren
our scene script.
Prototypes
So scenes can only load into the same world once - what if you want to create an instance of something repeatedly? We'll that's what a Prototype is for. This is often called a prefab
or similar, the idea is the same - it's a container of entities - but these can be spawned repeatedly.
A Prototype looks exactly like a scene on disk, it's a folder, contains entities, and is named (unsurprisingly) with a .prototype
extension, like pickup.prototype/
. They also return a root entity, and like with scenes, destroying the root entity takes down the rest. They also link transforms so they move as one.
We call an instance of a Prototype, an instance! Here's an example of one created inside of a scene:
Nesting + overrides
One very important difference from scenes, though, is that you can place a Prototype inside of another Prototype. This makes Prototypes different from scenes by design, as they are often used for creating a sort of blueprint of a thing you create multiple instances of (vs a scene, which are conceptually singular for clarity).
You'll also notice, that we have override
files in here. When you spawn an instance of a Prototype, you can customize it. This is true if it's inside a scene, or inside another Prototype. This makes them handle variants pretty well too. When you reset a value in the editor, it resets to the Prototype (instead of the default for the field). Nothing surprising!
In our game we have a shared implementation of a character, but we customize each one to be specific. This is an example below: Andie is a character that customizes the avatar Prototype to be a distinct version of it, say by changing what animations will be fed into the blend graph for locomotion.
Another example from our other game Mossfield Origins is buildings, each building is a Prototype with a building nested inside it, and then the Prototype contains the actual visuals and modifiers for the distinct nature of it while still being fundamentally a building.
Links to the original
The other key factor of Prototypes, is they they're connected to the original version. If you change the Prototype, all instances of it will be changed unless they override it. This is obviously important for building games!
Entity Templates
There's one special case type of Prototypes that is important - we call them entity templates. These are created much like a Prototype would be but with one key difference: they cannot have other entities, just the root entity.
When they are created in the world, they copy the single entity into place instead, so no links to the original.
It's extremely common to have one shot style entities that are ready to go, but not really need them to be linked to a context or nestable - we just want to treat them as something we create in less clicks. This is an entity template.
Some obvious examples: a Sprite
template - these include a Sprite
, Transform
and Tags
so that as a new user I can make a sprite in one click. Or a text object in the world via the text template.
Some examples from our games: A sphere trigger. It comes prepared with the trigger pre-configured, the collider, as well as wires, so I can immediately connect it to a receiver. We also have stuff like dialog triggers, interactables, and so on. They speed up workflows!
Entity Contexts
And finally, an honorable mention in passing is the context concept that is shared by Scenes and Prototypes. For example, you can ask an entity what context it belongs to Entity.get_context(entity: Entity) : Entity
- this will return the context responsible for creating the entity directly.
You can also ask for Entity.get_context_origin(entity: Entity)
- this is the very top level context that is responsible for instantiating it indirectly. e.g the Scene is the origin, but the parent Prototype is the context. The tree is walkable, since you can nest Prototypes, it looks like origin -> context -> context -> context -> entity
.
You can also query contexts. If you spawn a Scene or Prototype, you can pull specific entities out directly (if you want to) and a variety of other tools:
The context serves a lot of purposes, including addressing distinct entities via addresses in data and a lot more. This is more for documentation than intro posts, so now you know!
Bonus
Next time ... projects
We've created entities, we've made modifiers, we've stored them in scenes. The final part of what is it like to work in luxe is about projects, what they look like in practice.
Part 3: fin
Our journey in seeing what it looks like to use luxe continues! Below are links to the previous and next post in this series:
Get the latest news
All posts in this series: