Unity3D is a particularly programmer-friendly game engine. Others are designed to be good at one thing -- puzzles or top-down shooters -- and they provide limited, custom coding languages. Unity decided to be general purpose: need a 2D grid? You know how to use arrays, and Unity's API provides everything to display the game pieces and place the camera.
But there's lots of lots of game-engine stuff to learn -- everyone in the 3D game-making world knows what raycasting against a collider is, and why making something just "red" is non-trivial. But after figuring out that stuff, coding Unity scripts is 98% normal programming.
Some reassurances that Unity is fine:
double
for numbers -- it uses the weird shorter float
, and it's all through the API so you're stuck writing f
's everywhere -- 3.2f
. Gah! In general you can't trust people who play those time-wasting silly tricks, but it turns out using 32-bit floats is standard in the game engine world, especially considering mobile games.Compared to UnReal: Unity seems like it has nothing useful. No pre-made players, or doors or elevators, or dialogue trees. The Unity store might have a 3rd party part you need, maybe. Most likely you're coding elevators yourself.
There's no collider generator. You hand-make colliders by either 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 the isTrigger
box checked.
Compared to XNA or OpenGL: Unity does much more for you. It uses a scene camera and persistent objects. It auto-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.
Unity uses the standard parts of game engines, which can seem strange. I'll try to explain a few:
transform.position.y
. The last item is scale and not size because ... consider a model of a barrel 2 units high. A scale of (1,1,1) leaves it as 2 units high, while a scale of (3,0.5,3) makes it triple the girth and only 1 unit high. The size depends on the model, all we do is scale it.Quaternion myFacing = transform.rotation;
. It's easy enough to convert xyz-style rotation to and from quaternions. But game engines prefer quaternions since they're so much better at smoothly moving from one facing to the next.To make Hello World in Unity, follow the instructions to make a new script and add void Start() { print("Hello"); }
. Unity won't run that yet. You have to attach the script onto an item in the Scene panel -- the Camera or the Light or a New Game Object you've made. Then pressing the Run button will print "Hello".
Unity has no main entry point. It goes through every item in the game-world (the Scene), looks for scripts on them (an item could have several), searches for functions matching void Update()
, and runs them.
The philosophy is that each item has a script independantly controlling it: a carMove script goes on each car to drive it, and so on. Then, as needed objects can check how they bumped into (for example) and tell something to that script
Items in the game world (the Scene panel) are instances of the class GameObject
. Each has just a name and a single flat list of subparts, of type Component
. Those determine what each game object "is": the Camera is a game object with a Camera component added, while a shrub has two components: a Mesh
(with a shrub mesh dragged in) and a MeshRenderer
(with a dragged-in link to a shrub-like Material):
GameObject (a camera) Components: | Transform: with position, rotation, scale | Camera: with type, angle, max distance GameObject (a shrub) Components: | Transform: (ditto) | MeshFilter: [mesh: "Shrub1"] | MeshRenderer: [Material: "shrubPic1"] | CapsuleCollider (tells physics we take up space)
I got fancy and decided the shrub also needed a collider to prevent thrown frisbees from passing through it.
A Transform
is always the first component (which you might recall, holds location, rotation and scale). It might seem more natural to have that information right up there in the game object, but this way is traditional and works well enough.
SCripts inherit from Monobehavior
which inherits from Behavior
which inherits from Component
. So scripts are Components. They go in the component list next to Meshes and Cameras.
Of course, scripts are just classes. Dragging them onto a game object signals you want an instance created and put into the list for that gameObject. And of course all of a scripts "global" variables are instance variables.
Classes Behavior and Monobehavior have no functionality. All they do is signal the engine to search for and call the magic functions -- Update(), Start() and so on.
A gameObject just has one flat component list -- no trees of components. But you can make trees of game objects (simply drag them in or out of one another, in the Scene panel). This is the simplest way to make, say, a barbell -- create two balls and a rod and drag them as children of some empty game object.
There's no overall Scene parent, and generally no need. But it's common to use empty game objects as folders -- create a a game object named "car_lot" and drag in every car.
Unity's API has commands to build game objects from scratch, entirely through code, but you'll never need them. The prefab system does everything you need. Build the object by dragging and set values through the Inspector, install it as a prefab. And then clone it through the Instantiate
command. Unity will gladly clone complex multi-part items.
You'll never need to use new GameObject()
or go1.AddComponenet<Rigidbody>
.
The manual is a bit confusing about prefab copies because there are two ways of making them. A level designer can drag copies into the scene and they remain linked to the original. This is great -- if you update the prefab the copies also update. But you can ignore all that stuff with Instantiate
, It's just a standard clone, with no wierd extra linkage.
You'll need to find these prefabs, and will also probably want to swap around meshes and textures and sounds. You could fetch them directly using Resources.Load
, but it's almost always simpler to pre-populate links. This displays in the Inspector with spots for two prefabs, two images and as a resizable list of Meshes for us to populate:
public class forestManager: MonoBehavior { public Mesh[] Trees; public GameObject catPF1, catPF2; // links to cat prefabs public Texture2D ccatFacePic1, catFacePic2;
Note that prefabs are just type GameObject
. We should drag in a prefab from the Resource panel, but nothing stops us from dragging in one from the scene.
Likewise, Instantiate should use a true prefab -- Instantiate(catPF1)
-- but there's nothing preventing it from being used on an existing in-scene game object. That works, but there's no reason to do it.
Any link to any part of a game object can be used to find any other. So generally, instead linking to a gameObject, we'll link to the component we're most interested in using:
// link to an existing gameObject, but through it's transform: public Transform mySuitcase; // ditto: public Rigidbody myOrbOfPower;
This allows us to write simply mySuitcase.position
(instead of the longer mySuitcase.transform.position), and allows us to easily
use myOrbOfPower.velocity
.
Since "scripts" are components, the same trick works linking to them:
public carScript myCar; // link to car gameObject, through script component ... myCar.beginDriving(); // of course we can still access the gameObect: myCar.transform.name="Herbie";
This also works for prefabs. Instantiate gladly takes a link to any component of a prefab, returning a link to the parallel component in the new one:
// link to a prefab cow, but through the cowScript component: public cowScript cowPF1; .. cowScript c1 = Instantiate(cowPF1); // c1 is a link to the cowScript component on the new cow c1.eatGrass(); // let's use it right away! // of course we can use this to access any other part: c1.transform.position = Vector3(12,0,-5);
Every component has a backlink named gameObject
(scripts also have this, since they're components). We can find any component through GetComponent<className>
. And again, this works with scripts since they're components:
// get my rigidbody ("physics") component and reset speed: Rigidbody myRB = GetComponent<Rigidbody>(); myRB.velocity = Vector3.zero; // stop moving // change my main color to red: GetComponent<Renderer>().material,color=Color.green; // I have a link to a cow, get it's cowScript: c1.GetComponent<cowScript>().eatGrass();
Strangely, parent/child relationships are stored in the Transform. The link to the parent is parent
:
if(transform.parent==null) ... // do no parent stuff // turn my cow's parent red: myCow.parent.GetComponent<Renderer<().material,color=Color.red;
We can change parents by re-assigning: transform.parent = pasture2;
or transform.parent=null;
to un-child ourselves.
Children can be found using getChild(i)
and childCount
:
if(transform.childCount>0) { Transform firstChild = transform.getChild(0); ... for(int i=0; i<transform.childCount; i++) { Transform c1 = transform.getChild(i);
Or, in a really weird move, Unity decided foreach on a transform runs through it's children (they wrote an iterator for it):
foreach(Transform child in transform) { // child goes through all of our children
You can also search for children by name, through transform.Find:
// looks for a child of dog named leftLeg: Transform leg1 = dog.Find("leftLeg");
active
flag and the layer
, while Transform has the object's name and holds the tree structure. I assume it's tradition?c1.fieldOfView
in your code. But a few fields are in the API but not the GUI, or vice-versa. And a few have the names changed. But the same name in both places is the norm.transform.Find
for children, GameObject.Find(string w)
is a static function
which searches all game objects (all, including children). In practice you never need it since you already have links and lists of what you need.There may be objects which don't need a script with Update()
but have some data associated with them. It might feel natural to store that data in some master list, including a link to the object. But it's useful to store that data in a tiny script on the object. Here each ball is worth a certain number of points and knows when it was created:
// hold what we need to know about each ball: class ballData : Monobehavior { public int points; // how many points this ball is worth public float creationTime; public int myIndex; // in some ball array we have }
What happens is we often just find a gameObject -- the player collided with it, or mouse-clicked on it. In that case we don't know which list it's stored in, at what index. It's nice to be able to determine it's a ball (from the mere existence of that script on the object) and then any data we need is right there:
ballScript b = thingIHit.GetComponent<ballScript>(); if(b!=null) { // we hit a ball! do ball stuff... // ... possibly also use b.points and b.myIndex
If you want to have a single entry, controlling everything else, you can do it, and it works fine. Assume our game has lots of balls. We have a ball prefab with our tiny ballScript
attached. Our master class creates twenty, in a list, and moves them later:
... public class allBallMover: MonoBehavior { // ball prefab (it has a ballScript, which we use as our link): public ballScript ballPF; // links to created balls: public Transform[] Balls; void Start() { Ball=newballScript[20]; for(int i=0;i<20;i++) Ball[i] = Instantiate(ballPF); // position the balls and so-forth } void Update() { for(int i=0;i<Balls.Length;i++) { Balls[i].performMove(); // call member functions Balls[i].transform.position+= ... // or move by hand } }
One gameObject will gladly hold several scripts. A clever Unity style make modular objects that way. Have data scripts Monster
, Collectable
, Animal
and Robot
. A gameObject could mix-and-match to be a collectable
robot monster, or whatever, checking "am I a robot" with GetComponent<Robot>()
, and so on.
Or simpler, some objects have an optional speech script, used like this:
// apply injuryPoints of damage ... // and if we talk, say ouch: speechScript ss = GetComponent<speechScript>(); if(ss) ss.makeHurtSound(injuryPoints);
Inheritance from Monobehavior works the normal way. Here weasel inherits from pet which inherits from MonoBehavior:
public pet : MonoBehavior { public int cost; public string getCost() { return "$"+cost; } } public weasel : pet { // this is a "script" (a Monobehavior) int stinkiness; void Update() { ... } }
weasel acts like any other script -- you can drag it into a gameObject and the Inspector will correctly create slots for cost
and stinkiness
. As you'd expect, GetComponent<pet>()
can find a weasel, and a variable of type pet
can point to a weasel.
Together, Awake() and Start() are meant as a crude way to control initialization order of multiple objects. All Awake()
's are run first. In theory normal initialization should be done there. Then Start()
's are run, which gives a place to init object A when it depends on B or C having been initialized.
In other timing, the system runs everyone's Update()
in an arbitrary (but fixed) order. That's usually fine. If you need to force an order, there's a special Script Execution Order Window. Or rename all Updates()
and have a master script run them however you want.
Unity might run at 30 Updates a second, or 60, or might vary. The standard fix is using the time passed since the previous update, which Unity provides in Time.deltaTime
. To increase by 3 units a second, add Time.deltaTime*3.0f
each update. And then Time.time
gives the total seconds since the game was started.
All timing in Unity is done in terms of Updates. For example, waiting coroutines are only checked after each Update. Waiting for 0.2 seconds in reality means waiting for 11 or 12 or 13 Updates to pass. Waiting for tiny amounts, like 0.01 seconds, is the same as waiting for the next update.
FixedUpdate() always runs 50 times a second, but not evenly spaced. It's possibly run after each Update, skipped as needed, or run twice (it's use is to run code exactly in step with the automatic Physics system, which you rarely need to do).
Inputs (moouse clicks, moves, tuches) are also only checked between Updates. They can only safely be checked there (otherwise you'll likely miss some or see some twice).
Normal Unity functions can't sleep, but you're allowed to spawn thread-like things which can, called coroutines. This coroutine pauses for 2 seconds, then grows us a little each frame until we pop into nothingness:
IEnumerator explodeBall() { // <- IEnumerator is required yield return new WaitForSeconds(2.0f); // like sleep(2000) // get a little larger each frame, in a loop that can pause: while(transform.localScale.x<3) { transform.localScale*=1.01f; // gradually grow yield return null; // a one frame pause } Destroy(gameObject); // kill ourself } // start a copy of it running: StartCoroutine(explodeBall());
Coroutines are run in the same thread as everything else -- so no race condition issues -- and, again, only runs immediately after Update. Waiting 0.25 seconds means waiting until the first Update after which 0.25 seconds have passed.
Coroutines even have handles. This example runs two copies of a coroutine, then later decides to stop the second:
IEnumerator c1, c2; // handles, can be used to stop them c1=someCoroutine("abc"); // nothing happens yet c2=someCoroutine("def"); // ditto StartCoroutine(c1); // c1 runs, over time StartCoroutine(c2); // ditto ... StopCoroutine(c2); // c2 aborts, c1 continues
Back to that first example, it runs faster or slower based on the frame rate. It's a nice example of what Time.deltaTime
is for. A better version would use it to always explode after 2 seconds, something like: while(scaleAdd<2) { scaleAdd+=Time.deltaTime*2.0f; transform.localScale=(1+scaleAdd); ...
.
The most horrible thing about Unity is how an infinite loop in a script freezes the entire editor. You can't even press the Stop button. The only thing you can do is force close the whole program.
Normal classes work fine in Unity. But you'll need to add [System.Serializable]
if you want them displayed in the Inspector. Here we have a list of dogs currently in the dog show:
public class dogShowRunner: MonoBehavior { // an ordinary class: [System.Serializable] public class showDog_t { // hold data about an entry in the dog show public int showID; public dogData d; // find the dog (dog gameObjects have this script) public int score; } public List<showDog_t> SD;
We can also write standard classes, in regular files. Use the normal create->Script, delete the guts and write a class as normal. A file with a utility class and something for longer xyz's:
class miscStuff { // Random.Range is in the unity API public static int roll2To12() { return Random.Range(1,7)+Random.Range(1,7); } public static Transform getMaximumAncestor(Transform T) { while(T.parent!=null) T=T.parent; return T; } } struct DVec { public double x,y,z };
These can't start coroutines, since coroutines must be attached to the gameObject which started them. Our files can have coroutines, waiting to be called by "real" scripts (sadly, this means we can't give our off-site coroutines any drivers).
Unity has a hack where destroying a game object magically turns every reference to it, anywhere, into null
. Of course what they really do is set the object's dead flag and overload gameObject==. In other words, you would normally need to check if(g==null || !g.isAlive)
, but Unity does that for you:
GameObject g; // pretend this points somewhere GameObject g2=g; // g2 and g point to same thing Destroy(g); if(g2==null) // true (since it's secretly checking isAlive)
This can bite you with new non-overloaded stuff like g is null
.
The physics system can generate callbacks whenever two objects collide. Oddly, you don't "register" your callback functions. Instead they search for the preset function name. Any function named exactly OnCollisionEnter()
is called when your collider hits another.
The easy way to start "physics objects" movingis to set (or change) GetComponent<Rigidbody>().velocity
directly. Unity advertises an AddForce command for this, but it's confusing and a relic from older physics systems.
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're run instantly and have no side effects.
A gotcha: raycasts (and friends) purposely ignore colliders the ray starts inside of. If you shoot a ray starting from the center of your gun, it can't hit your gun. That's great, but if you shoot a ray from just past the tip of your gun, at something your gun is touching, it can't hit that either.
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 can run 0-1 from the lower-left -- (1,1) is the upper right. These are viewport coords. But others are in pixels (sometimes from lower-left, some from upper-right.) Canvases are in "virtual" pixels, and have user-setable Origin and Direction. But some canvases exist in the 3D world, where the units are in meters (this mode is useful for mixing 3D objects with menus). So good luck.
Physics velocity is units/second (which in theory is meters/second). For example, (user-adjustable) gravity starts at 9.8. Physics rotation speed is in degrees/second. Many other physics settings aren't specified, for example, spring springiness.
Traditionally Unity data is entered through the Inspector, letting Unity auto-serialize it. We might create this script, on an empty gameObject, enter data through the Inspector, and we've got all of our clown data saved:
class ClownDataHolder : Monobehavior { [System.Serializable] public class ClownData { public string clownName; public string[] tricks; public float hourlyRate; } public ItemData[] Items; }
Even better, we can put that in on a prefab and now it's a global Asset accessable from any scene. ScriptableObjects are prefabs specially made for this. This awkward code turns clown data into a ScriptableObject:
// this first line creates an entry in Unity's menu system, // ... the only way to create these: [CreateAssetMenu (menuName="scriptableObject/ClownData", fileName="sampleItemData")] // then a normal class for data: class ClownDataHolder : ScriptableObject { [System.Serializable] public class ClownData { ...
But we can also have real files. Read your data from them using your cutom format, if you like. To make it work cross-platform, Unity provides two device-specific strings: Application.dataPath
is to read-only files (files you bundled with the game, in some Resources folder) and Application.persistentDataPath
to a directory for read/write files.
We can also save data in PlayerPrefs. It's a list of name/data, which understands string, int and float: PlayerPrefs.SetFloat("quitTime",1.5f);
and myQuitTime=PlayerPrefs.GetFloat("quitTime");
.
Locating various resources can be done the long way: cowPic=Resources.Load("cows/cow1.jpg")
.
But again, it's almost always easier to have Unity keep a link:
public Texture2D cowPic; // we've dragged in cow1.jpg
In a build, Unity attempts to remove any Assets you're not using, which is nice. It knows how to track
Inspector links, but not things you got with Resources.Load
. To handle that, any
directory, anywhere, named Resources is always included in a build.
Unity changes editors as it needs. As of 2021 it uses Visual Studio Code (which is actually the open source monodevelope with a microsoft layer on top). It also runs on a Mac. It's finicky, but it will do code completion with Unity's API.
You shouldn't need to ever use the Compile button or make a project. Unity recompiles when you go back to any Unity window.
You should never need to use "tags". They're a traditional way of adding one piece of information to an object -- tag something as "monster" or "pickup" or "firezone". Tagging things by attaching a small script is better.
But layers are real. Raycasts can be set to ignore certain layers. The physics system can have particular layers not interact. Cameras can be set to skip displaying ("rendering") certain layers.
There's a LayerMask
class, but you don't need it if you know bitfields. 1<<3 | 1<<7
is the layermask for layers 3 and 7.
Vector3
. It holds 3 float's. It's used for position, scale, setting many rotations, speed.
It has all the member functions and operators you'll need.transform
. The Material on a Renderer is material
, and so on.transform.position.x=6
doesn't
work. position
is a GET, returning a copy of the position. Clearly, assigning to its x is pointless.renderer.material
clones the material if it was an original, and mesh.vertices
creates a full copy of that vertex array.public int turns=7;
but the Inspector says 9, the value of turns
is 9. The actual sequence is: create the instance, apply Inspector values, and finally run Awake/Start.Update()
is private and also isn't using override
. It turns out Unity finds them through C# reflection.
Comments. or email adminATtaxesforcatses.com