Customizable Navigation Bar

Friday, September 27, 2013

Unity3D Tutorial Requests 1: The Colour Picker

This is the start of a new series where I do a tutorial based on what you guys request. This request was for the ability to choose the colour of an object through a colour picker, and this is what I came up with. A very basic colour swapping script, and also a slighty more advanced script that uses vertex colours instead of the material colours.

I created a video tutorial that can be seen here. The text version is right underneath.


Now before you start this tutorial, grab the resources HERE.

Once you import the resources, open up the level included with the package.

You should see 4 assets in your project window. A C# script, a texture, a shader and a material. For this first half of the tutorial, we are only going to be using the script and the texture. You'll get a warning from the shader, but that is OK, because we will be fixing that in the second half.


Now open up the ChangeColour script so we can fill it out, and change the colour of the cube in the scene.

using UnityEngine;
using System.Collections;

public class ChangeColour : MonoBehaviour
{
    //Before we start, we need to store some variables that can be accessed in the editor, and can be used throughout the script.

    //Our first variable will be the texture displayed on the screen, so we can choose the colour from it
    public Texture2D colourTexture;
 
    //Next, we want to store the Renderer of the cube in the scene, this way we can change the colour of the material on it
    public Renderer colouredCube;

    //lastly we store a Rect that describes where our texture is going to be displayed on the screen.
    private Rect textureRect = new Rect (10, 10, 200, 200);

    //Now everything else is done within the OnGUI function.
    void OnGUI ()
    {
        //Simply just draw our texture in the position we gave it at the beginning
        GUI.DrawTexture (textureRect, colourTexture);

//We want to check what event is happening during OnGUI. In this case we want to check if the mouse button has been released, so that we know if the user clicked on the texture or not
        if (Event.current.type == EventType.MouseUp) {

            //get the mouse position
            Vector2 mousePosition = Event.current.mousePosition;

    //if the mouse position is outside the texture being displayed on the screen, just exit out because we dont want to do anything.
            if (mousePosition.x > textureRect.xMax || mousePosition.x < textureRect.x || mousePosition.y > textureRect.yMax || mousePosition.y < textureRect.y) {
                return;
            }

            //if we made it here, we know that the mouse is somewhere on the texture. Since we know this, we need to figure out a way to get the colour of the texture, wherever the mouse currently is. In order to do this, we need to calculate the UV coordinates of the mouse on the texture
            float textureUPosition = (mousePosition.x - textureRect.x) / textureRect.width;
            float textureVPosition = 1.0f - ((mousePosition.y - textureRect.y) / textureRect.height);

            //Once we have the UV coordinates, we use a function called GetPixelBilinear on the texture. This will return the colour of the texture at the given UV coordinates.
            Color textureColour = colourTexture.GetPixelBilinear (textureUPosition, textureVPosition);

           // and now that we have our colour, we just apply it to the cube's material
            colouredCube.material.color = textureColour;
        }
   }
}

Now that our script is finished, create a new GameObject and drag the script onto that object. You will see that the script asks for a Texture2D and a Renderer in the editor, so drag the Colours texture and the Cube onto the Change colour script to attach them.


Now that you have the objects attached to the script, you should be able to hit play, and choose the colour of the cube by clicking on the texture.


Now that we have this simple version working, lets try something else. We are going to get the exact same effect, but instead of changing the material colour, we are going to change the vertex colours of the mesh itself. Changing the colour of the object's material will cause Unity to instantiate a brand new material, which will add to the draw calls of the game. If you are working on mobile, the less draw calls you have the better, so this is why we will go over the vertex colouring.

So open up your ChangeColour script again and lets add that functionality.

In your script lets create a new function called changeMeshColour, make it return void, and make it so that it has a colour parameter, like so

void changeMeshColour (Color newColor)
{
    //Now the first thing we are going to so is create a colour array that is the same size as the mesh's vertex count
    Color[] colorArray = new Color[colouredCube.GetComponent<MeshFilter> ().mesh.vertexCount];

    //then we will go through each vertex and set it to the colour that was passed into the function
    for (int i = 0; i < colorArray.Length; i++) {
        colorArray [i] = newColor;
    }

    // and then we just reassing the mesh's colour array to the one we just created.
    colouredCube.GetComponent<MeshFilter> ().mesh.colors = colorArray;
}

And that's it!

so now that we have that function in our script, in our OnGUI function, we replace

colouredCube.material.color = textureColour;

with

changeMeshColour (textureColour);


So this is what your script should look like once everything is completed.

using UnityEngine;
using System.Collections;

public class ChangeColour : MonoBehaviour
{
       public Texture2D colourTexture;
       public Renderer colouredCube;
       private Rect textureRect = new Rect (10, 10, 200, 200);

    void OnGUI ()
    {
        GUI.DrawTexture (textureRect, colourTexture);

        if (Event.current.type == EventType.MouseUp) {

            Vector2 mousePosition = Event.current.mousePosition;

            if (mousePosition.x > textureRect.xMax || mousePosition.x < textureRect.x || mousePosition.y > textureRect.yMax || mousePosition.y < textureRect.y) {
                return;
            }

            float textureUPosition = (mousePosition.x - textureRect.x) / textureRect.width;
            float textureVPosition = 1.0f - ((mousePosition.y - textureRect.y) / textureRect.height);

            Color textureColour = colourTexture.GetPixelBilinear (textureUPosition, textureVPosition);
            changeMeshColour (textureColour);
        }
   }

    void changeMeshColour (Color newColor)
    {
        Color[] colorArray = new Color[colouredCube.GetComponent<MeshFilter> ().mesh.vertexCount];

         for (int i = 0; i < colorArray.Length; i++) {
             colorArray [i] = newColor;
        }
        colouredCube.GetComponent<MeshFilter> ().mesh.colors = colorArray;
    }
}

Now, in order to get this script to work, and to have the colour of your cube to change, we also need to create a new shader that supports vertex colouring. So open up the VertexColours shader in the project window and lets add that functionality.

For the most part this is just a basic shader, I'm only going to walk you through specific parts

Shader "Custom/Vertex Coloured" 
{
    Properties 
    {
     _MainTex ("Base (RGB) Alpha (A)", 2D) = "white"{}
    }

    Category
    {
        Lighting on
        ZWrite on
        Cull back
        Blend SrcAlpha OneMinusSrcAlpha

        //The call to BindChannels tells the graphics card which data it needs to pay attention to.
        BindChannels {
        Bind "Color", color
        Bind "Vertex", vertex
        Bind "TexCoord", texcoord
        }

        SubShader 
        {
            Pass
            {
            //This makes the material take ambient light and display itself as a regular diffuse
            ColorMaterial AmbientAndDiffuse

                SetTexture[_MainTex]
                {
                    //this combines the texture with the vertex colour which is displayed as primary. The keyword DOUBLE makes the outcome twice as bright. Without this it will end up much darker than it is supposed to be
                    Combine texture * primary DOUBLE, texture * primary
                }      
            }
        }
    }
}

And that is all for the shader. Now drag the Vertex Colours material onto the cube, hit play, and you should be able to choose the colour of the cube again, but in this case, you could have 40 cubes all different colours, and they could still be batched by Unity, saving draw calls.

Well, that's all for today folks. If you had trouble somewhere along the way, or just want the completed project, you can find it HERE. If you liked this tutorial, please share it, and let people know where you got it from.

Also follow me on Twitter at https://twitter.com/purdyjo

Take it easy everyone!