2D Character Customizer - Tutorial - Unity
Updated: Apr 30, 2020
As it turned out, the character customizer/creator which I used in our game for the Community Game Jam 2019 was one of the features the people liked the most. A lot have asked how I did it and due to it being not so complicated, I decided to make a tutorial for it. And here it is. Reading through it after finishing it, I have to say that it might be a little longer than expected, but I tried to explain everything in detail.
First of all, I decided to not go with our game graphics to show off that my method of doing it can be applied for more complex characters too. I have found this awesome asset from Kenney which provides 425 sprites for all body parts and cloths (https://kenney.nl/assets/modular-characters).
You will find a link to the projects files all the way at the bottom.
Gameobject tree structure
I think some of you might have read or watched tutorials on how to draw and import the graphics for your character and then animate it. Most of them draw all the graphics on one big file and slice it up in Unity. By moving the pivot point in the Sprite Editor they determine where the point of rotation of the sprite will be. This is not what we are going to do here. This has two reasons. First, we will have a lot of different graphics for the same body parts (e.g. arms) and it would be impossible or at least very tedious to align all pivot points perfectly. Second, I did not find a way to switch between the different graphics for each body part without increasing the effort for the animation creation.
This is the default structure you would go for when creating a character in Unity. Each, or at least most, of the gameobject will have a Sprite Renderer attached with the respective graphic.
The structure I'm introducing here is adding two additional layers. Here is what these two layers look like for the face.
Instead of putting the graphic for the face directly onto the "Face" gameobject. This game object will be used as pivot point for the face. It only has a Transform component and the Icon is changed to a yellow diamond to see it better in the Scene view. If you want to animate the face, this is the gameobject you want to manipulate, by changing the position, rotation or scale.
I used yellow diamonds for all the pivot points, because it is quite clear to which body part each pivot point belongs. If the character is more complex it would make sense to change to different colors or icons. The yellow diamond in the middle of the head is the one for the "Face" gameobject. The top one is for the hair and the bottom one is for the whole head.
Below the "Face" gameobject there is the "FaceGfx" gameobject, which does not have any sprite renderer attached either. Instead it functions as a container for the different faces. It only has a Sorting Group component attached which makes it easier to change the order of all the sprites in the structure below.
If you don't know what a Sorting Group component does, it is basically overwritting the Sorting Layer and Order in Layer settings for all sprite renderers which are children of this gameobject (here: all Sprite Renderers for the face graphics). This way I don't have to set the Sorting Layer and Order in Layer for each face independently. And having 425 different sprites in total this saves quite some time and makes changes much easier.
Let's finally talk about the face graphics. These are now the children of the "FaceGfx" gameobject, i.e. face1, face2, face3 and face4 in the screenshot above. These have now the Sprite Renderer component attached and as you can see the order in layer is untouched at 0, which is overwritten by the Sorting Group component attached to "FaceGfx".
Additionally, the Transform component of the face gameobjects is at the default setting (position and rotation at 0 and scale at 1). This is a really nice addition to the method I'm using, because as long as the graphics are drawn with the same resolutions and oriented the same, you just have to make them a child of the "FaceGfx" gameobject, reset their position and you just added a new face. You don't have to worry about positioning it right. Combined with my code for the character customizer it will be ready to use right away.
If you however need to control the positioning, this should be done by changing the Transform of the "FaceGfx" gameobject. You can see in the screenshot above that its Transform is not at the default position.
Taking a look at the whole gameobject tree for the character, it might look like a lot but it's just the same logic used everywhere. One gameobject for the pivot with the child for the graphics container and then the gameobjects with the Sprite Renderer attached.
User Interface (UI)
Let's now talk about the UI a little bit. It depends on how much customizable parts you have, but what you need is basically a button to switch to the previous part and one to the next part. And if you want a button for a random customization, which by far gives the funniest combinations. So here is the UI for this tutorial. I could have gone for more options for the face by changing the eyebrows, eyebrow color, eyes, nose, nose color and mouth separately, but that would have made the UI very small. If I wanted to include it, I would go with a separate UI just for the face and a zoom in to the face for that, like most games do it. However, for this tutorial it would just make stuff more complicated and I want to stick to the basics.
The UI structure is build up like this. I use the "UIContainer" gameobject inside my canvas to easily move all buttons at once. Each customizable part is then again a gameobject which has two buttons (PreviousButton and NextButton) and the name (Text (TMP)).
One neat little trick I used for the arrangement of each part on the UI is the Vertical Layout Group component on the "UIContainer" gameobject. This way I don't have to worry about the positioning of each part and I can easily add or remove parts.
Ok, now to the fun part, coding. Also here I went with a more simplistic approach for the code to really focus on the character customization. If you want to save the customized character and use it in another scene in your game, you have to change the code and add additional scripts. This is something I might explain in a future tutorial. Let's first see how the response to this tutorial turns out.
Only one script called "CharacterCustomizer" is used in this tutorial. It is attached to the "CharacterCustomizerUI" gameobject but you could also add it to the "Character" gameobject or any other gameobject. It has references to all the gameobjects from the character with function as containers for the graphics.
Here are all the fields (=class variables) of the CharacterCustomizer class. Don't get confused by "[SerializeField] private", which is basically just another way to write "public". However, the added benefit of this method is that the fields are not visible to the outside by another script, but you can still modify it in the Unity Editor. One way to make your code safer.
As you can see, some of the fields of type Transform are arrays. You will understand why later. The other private fields below are just integers holding the information about the selected part, e.g. which of the 4 faces is selected.
The only Unity callback function which is used is Start. Here the customization is reset to the most basic one, just to make sure that everything is setup correctly.
Three different private functions are needed. The first one is the ResetCustomization function, which sets all indices to 0 and then calls the UpdateGrahpics function.
The UpdateGrahpics function is just a helper function to call the ActivateGraphic function. This function might look a little confusing now, but let's just focus on the first line in the function which is used to activate the correct face graphic.
The ActivateGrahpics functions takes the a reference to the graphics container and the index for this graphics container as parameters. It then checks if the index is not outside of the child count of the grahics container to prevent errors when trying to access them. Then a simple for loop is executed that sets the gameobject of the child that corresponds to the index to active and all other children to inactive.
Finally, there is a public function for each UI button. Most of them are doing similar things and I tried to combine this functions, which makes the code more confusing. So, I decided to make more functions with simpler code. However, feel free to optimize my code or point out what and how I can improve it.
Let's again focus on the functions for the face first. The PreviousFace function decreases the faceIndex and in case it was is now lower than 0 it is set to the maximum value. In this case 3, because we have 4 face graphics (the counting starts at 0). Afterwards the UpdateGraphics function is called which then activates the correct graphic for the face. The NextFace function is doing the same thing, just increasing the index instead of decreasing.
These functions are simply assigned to the On Click callback for the face buttons.
This is basically how my method for the character customizer works.
However, there are two topics which I haven't explained:
What happens if you have a part that has different appearances and each of these apperances is available in different colors and you want to control both separately? E.g. Hair style and hair color.
What if you have different parts that should change at the same time? E.g. changing the skin color should change the sprite for the head, neck and both hands.
Changing appearance and color separately
Let's start with the first topic by looking at the hair style and hair color. Instead of having one graphics container as a child of the "HairStyle" gameobject, I have one container for each hair style. So the "HairStyle (1)" gameobject below contains all graphics for the first hairstyle in 8 different colors. "Hairstyle (2)" contains the graphics for the second and so on. "Hairstyle (15)" is a little different because it contains only one hairstyle which doesn’t have a sprite attached and is used as the bald option.
Now to the code which looks a little different. The selection of the hairstyle is the same as the one for the face whereas the hairStyle field in the script is attached to the "HairStyle" gameoject.
The selection of the hair color is different, because we have to get the reference to the gameobject through code. This is done by the GetChild function and using the hairStyleIndex as argument. This then gives us a valid hairColorIndex based on the amount of different colors (=graphics in the container).
The code that is now activating the correct graphic is located in the UpdateGraphics function. The activation of the correct hairstyle is the same as for the face. For the hair color I use a foreach loop to activate the same color in each hair style. By arranging the colors in the same order in the character tree, a switch of the hair style will always show the same color.
Changing multiples graphics simultaneously
Now to the second topic described above. Changing several graphics with one button click. This is now the point where we have to look back to the defined fields. As you can see, there are some fields defined as an array of Transform objects.
Let's take the skin color as an example. If we have a look into Unity we see that there are 4 gameobjects inside this array. The NeckGfx, HeadGfx, HandLeftGfx and HandRightGfx. Those are all the graphic containers that are affected by the skin color.
The code for the selecting the skin color is again not different to the one for the face. You just have to make sure that all of the graphic containers have a graphic for each skin color and that these graphics are in the same order. Otherwise you might get a mix of skin colors for the body parts.
The difference here is in the UpdateGrahpics functions, but again nothing new, because the same code is used for the hair color. The foreach loop iterates through all graphic containers inside the skinColorGfx array and activates the graphics with the selected skin color.
Last but not least, let's talk about the funniest feature, the randomizer button. This is also a public function and simply assigned to the On Click callback for the respective button. This function simply selects a random value for each index based on the childCount of the graphics containers. Afterwards, the UpdateGraphics function is called and the selected graphics are activated.
We are now finished with the character customizer, but what you most likely want to do next with your character is using them in your game and having him/her perform some awesome animation. Animating your character is very similar to what you have done before when using the default gameobject tree structure. You just have to be careful to select the pivot point gameobject and modify it when doing animations. If you by mistake select the graphics container or a single graphic this might break animations if you then select a different graphic. So, this is the static version of the character customizer.
It does not look bad, but having a simple idle animation will help make this scene a lot more appealing. I'm not going into detail on how you do animations, because there are a ton of videos on youtube already. Just make sure you are changing the marked gameobjects in this picture to not break anything. You are free to change other gameobject, but don't blame me if your character is behaving weird.
I have now added a simple idle animation which changes the main body parts and makes the upper body bounce a little bit. It might not be the best idle animation, but this tutorial is not about animations. What it does is adding more appeal to the scene and it shows that my method works perfectly using animations.
That's it for this tutorial. I hope that I explained everything good enough and I hope it helps you for your next games. If something is not clear or if you have any questions feel free to contact me.
Here is the link to the poject files so that you can play around with it and understand it better.
Kenney, thank you very much for providing the graphics for my tutorial. Check out his awesome and free assets here https://kenney.nl/.