public class PixelTestTest { public void nocabDrawPoint() { Texture2D nocabTex = new Texture2D(32, 32); int x = -1; // Numbers off the edge wrap int y = 16; nocabTex.SetPixel(x, y, Color.black); nocabTex.filterMode = FilterMode.Point; // Tell Unity not to blur neighbor pixels nocabTex.Apply(); // Expensive, do only at the end // Moved to PixelDrawer static helper class Rect rect = new Rect(0, 0, nocabTex.width, nocabTex.height); Vector2 piviot = new Vector2(0.5f, 0.5f); return Sprite.Create(nocabTex, rect, piviot); } }
This is the Pixel Art Toolbox, a collection of functions that allow us to programmatically draw pixel art in Unity. But before we can make anything complicated (or even before we can make anything simple) we need to be able to make something. How do we draw a single pixel in Unity?
We’ll use the Unity 2D Sprite Game Object. Every “Sprite Game Object” has a “Sprite Renderer” component attached. One element of this Sprite Renderer component is a “Sprite”, confusing I know. See the image below.
This is good, but we can’t create the green box Sprite fields very easily in code. However, every sprite has a thing called a texture. Textures can be created easily and tells the sprite what it should look like.
Take a look at this code that builds a 32x32 texture and draws a pixel to the center.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class NocabPixelArtScratch : MonoBehaviour { public SpriteRenderer sr; public void nocabTest() { Texture2D nocabTex = new Texture2D(32, 32); int x = 16; int y = 16; nocabTex.SetPixel(x, y, Color.black); /*16*/ nocabTex.filterMode = FilterMode.Point; /*17*/ nocabTex.Apply(); // Expensive, do only at the end /*19*/ Rect rect = new Rect(0, 0, texture.width, texture.height); /*20*/ Vector2 piviot = new Vector2(0.5f, 0.5f); /*21*/ return Sprite.Create(texture, rect, piviot); } }
Review the battle strategy:
Step 3 is important, there are some magic words that need to be used in order to tell unity that we want pixel art and not regular sprites.
Line 16 tells Unity that we want to draw pixel art and to use crisp edges between pixels. The other options (FilterMode.Bilinear and Trilinear) cause the pixels to blur and smooth into their neighbors.
Line 17 actually causes the pixel changes to go into effect. Changing pixels on a texture is a moderately expensive operation, so any SetPixel(...) functions don’t immediately change the pixels. Instead they are all queued up and done when the Apply() function is called. This should be done as infrequently as possible.
Lines 19-21 actually don’t accomplish step 4 in the battle strategy. It turns out that it’s easier to build a brand new Sprite with the new texture than it is to update an existing Sprite’s texture. Line 19 defines the new position and size of the sprite, (0, 0) and (nocabTex.width, nocabTex.height) respectively. Line 20 defines what part of the rectangle should hover over the (0, 0) point. Basically we want the center of the sprite to be this important point, 50% the width, 50% the height. Line 21 takes all this info and packs it into a new sprite object using the Create(...) method. Then we simply tell the sprite renderer to use this new sprite.
Lines 19-21 are annoying to write, so I built a new static helper class that can take care of it for me.
using System.Collections; using System.Collections.Generic; using UnityEngine; public static class PixelDrawer { public static Sprite packTexture(Texture2D texture) { Rect rect = new Rect(0, 0, texture.width, texture.height); Vector2 piviot = new Vector2(0.5f, 0.5f); return Sprite.Create(texture, rect, piviot); } }
public void nocabTest() { Texture2D nocabTex = new Texture2D(32, 32); int x = -1; int y = -1; nocabTex.SetPixel(x, y, Color.black); nocabTex.filterMode = FilterMode.Point; nocabTex.Apply(); // Expensive, do only at the end sr.sprite = PixelDrawer.packTexture(tex); }
Let’s add a Start() function and make a SpriteRenderer object in our scene and see what the code does does.
public class scratch : MonoBehaviour { public SpriteRenderer sr; public void Start() { nocabTest(); } public void nocabTest() { Texture2D nocabTex = new Texture2D(32, 32); int x = 16; int y = 16; nocabTex.SetPixel(x, y, Color.black); nocabTex.filterMode = FilterMode.Point; nocabTex.Apply(); // Expensive, do only at the end sr.sprite = PixelDrawer.packTexture(tex); } }
We can now draw a single point! Huzza! We conquered the point, next the line, then the circle, and finally the world...
But there’s one last thing to know about Unity Texture2D pixel art. The point (0, 0) is the bottom left with positive x going left, and positive y going up. For more experienced programmers, you may be used to (0, 0) being the top left point of the sprite but unfortunately I don’t make the rules. At least the Texture2D is 0 indexed.
Change x and y to be 0 and see where the point is drawn.
public void nocabTest() { Texture2D nocabTex = new Texture2D(32, 32); int x = 0; int y = 0; nocabTex.SetPixel(x, y, Color.black); nocabTex.filterMode = FilterMode.Point; nocabTex.Apply(); // Expensive, do only at the end sr.sprite = PixelDrawer.packTexture(tex); }
The last important thing to note about pixel art is that the Texture2D “wraps”, in other words the moving off the edge of the canvas texture will cause the point be drawn on the other side of the texture. Consider the drawn point (-1, -1):
With this understanding we can now overengineer the ability to draw a line.