Sponsored By

How do you actually program a multi-platform, multi-hour narrative game using the Unity game engine in detail?

Christoph Ender, Blogger

August 25, 2023

18 Min Read

How do you actually program a multi-platform, multi-hour narrative game using the Unity game engine in detail?


Car_Ride_2.png


Four years ago, we had completed a truck simulation for mobile devices. For this, we had spent several person-years creating a game world with a vast road network for the well-known German publisher Astragon and were thinking about what else it could be used for. We joked that a well-known German publisher had not yet brought out a front-seat passenger simulator. Years later, this snap idea has grown into a mature and profound narrative game.

Besides the obligatory passive activity, the player conducts dialogues with the driver and enjoys landscapes across rural America. In between, he solves simple to challenging puzzles with objects inside and outside the vehicle. The relaxed atmosphere soon gives way to the eerie and surreal. To entice a publisher, we made or had to make a prototype of the first level with 30 minutes of gameplay with a lot of effort. The players liked it a lot, and the prototype was even released on Humble Bundle and won prizes. For the main game, we learned from the prototype‘s experience, made a plan and started again.

Scenes

We used Unity and here, programmers have several options for the coarse and fine structuring of the game. Usually, Unity uses scenes that can be loaded and unloaded immediately (synchronous) or in the background (asynchronous). In the game, each level consists of these scenes:

• Game

• World

• Car

• Gameplay

The first scene contains the user interface and scripts common to all levels, such as the registration manager.

The second scene includes the landscape with the road, buildings and the like. Sky and light settings should also be saved here so that the designers can already see in the editor how it will look in the game. There are camera movements for the intro, data for occlusion culling and very little code, e.g. the script for turning the windmill blades.

The third scene stores all playable objects and locatable information, such as the route of the vehicle, the vehicle itself, things in the vehicle, the characters and cameras in the vehicle. A lot of code exists here, which is necessary for the use of the objects, e.g. that the car can drive.

In the fourth scene, the narrative graph is loaded and played. This controls all playable objects and handles events and inputs. More about this later. This allocation of a level into scenes is common and important for simultaneous editing, for tracking changes and for maintaining dependencies and sequences.

In Unity, objects cannot be referenced between scenes. Objects that can be changed in the game are registered with their name when they are loaded and entered into a list and can thus be conveniently used by the gameplay scene. This prevents chaos, and the level and narrative designers can create functioning gameplay relatively independently without waiting for the programmers.

World and terrain

For the sake of simplicity, the car in the prototype drove in roughly a circle in a square world with an edge length of one kilometer. As a result, the landscape repeated itself every 30 seconds, and the sun‘s position rotated permanently. The many left turns did not improve the believability of the world. The remedy for the main game is a long road at the end of which the vehicle is placed back at the beginning.

Using a script, we copied the beginning of the road and terrain to the end so that both look the same. At the time of teleportation, all visible dynamic objects such as blowing grass, passing clouds and spinning windmills should be synchronized with the corresponding objects at the start. In the game, the player then notices a tiny jerk with tiny visual changes. Through clever level design, the conspicuousness could be reduced, it doesn‘t have to be a tunnel. With this trick and some route variations, we created a driving experience with little effort that doesn‘t feel obviously repetitive even after half an hour. Also, the sun‘s rays now always come from the front, which is scenically valuable. This limitation allows optimizations in the shadow-casting geometry. Further teleportation problems can be caused by global particle systems or physical calculations, which have been deliberately omitted.

World_Ride_1.png


When reaching the level end (right white dot refers) the car is teleported back to the beginning (left white dot).

Visibility_Map.png

The Visibility Map of the first level. Brighter areas indicate terrain more visible to the car`s onboard camera.

The landscape was originally created with Unity Terrain and the roads with the asset „EasyRoad3D“. We already had extensive experience with both in the TruckSim project and both are still terrible. Unfortunately, there are no real alternatives in the Unity Asset Store, but what doesn‘t fit is made to fit. While playing, Unity adapts the tessellation of the height field terrain to the camera position and direction. This looks good but is slow. We exported the finished Unity terrain as mesh geometry and connected it to the geometry of the street. A custom shader was used to create visually appealing transitions along the roadside. For a 2x2 km landscape, the export results in over 8.3 million triangles. That is too much.

Since the player is not allowed to control the vehicle, all possible camera positions are predefined by the path of the vehicle at developer time. Many triangles of terrain are never visible because they are behind a hill. So we wrote a tool that calculates if and at what angle and distance the player sees each triangle of the terrain. We wrote these three values into a texture, the Visibility Map. Areas of the landscape that are not visible from the road are black, and areas next to the particularly visible road are white. This texture can be used to control the terrain mesh and grass density. The terrain can be reduced to 100,000 triangles without much loss of visual quality, which is 0.12%.

The terrain is much larger than the maximum view range of the camera. For optimization, we divided the terrain mesh into 12 segments. With occlusion culling, only the required segments are rendered at runtime. The mobile GPU is happy about this measure, the thick-armed graphics card of a PC remains unimpressed. The terrain now renders fast enough. Further polygon-reduced variants of the segments could be created, which are selected depending on the distance to the camera (level-of-detail). If you have something like this in mind, you should take a look at assets like „Open World Terrain“. We chose a different approach in the second level. For a (deliberate) loss of orientation of the player, a small world of 200x400 meters with streets and identical houses is sufficient. The long predetermined path of the vehicle leads in a zigzag course through a suburb and only repeats itself after a while. This level renders on the PC with almost 1 million triangles, half of which are grass.

Vegetation

The majority of the terrain should be planted with wheat fields. In Unity you can place vegetation on the terrain. Grasses, bushes or trees are displayed there at runtime. Unfortunately, it is impossible to set minimum distances between the plants so they can stick together awkwardly. Furthermore, there is a lot of lagging at runtime when the camera is turned, which is the case when driving a car around bends. Although this can be switched off, it consumes an inordinate amount of memory. Unity limits the distance at which the grass is still displayed. In the game, an ugly border appears at the transition to the distant grass-free area. Well, what does the convenience loving developer do? He looks in the Unity Asset Store!

We bought and evaluated all the assets for creating and displaying vegetation. Unfortunately, none could display grass on all the required target platforms. Mostly it failed due to the Metal implementation on Apple or the lack of compute shader support on Nintendo Switch. The closest to a solution and the one we liked best was the asset „GrassFlow“. Again, something that doesn‘t fit had to be made to fit.

World_Ride_2.png

The predefined path of the car in the second level confuses even people with a great sense of direction.

Unity particle systems are great for rendering grass because they run on all platforms and render quickly. The properties of each particle can be set individually. In our case, the grass is not painted onto the terrain but calculated automatically. The visibility map mentioned above contains valuable information about the level. There is still information missing about objects such as houses under which no grass should be rendered. We get this information with an editor script with ray shots from above. If the ray does not hit terrain, then no grass should grow there. In the script we could create a particle system for each segment of the terrain and place each particle or tuft of grass exactly. The created particles were saved and loaded with a script. Unfortunately, with millions of particles, this takes a few seconds at runtime and the memory consumption is less than 100 MB.

As an optimization at runtime, Unity Occlusion Culling is recommended, which only switches on particle systems in the camera‘s visible area. To prevent micro-lags, it was replaced by a separate system. A complex shader is used to influence the color of the particles. As the distance to the camera increases, the color adapts to the terrain so that the visible transition disappears. The terrain also got its own shader. With lower distance, it got visually more noise or blotchiness to make it look like grass. The obligatory wave movement in the wind must not be missing. Despite the many textures, the grass material renders surprisingly fast. For mobile devices, the grass density must be reduced in the distance. In the end, we were able to drive through pretty hilly fields and suburbs with grassy areas on all platforms. Overall, the development of the vegetation was a considerable and unexpected effort.

Narrative

Fortunately, story and dialogue texts were ready in the first months of production, so all the team members could plan well. Our author wrote them with the tool „twine“. This is a free specialized tool for creating dialogue visually. In twine you can even run the story and test it interactively. In Unity, visual scripting is a duty for a project of this size so that even non-programmers can work on the narrative. Such a narrative graph is a kind of flowchart of connected nodes that describe which character says and does what and when, and what else happens. In the prototype, we had implemented a simple visual editor of our own, but this was to be replaced. Twine documents can be imported into Unity. Due to the complexity of the stage directions in the script, we preferred a dedicated editor. After evaluating all visual scripting editors in the Unity Asset Store, we decided on „FlowCanvas“. Here, sub-graphs or macros can be created and played. This allows a narrative graph to be saved in sections in several files, which is important for collaboration. Meanwhile, Unity has integrated the visual scripting tool „Bolt“. Besides the obligatory narrative node for a character‘s speech text and response options, over 40 other types have emerged.

Narrative_Graph.png

An overview of the node types in the narrative node graph. This is the set of command the game is made of.

• Start and end of the level

• Change of environment/scenery

• Interaction, visibility, change of state of objects

• Control of characters and the dialogue

• Playback of animations, audio, video and cutscenes

• Playing procedural animations/tween

• Control of interactive puzzles

• Control of the vehicle

• Control of cameras and camera effects

• Control of inventory

• Control of particles

• Waiting for events

• Savepoint

• Triggering Achievements

• Display of hints

• Black screen

• Sequence/flow control

At first glance, a game that is essentially a dialogue seems easy to program. However, it becomes complex in connection with the possible interaction of the objects in the vehicle. For example, the driver asks the player to open the side window. If the player does not do so, he should be asked again. If he does, he should be thanked. If he has done it before, he should not be asked to do it. If he closes it again afterwards, he should be asked again. It becomes more complicated when the driver is supposed to comment on the player‘s action. At any time, the player can take a photograph out of the glove compartment. The driver should then briefly explain who is on it. However, this only works if the mood is not disturbed, i.e. the driver is not excited, does not hold an important monologue or tells a joke, or is already commenting on something. Sometimes the next sentence doesn‘t fit the comment before it. It also looks funny if the driver repeats the explanation when looking at the photo again.

All in all, you have to consider that all is interruptible, nestable and repeatable. The narrative graph consists of about 5300 nodes for the game duration of about 3-4 hours. Originally, the loosening up mini-riddles and puzzles were also to be implemented with the narrative graph. This quickly turned out to be impractical due to the high complexity. Overall, the implementation of puzzles took us much more effort than planned.

Animation

All animations of the characters are made with motion capture (MoCap), i.e. real actors were filmed in a professional studio and the movements were transferred to the computer. With a lot of enthusiasm, the geometries of a wooden car seat and steering wheel were recreated for this purpose so that the dimensions matched the virtual model. Since we didn‘t know exactly how much time the post-processing would take, the recordings started right after the project began. In retrospect, that was good, because it took a year to post-process hundreds of animations. In the game, a body animation is usually played at random for each sentence. It becomes strange when, for example, the driver scratches his head conspicuously often or looks around. It‘s the mixture that counts.

The driver‘s hands are pinned to the steering wheel at runtime with inverse kinematics so the arms are carried along in curves. Very tight turns are to be avoided so that the arms do not get knotted. Facial movements were filmed while the texts were spoken by professional speakers. There is practically an animation for every sentence spoken, which brings us straight to the crux of the matter. There are simply too many. Subsequent changes to the setup are thus expensive. The visual result in the game is good. Facial animations can alternatively be realized by automatic audio analysis and tags in the dialogues.

Motion_Capture_1.png

Motion Capturing in action in our very sophisticated hand crafted ‘car’.

Assets

Some game developers keep their used assets secret, like chocolate makers their recipes. The following tools were purchased and are recommended, but not all of them are in the final game:

• Terrain Resolution (editing terrain)

• Terrain To Mesh (terrain conversion)

• EasyRoads3D (making road meshes)

• Curvy (splines)

• Flow Canvas (visual scripting)

• InControl (input wrapping)

• FMOD (audio)

• I2 (localisation)

• Prime31 (cloud save on Apple devices)

• Amplify Shader Editor (rendering)

• Double Sided Shader (rendering)

• Highlight Plus (rendering)

• PostProcessing (rendering)

• Volumetric Light Beams (rendering)

• OBJ Export (meshes)

• FBX Export (meshes)

• QuickSetObjectPlacer (level design)

• EZ Camera Shake (visual effect)

• Instant Screenshot (high res)

• Skybox Capture (3d screenshots)

• MeshDebugger (investigate meshes)

• Console Pro (better debug log)

• Console E (better debug log)

• IngameDebugConsole (debug)

• Debug Drawing Extensions (debug)

• RuntimeInspector (rendered hierarchy)

• Compile Time Tracker (optimization)

• Odin (editor inspector elements)

• ResourceChecker (checking assets)

• Find Reference 2 (checking assets)

• Better Build Info (checking assets)

• SceneReference (editor)

Particularly positive are I2 for localization, IngameDebugConsole for displaying error messages on the target hardware, RuntimeInspector for switching game objects on and off on the target device, Instant Screenshot for rendering with a transparent background and ResourceChecker for checking which textures are rendered in the scene and Graphy for graphically displaying the frame rate. Not to forget the SDKs/APIs from Steam, Nintendo, Sony, Microsoft and Apple.

Art assets were also bought, of course, but all gradually replaced with our own content. Polygon‘s assets are very handy for prototyping levels. We had help from an Indian outsourcing studio despite having a team of graphic designers. As expected, remote communication with a time difference is more time-consuming than discussing things briefly on site. As a fun fact not a single byte from the world of TruckSim made it into the game.

Shadow

When the direction of the sun is fixed, lightmaps are attractive for the pre-calculation of the shadows. Due to the sparsely populated kilometer-sized areas, we opted instead for real-time shadows outside the vehicle as well. The virtual camera in the passenger‘s head was doubled. One camera renders the car interior and one the landscape. This has several advantages:

By reducing the camera‘s range to three meters (far plane), the shadow becomes much more high-resolution. The known render order increases performance. Due to the different render layers, no wheat stalks are rendered in the car when driving through a waist-high wheat field. By a trick, we could eliminate the slight trembling of the shadow in the car. I have to elaborate a little on this. Jitter is typically caused by floating-point inaccuracies due to camera positions of more than ten thousand units or - as in our case - by moving the car based on a curve. One solution is to reverse the relative motion. It is not the car with a camera that is moved in the world, but the car is stationary and everything around it is moved. The player does not notice any of this.

Prototype_Car_1.png

This is how the driver looks in the prototype.

Car_Ride_1.png

The same driver seen in the final game. The lad beefed up in the gym

Porting

The attentive reader will have noticed that the game is not available for Android. In the middle of the production, we received a message from our American publisher Versus Evil that Apple would like to have our game in the Apple Arcade program. At the time, that was a new planned subscription service for games for iOS and tvOS. The technical requirements and conditions of this program were only being developed. Apart from the iPhone 6, the hardware with the lowest performance to be supported is the AppleTV1. On this device, our game ran partly with single-digit frame rates, so that optimization would be necessary here. When the memory limit of 1.2 GB is reached, the application is killed by the AppleTV system. You can connect a game controller on all three platforms. The porting to PS4, Xbox One and Nintendo Switch went without any noteworthy problems and much faster than planned. With the help of the publisher‘s quality assurance, almost all platform relevant requirements could be checked in advance. Publishing on Steam is unproblematic as usual, I heard that adapting to the stores GOG and Epic was also painless. The port to Virtual Reality … this could be made into a separate article.

Conclusion

I found the weekly meetings particularly interesting, where the entire team came together and everyone could present their new ideas for the game in a grassroots democratic way. That‘s why we are indies and not on the board of a DAX company. So, back to the jokes. Until the next article, maybe about the backseat simulator?

-------------------------------------------------------------------------------------------

Game

Hitchhiker - A Mystery Game Set Along Lost Highways

is a surreal and narrative adventure game for Apple Arcade (iOS, macOS and tvOS), Steam/GOG/Epic for PC, Sony Playstation, Microsoft Xbox and Nintendo Switch and VR.

http://hitchhiker-game.com&nbs...;

Developer

Mad About Pandas

Mad About Pandas is a award-winning game- and creative media production studio founded by Patrick Rau, a spin-off of kunst-stoff GmbH, based in Berlin, Germany.

http://madaboutpandas.de/about...;

Publisher

Versus Evil

Translation: Publishing games on all major mobile, PC and next gen consoles, Versus Evil works with indie studios around the world in North America, South America and Europe.

https://versusevil.com ;

Author

Christoph Ender

Chris has been technical director at Mad About Pandas from 2018 to 2022 and technically led the Hitchhiker project. Since his Master in computer science with a focus on computer graphics in 2004, he has worked at various game studios and has a track record of nearly 30 game titles.

https://www.linkedin.com/in/ch...;

Read more about:

Blogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like