*This tutorial was written for Gamemaker Studio 2, but should work in Gamemaker Studio 1.4 as well.
What is a palette based sprite?
A long time ago, in the days of the NES, SNES, Genesis, and old computers, a lot of graphics used palette based sprites. How it works is that your sprite has a selection of colors, and then each pixel specifies the index of the color to use. Multiple sprites could use the same palettes, and usually you only had so many palettes you could use.
In Kirby's Adventure, you can see 4 palettes being used (the limit of the NES.) The palettes are:
- The greens that make up the platforms
- The blues that make up the waterfall and water
- The brown/tan that make up the display at the bottom, as well as the background platform
- The pinks, which are on Kirby, the enemies, and the M Tomato
Each palette on the NES could have 3 colors and a transparent color, and then you could specify the background color. The background color here is the light blue in the background, with the white from the palette being used by the waterfall to show the clouds.
Where can we encounter palettes today?
Here is a sprite of Lance. On the left, you can see his color palette, and on the right, you'll see his sprite. If you mess around with pixel are, you've probably encountered palette based GIF or PNG images before, or like this in Aseprite. However, modern computers are not limited by the amount of colors they can show, and therefore there is no "palette"
How can we get modern computers to show a palette based sprite?
It's pretty abstract and weird at first, but once I explain it all the way to you, you'll understand how it works in pretty much the same way.
Here is the same sprite of Lance from above, but now he's colored VERY strangely! What's going on?
Now, you're probably used to RGB colors being Red, Green, and Blue, and together they make a color that shows up on the screen. However, the RGB values are just numbers, so what if we use them as coordinates instead?
If you look closely at the palette in the top right, you'll notice that it is greener in the top right corner, and more blue in the bottom left corner. Here, I am using the green value as the X coordinate, and blue value as the Y coordinate, starting from the top left (0, 0) and going toward the bottom right (which isn't used in this palette)
Why are we using colors as coordinates?
In modern computers, graphics are stored in Texture Pages. When you draw a sprite in Gamemaker, it is somewhere on a texture page, and the program uses UV values to find the sprite in all the other graphics and draw it to the screen.
UV values are always a value between 0 and 1, where (0, 0) is the top left, and (1, 1) is the bottom right.
Consider the above:
The color selected here has an RGB value of (0, 96, 96). This translates to (0.375, 0.375) in UV coordinates.
The color selected here has an RGB value of (0, 224, 160) which translates to (0.875, 0.625) in UV coordinates.
What do we do with these UV coordinates?
Consider the following image:
The sprite has been upscaled so you can see it, it is actually only 4x4 pixels in size!
Now, UV coordinates just tell the game where a specific pixel is in the texture page. But, we can use that to our advantage:
By using the Green and Blue values as UV coordinates, we can specify colors on this 4x4 sprite that has the colors we like on it. So, if you look at the sprite of Lance from above, you'll see now why he's green and blue - each color specifies a different part of the "palette sprite" as I'll call it.
Behold, all the different color Lances! What I do is color him using his regular colors in Aseprite, and then when I export the sprite to a GIF to import it in Gamemaker, I'll load the weird green palette on the GIF.
Ok, what do I do in Gamemaker?
First, you'll need to load your really weird looking sprite into Gamemaker (or else it won't work properly later)
I have also loaded the different palettes into Gamemaker (Remember, these are 4x4 sprites! I have just zoomed them in for simplicity.)
I've got the sprites in, now what?
Now it's time to write a shader!
We will be using a Fragment Shader. Whenever a pixel is to be drawn on the screen, it goes through the fragment shader. The fragment shader will find out exactly what part of the sprite/background etc is supposed to show up on that pixel, and then draw it there.
This is how Lance looks in the game currently, without any special Fragment Shader applied. The default shader is simply taking the weird colored pixels from Lance's sprite and drawing them directly to the screen.
How do we get Lance to not be blue and green?
You will need to create a new shader:
Then you'll need to add some stuff to it:
I have called mine lance_recolor, but you can call it whatever you like!
At the very top are the arguments for this shader. They are as follows:
- varying vec2 v_vTexcoord - The texture coordinates of the sprite we're drawing in the texture page. These are the UV coordinates for the exact pixel that we're drawing of Lance's sprite, so for instance, if we're drawing his big otter nose, this will tell us where in all the graphics that is.
- varying vec4 v_vColour - If you ever used Gamemaker's sprite blending in functions like draw_sprite_ext, this is where the color you use goes in. So, if you used c_blue, then this will be blue. If you didn't use anything, it will be white.
- uniform sampler2D palette_surface - This one is VERY IMPORTANT. This contains the palette that we're going to be sampling from. (I'll explain this more soon, but it basically contains the sprite as shown above with the colors we'd like on it.
Now then, what happens next?
vec4 tmp_color = texture2D( gm_BaseTexture, v_vTexcoord );
Here we find the pixel from Lance's booper that we're going to be drawing. gm_BaseTexture is the variable that represents which texture page our sprite is on, and v_vTexcoord represents where on the page the pixel for the nose is at. Normally it's multiplied by the blend color specified by draw_sprite_ext and other functions, but if we do it now, then our palette recoloring wont work! We take the color that we find from Lance's sprite, and store it in tmp_color.
gl_FragColor = v_vColour * texture2D( palette_surface, vec2(tmp_color.g, tmp_color.b));
This is kinda complex, but if you read what I said above, you'll get it. Since we're sampling from our sprite of Lance with the weird greens and blues, we now have the UV coordinates we need. So, we use vec2(tmp_color.g, tmp_color.b) to find the color we'd like from palette_surface, where our palette is stored. THEN, we multiply it with the blend mode from draw_sprite_ext or whatever, and then finally, we store it in gl_FragColor. This variable represents the pixel that will actually appear on the screen.
gl_FragColor.a = tmp_color.a;
This just makes sure that all the alpha transparencies are correct, because they can get messed up sometimes while doing this.
Now that we used the green/blue values to find the colors we like, Lance looks correct!
So what's palette_surface?
If you've never used a surface in Gamemaker, it's pretty much just a square on which you can draw whatever you like. Here is some code from the Draw Event:
It is important to note that I declare these variables in the Create Event, with values of -1.
Let's break it down:
lance_palette_surf = surface_create(4, 4);
If you try to create a surface in the Create Event, it can go wrong, so I just start off the Draw Event by creating the surface. Here, I create a surface that is 4x4 in size.
lance_palette_sampler = shader_get_sampler_index(lance_recolor, "palette_surface");
lance_palette_surf_tex = surface_get_texture(lance_palette_surf);
Remember how in our Fragment Shader, we have a variable named palette_surface? shader_get_sampler_index pretty much grabs a reference to that variable, so we can change its value later.
surface_get_texture tells us which texture page our surface that we created is on. We'll need to know later.
draw_sprite(lance_pal_shield, 0, 0, 0);
This changes everything we draw to the 4x4 surface that we created. First, we use draw_clear_alpha to make sure we get rid of any garbage that might be on the surface. Then, we draw the palette sprite to the surface, and then return drawing to the game screen itself. We draw the sprite at (0, 0) because that is the top left corner of the surface.
Here is Lance with the surface we created, upscaled so you can actually see it.
If we draw one of the other palette sprites to the surface, Lance changes colors!
Here are 4 Lances. The first has the weird green colors, and the other 3 are drawn using the surface with different palettes drawn to it before drawing each sprite.
How do you draw the sprite?
Here is some code from later down in my Draw Event:
First, we change the shader to the shader we created. Now, and this is very important, we use texture_set_stage.
This function takes two arguments. The first one, lance_palette_sampler, refers to the variable palette_surface in the shader itself. The second one, lance_palette_surf_tex, refers to where our 4x4 surface is in all the other graphics the game has.
What this function does is, it takes our 4x4 surface that we created and drew the sprite to, and then passes it to the shader. Now, the shader is working with TWO textures - one being the one that has Lance's sprite on it, and one that has the colors we like on it.
Since we are using a Surface to sample the textures from, we can draw to the surface at any time and the colors on the sprite will change as well!
Or we can just go wild and make Lance all kinds of colors!
Hopefully you are able to use this tutorial! If you do use it, please let me know because I'd love to see what it's used for! Thank's for reading, and if you have any questions, please don't hesitate to ask!