This week I did some nice polishing work on the panels in order to make them look nicer. I also switched the game from an orthographic camera to oblique rendering. If you have no idea what that is, essentially it means that the game is in 3 dimensions, with the third dimension simply being represented as an offset on the first two. It’s the method that most 2D top-down games use to render their art, although usually it’s not actually computationally represented as 3D objects.
The nice thing about moving to oblique rendering is that you can put 3D objects in the scene and they “just work.” I really only switched because for the snake panels I wanted to do an effect which looked like this, where the individual tiles popped up and down:
There is a problem with doing this in a completely 2D method, because then you need to worry about sorting of the individual tiles. Although this is not a particularly complicated problem, it just seemed easier for me to switch to an oblique rendering mode and make each tile literally pop up on the Z axis.
I may regress on this decision at some point, but really there’s no downside to rendering with this method, as if you keep everything on the same z plane, it’s exactly the same as 2D. The main issue that kept me from going to this method earlier was a weird sorting bug, which I eventually tracked down to the fact that my multi-pass rendering method (which I use to get smooth sub-pixel motion that preserves the sharpness of pixel art) was set up to use a render texture without a depth-buffer.
But as for the actual 3d objects part of it for this particular effect, I may change my mind, as I want to have tiles both on the floor and on walls, and due to the way the projection works, the tiles on the wall would need to be rotated along the Z axis to properly be drawn, which would require changing the way the panels work somewhat. Either way, it shouldn’t be too much work to switch it over.
Here’s a (poorly compressed) clip of the results. I think it’s pretty close to the above mock-up:
— Matthew VanDevander (@mvandevander) June 7, 2016
The only downside to doing this polish work is that I ended up adding a lot of fancy interpolation and additional detail objects to the tiles on the panels, which resulted in some performance issues. The game got down to running around 20fps on my (reasonably good) desktop PC.
So, I decided to take some time to do an optimization pass on the game. Which was pretty fun. I relied quite heavily on Unity’s built-in profiler tools, which can be very helpful sometimes and not really helpful at all other times. In particular, I’m still having a NullReference issue on script reloads which I am not really sure what to do about yet. I’ll have to build a small test scene to really track that one down.
I won’t go too much into the details of the optimization, but I did want to point out a couple of fun things which I learned.
Color == Color is quite slow in Unity because under the hood it is doing a approximate equality test. So I gained a 10x speedup just by switching to a component-wise test on color equality. I know, I know, you’re not supposed to directly compare floating point values, but in this case I believe it was acceptable as I’m only trying to see if I already set a value or if just changed this frame. You might ask why I didn’t just use a has_changed flag and a setter function. And the answer is somewhat complicated, and mostly has to do with having a lot of state that is not being properly tracked in one place at the moment. Which means I probably need to redo a lot of the panel code to consolidate the state tracking and so something a bit more of a FSM per each tile than what I’m doing.
The other interesting thing was, there was a serious overhead just from the number of active GameObjects I had in the world. (Over 10,000) For active GameObjects, at least those with an attached script, Unity has to call the Update() function on each of them, even if it immediately returns. The optimization here was pretty simple, just disabling panels when they are not on the screen.
Another little bit there that was kinda wierd, I was using Unity’s built-in Bounds object to store the boundaries of both the screen and the panels. Unfortunately, again, accessing Bounds.min and Bounds.max was secretly under the hood doing some computations to figure out those values, as they are not actually stored. And since I was checking each panel each frame to see if it had become visible simply by comparing the bounds, and there are over 200 panels in the game, that was contributing a few milliseconds just by itself. A fairly simple fix again, just cache those values and compare the cached versions rather than using the Bounds object directly.
Perhaps the lesson I’ve learned here is to avoid using the built-in Unity types as much as I can, as they all seem to do unexpected and somewhat computationally expensive things under the hood when you are just attempting to access a value or compare equality. Also, the dangers of overrides.
Anyhoo, a bit of a technical week, as I continue implementation of the new interface system, but hopefully it’s been fun to read about some of those details for some of you. 🙂