Monday, March 25, 2013

Back in the Triple Digits

Just truckin through my backlog, on my way to full vertical slice, and not that far off either. The shooting and jumping aren't exactly ... inspiring, but at least they work. I spent a lot of time fooling with gravity when I was making the floor and platform tiles, trying to find that right scale for the "running jump" mechanic. The way I wanted it to work, you could complete the level just walking, like if you didn't ever realize holding the fire button makes you go faster (I do change the animation, I tried to make it obvious), you should be able to get all five bus transfers just by jumping normally, although you'd have to take some circuitous routes and it would be a pain in the ass. At the same time the running jump couldn't be too powerful, or since the platforms are so close together you get this queasy thing where the player just jumps toward the side of a platform, slides up, and ends up standing on top, which isn't very jump-like. I tried adding a RigidBody component for physics jumps in the hopes of getting something springier, but the intersection of the RigidBody and character controller was causing some Exorcist-like behavior so I bailed on that one. I'm not thrilled with where it's at now, but I can live with it a while longer.

Anything the player can touch is a primitive 32x32 cube displayng a frame (via a scaled and offset material) of one of two 256x256 tile sheets. The buildings and clouds are big textures (the clouds are 4096 wide) painted on scaled up quads, and scripted to move in parallax to the player. Most of the collision volumes are cubes the size of the tiles, but platforms have scaled down collision boxes, as do some of the objects. I'm still using the old pickups but I'll probably make them tiles too eventually. The frame rate hangs reliably in the triple digits. This concludes, knock on wood, the technical implementation of the guts of the game. The rest is window dressing, oh and design.

I re-learned some things about instantiating and destroyng prefabs. I spent a little too much time re-learning that a switch/case runs on break; statements and yes, they mean it, and any code you had in that function below the switch/case is not getting run, which is probably why it is not as effective as you thought it would be. At least, I think that's what happened ... regardless, it works now. I re-learned that your first level design instincts are often too big. This level is actually pretty small for a platformer now, but only because I wanted everything to feel closer together. In a sense all the objects really are kind of tetris-jammed together, but it's really the same set of elements from the larger draft of the level, just brought in tighter, I think I only deleted like two objects. It's odd that I got a lot of stuff right the first time but I got the scale totally wrong.

The actual design of the level, given the constraints imposed by the grid, the jump tuning, and the five bus passes (plus a bus stop), was dirt simple. Figure out how to make a bus pass hard to get to, then do four variations. One is on a catwalk, another is tucked behind a roving skeleton, one is in a corner. None require run jumps but all are made easier by run jumps, run jump is frankly a little OP right now but it feels better than it did. I even started to polish a little by adding particle effects to the bus passes, but of course now I'll need particle effects for everything...

Next is the level-end bus-pickup "experience", the fail/restart, maybe some options geegaws, and another say three levels, all built with similarly small tilesets, maybe one extra enemy per level and that's about it. With my schedule the end is still months away, but I can see it, coming ever more clearly into focus. Gotta get this puppy out the door!

Tuesday, March 12, 2013

Performance Anxiety

What little rationale there was behind building a 2D tile-bsed platformer in Unity went something like this: learn the tools and workflow on something small enough where you won't have to worry about optimizing for performance. This was naive.

The tile map implementation I described previously involves painting tiles in the scene view, each tile consisting of a textured mesh. When I picked my head up from my second layout of the opening level (wonder what number that will get up to) and pressed Play, Unity thought for awhile. Twenty-one seconds, to be exact. My gameplay and scenery grids combined were holding 8087 tiles, each of which contained a single quad.

Many things were vexing about this. For one, the stats window showed 384 verts comprising 192 triangles, which didn't seem like all that many to me. For another there was the fact that after that twenty second startup, Unity seemed pleased enough to hum along at a triple-digit FPS while the playfield was traversed; there was no in-game performance hit. Without a deeper understanding, or the CPU/memory profiler Unity offers for a mere fifteen hundred bucks (along with a Pro license), there was little I could do but conclude that I was just Loading Too Many Things.

Idea #1: "Well, how many is too many?"

My Test mind kicked in. I wanted to repro the issue, isolate it from its context. I made the scene shown above, with a total of 10180 red cubes, boasting 104,800 vertices making 52,400 triangles. The whole thing loads in less than three seconds. OK, interesting. Apparently 8087 tiles is not too many. What else is going on?

Idea#2: "It's the materials."

I was doing what I thought was a really swell thing by confining my entire city scene to one 256 x 256 texture made of tiles, which were arranged for display, uv-wise, by a material unique to each tile. Maybe that's bad? Really grasping here, not even mentioning stuff I thought of before the red cubes, like stitching all the meshes together at runtime (possible, but goodbye texture data and anyways I tested it and that would happen in script after my startup problem is already come and gone, so who cares?).

Anyway the point is I was desperate for a simple answer. I decoupled all the materials from all the prefabs, turning everything in the level pink. My start time somehow went up to 24 seconds.

Idea #3: "It's a script"

Since this was a one-time perf hit at startup, perhaps one of the scripts was looping over the tiles at startup in some poorly thought out way. I feel like I would have noticed that earlier than I did? Whatever though. Let's comment everything out. 24 seconds.

Idea #4: "Uhh maybe ... the tile editor's Instantiation method, isn't that deprecated?"

Ugh fine whatever. Let's rebuild the level from scratch, with the same tools, and figure out where and when the perf hit starts to happen. OK looks like when we get over about a thousand of these we start to bog down. With the new version of Instantiate, and without materials. These objects are piling up at around 1000, and the cubes were loading like butter into the quintuple digits.

Idea #5: "Something about a mesh as opposed to a cube?"

Turned off shadows. Tried a cube. Tried a sphere. 

PrefabUtility.InstantiatePrefeab and it's deprecated Editor cousin allow you to do things like paint in scene view, you're painting with prefab instances, so it's hard for me to conceptualize that you would need to Instantiate all those again at runtime, because I already did that, I'm looking at them, they're right there, but I guess what I'm looking at could be a preview of some kind. It would make sense that it would take some time and resources to bring all those cubes into being, and that's really the only thing left that seperates that tower of cubes from my level: the cubes aren't prefab instances.  

That points me toward really dreadful things like asynchronous level loading (another pro feature), and eventually I arrive at

Idea #5: "I guess we'll have to find a way to render a level without loading thousands of tiles"

I guess you could, you know, use a hundred or so tiles for sidewalks and platforms, then just draw some backgrounds and put them on big quads. Rather than, you know, making a skyscraper out of hundreds of individual meshes, each uv mapped to part of a single small texture, a "solution" that somehow joins the worst aspects of all possible approaches. I was so close, though! Throw that texture in and just paint the level, so convenient! Wait, hold on, if prefabs are the problem, why not

Idea #6: Alter the editor window script to stop instantiating prefabs and instead make meshes from scratch with the appropriate materials on them!

Oh lord. All right, worth a shot. Redid the tileMap code to make a primitive again, like the demo script, instead of instantiating. Used cubes as Unity has no Quad primitve type. Rotated the cubes. Updated the custom editor to paint materials instead of prefabs. Applied a variety of textures to simulate what I'm actually doing in the level.

Bam. 7956 cubes. Feels like between three and four seconds. Now, though, even that length of load feels kind of intolerable. Eh, I think if I do the buildings as scaled-up single quads and leave the tiles for the floor and platforms I'll be OK, shouldn't be more than a second. The lesson, as far as I can tell is: prefabs are slow. That is, unless after my third buildout of the level I find out the problem is actually something else...

Saturday, March 2, 2013

bool GoodEnough == true;

I'm wrapping up my adventures in custom editor land, at least for the time being.

The EditorWindow script actually gave me some serious headaches because of the way I wanted to structure the grid system. The idea was to have three separate grids, each stacked on the z-axis with 32 pixels between them, one for gameplay objects and tiles, one for buildings and other scenery, and one for the backdrop. Problem was, the EditorWindow was set up to find the grid to edit like this when it was opened:

tMap = (tileMap)FindObjectOfType(typeof(tileMap));

which works great when there's only one grid. With multiple grids, each with a tileMap script attached to it, the EditorWindow will only ever find the first one in the list. I tried a number of ways to allow you to choose a grid from within the window, but my first several attempts caused some kind of recursive error that locked Unity up by throwing dozens of null reference errors every second. This slowed down my problem-solving workflow a bit. After a bunch of Googling and cobbling things together, I finally came up with what is perhaps the single ugliest line of code I've ever typed:

allTileMaps = (tileMap[])tileMap.FindObjectsOfType(typeof(tileMap));

Christ, look at that thing. Just look at it. I know what it does but I couldn't tell you why. For good or ill, this monstrosity turned out to be completely irrelevant anyway. After grappling with various methods to expose the members of that array in the editor window, I finally realized that all my trouble was being caused by this line:

tMap = EditorGUILayout.ObjectField("Active Grid", tMap, typeof(tileMap), false) as tileMap;

The culprit was that "false", which is setting the boolean variable "allowSceneObjects", which was keeping me from applying any of my three grid GameObjects within the editor window. This made me think I needed some kind of superstructure to hold every object with the tileMap script attached, which just wasn't true. The embarrassing takeway: just because you found some functional code to copy from somewhere doesn't mean you've solved your problem. If you don't understand exactly what the code is doing, you're as likely as not to cause yourself more problems. I guess this is one of those obvious things that you need to teach yourself the hard way, like a child touching the burner of a stove to see if it's hot.

So here, below, is the final version of my built-in level editor.

That's it - a few data fields, a color picker for drawing the grid, and a Texture2D field that was supposed to show a visual preview of the selected prefab, but which I wasn't able to get working and eventually gave up on. It's more than a little pathetic, but here's the thing: it works. I can click the Active Grid field to select which grid to operate on, then select my prefab "brush", then press 'a' to paint tiles at the mouse location. It's much faster than my old way, and it keeps everything aligned properly, which allows me to get on with building the levels.

I recently listened to a fantastic talk by Jonathan Blow called "How to Program Independent Games" . There's a lot of good advice in there that really brought the dangers of premature optimization home to me in a way I hadn't really grasped before. The key quote, to me, was this:

"I'm a big fan of 'good enough for now', because 'good enough for now' often ends up being 'good enough to ship the thing.'"

We all want our projects to be elegant, sharp, and impressive to our peers. We all respect and want to emulate those heroes of old who had to bend over backwards to cram every possible byte into primitive cartridges and arcade cabinets. We all want to feel smart and capable and professionally ingenious. These are not bad things to want. Ultimately though, as Blow points out in that talk, there are only so many hours in each human lifetime, and there are only so many projects that you or I will ever complete. At the end of your time as a game developer, however that end comes about, which would you rather have: a few completed projects that emphasize clean, correct, bulletproof code, or a bunch of completed projects that emphasize fun, storytelling, cool levels, innovative gameplay and all the other things that motivated you to get started in the first place? It's not a trick question, and it's not a hard choice.