Want to draw your own sprites? Then Mark has the pixel editor for you, fresh from the pages of the new Wireframe magazine.
Every game needs graphics, and if you’re into making retro games like us, you’ll need to have a program to draw them. You could use free programs like Microsoft Paint, but alternatively, you could write your own custom pixel editing program, which we’re about to do here using Python and Pygame Zero. It’s relatively easy to set up the basic drawing tools that you need to start making sprites for your own games.
First, we need to set up a drawing surface in which you can see and edit individual pixels. To do this, we’ll need a function that’ll let us zoom into an image. If we use an area of 600 × 600 pixels and display the image six times larger than the actual sprite, that means we end up with an image which is 100 × 100 pixels in size. We can set up our blank sprite image by declaring it as a Pygame Surface. We’ll also set up a variable to define which colour we’re going to use as transparent (in this case magenta) and fill the Surface with this colour, having set the colorkey property of our blank sprite to the transparent colour. Now we’re ready to set up our drawing system.
It’s useful to have a grid over the drawing area so that we can see where the individual pixels are. We’ll need to draw a grid of 100 horizontal lines and 100 vertical lines, but rather than redrawing the grid every frame (which would be quite slow), we can draw the grid to a new image surface and just use that in our
draw() function. This means that the grid will be drawn using a blit call and will be much quicker than drawing it line by line each frame. We create our grid image at the beginning of the program by making a Pygame Surface of 600 × 600 pixels. We set the colorkey property of the image to our chosen transparency colour, fill in the image with the transparent colour, and then start drawing the grid with two loops, one for the x direction and one for the y direction.
We’ll need to set up some variables to track things like the current mouse coordinates and the current drawing colour, which we can set to something like red to start with. The three values we pass to the
pygame.Color() function are red, green, and blue values, so (255, 0, 0) represents red. We also need to define which drawing tool we’re using, but more on that later.
Let’s set up our interface next. Using 600 pixels of the window for the drawing area leaves us with a column of 200 pixels if we’re using the standard 800 × 600 Pygame Zero window. If we have our drawing area on the left, we can put our tools interface on the right. In this tools section, we’ll need a colour palette, some buttons to switch between different drawing modes, a normal size drawing of our sprite, and perhaps a save button so that we can save our work once we’re finished.
The colour palette is probably the easiest part of the interface. We just need to draw an image with a range of different colours on it. The one we’re using has gradients between all the colours, so we can select any shade we want. You could use an image with fewer colours if you wanted to stick to a more defined palette. With our palette image being drawn in the tools section, all we need to do to change our drawing colour is catch the
on_mouse_down() event and test to see if the mouse is over our palette image. If it is, we change the
curColour variable to the colour of the pixel under the mouse.
We need to show what the sprite we’re drawing will look like, so we draw the surface we created for our sprite to the tool interface panel. It will be transparent to start with, so it’s probably a good idea to draw a border around the sprite so we can see where it is. We want the same image to be displayed in the drawing area but six times larger – to do this we can use the
pygame.transform.scale() function, which will return us a surface that is 600 pixels square but a larger copy of our sprite, which is 100 pixels square. We can blit this larger surface to the screen and then blit the grid surface we made earlier over the top.
We can track the mouse movements over the drawing area by using the
on_mouse_move() event, and in doing so, we can then draw our brush that will change the pixel colours of our sprite. This will not only show where the pixels will be changed if we click the mouse button, but also what colour is going to be used. We’re going to set up two brushes: the first will represent one pixel in size, and the second a larger circle to fill in more pixels at once. As we track the brush around the drawing area, we’ll either draw a square six pixels by six pixels in the current colour, or if the second tool is used, a circle of radius 24, which will end up drawing a circle radius four pixels on our small sprite.
So now we have a basic way of moving the brush around the drawing area and seeing where it’s going to draw. If we click the mouse button, we want the program to change the colour of the pixels in our sprite. To do this, all we need to do is catch the
on_mouse_down event, and after checking that we’re over the drawing area, divide the mouse x and y coordinates by six to get the position on the small sprite, then either set the pixel to the current drawing colour at that location or draw a circle if we’re using the larger drawing tool. Because our drawing area is just a scaled-up version of the smaller sprite, the next time the draw() function is called, we will see the drawing area update.
The on_mouse_down() event provides us with the moment that the mouse button is pressed, but usually when we’re drawing we’d want to hold the button down and drag the mouse to continue drawing a line. We can make this happen by setting a variable called drawing to ‘True’ when we get the on_mouse_down() event, and setting it back to ‘False’ when we get an on_mouse_up() event. Then, when we detect an on_mouse_move() event and the drawing variable is True, we know that the mouse button is being held down and we continue setting pixels under the mouse cursor as it’s moved. This means we can draw lines rather than just setting one pixel at a time, or one round splodge with the larger brush.
Now we need a way of switching between our different brush sizes. We can create some buttons on our toolbar interface. To do this, we need a button image for a single pixel, a button image for the larger circle brush, and a highlighted version of both buttons. When we draw the toolbar area, we check to see if our curTool variable is set to ‘1’. If it is, we draw the highlighted version of the single pixel button – if not, we draw the normal version. We do the same with the second tool button, but checking to see if the curTool variable is set to ‘2’.
We also need the ability to erase pixels back to transparent. With this in mind, we can add a third button to our toolbox in the form of an eraser. We can toggle this on and off with a variable
eraseOn much the same way as the other buttons, but these variables can work together so that the eraser can erase single pixels or larger circles much the same as when we’re drawing with colour. All we need to do if the eraser tool is selected when the mouse is clicked is draw with the
transColour colour, which we used to set the
colorkey property of the sprite.
We can now draw in any colour on the enlarged drawing area with two different sizes of brush and erase any mistakes we’ve made, and we can also see the sprite displayed at its actual size in the toolbar interface. All we need now is a way to save our work. The simplest method to achieve this is for us to make another button on the toolbox panel labelled ‘Save’ and wait for a mouse-click over the button. You might want to add a highlighted state like the other buttons so that the user can see that it’s been pressed. When the button’s clicked, all we need to do is call the
pygame.image.save() function and that will save our sprite image to the file name of our choosing. We’ll save the image as a PNG file as that will enable us to have transparent pixels. To make sure that the transparent colour’s handled correctly when saved, we’ll call the
convert_alpha() function on our sprite surface to make the transparent pixels compatible with the PNG format.
So there we have it: a basic pixel editing program. There are many more tools and features that you could add to make it more useful and easier to use. You could incorporate the Tkinter module to provide load and save dialog boxes, or have other shapes as drawing brushes. The possibilities are endless, but we’ll leave you to explore those for yourself.
Get your copy of Wireframe #68
And if you prefer your magazines in digital form, you can also download Wireframe issue 68 as a free PDF!