Programming Chapter 3

From Unify Community Wiki
Jump to: navigation, search

Contents

Class.ified

This whole chapter will be somewhat of an example, so go ahead and create a new asset folder "Example-3 Assets" and a new scene "Example-3" placing it in the "Example-3 Assets" folder. OK, buckle up...

A class is a custom type that allows variables and functions that are related to be grouped together and treated as one thing. Note that variables are sometimes called fields or attributes within a class, and functions may be called methods or behaviors in a class. For a class example, let's look at a point. A point in 2D space is made up of an x and y coordinate, and you can translate that point to another location. Go ahead and create a new script in Unity, and name it Point, then open it and fill in this code:

// File: Point.cs
public class Point
{
    public int x;
    public int y;
 
    // Move from current position the requested distance
    public void Translate(int dx, int dy)
    {
        x += dx;
        y += dy;
    }
}

And save. Everything inside of the begin and end block is part of the object. In Unity when you create a new script it automatically creates a new class for you and inherits from the class MonoBehaviour. For now we've removed the MonoBehaviour (we don't need the using UnityEngine; or using System.Collections; either, those are imported namespaces), and just made a simple class. Why don't we need any of those using namespace things and what's this inherit thing? We'll cover all of that in a second, just hold on.

First try this out. Create an empty object in our scene and attach the point class... oops, that doesn't work because our class doesn't inherit from MonoBehaviour. What does MonoBehaviour do? Well that's why there exists documentation, go ahead and look at MonoBehavior.

OK, so you can only attach scripts in Unity that inherit from MonoBehaviour, got it? For now we'll leave it off, lets just carry on and first take a look at scope modifiers.

Scope Modifiers

Scope modifiers are a way to tell what "scope" a variable, method, class, etc. has. An object has full access to everything inside, however non-public items cannot be used from the outside. Using the Example-1 scene again, open it, and change the message type in your HelloWorld.cs script to private and save...

private string message;

Now look in the inspector at your GameObject with the attached HelloWorld script... message is gone! This is because the inspector will only show public variables because that's all it has access to. private only allows access from inside the class. Another type protected allows access only to the class and child classes. And public allows anyone access.

I Object

Still getting there, we need to cover what an object is. An object is a class that has been instantiated. If you tried to use the Point class that we made above like:

Point p;
p.x = 2;
p.y = 3;

You'd get an error saying something like: "Use of unassigned local variable...", or "Object reference not set to an instance of an object...". This is because your class is not instantiated or created, it's unassigned and not set to an instance. A class is just a representation of an object, but not an actual object itself (meaning the computer has not allocated any memory for it). To create an object you have to use the operator new... deja vu.

New Operator Revisited

Point p = new Point();
p.x = 2;
p.y = 3;
p.Translate(1, 1); // Point is now [3, 4]
p.Move(3, -4);     // This will give an error since there is no method named "Move"

New is followed by a class constructor. By default an empty constructor (has no parameters) is created for you (in our case Point()). A constructor initializes all of the class members (x and y in our Point class), and is required to create an object. It also must be the same name as your class. We can also create our own constructor (including our own empty constructor).

Constructor Bot

// File: Point.cs
public class Point
{
    public int x;
    public int y = 5; // We can initialize here too, setting a default value if none is supplied
 
    // Empty constructor, initialize point to [1, 1]
    public Point()
    {
        x = 1;
        y = 1;
    }
 
    // Constructor to let user set initial point
    public Point(int ix, int iy)
    {
        x = ix;
        y = iy;
    }
 
    // Move from current position the requested distance
    public void Translate(int dx, int dy)
    {
        x += dx;
        y += dy;
    }
}

Most of the time in Unity you don't need a constructor, as you'll want to set the initial values in the editor. But you can place a default value if none is supplied by initializing variables where they are created (such as our y = 5). Lets go back to our example and use the constructor we made:

Point p = new Point(2, 3);
p.Translate(1, 1); // Point is now [3, 4]
p.Move(3, -4);     // This will give an error since there is no method named "Move"
p.ToString();         // This won't give us an error. Huh?

If you noticed, we also added a call to the method ToString(). But there is no method ToString in our Point class you say... well, not exactly.

Inheriting Good Genes

OK, we've finally circled the block and come back to this thing called inheriting. In .NET, every class is a child of the object class. A child basically means that it inherits all of the members and methods of the parent. You can inherit any public class you want (making it a child) by doing:

// File: Location.cs
public class Location : Point
{
    public string description = "";
}

Now we can use these new classes and every property of their parent. So now we can do:

Location home = new Location();
home.description = "Home";
home.x = 0;
home.y = 0;

The object class (which every class inherits even without explicitly declaring it in code), has some basic characteristics that are defined for everything (which is why everything is a child of it). One of them is ToString, which gets a string representation of your class. By default the value of ToString is the name of your class. Open Example-1 (if it's not already open), and change HelloWorld.cs and add this to the OnGUI method:

void OnGUI()
{
    Point p = new Point(2, 3);
    GUILayout.Label(message + " " + p.ToString());
}

Now run the program and the debug log should now print "Hello World! Point". Of course, that's not too useful. More useful is the ability to override the parent. Change your Point class by adding this method (needs to be inside the class block of Point but not inside any other method):

public override string ToString()
{
    // Concatenate and format so Point appears as "[x,y]"
    return "[" + x.ToString() + "," + y.ToString() + "]";
}

Now if you run the program the debug log should print "Hello World! [2,3]". Pretty nifty huh? Well, one more thing... everything is an object, or at least appears to be. Add this to an OnGUI method such as in Example-1:

GUILayout.Label(21.ToString());
GUILayout.Label("Done".ToString());

Namespacing

Now what about those using statements? A namespace can be thought of like a named section of a program. Unity in a sense doesn't really support creating your own namespaces, because the name of your script files must match the name of your class. However when you put your class in a namespace, the name of the class is the "namespace.class". But namespaces are important to know about, because the using keyword imports namespaces into your code. What? Even if you don't have MonoDevelop you may want to read through this next section.

Do you still have MonoDevelop around? It's optional, but useful to know what's going on. Now's a good time to fire it up. From the first project we created (in Chapter 1) that we named Tutorial1, open up the Main.cs file. If you look at it, by default it creates a namespace for us called "Tutorial1". Everything inside of the block { } of the namespace is included in that namespace. Confused? Lets learn by example. Here is what our "Main.cs" file looks like with comments on what is happening:

// Import the System namespace into our application, this is for Console.Writeline
using System;
 
// Declare the namespace Tutorial1
namespace Tutorial1
{
    // A class MainClass
    class MainClass
    {
        // public static? function, returns void, named Main, has a string array parameter named args 
        public static void Main(string[] args)
        {
            // Writes a line to the console
            Console.Writeline("Hello World!");
        }
    }
}

So all of that should be somewhat familiar except static...

Static Shock

Static is another scope modifier which makes a function or variable exist without needing to create an object for it (it is automatically created at runtime but you still need to allocate memory for variables). Only one ever exists at a time. In this case, "static void Main" is the entry point into the program (where the program starts). You can set the "Main Class" in the Project Options (under "General -> Compiler Options -> Main Class"). Every program needs an entry point. In Unity this is taken care of for us, so we never need an entry point. We'll use static more in the future, but for now, back to namespaces...

Namespace IT

Now lets add the Point.cs file to your MonoDevelop Tutorial1 project (remember how to add files to a project?). And lets also add our Point class to the Tutorial1 namespace by doing this:

namespace Tutorial1
{
    public class Point
    {
        //... all the point methods and variables...
    }
}

And now change our "Main.cs" file to look like this:

using System;
 
namespace Tutorial1
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            Point home = new Point();
            home.x = 3;
            home.y = 1;
            home.Translate(5, -2);
            Console.WriteLine(home.ToString());
        }
    }
}

If you typed that out you may have noticed that MonoDevelop pops up code completion for you! This is one of the nice things about having MonoDevelop, you don't have to search the documentation as much as it pulls the information about a type into the code completion. Anyways, now build and run. We should get printed out in the console a nice "[8,-1]". So what did the namespace do? Well say we also want to add a new Point class for floats! So we'll edit our "Main.cs" file so that it looks like this:

using System;
 
namespace Tutorial1
{
    class Point
    {
        public float x;
        public float y;
 
        // Move from current position the requested distance
        public void Translate(float dx, float dy)
        {
            x += dx;
            y += dy;
        }       
    }
 
    class MainClass
    {
        public static void Main(string[] args)
        {
            Point home = new Point();
            home.x = 3;
            home.y = 1;
            home.Translate(5, -2);
            Console.WriteLine(home.ToString());
        }
    }
}

Now build and run this. Uh oh, we get an error that "Tutorial1" already contains a definition for "Point". This is because you can't have an objects with the same name. So we could rename our Point class for floats to PointF, but then what happens if you use someone else's code that already uses a Point class of their own? You either have to change yours and all of the places yours was used... or use namespaces. So lets see if we can clean this up. Change the namespace name in your point class to "TutCommon":

namespace TutCommon
{
    public class Point
    {
        //... all the point methods and variables...
    }
}

Now build again and we're now successful! Now run and the out put is... "Tutorial1.Point". Um, what if we wanted to use our other Point class, "TutCommon.Point"? Well, change the line:

Point home = new Point();

to:

TutCommon.Point home = new TutCommon.Point();

Build and run and we now get back our "[8,-1]" output. Whew. So a namespace requires the namespace name, the scope operator, then the class name! Well, unless you import the namespace. Change the code back to:

Point home = new Point();

And lets remove the Point class with floats from your "Main.cs" file and build. Hmm, an error displays that the type or namespace "Point" could not be found. Well because, your princess is in another castle... er namespace. Now try adding this to the top of "Main.cs" after the "using System;":

using System;
using TutCommon;

Build again. Everything is OK. Since we've imported the namespace into our current namespace we don't have to type the whole namespace out for every type. Just for fun add this line to the end of the Main method in "Main.cs":

UnityEngine.Vector2 vec = new UnityEngine.Vector2(2f, 4f);
Console.WriteLine(vec.ToString());

We're using the UnityEngine namespace for type Vector2. And of course if you add:

using UnityEngine;

At the top like you did for TutCommon, you can now change the code to:

Vector2 vec = new Vector2(2f, 4f);
Console.WriteLine(vec.ToString());

Nifty! Switch back to Unity and... doh, it doesn't recognize our namespace because it wants the filename to match the class name, but our class name is now TutCommon.Point, and we can't rename a filename in Unity to have a period. So we can remove the namespace or...

Trip to the Library

Lets create a new project in MonoDevelop. Right click on the "Solution Tutorial1" and select "Add -> Add New Project" (with a one button mouse create a key binding for "Preferences -> Key Bindings -> Project -> Add New Project" like we did in chapter one). This time select "C# -> Library" and give it a name of TutCommon, click next and finish. Delete the MyClass.cs file (and check "Delete from disk"), then add the file Point.cs from our Unity project to this new Library project, but this time select "Move" instead of "Link".

Now click on the project TutCommon and select Build ("Project -> Build TutCommon" or the icon with little blocks). The build should succeed, but there is nothing to run because this is a library. But now we've broken our Tutorial1 Console project (try building it). That's OK, just remove the file "Point.cs" from the project. Now edit the References (remember how?) of Tutorial1, but select the tab "Projects" instead of ".NET Assembly". Check the box next to "TutCommon" and click OK. Your Tutorial1 project should now build with no errors!

Now go to Finder and find the directory of TutCommon and open it. You should notice the Point.cs file there and another directory called bin (for binary), open this. Now there is a folder called debug. A debug build adds extra information useful for debugging. You can set which type to build in MonoDevelop by changing the dropdown next to the build icon to "Release" instead of "Debug". For now lets just open the Debug folder in Finder. You will notice a library called "TutCommon.dll". Copy this to your Unity project "Assets" folder. Now switch back to Unity.

You'll notice that we're getting an error that the type or namespace "Point" can't be found in our HelloWorld.cs file, sound familiar? Know what to do? Open the HelloWorld.cs file and add this to the top:

using UnityEngine;
using System.Collections;
using TutCommon;
 
//... rest of code

Save and the errors disappear! Now run and... it's all working! Why go through all this trouble? Sometimes it's useful to group common files that won't change from program to program together. Instead of copying all of these files over and over from project to project having them all over the place (what happens if we need to change one?), we can change in one place and then just copy our library to all of our projects that use it.

Recap

Things we learned:

  • A class is created using the keyword class and contains methods (functions) and members (variables)
  • Scope Modifiers
    • private is only accessible to the class defining it
    • protected is only accessible to the class defining it and classes that inherit from it
    • public is accessible to everything
  • An Object is created by instantiating a class using the keyword new followed by the constructor
  • ToString() is a method in the object class, which all classes are children of
  • Almost everything is of type object (and can be cast to an object)
  • namespace is a keyword to group types into a named space
  • using imports a namespace into your code so you don't have to type the full location

Programming Index : Previous Chapter : Next Chapter

Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox