Twitter
Saturday
Sep102011

Baking down Textures for Character Customization

Welcome to the first of many little how to’s from Doki Doki Games. Today we’re going to talk about how we do our character customization for our prototype game.

All the NPCs in our prototype are based on the same base mesh that looks like this. 

The art style for the game is very cute so we are able to get away with the same base mesh for both male and female characters with the main difference being the hair model that we attached to the head.

In our prototype we allow the player to customize their character by choosing the following:

  • Skin Tone
  • Face Detail
  • Top
  • Pants
  • Skirt
  • Shoes
  • Hands
  • Hair Color
  • Hair Model 

To do this we divide up the UV’s for the base NPC mesh into distinct regions. 

Note: The Head, Hair, and Skirt are separate models with the hair and the skirt not show attached to the model in this scene.

Using this zoned UV setup we can dynamically copy new texture into distinct regions of the texture without disrupting any of the other customization. For Example the player can change their pants without affecting any of the other clothing.

Let’s see this in the prototype (NOTE: ALL TEXTURES ARE TEMP AND FOR PROTOTYPE PURPOSES ONLY!):

This is the current player character setup as loaded in from the player DB.

We now change the face without effecting the rest of the customization...

...and now we changed the top.

Side Note: All our item preview icons are generated inside Unity automatically for speed of UI development. This is a cool bit of code that snap shots items, color swobs, etc then builds a single texture page per item group with an accompanying text file that holds all the meta-data. All the UI code needs to know is the name of the item to display and it shows up on the browser as an icon. Ultimately all these icons will be replaced by a some better graphics. All the UI artist has do is draw over the created texture page and the new icons are in the game! Magic!

For our character customization to work we have to build the base texture for an NPC on the fly in the game. So let’s look at the code behind it. First we need to setup a few things:

	public Texture2D skinTone;
	public Texture2D faceDetailLayer;
	public Texture2D bodyTopLayer;
	public Texture2D bodyPantLayer;
	public Texture2D bodyShoeLayer;
	public Texture2D bodySkirtLayer;
	public Texture2D hairDetailLayer;
	public Texture2D hairColorLayer;

	public Texture2D texMerge;
	
	GameObject npcBody;
	GameObject npcFace;	
	GameObject npcSkirt;
	GameObject npcHair;	
	
	Material myMat;

We have a number of Texture2D’s that are used in the bake down. These are the individual clothes, skin etc. These textures are only as big as the UV space they have assigned.

We also need to find the objects that make up our base NPC. These are the Body, Face, Skirt and Hair. These textures in our prototype are resources that are loaded based on the player configuration stored in the playerDB. This means every change the player makes is automatically stored in a MySQL database on our server. We’ll get to how we store that in a later tutorial.

The last thing we do in the startup is create a material called myMat.

In the startup of each NPC/Player object we setup the texture we’ll use to bake down the individual textures, assign it to myMat and let each object know to use that material.

	texMerge = new Texture2D (512, 512, TextureFormat.ARGB32, true);
	texMerge.mipMapBias = globals.instance.mipBias;
	npcFace = GameObject.Find(this.name+"/face");
	npcBody = GameObject.Find(this.name+"/body");
	npcHair = GameObject.Find(this.name+"/hair");
	npcSkirt = GameObject.Find(this.name+"/skirt");
		
	myMat = npcBody.renderer.material;
	myMat.shader = Shader.Find("VertexLit");
	myMat.SetColor("_SpecColor", Color.black);
	myMat.SetColor("_Emission", new Color(0.18f,0.18f,0.18f,1f));

	npcFace.renderer.material = myMat;
	npcHair.renderer.material = myMat;
	npcSkirt.renderer.material = myMat;

Note: the mipBias is set by a global so that it can be tweaked for all objects using the bake down textures.

Now we get to the code that bakes down the texture. 

	public void mergeTex()
	{
		ddsl.copyColour(skinTone,texMerge,0,0,512,512);
		
		if(bodyTopLayer != null)
			ddsl.copyTexture(bodyTopLayer, texMerge,
			256,256,256,256);
		
		if(bodyPantLayer != null)		
			ddsl.copyTexture(bodyPantLayer, texMerge,
			0,0,128,256);
		
		if(bodyShoeLayer != null)
			ddsl.copyTexture(bodyShoeLayer, texMerge,
			384,128,128,128);
		
		if(bodySkirtLayer != null)
			ddsl.copyTexture(bodySkirtLayer, texMerge,
			384,0,128,128);
		else
			npcSkirt.renderer.enabled = false;
		
		if(hairColorLayer != null)
			ddsl.copyTexture(hairColorLayer, texMerge,
			256,0,128,128);
		
		if(faceDetailLayer != null)
			ddsl.copyTexture(faceDetailLayer, texMerge,
			0,256,256,256);
		
		myMat.mainTexture = texMerge;		
	}	

We are using a call here to our scripting language (DDSL) to do the texture copying. The code for this is below.

The first thing we copy is the skin. This covers the entire texture and uses no alpha channel. After the skin we sequentially copy all the clothing onto the texture.

This code does the whole NPC body. If we are just affecting a single part of the texture we can wipe out the UV space by copying the base skin tone again to that region and then just copy that required element to that space, for example just the pant texture. 

Note that we do a little error checking to make sure that a texture is assigned before we copy.

Finally here is the code from the DDSL library that handles the texture copying. Notice that we can do a color copy using either a texture as a swob or by directly defining a color we want to use. 

	public void copyTexture(Texture2D source, 
	Texture2D destination, int sourcePosX, 
	int sourcePosY, int destPosX, int destPosY, int amountX, 
	int amountY)
	{
		cols1 = destination.GetPixels();
		cols2 = source.GetPixels(sourcePosX, sourcePosY, amountX, amountY);
		
		count = 0;
		offJump = destination.width - amountX;
		
		offset = destination.width * destPosY + destPosX;
		
		for(int i = 0; i < cols2.Length; ++i)
		{	
			if(cols2[i].a > 0.1) 
			{
				cols1[i+offset] = cols2[i];
			}
			count++;

			if(count == amountX)
			{
				count = 0;
				offset+=offJump;
			}
		}

		destination.SetPixels(cols1);
		destination.Apply();
	}
	public void copyColour(Texture2D source, 
	Texture2D destination, int destPosX, 
	int destPosY, int amountX, int amountY)
	{
		cols1 = destination.GetPixels();
		cols2 = source.GetPixels(0, 0, 1, 1);
		
		count = 0;
		offJump = destination.width - amountX;
		
		offset = destination.width * destPosY + destPosX;
		
		for(int i = 0; i < cols1.Length; ++i)
		{	
			cols1[i+offset] = cols2[0];
			count++;

			if(count == amountX)
			{
				count = 0;
				offset+=offJump;
			}
		}

		destination.SetPixels(cols1);
		destination.Apply();
	}

	
	public void copyColour(Color myCol, 
	Texture2D destination, int destPosX, 
	int destPosY, int amountX, int amountY)
	{
		cols1 = destination.GetPixels();
		
		count = 0;
		offJump = destination.width - amountX;
		
		offset = destination.width * destPosY + destPosX;
		
		for(int i = 0; i < cols1.Length; ++i)
		{	
			cols1[i+offset] = myCol;
			count++;

			if(count == amountX)
			{
				count = 0;
				offset+=offJump;
			}
		}

		destination.SetPixels(cols1);
		destination.Apply();
	}
	

Building textures like this on the fly doesn’t need to just be for NPCs. We use this also for the buildings in the game to allow the player to customize them and have everything on a single texture/material. 

If you have any question or suggestions on how to improve this code and process, please share with us and our readers in the comments. We’d love to hear from you!

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>
« Using a Server to keep Game Time in Sync | Main | Building a Social Game Framework »