Making a Small 2D Tool Kit With Minimal Experience
Coming from a long held appreciation for retro or pixelated graphics and a desire to bridge the gap between these styles and more modern approaches to graphics and animation, PixelSprite is a work-in-progress plugin for the realtime generation of dynamic pixel art in Unity. Also included in the package are generic 2D tools for bendable objects, color ramping / palette swapping, mesh vertex snapping and more. With no real software development experience I've set out to put this project together, and now that's it's at an almost finished (maybe?) state I figured I share it publicly!
What does the thing do?
Simply put, PixelSprite is a toolset and pipeline which allows for the conversion of arbitrarily rendered objects in Unity to downscaled pixel art. Compatible with sprites, 3D objects, particle effects, and more, PixelSprite serves as a bridge between modern game engine functionality and the stylized graphics of yesteryear.
The first project made using this plugin was actually Too Many Bears, my entry into the 40th Ludum Dare game jam. I had started trying to figure out how to achieve a convincing pixelation effect shortly after I first dove into Unity a few years ago, however it wasn't until the week of this Ludum Dare that I developed many of the final techniques used. Deciding to take part in the jam just a few hours before it started, I figured it would be a great opportunity to test out what I had made.
This is also when I made a simple implementation of bendable limbs using a sort of 'pseudo IK' technique using a simple line renderer. In the context of a 72 hour long game jam, this technique saved me quite a bit of time, letting me do all my animation through Unity's native keyframe-interpolation system rather than having to draw or pre-render hundreds of frames of animation as one might need to when working with a traditional pixel art production process. The image above also shows a simple use case of basic color LUTs for palette swapping.
In line with my vision for over-the-top flashy visuals, I made some additional tweaks to get the system working nicely with particle systems, opening up a whole world of possibilities for producing effects that would be impossible to recreate by hand (in a timely fashion, of course).
Much of the motivation to continue working on this project came through the wonderful reception of the game at the end of the jam. Despite its simplicity and lack of much actual content, Too Many Bears managed to place 45th among all the "jam" entrants (72 as opposed to 48 hours long), it's highest rating being graphics where it received a very strong 16th place! With such positive feedback I realized that there would certainly be interest in a general-purpose toolkit allowing for easy production of these types of visuals.
How does it work?
Early attempts at getting consistently oriented pixels to be drawn on screen (rather than the rotated squares you typically get with sprites in Unity) used shaders that would rotate the sprite's UVs while resizing an axis-aligned mesh. This generally worked pretty well but didn't handle scaling in an elegant way, nor was it extendable to 3D geometry or effects. The current process consists of rendering each object possessing PixelRenderer component with an additional camera directed to render into a RenderTexture of a given pixel size.
This camera essentially takes a picture of each object and these pictures are then drawn as simple meshes with various values set in a MaterialPropertyBlock. No additional game objects are created since the DrawMesh commands are inserted into a CommandBuffer attached to the actual main rendering camera.
While this approach seems to work even with many objects on screen at one time, additional optimizations will hopefully remove some of the steps involved with having additional cameras and and extra Render call on each object. Additionally, the calculation of the drawn meshes' transforms will eventually take into account properties of a 3D camera, removing the restrictions of 2D movement and reliance on orthographic projection.
The final meshes are rendered using either a default material specified on a ScriptableObject 'profile' or an override specified on each PixelRenderer component. This allows arbitrary shader code to be used on the resulting downscaled images, including the simple outlining and color ramping shown earlier. Outline colors and color LUTs also have a default value specified per profile and overrides specified per object, passing values to the material using a MaterialPropertyBlock.
As mentioned earlier, there is certainly room for optimization across all parts of the added pipeline. Additionally, while I managed to fix some of the more atrocious general-use issues I had encountered while making my game for the Ludum Dare (namely a lack of ability to specify final render order on a per object basis), there are undoubtedly many more issues that will come up as I continue to test drive the system and add new functionality.
Since this is something I'm only working on every now and again when I need a break from other game projects and school work, there isn't any sort of planned release date or time frame. Once it's in a place where it seems reasonably usable, scalable, and maintainable I plan on releasing it for free on the Unity Asset Store, as well as putting it all up on GitHub so people can contribute and and all that good stuff. (Yet another learning experience for me since I don't really know how all that open source stuff works yet!)
If you're interested in the project and think you'd be able to make something with it, let me know via email (email@example.com) and I'll get you hooked up with whatever the current iteration of the plugin is looking like! No guarantees yet about performance or scalability so it probably wouldn't be a good idea to start using it in an actual project, but I'd love to get feedback on usability and other feature suggestions!
Also if you're interested in Too Many Bears, the source of many of the .gifs and screenshots above, check it out here!