Unity3D for programmers

home

 

Overview, Reassurances

Unity3D is a particularly programmer-centered game engine. A typical engine likes to be good at one thing, and provides a simple custom coding language. Then you can dig into the engine's code but you're fighting the system. In contrast Unity is completely generic, provides all the basics but nothing else, and you use one language code from scratch whatever you need. Then it just has GUIs to make that easier.

The unity docs are great, in general, but they're written for several audiences and somewhat pitched to game designers. It took me a while to realize the latter, and that thinking like a programmer is the best way to use the system. Some things you may have seen about Unity might scare you away. A run-down, and why it's not that bad:

If you've haven't used a game engine before

Unity borrows the standard way of doing things from each domain. The coding is the regular way anyone would code anything. But the gamey elements are done the standard game way. For example nothing has just a color - colors go onto Materials in the usual 3D graphics way. Visible objects officially take up space according to a collider, which is probably not the same as the shape you see.

The Unity docs sometimes sort-of describe these things, but you're really expected to look them up elsewhere. One more example: you can create 3D shapes purely in code; but nowhere in the docs will it fully explain verts, tris, normals and UV's, since they're standard..

But, again, when you look in the docs for how a function works, the description looks like it was written by a programmer, following the usual conventions.

Game engines often have a "Physics" system which independently moves things around. Unity has one. At first it can seem like a fight. Some things are moved by you, hand-checking for collisions. Then others are moved by the game with collisions as call-backs.

If you've used another game engine

Compared to UnReal, Unity has nothing useful. No players, doors or elevators, or dialogue trees. The Unity store might have a 3rd party part you need, but probably not or too hard to adapt. Most likely you're coding door-opening yourself. The payoff is it's easier to create oddball objects and get the exact behavior you want.

There's no collider generator - you hand-make them by combining box, sphere and capsule colliders; or create and add your own Mesh collider. There are no explicit trigger zones. A trigger zone in Unity is a collider with isTrigger checked.

If you've used something simple like XNA or OpenGL, Unity handles most of that stuff. It uses a scene camera and persistent objects. In the background it handles matrixes, draw calls, batching and frustum culling. All you need to do is put things where you want them and aim the camera. You're allowed to set camera matrixes and so on, but I've never needed to.

And just in case: the panel with Assets is misleadingly named Project. You get used to it.

Object organization

Everything in the game world is a stub container with a flat list of parts (in Unity terms, a GameObject with a list of Components.) Oddly, the position is stored in a part - the Transform. For example a camera is a gameObject with two components: the required Transform and a Camera component:

How every camera looks in Unity:

GameObject (main camera)
  ComponentList: Transform | Camera |
                --------------------

Everything is crosslinked, so you can use any part as a "handle" to the entire thing. For example, we might store a link to the Camera component and use it to change any part:

public Camera cam; // linked to camera through the GUI

  cam.fieldOfView=20; // direct use of field
  cam.transform.position = new Vector3(0,10,0); // reach into the transform to move us
  cam.gameObject.setActive(false); // reach up to the gameObject

Everyone's link to the top item is creatively named gameObject. The link to the position (which also stores the rotation and scale) is named transform.

At first it seems like common object data is oddly split between the GameObject and the Transform, but it's not too bad. A trick is to remember that even though the transform is a sub-part, it actually does more. Besides position, rotation and size it stores the name and info on parent/children (later.) The gameObject only has the active flag and the layer.

You can't directly look through the Component list, but everyone has access using GetComponent<theType>(). Suppose instead we had a link to the camera's Transform:

public Transform camT; // link to the Transform sub-item (instead of to the Camera)

  // we now need GetComponent to find the camera sub-item:
  camT.GetComponent<Camera>().fieldOfView=20;
  
  // but can skip the step where we jump to the Transform:
  camT.position = new Vector3(0,10,0);
  
  //  jumping to the gameObject is the same as before:
  camT.gameObject.setActive(false);

Unity's GUI cooperates here. Our camera object could be dragged into a slot for a GameObject, a Transform or a Camera variable. The matching item will be linked.

No gameObject or Component can ever be free-floating. Every gameObject is in the main list (the one you see in the Hierarchy panel.) Every Transform is on a gameObject and vice-versa. Every Component will be in the list of some gsmeObject: Camera c=new Camera(); is an error (there's an AddComponent command, which you rarely need.)

A fun note: cam.transform is really a shortcut for cam.GetComponent<Transform>(). Previous versions of Unity had shortcuts for most common components but they were removed (old code is full of g.rigidbody.velocity=, replaced by g.GetComponent<Rigidbody>().velocity=.)

Another fun fact: most GUI labels are an exact match for the field names, except with added spacing. The fieldOfView camera field is listed as Field Of View in the GUI.

Parent/child

Each gameObject is flat - one list of components, and those components have no children. But you can arrange gameObjects into trees. That's the preferred way for most things. For example, you could add 2 BoxColliders. The component list would show them side-by-side. But it's better to create 2 gameObjects, each with 1 BoxCollider, and child them to yourself.

In code these trees are accessed through the Transform: puppy.transform.parent=dog; sets puppy as a child of dog. The system checks for and refuses to make cycles. Children are accessed through functions childCount and getChild():

Transform dog;

if(dog.childCount==0) {} // no kids
Transform firstKid=dog.GetChild(0);  
firstKid.parent=null; // first kid is free

You can also search for a child by name using Find through the transform:

Transform bowser = dog.Find("bowser");
if(bowser!=null) ...

There's another Find which can be confusing. GameObject.Find("bowser") is a static function which searches the entire world for bowser, including all children. transform.Find is a member function, searching only the immediate child-list.

There's also a very odd shortcut to run through all children: use a foreach on a transform. Unity wrote a special iterator for this, since why not:

foreach(Transform ch in transform) { print( ch.name ); }

A note: Unity "user culture" uses parent and child backwards. You might find a useful code-snippet containing transform.parent=dog where they claim you're parenting yourself. You really are childing yourself to the dog. That's just how they learned to say it. The rest of what they wrote may be very insightful.

Scripts, main entry

There's isn't an official main entry point to your code. Unity expects you write write code in "scripts," attached to gameObjects. Each script is expected to drive the item it's attached to, and Unity independently runs them all.

Java-style, each script is a class. Inheriting from Unity's class Monobehavior allows it to be attached to world objects (just drag it in using the GUI) and signals the system to run it's Awake() function once, then Update() every frame. A simple script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ballMover: MonoBehavior { // <-inherit from this
  public Vector3 movePerSec; // the Inspector will make a nice field for you here

  void Update() {
    transform.position += movePerSec * Time.deltaTime;
  }
}

Notice the use of transform. A key observation about scripts is they're components. This means they can look around using transform, gameObject and GetComponent; they can be found using GetComponent and can be used as a link to the whole (examples later.)

Typical Unity-style would have you create several balls and drag this onto all of them. For free you get an instantiated copy for each ball. Obviously each has its own movePerSec field.

If you want a more traditional single thread of control you can fake it. If you place only one script on a gameObject you have only one Update(). This alternate approach could move all balls. It would typically be placed on some dummy/empty object:

...
public class allBallMover: MonoBehavior {

  public Transform[] Balls; // fill through the GUI
  public Vector3[] MoveAmts; // ditto

  void Update() {
    for(int i=0;i<Balls.Length;i++)
      Balls[i].position += MoveAmts[i] * Time.deltaTime;
  }
}

A note: you may have noticed how Update is private and isn't using override. Unity doesn't use inheritance to run these. It uses reflection (pre-run it searches for the magic function names and saves pointers to them.)

Data-only scripts

It's often useful to have a simple struct-style class attached to game objects. Simply don't write any of the magic function names. A rewritten ballMover that merely allows us to associate a speed and score to each ball:

public class ballData : MonoBehavior {
  public Vector3 movePerSec;
  public int pointValue;
  
  public void doMove() { transform.position += movePerSec*Time.deltaTime; } // since why not
}

The advantage over a normal class is the link from the item it's attached to. Sometimes you're only given a gameObject - like for a tapped item or in a collision callback.

Scripts as components

This is expanding from the note before. Scripts are components, so can use and be found with GetComponent and used as handles. Here's ballData taking advantage of GetComponent, to reach into its renderer and change color:

  public void turnRed() { GetComponent<Renderer>().material.color = Color.red; }

More interesting is using scripts as links. In this rewrite every ball has a ballData script from above, and this master script runs them:

public class allBallMover: MonoBehavior {

 // link to every ball, through their attached ballData class:
  public ballData[] B; // fill in using GUI

  void Update() {
    for(int i=0;i<B.Length;i++) {
      B[i].doMove();
     
      if(B[i].transform.position.x>100)
        B[i].GetComponent<Renderer>().material.color = Color.green;
    }
  }
}

The key here is using ballData as a link to the whole object. We can use B[i].transform or we can run B[i].GetComponent to find whatever else we need (but yes, yes, proper OOP says we should be calling something like B[i].changeColor(Color.green).)

Scripts can also be used as flags, searching with GetComponent. This script might go on a wall. It checks for balls hitting it and blasts them away:

public class ballBlaster : MonoBehavior {
  // a callback from the physics system:
  void OnCollisionEnter(Collision c) {
    Transform intruder = c.transform;
    // is the intruder a ball?:
    ballData bd = intruder.GetComponent<ballData>();
    if(bd!=null) {
    	intruder.position = Vector3.zero;
    }
  }
}

GameObject as script container

One gameObject will gladly hold several scripts. The system will run Update(), and so on, on all of them. The most common way this is used is accidentally dragging 2 copies of a script onto the same thing. The system runs them both. In Unity style you might have two independent scripts: a ball mover and another pulsing the color.

But you can also use this as a poor man's multiple inheritance. You might have data scripts Monster, Collectable, Animal and Robot, which mix-and-match on an item. The Collectable script might use Robot r=g.GetComponent<Robot>(); to test whether it was a robot collectable.

Or you might have an optional speech script, possibly attached to you:

  health-=injuryPoints;
  // more injury stuff
  speechScript ss = GetComponent<speechScript>();
  if(ss) ss.complain(injuryPoints);

In either case, you're thinking of a script with possible plug-ins. This lets you conceptually plug them in by adding them next to it.

Inheritance and scripts

Inheritance trees from Monobehavior work the normal way. Here weasel inherits from pet inherits from MonoBehavior. We can drag weasel onto a gameObject and it works fine:

public pet : MonoBehavior {
  public int cost;
  public string getCost() { return "$"+cost; }
}

public weasel : pet {
  int stinkiness;
  
  void Update() { ... }
}

As you'd expect, GetComponent<weasel>() and GetComponent<pet>() will find a weasel. And a pet or weasel variable can point to a weasel. Unity will display cost and stinkiness next to each other in the GUI.

Synching, timing

The system runs everyone's Update() functions in arbitrary order, which is usually fine. If you need A to run before B there's a Script-Execution-Order window. But there's nothing wrong with having a master Update run everyone else (the ballData example.) The system won't complain, it works fine, is just as fast.

For initializing, Awake() and Start() provide simple synch. The system runs all Awake()'s, followed by all Start()'s. In theory, regular init'ing goes into Awake, and anything depending on someone else being finished goes into Start. That usually works well enough. But, again, you can always go to a master Start() running init()'s for everyone in whichever order you need.

 

Everything else in Unity runs or is checked once per frame. Some things look like they run on their own timing, but not really.

FixedUpdate() gets mentioned a lot, but isn't that useful. It simulates 50 evenly-spaced frames a second, but it runs them in batches during each frame. If you're at 60fps, it skips 10. If you're at 30fps if runs twice during 20 of them. The simplest way to write frame-rate independent code is to stick with Update and use x+=2.0f*Time.deltaTime. Time.deltaTime is the seconds since the last Update.

FixedUpdate() is also wonky for reading inputs. Those are checked/cleared between frames. Depending, FixedUpdate can miss or double-count them.

Threads, sleep (coroutines)

Unity has a fake system for threads, called coroutines. They run on the main thread, which means no race conditions. The point of them is allowing a sleep-like command, yield. A simple coroutine and use:

IEnumerator explodeBall() {
  yield return new WaitForSeconds(2.0f); // like sleep(2000)
  while(transform.localScale.x<3) {
    transform.localScale*=1.01f; // gradually grow
    yield return null; // pause here one frame
  }
  Destroy(gameObject); // kill ourself
}

public void getDamaged(int amount) {
  if(amount>5)
    StartCoroutine(explodeBall()); // spawns a thread running explodeBall
}

The timing note about everything running in a frame applies to yield. It merely skips frames until the time is up. The smallest amount it can wait is one frame, and it can only wait in frame intervals.

Early versions of these didn't have handles, which meant you couldn't stop them very well. You now can. This example runs two copies of some coroutine, then later decides to stop the second:

IEnumerator c1, c2; // optional handles which can be used to stop them

  c1=someCoroutine("abc"); // nothing happens yet
  c2=someCoroutine("def"); // ditto
  StartCoroutine(c1);
  StartCoroutine(c2);
  ...
  StopCoroutine(c2);

There's a second nicer syntax which looks like: Coroutine c1=StartCoroutine(someCoroutine("abc"));. But I remember it as being a little more fragile for cases using chained coroutines.

Fun tip: you're allowed to call coroutines like normal functions, but nothing will happen. Forgetting the StartCoroutine() is a common hard-to-find mistake.

Infinite loops

The Unity editor runs (when you press Play) in the main editor thread. If your oode has an infinite loop. the entire Unity editor will freeze and need to be force-closed. That seems crazy, but I assume it's the only way Unity can give access to every tool and view while in Play mode.

Instantiate and Prefabs

Unity has a nice way of creating new gameObjects on the fly using prototype gameObjects and a smart clone function. You almost never need to write code to assemble an item.

The smart clone is named Instantiate. The prototype objects are named Prefabs. Instantiate takes a link to any part of a prefab and makes a proper copy of the whole thing, placed in the world (it obviously copies the component list, and also all public script variables; and all child gameObjects.) An example: this makes a cat and a dog:

public GameObject catPrefab;
public dogData dogPrefab; // assuming our dog contains this script
 ...
GameObject c1 = Instantiate(catPrefab);
dogData dd = Instantiate(dogPrefab);
// creates the complete dog, with dd linked to the dogData sub-part

// now we can do the usual things through the links:
c1.transform.position = Vector3.zero;
dd.gameObject.layer=6;

The official manual initially explains prefabs as a shortcut for manually copying an object by dragging through the GUI. It is, but code-wise it's a real instantiated gameObject that's not in the game world and ignored for Awake and Update, accessible from any scene. So perfect to use as a master item.

The Instantiate command will gladly copy a real object, but there's no reason to ever do that. There's also no simple way to specify "public GameObject catPrefab;" needs to point to a proper prefab.

You're allowed to create items from scratch, but because of prefabs you rarely need to. new GameObject() will also add a Transform and add you to the scene. Then AddComponent can create the rest. But in practice components can be pre-made with the proper settings and enabled/disabled. I've only needed AddComponent for physics joints - they save the items' relative positions when first created as the resting position. The only way to change is to destroy the joint, move the items, then add it back.

Using regular classes

You don't see many examples using regular classes in Unity, but they work fine. The one trick is you'll need to add [System.Serializable] in front if you want them displayed in the GUI. "Inner" classes are quick to write. This assumes we have lots of dogs, each with a dogData script. We want a sub-list of the ones currently in our dog show:

public class dogShowRunner: MonoBehavior {

  [System.Serializable]
  public class showDog_t {
    public dogData d; // assume this is a script, on all dogs
    public int score;
     ...
  }

  // GUI will show a list of paired dog-links and scores:  
  public showDog_t[] SD;

  void Update() {
    for(int i=0;i<SD.Length;i++)
      // the usual things we can check:
      if(SD[i].score ...
      if(SD[i].d.breed ...
      SD[i].d.GetComponent<...>()
   }
}

The idea is that the Unity way was best for common dogData - automatically and permanently attached as a script to each dog. But for this the Unity way would be a pain. We'd add a proper showDog_t script to the dogs in our show, and remove when finished. The more traditional approach, above, seems to work better.

You can also write standard classes, in regular files. Use the normal create->Script to get it in the project. Then hollow it out and write as normal. You're allowed to use all Unity API commands from "normal" classes. The system handles makefiles and dependancies invisibly.

For example, a static class full of static functions is fine:

class miscStuff {
  // Random.Range is in the unity API
  public static int roll2To12() { return Random.Range(1,7)+Random.Range(1,7); }

  // can take and return Unity transforms:
  public static Transform getMaximumAncestor(Transform T) {
    while(T.parent!=null) T=T.parent;
    return T;
  }
}

Or the showDog_t class from above could be pulled out and written as a normal class, in a normal file (dogShowRunner would use it the same way.)

The one thing regular classes can't do is start a coroutine. For the system to manage them, they need to be attached to gameObjects (they attach to the one calling StartCoroutine. They cleanly abort if it's destroyed before they finish.) You can write coroutines in normal classes (but the can't-call-them rule means you can't chain them, or write a front end, so it's often not worth it.)

Destroy and smart nulls

Every pointer to a gameObject or component is really a smart pointer. If you Destroy something (an API command,) every pointer to it will magically become null (we know they don't really - they secretly check for the destroyed flag and reported that as null.) For example:

GameObject g; // pretend this points somewhere

  GameObject g2=g;
  Destroy(g);
  
  if(g2==null) // true
  g2.transform = ... ; // null reference error

A typical use is Transform currentTarget;, aimed at something which can independently call Destroy on itself. Checking if(currentTarget==null) doubles as a test whether it's really null or if we're pointing to a dead one.

General Notes

Physics

The callbacks from the physics system don't need to be registered, and can't be. They key off the the exact function name - OnCollisionEnter on the items involved in the collision. This means the callbacks can't be directed to where you want. When A hits B, the system notifies A and B (it attempts to call OnCollisionEnter on every script on both. But In practice B is a script-less wall, and only A is notified.)

For pushing things around, AddForce the first thing you'll see but it's a relic from older game engines. Setting/changing velocity directly is easier. Oddly, AddForce doesn't change the speed right away. It saves them and applies changes during the physics step.

 

Raycasts (and Spherecasts and OverlapSpheres) are the primary way of finding things ``in the world." They rely on colliders, so feel like part of the physics system, but aren't. They' run instantly and have no side effects.

There's some ugliness. The *Cast family ignores anything enclosing where it starts. This is traditional - if you shoot a ray from the center of a box, it should ignore the box. But, for example, you might run a spherecast which starts centered on a sphere, using the same radius. But your real sphere is interpenetrating slightly with a wall (the physics system allows some give,) causing the ray to ignore the wall directly in front if you.

The selection of casts is good, but limited. You currently can't cast using a concave mesh. You can using a SweepTest, but that requires a created object and has no parameter for layers. A spherecast gives a "thick" line, but it's still a sphere at the end - the center will hit something before the edges will.

Units, coordinate systems

The docs don't always do a great job telling you the units.

World coordinates are unit-less. Technically in meters, but not really.

Various screen coordinates are in 0-1 from lower-left (viewport coords); or in pixels (from lower-left or upper-right.) Canvases are in virtual pixels, scaled to the actual screen size (depending on settings.) Origin and direction are user-settable. But canvases are also in meters (if you want to mix 3D objects and menus.)

Physics velocity is meters/second. The system handles the per-frame movement. Physics rotation speed is in degrees/second. Many other physics settings aren't specified, for example, spring springiness.

Files, data, loading

You usually won't need to hand-load and save files, hand-load assets, or hand-build complex items.

Read-only data can often be saved by the GUI. A dummy gameObject holding a data-only script will convince Unity to serialize the data:

class itemDataHolder : Monobehavior {

  [System.Serializable]
  public class ItemData {
    public string name;
    public int[] someList
    ...
  }

  public ItemData[] Items;
  ...
}

Anyone needing it could link through: public ItemDataHolder ID;. And, of course, ItemData could also have been defined outside, in a boring regular dot-cs file.

A limitation is this needs to be created in some scene. You often need to make sure it's created before anything using it and then have it survive scene changes. The "new" ScriptableObjects let you create serialized class instances as an Asset:

[CreateAssetMenu (menuName="scriptableObject/itemData", fileName="sampleItemData")]
class itemDataHolder : ScriptableObject {
 ...

The CreateAssetMenu line isn't required, but it is. It adds us to the Create dropdown (otherwise it assumes you're using an Editor Script and creating them through code.) You'd link to one of these using public ItemDataHolder ID;, the same as before.

There's a simple read/write file named PlayerPrefs. Example use: PlayerPrefs.SetFloat("quitTime",1.5f); and name2=PlayerPrefs.GetString("name2");.

If you need to, you can read and write actual files. To make it work cross-platform, Unity provides two device-specific strings: Application.dataPath to read-only files (things which came from your project's Resources folder) and Application.persistentDataPath leads to a directory for read/write files (I've used these, but barely.)

Locating various resources can be done the long way: Resources.Load("cows/cow1.jpg"). But it's almost always easier to have Unity keep a link:

class globalData : MonoBehavior {
  public Texture2D cowPic;
  public Mesh cowModel;
  public Material[] DamageLevelLooks;
}

These also signal Unity to include the assets in a build (it strips out anything unused.) With the hand-load method, assets need to be moved into any directory (which you create) named Resources (all the name does is tell the builder to always include the contents.)

Editor

Unity ships with free editor Monodevelope. You should be able to select it. In theory Unity also works with Visual Studio (not tested.)

You shouldn't need to ever use the Compile button in your external editor. Unity recompiles after changes when you go back to any Unity window. It handles creating any Make/Project files.

Monodevelope can be touchy. It will auto-complete and syntax highlight the full Unity API, but it may take some work to set that up.

Layers/Tags

The only use for tags is if your script wants to read them. They're a crude (and traditional) way to add 1 piece of information. Adding a simple data script is better (unless you have objects which are pick-ups, needing no further information, such as a subtype, value ... .)

Layers have real effects: raycasts can be set to ignore certain layers. The physics system can have any two layers not interact. Cameras skip rendering certain layers... .

There's a LayerMask class which you can skip if you know bitfields. 1<<3 | 1<<7 is the layermask for layers 3 and 7.

More misc

 

 

Comments. or email adminATtaxesforcatses.com