What follows are detailed design notes on the game THRUST//DOLL. Please see the original design document for the game concept.
The following notes are written over the course of development, so they may be a little disorganised.
DOTS vs GameObject representation
With the introduction of DOTS, Unity has two parallel data structures. On the one hand you have the tree of GameObjects, which are updated by calling the C# functions attached to each of their
MonoBehaviour components. This introduces a bunch of overhead—it can’t be compiled with Burst, it’s got to deal with a lot of OOP issues, and it’s not cache performant.
The second is the ECS. There’s a separate hierarchy of entities, which are just data, tiny structs. Then there are systems designed to iterate over entities with certain components and do something to each one. This can be vectorised and made parallel really easily, so it’s incredibly fast. But the new way has its own problems: the documentation is fairly opaque, the APIs don’t cover important parts of a game like input handling, and the API is still not definitely stable yet. And it doesn’t fit neatly into Unity’s editor, so you have to write a bunch of boilerplate to translate whatever you build in GameObjects into the ECS. GameObjects and GameObject components don’t map 1:1 onto Entities and Components.
So, if I want to write a program with DOTS, the sticky part is dealing with the parts that DOTS does not handle well.
problem 1: UI
Like all things in Unity, there are multiple ways to do everything.
The old Unity interface system is the GameObject-based UI. This has the advantage of allowing you to place interface elements into 3D space, which is useful for my purposes. However, it’s known for being slow and inefficient. Having interface elements in 3D space could be useful, but it could end up being an overly complex way to accomplish the task I want.
The newer Unity interface system, which I used for the assessment task, is the UI Toolkit. This has some nice features: it’s faster, it’s based on familiar HTML/CSS idioms (FlexBox, bubbling events), and I’ve used it before. However, there is one overwhelming problem. Building a Unity game with the UI Toolkit requires the UI Toolkit package,
com.unity.ui. When I try to install this package, Unity silently fails to add the package, and I have no idea why. Until I solve this problem, if I use UI Toolkit, I can only run it in the editor. This could lead to a situation where I have a finished game that I can’t release.
The other problem is that neither UI system integrates with the DOTS Entity Component System. It is therefore necessary to implement them with GameObjects, and create some kind of bridge to DOTS, if I’m using DOTS in the same scene as the UI Toolkit. One way to do this would be to cause UI events to create a corresponding entity, which can then interact with systems in the next frame, notably the DOTS physics system which we could use to perform a raycast. This is the approach taken in this video.
The easiest way around this is to not use DOTS in a scene where I need to do heavy UI interaction, such as the configurator. This then introduces a different problem: if one scene is built around DOTS, and another isn’t, I will need to create a suitable bridge. This is discussed further later.
problem 2: clickable toggles
My interface design involves two parts. One is a simple screen space interface, similar to the one I made in the assessment task. I would ideally implement this in UI Toolkit. The second part involves clickable toggles attached to parts of the player character model. This part of the design would, I believe, teach me a lot, so I don’t want to abandon it because the approach is not clear.
The simplest way would be to avoid any direct connection between the 3D model and the toggles. However, I would like the user to be able to rotate their character and view it from different angles, like in Warframe’s character editor. So we need to project from world space to UI elements, and update their positions, in an efficient way.
A further problem is occlusion. For example, imagine a toggle that’s attached to a shoulder. At certain angles, this shoulder is hidden behind the model’s head. The toggle should be invisible in this circumstance.
We also need to decide how to draw these UI elements. I imagine it would be a white circle outline, a small gap, and then (when the toggle is enabled) a filled white circle. This could shrink to a smaller, translucent circle when the player isn’t mousing over it to make the model clearer. i.e. something like…
So that raises the question of ‘how to best draw a circle in Unity’. There are a few ways I can think of:
- 2D sprite: pixellated, need to learn how to draw in a separate render pass. however, can be relatively easily placed in Unity UI.
- thin cylinder: would be affected by perspective projection, which I don’t really want. would need to use many polygons to draw a smooth circle.
- Line Renderer: potentially complex drawing commands, requires me to generate a list of points to tesselate into triangles, which seems overcomplicated.
- Freya Holmér’s Shapes library: very pretty and polished but costs £100, and severe overkill unless I decided to use it elsewhere in the interface.
- shader: requires minimal geometry. the shader itself is pretty simple. some complications working out exactly where to place it in the 3D scene so it does not intersect with geometry, but we could potentially project positions onto the camera’s near clip plane, or just far enough forward to avoid intersections?
something like a plan
So drawing a circle with a shader seems like the best way to go. Each toggleable character element could have a child with a billboard shader on it to show the toggle switch. We’ll draw these in world space; the billboard shader will avoid any weird perspective projection jank. In fact, we can generate these automatically at runtime corresponding to a certain component, rather than cluttering up the project with pure UI GameObjects.
Then, as the user moves their mouse around, we have a GameObject that passes the mouse’s state into an entity, which we use to perform a raycast against the relevant entities and change the hover state of one that appears under the mouse. If we get a click event, we can toggle the visibility of that upgrade in the configurator, handling any relevant logic.
problem 3: getting the configured character ready for the level
After I’ve finished the configurator, I will begin work on the game proper. We need to load a level with the configured character.
In Unity ECS, it seems the design has moved from ‘scenes’ to ‘subscenes’. A subscene is a
MonoBehavior that links to a Scene. It can be loaded, introducing all their various entities and components, which will then be detected by ECS queries. This is called ‘streaming’ and it’s discussed in the documentation here.
We won’t worry too much about the details of this process just yet. But essentially what we’ll need to do is unload the configurator subscene, but keep some information about the player’s configured character.
The most elegant way to handle this would probably be to have the same hierarchy of entities in the configurator as in the ‘gameplay’ scene, and load different systems that interact with those entities. Upgrades would have both a ‘toggleable upgrade’ component, which interacts with an upgrade toggling system, and then some other component e.g. ‘damage shield’ which interacts with some other systems that get loaded during the level.
This does imply building the whole configurator in ECS. To cleanly separate everything, we might want to have a subscene for the player character, a subscene for the rest of the configurator screen (so this could be cleanly unloaded and loaded), and a subscene for the level—and when we load the level we would need to . This doesn’t imply very much at the start of the project except putting the player character in its own subscene and using ECS at this point.
The alternative is to build the configurator entirely in GameObjects. When beginning a level, we’d have some script that walks the final hierarchy of GameObjects and generates a set of entities, then deletes the GameObjects.
However, the problems I need to solve to build a configurator in DOTS, such as getting user input, will need to be solved sooner or later anyway, so I don’t expect I will save a ton of time by not just going fully DOTS now. Trial by fire maybe but I think it’s at least worth an attempt.
what code I need to make
For the second ‘milestone’ of the configurator project I need to create a ‘greybox’ prototype. So I won’t do any design work on the player character model and surrounding scene just yet. Instead we have…
I want to handle mouse inputs in DOTS.
The most basic way to show this is working is a script that, when I click into the scene, can print the entity I’ve clicked on into the console. This is largely covered by a tutorial video, so it shouldn’t be too hard.
I need to create a shader to draw a circle at different sizes, and draw it on a ‘billboard’ plane.
I also need to determine how to instantiate this object as a prefab for each toggleable part then control this shader from within DOTS.
showing and hiding an entity
When the user clicks on an entity with a certain component, that entity will disappear and reappear. The UI element must stay visible.
This amounts to wiring together the two previous headings. It should—should—be the easy part.