Wednesday, July 4, 2012

Unity Coding Tutorial pt 2

Hello again,

Welcome to Unity Coding Tutorial pt 2. As I noted in pt 1, this time I would like to discuss some tricks I've found in Unity. Due to the length of this tutorial already, I have decided to move GUI elements to a later date.

Communicating across scripts.

This is a huge part of Unity. You never want a vastly complicated script that can do alot of things, in my opinion the smaller the script the better. However, this creates an issue as, often times, one script will keep track of things that another will want to depend on. There are a couple ways to fix this, each with its own advantages. The simplest way is with static functions and variables.

Static power.

Its easy to make a static variable. All you have to do is

public static int /*or whatever kind of type*/  MyVariable;

Then, anywhere else in your code, you can read and write to that variable by doing something like

className.MyVariable++;

You can also do the same with functions.

private static int myVariable;
public static void IncVariable()
{
   myVariable++;
}

You can access functions by doing something like

className.IncVariable();

Notice that in order for myVariable to be accessed inside of the IncVariable function, it needs to be declared as static. Why? Static variables (and functions) essentially exist everywhere. They are created at the beginning of your program, and destroyed at the end. In a way, they have global scope. Therefore, since they are not bound to to a particular instance of a class, they can only access items that have similar scope. They don't "know" about any variables tied to an instance of a class. There are some work arounds that can be applied to certain situations, such as giving static functions some arguments and passing it the data it would need. But, since other scripts don't always have the variables needed, there aren't very many cases where this is practical.

Another draw backs to static variables is, well, they exist everywhere. This means from the start of your program to the end of your program, they are alive and have a value. Even across scenes. This has caused hundreds of programmers to run into issues. Data from one scene is still exactly as it was in the old scene, it doesn't get reset. There are advantages to this, one of them being a great way to carry data over from scene to scene in your game. A work around to this issue is to initialize all your static variables in Start() or Awake() to whatever they should be at the start of a scene. This will reset data before every scene and ensure you never run into any weird issues with data from old scenes. However, there are other ways to share data across scripts.

Singletons And Member Variables.

Singleton patterns are a favorite of mine. Of course, like anything else, they can be overused. The pattern essentially goes as such.

class myClass
{
   private static myClass mInstance = null;
   public static myClass mInstance Instance()
   {
        if(mInstance == null ) mInstance = new myClass();
        return myInstance;
   }

   private myClass(){}
}

If you want your MonoBehaviour to be an singleton, you need to do some variation of this.



class myClass : MonoBehaviour
{
   private static myClass mInstance = null;
   public static myClass mInstance Instance() {return mInstance;}

   void Awake()
   {
       if(!mInstance) mInstance = this;
   }
}

All the singleton pattern does is make your one class available anywhere in any script. It contains a pointer to an Instance of itself. And since this instance has a scope of the scene it is in, it won't ever carry over data from old scenes! It will be created and destroyed in every scene it is in. You can access data from this script by doing something like this.

var inst = myClass.Instance();
if(inst) /*If there is one in this scene*/ inst.anyPublicFunctionOrVariable;

This pattern, of course, can only be applied to things that there can only ever be one of per scene. If you have multiple objects of a type in a scene, you may need to make that script have a variable of whatever script it needs to know about. Something like

myClass1 : MonoBehaviour
{
    public myClass2 thingsINeed;

   void Update()
   {
       thingsINeed.anyPublicFunctionOrVariable;
   }
}

In the editor, you can then drag on drop the object that has the script you need attached, and it will automatically declare it for you.


Making Objects move.

By editing an objects transform.position, you can essentially make the object move. A full reference of transform and all the things you can do with it can be found here :

http://docs.unity3d.com/Documentation/ScriptReference/Transform.html

You can receive input from from the keyboard, mouse, joystick, or touch screen using the input class. A full reference can be found here :

http://docs.unity3d.com/Documentation/ScriptReference/Input.html

With this knowledge, lets look at a simple 2D controller.

//This should be attached to the object it is supposed to move
//We'll also assume the object is also always facing forward
class Controller2D : MonoBehaviour
{
   public float Speed = 1;

   void Update()
   {
       if(Input.GetKey("d"))
      {
           transform.position += transform.forward * Speed * Time.deltaTime;
      }
      if(Input.GetKey("a"))
     {
          transform.position -= transform.forward * Speed * Time.deltaTime;
      }
   }
}

Even though the code is pretty self explanatory and this is the simplest controller ever, let's break it down. Feel free to skip over this if you already understand everything that's happening.

First thing's first, as the comments say, this must be on the gameObject it is going to move. It also assumes that the gameObejct is always facing in the "correct" forward direction. On Update() (which we remember is called every frame), it will check Input to see if the "d" key is currently being pushed. If it is, it will update its position based on its forward position, Speed, and something called Time.deltaTime. transform.forward is just the direction we want to move in. This will often not be its own forward direction, but for this simple example it works fine. Speed is a variable just to tweak the speed, it is set to 1 by default. Since its public, a designer could easily tweak this variable and make this object faster or slower. Time.deltaTime is a variable that keeps track of the time between this Update call and the Update call before it. It essentially will make your object move smoothly on any computer, since every call to Update will not always be after the same amount of time for every computer (or even on the same computer). We see that the rest of the code is exactly the same for when "a" is pushed, only the minus sign will make the gameObject move backwards (still facing forward though).

Lets look at a (very) simple enemy AI. Notice its very similar to the code above.

class Enemy : MonoBehaviour
{
    public GameObject Player;
    public float Speed = 1;

    void Start()
    {
        if(!Player) /*Player was not set up in the editor*/
            Player = FindWithTag("Player");
    }

    void Update()
    {
        Seek(Player.transform.position);
     }
   
     void Seek(Vector3 target)
     {
         Vector3 direction = transform.position - target;
         transform.forward = direction;
         transform.position += transform.forward * Speed * Time.deltaTime;
     }
}

Again, we're moving by our own forward direction time Speed time Time.deltaTime. Here however, we are not waiting for any input. Instead, we set our own forward direction based on where our target is, which for this enemy is whatever is tagged as "Player." We then move according to the new forward direction.

An annoying  (although necessary) aspect of transform (and many other things like transforms in Unity) is that you cannot simply edit a transform x, y, or z. You will have to create an entirely new Vector3 and assign it into position.


Well, hope you got some good ideas from this tutorial. If you think you got a good grasp on all of the above, try this for practice:

-Make a 2D controller that actually changes the forward direction when they move backfowards and forwards.

-Make the Controller into two different scripts: An InputHandler and a Motor. The InputHandler should detect input and call the appropriate function in the Motor. The Motor should actually move the player.

-Make an enemy that runs away from the player instead of towards it.

Best of luck.

--daviD Ward

1 comment:

  1. You glossed over the ever useful GetComponent() when talking about communication between scripts in Unity. So I thought it might actually be worth commenting on.

    It's perhaps the cleanest way for communication to occur dynamically between objects so that one script can obtain a reference to another.

    it takes the form:
    OtherScript otherScript = someOtherGameObject.GetComponent();

    otherScript will have a reference to the component of type OtherScript attached to someOtherGameObject or NULL if there was none. You can then easily call public functions on it as you mentioned in your post.

    See the docs here:

    http://docs.unity3d.com/Documentation/ScriptReference/GameObject.GetComponent.html

    ReplyDelete