When it comes to my own personal projects or ideas, I have a very hard time finishing anything. I love dabbling and tinkering and fulfilling some curiosity, but it is almost impossible for me to 'deliver'. So when I was recently motivated to play with some pixel art, what started as a small toy project pixel-art experiment evolved into a small game that was completed within a few weeks. This is the story of the process of making that game.
Inspiration
The original inspiration came from a few sources. Firstly, I recently attended the Seattle Retro Gaming Expo. Among other things, there were a few booths in which some indie devs were showing off their retro-looking games. Needless to say, I saw some mediocre pixel games that had been worked on for mere weeks before being presented in the Expo and felt I could do better. Secondly, I attended the Seattle Code Camp recently, at which there was a Unity demo in which the presenter created a Flappy Birds clone within an hour. It helped me remember how simple things can be behind the scenes and still look good and be fun. Finally, I have not yet dabbled with Unity 5 or any of the newer features even in previous versions, such as 2D sprite animations, new UI system, and HTML5 deployment.Environment
One interesting aspect of this project was the environment. Since I didn't have Unity 5 installed on my laptop, nor the space to install it, I opted to try development on an Amazon Web Services EC2 instance that had GPU capabilities. I had dabbled previously with that, having set up UE4 on there since it ran slowly on my laptop. Basically, this was a virtual Windows machine in the cloud with a GPU that I could remote desktop into and work on as if I was running Parallels locally on my Mac. In order to do work, I would start up my instance, remote-desktop into it, work for a while, then commit changes and shut down the instance. Interestingly enough, it was much better on my laptop's battery life than running Unity locally. Maybe I'll discuss this more in a later post.
Day 1
I'll discuss the first night in a bit more detail than other nights since it really set the tone for the entire project. I sat down on a single night with nothing but the inkling to play with pixel art in Unity. I knew I was just going to spend the night gratuitously slinging a bunch of animations and things together to make something. Anything.Continuation
It wasn't until after the first night that I decided it was something I could feasibly bring to completion. The first goal was to improve the pixelation effect, which was producing a strange wobble due to how the background was being discretized to the render target. Adding game elements would be less of a priority than the pixel effect, and honestly not quite as interesting.
The single biggest technical hurdle was proper pixelation. That was the whole impetus for the project, so that was the one thing I was determined to get as good as possible. The first version had a noticeable wobble or shimmer effect, and took a few days to understand where it was coming from and how to fix it. Here come some technical details.
Scene Organization
The scene is split into 2 smaller sub-scenes. One represents the 'world' where the objects reside, which is a 20 x 10 Unity units (meters) area where game objects reside as sub objects. The camera of the 'world' scene renders to a 128 x 64 pixel render target. It uses an orthographic camera so all of the size and speed scaling based on distance is handled manually. More on that later. The 'screen' scene simply renders that render target to a quad and displays it to the main camera.
Interesting to note, in the version that I released, the screen scene's camera frustum extends into the world scene, which probably means it's not culling the objects in the world scene behind, which could be a huge performance issue. Whoops.
Texture Filtering and Source Art Size
This topic is the single most important part for proper pixelation. Point-sampling was used by all textures and sprites to prevent the default texture sample filtering from making it look blurry. Without point-sampling, everything looks too fuzzy and objects have a semi-transparent boundary, which ruins the effect. Pixel art is very high-contrast, usually with no semi-transparent sections or borders. Secondly, keeping the source art of some multiple of the final resolution kept the objects from having semi-transparent edges and prevents the wobble effect. The following examples highlight the need for this.
- Bilinear filtering is on the source image and the image is a multiple of the screen pixel resolution. The result is intermittent blurriness as the background slides across the screen. Filtering calculates averages as the the image pixels map to different screen pixels over time, making it blurry.
Bilinear filtering on source texture causes fuzziness |
- Point sampling is on the source image, but the source image resolution is not a multiple of the screen pixel resolution. The result is a wobble or shimmer as source image pixels are discretizing to screen pixels inconsistently over time. In this case, a 512 x 256 image is inconsistently mapping to a screen pixel resolution of 140 x 70, resulting in wobble.
Point sampling on source texture whose resolution not multiple of pixelated screen resolution causes wobble |
- Point sampling is on and the image is a resolution a multiple of the screen pixel resolution. This results in a proper and consistent mapping between image pixels and screen pixels over time.
Point sampling and source art resolution is a multiple of pixelated screen resolution results in good movement |
Those with a keen eye will notice some of my background art suffers from some issues still. Namely, there are some semi-transparent edges on some of the parts of the building. It's true, I wasn't that diligent about ensuring pixel-perfect source art, and so there are a few small issues in the backgrounds.
Object Position Discretization
There were two types of objects in the scene. Backgrounds (mentioned previously) were simple scrolling textures on quads that were scaled to the entire 'world', that had texture wrapping on. Background props and enemies, other the other hand, were Game Objects that had a sprite renderer and had a physical position within the 20 x 10 world. Removing the shimmer / wobble on objects required extra work.
I tried and failed to find any built-in Unity solution to this, though granted I didn't look too hard. While there were options in the Sprite importer about specifying 'units per pixel' or whatever, which helps you define how the sprite would map to your world-space units, there didn't seem to be anything that could lock or discretize transform or physics positions to the screen pixel boundaries.
I created a script to keep them snapped to the pixel grid kept the pixelated object consisted despite its position, rather than shimmering across screen pixels. Interestingly, at the time of writing this blog, I realized that script wasn't necessary because the source-art, already being a multiple of the screen pixel resolution and being scaled appropriately to match texture pixels to screen pixels, was already correctly preventing the wobble. But such a script would be necessary to allow for arbitrary pixel sizes or screen pixel resolutions if it was desired that the source art wasn't authored at a resolution that was a multiple of the final resolution. Though you could still have fuzzy borders in that case. In retrospect, the sprite asset's 'unit per pixel' attribute could potentially handle this correctly.
The rest is fairly simple. Scripts on the background props slide them to the left at a designer-specified rate per frame. The ones further back are smaller and scale more slowly. After a certain x-position threshold, they teleport to the right and recycle. Simple.
The rest is fairly simple. Scripts on the background props slide them to the left at a designer-specified rate per frame. The ones further back are smaller and scale more slowly. After a certain x-position threshold, they teleport to the right and recycle. Simple.
Other Gamey Junk
There are aspects of game development that I love and other aspects I find extremely tedious. I preferred to concern myself with the aesthetic, technical implementation of the aesthetic, and over-all look and feel for this project. This involved Googling for more borrowed art, such as the sprite animations for explosions and the weapon firing. I did not want to focus on the 'gamey' aspects, such as score, enemies, rotating projectiles, and player death / loss condition. I found those elements extremely tedious, though necessary to make it interactive and more appealing to a broader audience. Also, those aspects aren't particularly interesting to read about since any game has those hurdles to overcome. And that's also why the game isn't particularly interesting - no interesting enemy waves, or escalation of difficulty, or anything that makes for a good game. Oh well. Oh yeah, You can see some of the sprite sheets I found below.
Sound Effects
All sound effects were added within a few hours. Most sounds were found on Freesounds. Again, this goes in line with 'borrowing' art to make a lot of forward progress. The 'music' was constructed from scratch using Ableton Live. The shooting, explosion, footstep, and death / respawn sounds were from Freesounds, though the footsteps were massively modified in Audacity, and the death / respawn sound was the combination of multiple sounds, also done in Audacity. Also, the explosion and shooting sounds were played with slightly randomized pitch and volume to prevent it from sounding too unnatural. Ultimately, sound effects were one of the last things to go in, but they turned out pretty well and really helped add a final level of polish despite being done pretty quickly. But again, the implementation was nothing special.
Post-Mortem / Restrospective
Simply by writing this blog and attempting to explain how it worked, I discovered a few small issues with the pixelation that I spent a non-trivial amount of time and effort trying to work around. Had I been in less of a rush, I could have found simpler solutions sooner rather than trying to build around them.
What Went Right
What Went Right
- Borrowing art - looking good early on helped with motivation
- Working within constraints
- Following motivation (local maximums for motivation) rather than having a grand vision for the entire thing I just worked on each little piece as I was inspired to address it.
- Finishing something
What Went Wrong
- Lack of a game/interactive design
- Pixel shortcuts - working around some pixelation issues rather than finding the cause
- Taking longer than expected - I had hoped to release within a week, but I didn't have time so I counted days that I worked rather than an actual amount of total time.
- Borrowing art - lack of complete ownership
All commits for the project, giving a sense of the order things were done |