Welcome back to another postmortem post for TPEP……this name is too long to input, I’ll change a name next time. Last time we talked about audio and subtitles. They helped building the story of the game, and added color and liveliness to the whole environment.
In this final post, we’ll go into some other features in the game, including the items to be picked, the terrain and some optimization.
The Orb of Memory
In this experience, you play as a girl who finds herself wandering into a forest. As she explores the woods she will find several (20) orbs of memory, which will reveal the stories that happened within these woods, her past and her destiny.
Whenever the player interacts with the orb, it will grow brighter gradually, and flash-disappears.
In the first post I mentioned that the orb would look something like this:
But if I were to copy and use this design, I fear that there would not be enough visual cues that could make this scene more believable. Also because it’s just pretty easy to make a light orb that looks like what’s shown above, I decide to make some subtle changes.
To begin, I imagined that the orb would emit ancient symbols, so I drew some symbols with my iPad:
In Unity, I set up a Game Object with a mesh renderer / filter rendering the shape of a sphere. I also added a point light that would emit light from the center of the orb. The material of the orb is nothing special though: it’s just something that runs a default shader.
However the light on the orb is hardly noticeable itself, unless the orb is close enough to a surface for the light to take effect. To overcome this problem I added a Halo component, which will simulate lit dusty environments. Now my orb looks brighter and has a mystical feeling.
For the particle system, I imported the sprite I drew above as a particle material and imported it to the component.
That’s totally not enough, though, for that the particle system would only emit the entire spritesheet as one particle, which is hilariously (and obviously) not what I wanted.
Now here’s the magic: the particle system component is comprised of many modules. I headed over to the Texture Sheet Animation Module. This module basically controls the animation of every individual particle, which means that if you have a spritesheet that contains different frames of a particle animation, you can use this to create an animation.
As of me, I don’t exactly need an animation, but I do need different particles displaying different symbols, so I adjusted the Tiles option to 4 by 4, and it slices the spritesheet into 16 blocks evenly.
Now my orb looks like this:
As I looked at my orb, I noticed that something crucial was missing: motion. Without some form of orb movement, the light ball itself looked dull, and unconvincing.
So I added a small script to it: this script is used to control the movement of the ball around a certain position (I set it to the initial position of the orb).
During this process I’ve spent quite some time trying out different ways to control the orb. In my imagination the orb would move randomly, so obviously I would use
Random.Range() to decide the orb’s next position; but looking at this matter in an animation perspective it means that the orb will “jump” to different locations instead of “slowly drifting” to them.
After some attempts, I finally ended up with this workaround:
Animation.Curve to control the changes on the three axes. Notice that the three curves have different shapes. Also, though cannot be observed from this screenshot, these curves have different lengths. These curves will create a fake random movement.
Finally, to let the player be able to interact with the orb, I added a sphere collider around the orb, a much bigger one.
That’s pretty much what I’ve done with the orb itself.
Backing up a little bit to a bigger scale, I need to control the instantiation of orbs and the destruction of them.
For instantiation because the audio clips and subtitles needed to be played in a fixed order at certain locations, I figured that it would be best for me to just place one orb at a time in the scene. This would also save me a lot of processing power. To accomplish all of these I wrote a simple instantiating script that takes 20 different locations (Transforms), and instantiates it each time it was notified that a previous orb has been destroyed.
On the other hand, the destruction of the orb took me some more effort to make. I used Coroutines to stage the adjustment of the light’s intensity, then invoked an animation that controlled a
UI.Panel component, and flashes a white color matte on the screen. Finally while the screen is still bright, I destroy the orb itself.
Terrain (of Terror)
Finally, here it comes. The terrain of terror.
In this post I’m going to briefly talk about the methods I’ve tried throughout the design process, drawbacks, and finally how I’ve come to my current (and very possibly my final) design and implementation.
To begin with, let me bring back this picture once again:
As you can see, it’s quite of a large terrain. When it comes to large to infinite terrains, the first problem that came to my mind is the skybox: skyboxes have horizons, and if you intend to design a large terrain, you will soon run into this problem:
As you can see above in the circled area, the part below the horizon can be seen by the player. In fact, because terrains are always finite in size, so theoretically no matter how big you set your terrain, you could always see the depths of the horizon.
In the beginning I used a plugin tool called Terrain Composer. It works very like World Machine, and it creates stunning terrain. Although it’s a great tool, I only need like, 10% of the terrain for the player area. Using this tool creates a big terrain with high details, and I fear that it will lead to a major performance issue. Also, when the terrain gets bigger, it takes longer time for it to respond and it’s slowing me down, and it still doesn’t solve the horizon gap.
Then I turned to another plugin called Horizon[ON]. If I understand correctly, this plugin uses big planes and shaders to mimic big terrains, which does improve performance a lot. The only question I have with it is that sometimes it doesn’t come up in the editor view, making it impossible to edit.
However I did notice something: the edges of the planes used in Horizon[ON] are curved upwards, hiding the horizon. So I thought to myself, I can create two terrains, one for the far view and one for the near. For the former I can just briefly drag some mountains, and add some upward landscapes to the edges of the plane. For the latter I can use more design.
Finally, I added trees and vegetation to these terrains. For performance optimizations I imported two resolutions of the same speedtree: Desktop and Mobile. I planted the Mobile version trees on to the far terrain, and the higher-resolution on to the near one. For further performance adjustments I even removed all LOD details for the mobile trees, leaving only billboard:
The vegetation (grass) though, is something to take extra care of. Originally I just planted the entire play area with grass; this overloads the CPU, for it has to take care of all the lighting coming from the ambient light and the light orbs. Remember, grass in Unity 5.5 cannot be made lightmap static.
My workaround: I reduced the density of the grass, and tried planting multiple different kinds of plants, each with different height. Some are almost as tall as the player is. This not only saves the CPU power, but also gives a good illusion to the player that the terrain is nicely covered with wild vegetation, adding more to that abandoned, wilderness taste.
Finally, I used the pre-made water pro prefab shipped with Unity 5.5 to make the river. Now it looks pretty awesome.
Optimization and Finalization
Towards the end of our production, one thing that bothered me very much was the performance. Every time when I execute the game in Editor mode my CPU workload get boosted up to around 90% to 100%, and my fan speed went up to around 3000~ 4000 RPM. Apparently the game was too computationally heavy, and so I had to take action.
Anyways, the first thing that came into my mind was the flickering shadows. Whenever the camera was pointing at a certain direction (or angle in respect to the directional light), the shadows casted by the trees started flickering. I consulted my friend on this issue, and it turned out that it’s because all my lights are all computed real-time.
To tackle with this issue, I simply have to bake lightmaps…… given the complexity and the structure of my scene, it took me almost forever to bake them. At first I was really surprised by how long these lightmaps would require, but then at UDC 2017 I’ve found out that almost everyone is experiencing the same problem.
Then, I looked over to occlusion. By default Unity does frustrum culling, which makes the camera cull out all items that are not present in its frustrum. But what it does not do is occlusion culling. Occlusion culling means culling out the things that are blocked behind something else, like a bonsai behind a wall, a chair behind a door, you name it. This option is closed by default, and requires another baking process if you wish to enable it. Normally it wouldn’t take long to compute the occlusion, but given the extensive scene I have, it still took me about 30 minutes to one hour.
I do know that there are still a lot of optimization I can look into, but given the remaining time I have left (and laziness), I decided to stop here and conclude my work. Do notice that I did much more than just the two mentioned above, such as revising C# scripts to increase performance.
Finally, for the title:
And an ending shot of the game:
And that concludes the postmortem of this project! Thanks for everyone who stayed until the end. I have to say it’s been a wonderful three weeks of hard work, and I hope that you’ve learned as much as I did through the process. I’d also like to thank my friends and CMU for technical advice (especially Ben Scott, really solid advise on optimization and lighting issues), and my friend Rainbow for the awesome voice acting.
Life is indeed strange but surprising. I had this crazy idea of slightly modifying my game into something like this……
Yep, I used this game to ask Rainbow (the voice actor) to be my girlfriend. Although there were some bumps during that process (like a laptop that ran out of battery in the middle), we still managed to get to the end, and it’s. super. effective.