There are several components that make up the Unity game engine, all of which can become important depending on the project being made in it. Just off the top of one’s head, there’s transforms, animators, navigation, shaders, debugging, and the ever-present GameObject
. And that’s before you get into any assets you can download to add onto the engine or the more niche elements of Unity. Even those familiar with the engine can risk becoming overwhelmed as they figure out what they need to know to make their project come to life. The necessary knowledge of the Unity game engine will differ depending on one’s role in the project, and programmers are no exception. In order to assist newcomers to the engine as well as those who need a refresher, we’ll be covering what is considered by Unity Technologies themselves to be the most important classes coders will likely utilize when creating the scripts for their projects. As we progress, code examples and other illustrations will be used to help demonstrate how a class is used to accomplish goals where applicable.
GameObject
For Unity, this class acts as the starting point for the vast majority of objects you code for. It is a base class that represents any object which can appear in a Scene (a Scene being the environment that the user interacts with the app’s systems in). Whether they be a player avatar, a source of light, or an invisible object that manages data, almost everything in a Unity project begins life as a GameObject
. This is arguably the one part of Unity that everyone, whether they be programmer, writer, or artist, should have a basic familiarity with. As far as programmers are concerned, the GameObject
will ultimately be what holds different components based on the object’s needs, contain variables and references to other objects, and of course, perform operations as defined in custom scripts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class GameObjectDemo : MonoBehaviour { // Start is called before the first frame update void Start() { GameObject myObject = new GameObject(); myObject.AddComponent<Transform>(); myObject.AddComponent<BoxCollider>(); myObject.GetComponent<Transform>().localScale = new Vector3(2, 2, 2); Destroy(myObject.GetComponent<BoxCollider>()); if (myObject.CompareTag("myTag")) Debug.Log("I have the special tag!"); myObject.SetActive(false); GameObject secondObj = GameObject.Find("obj2"); Destroy(secondObj); } } |
Take, for example, the code shown above. We first create a GameObject
, then give it some components. Depending on its purpose, you may also instantiate the object shortly after this. For this example though, we’ll get the Transform component using GetComponent
and set its scale to two, effectively doubling its size, followed by destroying the BoxCollider
component, once again using GetComponent
to specify the component we wish to destroy. This is to demonstrate that Destroy
doesn’t have to remove entire objects if you don’t want it to. Moving on from that, we have a check for an object tag. Of course, we won’t get the Debug.Log
message since we never assigned a tag to the newly created object in the first place. If it did have the “myTag
” tag, we could print the special message and, in real world scenarios, perform tasks specific to objects with that tag.
As we wrap up this section, we use SetActive()
to hide the object from the scene as well as stop any scripts and components attached to the object, including this one. It’s a handy tool, but caution is needed when using it. You don’t want to accidentally disable an object without any way to bring it back online later. For the final demonstration, the above code shows off the Find method, which looks for any object with a specified name. In this case, we try searching for an object named “obj2”
, and assign it as our secondObj
. And as one final demonstration of the Destroy function
, we destroy the object we just found.
MonoBehaviour
Closely related to the GameObject
is the MonoBehaviour
class, which is the default class that Unity scripts derive from. Whenever you create a C# script in Unity, the created class will automatically inherit from the MonoBehaviour
script. MonoBehaviours
are what allow you to attach scripts to GameObjects
as components, and also grants access to methods like Start, FixedUpdate
, and OnDestroy
, all of which are regularly used with GameObjects
. Unless otherwise noted, all the code displayed here is used in scripts that are inheriting from MonoBehaviour
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Start is called before the first frame update void Start() { myRigidbody = GetComponent<Rigidbody>(); } // Update is called once per frame void Update() { // Useful for things that require constant update, //like timers or user inputs. myTimer += Time.deltaTime; } void FixedUpdate() { // use FixedUpdate for physics calculations. myRigidbody.AddForce(new Vector3(0, 0.1f, 0)); } void LateUpdate() { if (myTimer > 1.0f) myTimer = 0; } |
The code above shows the Start
method as well as all the different types of Update
methods. Of particular note is the varying Update
methods, which all work similarly to each other but have some important key differences. Update is run once per frame, meaning any logic you want to execute all the time would go here. A good example of what to do here would be timer control. If you’re wanting to increment every second, you can do that in Update
using deltaTime
like in the code above. FixedUpdate
, meanwhile, is generally used for physics calculations thanks to its regular call time. It’s not framerate dependent like Update is, hence, the recommendation for use with physics. Finally, LateUpdate
is generally used for ordering tasks. It will wait until all the other update functions have executed before performing any tasks specified within LateUpdate
. It’s best used for guaranteeing certain tasks are done after all other code has been run.
1 2 3 4 5 6 7 8 9 10 11 |
// OnGUI called when user interacts with GUI, usually for button clicks. void OnGUI() { if (GUI.Button(new Rect(20, 20, 200, 200), "Click Me!")) buttonClicks++; } // Called when an object is destroyed. void OnDestroy() { Debug.Log("Goodbye!"); } |
Two more Monobehaviour
methods worth mentioning is OnGUI
and OnDestroy
. OnDestroy
is pretty intuitive, as it simply executes commands once the object is destroyed. It’s handy for any last minute cleanup you wish to do before an object is removed from the application. OnGUI
is often used for making quick and easy buttons in an application. Unity does have a UI system that let’s you create very nice menus, buttons, and more, but if all you want is something really simple and basic then OnGUI
is a good choice.
Transform
One of the more straightforward classes in Unity, the Transform class is responsible for working with a GameObject
‘s position, rotation, and scale. Whenever you need to update any of these object attributes at runtime, Transform is what you’ll need to use. These are so important to GameObject
that Unity does not allow you to remove a GameObject
‘s Transform
component. While managing position, rotation, and scale is the Transform
‘s primary job, it is also used in regards to parent and child relationships in GameObjects
. Anytime you need to access the parent or child GameObject
, the Transform
class will be your gateway to that object.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Start is called before the first frame update void Start() { myTransform = GetComponent<Transform>(); Transform childObj = myTransform.GetChild(0); Debug.Log(childObj.parent.name); } // Update is called once per frame void Update() { myTransform.Translate(Vector3.right * Time.deltaTime); myTransform.Rotate(0.5f, 0.5f, 1, Space.Self); } |
The code shown above shows everything I just described in action. In the Start
method we first get this object’s Transform
before then finding the object’s child. We could then use that information later if we wanted to do something with the child object. The code in the Update
method is a bit simpler, showing an example of moving and rotating an object. Since there’s no physics involved, it is okay to execute this code once per frame.
Object
The base class of GameObject
, Object is a class for any object Unity can reference in the editor. It is in the UnityEngine
namepsace
, and encompasses items like components, materials, text, and more. This is not to be confused with the Object
class derived from .NET’s System namespace. Though they share names, they are not at all the same things. That said, you can still use .NET’s Object class as desired in your C# scripts if you have no interest in the script being assigned to a GameObject
in the Unity inspector.
Object
brings with it a handful of methods helpful for managing other objects. Of particular usefulness is the Destroy
and Instantiate
methods, which is used for deleting and spawning new objects respectively. There’s also DontDestroyOnLoad
, which is best used for any objects you want to persist between scenes like game managers. Finally, there’s the FindObjectOfType
method and its sibling, FindObjectsOfType
(note the difference between single and plural “Object”
). Though slow in performance, these methods are useful for locating specific objects in a scene and doing something with them later. Just be sure you’re not calling either of these every frame, or prepare to see your project take a hit in performance.
Debug
Debugging in Unity is one topic that has been covered here previously. You can read more about that here. To recap what was said before, the Debug
class helps you diagnose any issues in your project using console messages, lines, and more. Utilization of Debug
is inevitable, as it will often be the sole way you’re able to fully understand a situation you may be presented with. Some helpful tips before moving on – first, you should know that clicking messages in the Unity console window will highlight the object the message came from, and double clicking will take you to the line of code that generated the message. Second, you can make a distinction between a log, warning, and error message in code to further assist your debugging. Error messages in particular will pause the Unity editor so long as Error
Pause
is enabled in your console. Finally, don’t ignore the DrawLine
and DrawRay
methods. In 3D applications specifically, this is a great way to figure out where any raycasts or other lines drawn in the app are located and the direction they’ve gone.
Time
You’ll likely need to perform some sort of time-related task in your projects such as counting the number of seconds that have passed between one moment and another. The Time
class is here for scenarios such as this. Closely linked with Time
is the project’s FPS (frames per second), which can affect the results of your scripts depending on the exact methods used. For this reason, it will be important to know your target FPS and do your best to maintain that FPS. This will vary based on the device the project runs on and how demanding of said device the project is expected to be. As you’ll see in the code example below, deltaTime
is generally what you want to use when tracking time since it returns the time since the last completed frame. This way, you’ll be able to get consistent results from your code regardless of the FPS.
1 2 3 4 5 6 |
Time.timeScale = customScale; transform.position += Vector3.up * Time.time; // or, you could instead... // This is more reliable! transform.position += Vector3.up * Time.deltaTime; |
Our example code is very simple, but there’s one key difference between the two lines that update position. Moving the position based off Time.time
results in an object moving according to the number of real-world seconds that has passed since the start of the application. That’s okay if the project is running flawlessly, but your app running perfectly on every conceivable machine is quite unrealistic. Instead, it’s better to base it off Time.deltaTime
, which tracks the time passed based off the last completed frame. In other words, regardless of if your application is running at sixty frames per second or six, the object’s position will be remain consistent. You won’t have to worry about an object suddenly teleporting upwards if the application is running slowly.
Random
Randomized elements are often useful for giving projects an exciting and suspenseful feel. For instance, if you want to shuffle a deck of cards in a game, you’ll need to utilize some randomness to make that happen. Thankfully, incorporating those random elements into a project has been made easy thanks to the Random class. Much like Mathf
, the exact uses for Random
will vary. Sometimes, you just need a simple random number in a range, at which point the Range
function will be utilized. But Range
need not be limited to one number. You can create a random position in your scene using multiple numbers that are randomly generated. Or, if we go back to our earlier example where we needed to shuffle a deck of cards, you can setup a loop that gets a random number in each loop and orders your cards accordingly. If you desire, you can change the seed from which Unity’s random generation is based upon, or you can simply leave the seed at what Unity itself generates, which is the time at the start of the application.
Executing randomization doesn’t have to just be about randomizing numbers. You can also get random points in an area, a random rotation, or a random color to name a few. For random points, you have insideUnitCircle
, insideUnitSphere
, and onUnitSphere
functions. The first two simply find a random point in a circle, handy for 2D applications, and finding a random point in a sphere, which is tailored for 3D. onUnitSphere
is unique in that it finds a random point on top of a surface of a sphere. If you take a ball and call onUnitSphere
and insideUnitSphere
on it, insideUnitSphere
will find a spot on the inside of the ball, while onUnitSphere
will find a random point along the outside of the ball. Depending on your project’s needs, this can be a very important distinction. Random’s last two functions are a lot more straightforward, with rotation
being used to generate a completely random rotation, and ColorHSV
gets a random color with HSV and alpha values within a certain range. You can even specify the range on ColorHSV
on as many or as few parameters as you wish. Below are some examples of Random in action.
1 2 3 4 |
Debug.Log(Random.Range(0, 11)); // print a number between 0 and 10 // move somewhere in random point inside sphere. transform.position = Random.insideUnitSphere; someColor = Random.ColorHSV(); // get a random color. |
Vectors
You might know Vectors as a mathematics concept that describes things like direction. The same basic logic is applied to the Vector class in Unity, used primarily for items like position of a GameObject
or the distance between two objects. Vectors can be broken down further into Vector2
, Vector3
, and Vector4
classes, corresponding to 2D, 3D, and 4D vectors respectively. Each of these vectors use many of the same functions, so the information presented here is relevant to all three types. While working in Unity, a Vector
will most commonly be used alongside Transform
objects to update an object’s current position and perform vector arithmetic which can then be used when gathering information like checking a minimum distance between two objects or figuring out an object’s current direction.
Vectors have several properties and methods that make working with Vectors easier. For starters, Vectors have handy shorthand for moving a direction by one. As an example, Vector2s have down (which is like writing Vector2(0,-1)
), left (Vector2(-1,0))
, one (Vector2(1,1))
, and more. Vector3
s and Vector4
s have most of these shorthands as well, though the exact word used for each may be a little different. Additionally, you can use Equals
and Set
to compare vectors and update existing vectors. There are also methods for finding distance, interpolating between two vectors, calculating angles, and more.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Start is called before the first frame update void Start() { pos1 = new Vector3(2, 2, 2); pos2 = new Vector3(5, 5, 5); Debug.Log(Vector3.Distance(pos1, pos2)); } void Update() { if (lerpTime < 1) { lerpTime += Time.deltaTime * speed; // set current step transform.position = Vector3.Lerp(pos1, pos2, lerpTime); // this does the same thing as the previous line, //but with a different method. transform.position = Vector3.MoveTowards(pos1, pos2, lerpTime); } } |
Lerp
is perhaps the most frequently used of the Vector
methods. It’s used to move an object from some starting position to another ending position gradually over time. The Transform
section had code that behaved very similarly, but in that case we were simply moving an object to the right forever. With Lerp
, you have more control over where you move. MoveTowards
works pretty similarly to Lerp
, with both serving the same broad purpose. Lerp
uses the lerpTime
parameter like a percentage, while MoveTowards
operates more like a time limit.
Quaternion
The Quaternion
class is another class that works closely with Transform
, specifically in regards to an object’s rotation. These are not quite the same as Euler angles, which is what you see displayed in the Inspector
window in Unity. Euler angles are a Transform
coordinate which represent the object’s angle of rotation displayed like a Vector
. Quaternion
s are the mathematical notation for this rotation, tracking the rotation as well as direction of the object. While the two work hand in hand, generally you’ll be converting Euler angles to Quaternion
s for the purpose of efficiency and stability. It’s perfectly fine to use Euler angles in your scripts, but it’s important to eventually convert those to Quaternion
s somewhere in your code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Start is called before the first frame update void Start() { Quaternion rotation = Quaternion.LookRotation(lookTarget.position); transform.rotation = rotation; transform.rotation = Quaternion.Inverse(lookTarget.rotation); } // Update is called once per frame void Update() { if (lerpTime < 1) { lerpTime += Time.deltaTime * speed; transform.rotation = Quaternion.Slerp(transform.rotation, lookTarget.rotation, lerpTime); } } |
The above code shows two examples of doing the same task but in different ways. Start sees us instantly rotating this object towards another object somewhere in the scene, while Update makes use of Quaternion
‘s Slerp
to smoothly rotate the object towards its target over a period of time.
Mathf
Of course, it would hardly be programming without some kind of mathematics involved. Unity’s Mathf
class provides a helpful collection of common functions you’re likely to use during development. The functions can be broken down into categories. There are trigonometric functions, like Sin and Cos. Exponential, square root, and powers form another category, while Log and Log10 cover the main logarithmic functions. Interpolation functions are available as well, with MoveTowards
and Lerp
both seeing regular use in many Unity projects due to their frequent relation to movement related code. Finally, there’s limiting and repeating functions like Clamp
, useful for keeping values within a certain range, and Min
which gives you the lowest between two numbers.
The exact ways you use these math functions will run the gamut. Sometimes, you may make use of them to make simple decisions based off what numbers get returned. Other times, you can use them to help calculate items like in-game power or pricing, while making sure those values don’t go beyond a certain point. Some use math functions alongside their movement code, while others utilize them to assist with how numbers are displayed on screen. Whatever your goal is, there’s likely a Mathf
function that can assist you. With all of them being conveniently called from Mathf
, it’s been made easy to incorporate mathematics of all forms into your project. Here’s a few examples of what you can use the Mathf
class for:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Start is called before the first frame update void Start() { // Mathf.Sin returns value between -1 and 1. displayText.text = Mathf.Sin(angle).ToString(); // prints true if power of two. Debug.Log(Mathf.IsPowerOfTwo(100)); // limit a user's numerical input to 0 and 100, // then display the result. someUserInput = Mathf.Clamp(someUserInput, 0, 100); Debug.Log(someUserInput); // print logarithm of user input. Debug.Log(Mathf.Log(someUserInput)); } // Update is called once per frame void Update() { // animate object moving away from screen using // Mathf.Lerp. "t" is number of seconds. transform.position = new Vector3(0, 0, Mathf.Lerp(minNum, maxNum, t)); t += Time.deltaTime; if (t > 1.0f) t = 0; } |
ScriptableObject
Utilizing ScriptableObjects
has been covered before, so it’s recommended you check out this article if you want more in depth knowledge of the class and how to use it. But in brief, ScriptableObjects
are data containers meant to hold large amounts of data that’s independent of individual class instances. As an example, if you have a consumable item such as an apple in your project, you can make an apple ScriptableObject
which holds the data for the apple (weight, price, etc.), then apply that data to each instance of the apple class. Then, if you decide later you want to adjust the properties of an apple, you can instead update the ScriptableObject
, thus updating every apple instance in your project all at once. It’s a handy way to save time in the long run as well as reduce the project’s memory usage.
Gizmos
Allowing the developer to create lines, shapes, and full meshes in the scene view, Gizmos
are an excellent way to extend your debugging or create visual aids for development. This can be especially helpful for communicating the locations of otherwise “invisible” objects like light sources or managers. For debugging, you can use it to give additional visual information on anything from collision info to spawn locations. What you do with Gizmos
is really up to you as the developer. There aren’t as many methods here as there are in other classes, but what’s here is clear and easy to understand, thus they are also easy to integrate into your workflow. Here’s an example of how one can easily create some kind of visual element for an object that is otherwise invisible, such as a trigger area.
1 2 3 4 5 |
private void OnDrawGizmos() { Gizmos.color = Color.blue; Gizmos.DrawWireSphere(transform.position, 2f); } |
That’s it! The key thing to remember when utilizing gizmos is the OnDrawGizmos
method. This is how Unity will know to draw a gizmo
for you at all, so don’t forget it. Here’s another example that demonstrates making a gizmo
which displays a custom icon for the object.
1 2 3 4 |
private void OnDrawGizmos() { Gizmos.DrawIcon(transform.position, "myIcon"); } |
This code may look even simpler than the previous example, but there’s one important catch. For the above code to work, you’ll need to place your icon in a folder named “Gizmos”. The default filepath
Unity searches for icons is Assets/Gizmos
, so not having that folder and placing your icon of choice there will lead to some confusion from Unity. Once you have that set up, you’ll be able to display any icon you wish for your object.
Handles
Complimenting Gizmos
is the Handles
class, which focuses more on interaction and manipulation as opposed to Gizmos'
emphasis on information. The 3D controls that you see in Unity’s scene window are one such example of a Handle
. With it you can quickly move, rotate, and scale an object to your heart’s desire. This and other built-in tools work well, but sometimes you simply need a tool that is tailored to your project. Using Handles
, you can create your own waypoints, tools for defining “safe” areas in a scene, and much, much more. They’re a great tool for streamlining your own development or creating helpers to anyone on the development team that’s not necessarily a programmer. Customizing how the handle looks is also an option, letting you choose which graphic to represent the handle, the color, displaying of text, and the list goes on. There are many options with Handles, too many to thoroughly showcase in this section, but here’s a few examples gathered into a single script. Given how much is here, comments have been added to the code to clarify what’s going on as you go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// the parameters users will be able to change with handles. public class HandleDemo : MonoBehaviour { public bool isSpecial; public float customNumber = 1f; } [CustomEditor(typeof(HandleDemo))] public class DrawHandle : Editor { // display the handle when the object is selected private void OnSceneGUI() { // get the object this is attached to HandleDemo myObj = (HandleDemo)target; // change color depending on boolean value if (myObj.isSpecial) { Handles.color = Color.red; } else { Handles.color = Color.yellow; } // make handle button, change bool on button click. // Button will appear as wireframe square if (Handles.Button(myObj.transform.position + new Vector3(2f, 0, 0), myObj.transform.rotation, 1f, 1.5f, Handles.RectangleHandleCap)) { myObj.isSpecial = !myObj.isSpecial; } // make a label displaying the current value of // customNumber, move it slightly above and to the right // of the object Handles.Label(myObj.transform.position + new Vector3(0.25f, 1.5f, 0), "Current value of variable customNumber is: " + myObj.customNumber.ToString()); // create an arrow shaped scale handle that lets // us change our float variable. Value cannot go lower //than 0.25 or higher than 3. float newNum = Handles.ScaleValueHandle (myObj.customNumber, myObj.transform.position, myObj.transform.rotation * Quaternion.Euler(0, -45, 0), 15f, Handles.ArrowHandleCap, 1f); if (EditorGUI.EndChangeCheck()) { myObj.customNumber = Mathf.Clamp(newNum, 0.25f, 3); } } } |
By including all this, any object that has this code attached as a component will give helpful controls that change the object’s parameters without having to navigate the Inspector window. You’ll notice that the file has two classes, one for the object itself and one for the handles. HandleDemo
inherits from MonoBehaviour
, and recall that scripts can only be attached to game objects as components if they inherit from that class. But the Handles methods used throughout DrawHandle
asks that we inherit from the Editor class, thus we split them up into separate classes. In order to make the two talk to each other, we first get the object we have selected and inform DrawHandle
of who it is. Once that information is established, the Handles
can then take user input through the button or arrow handle that acts as the object’s controls. It applies that input to the object itself while also updating its own displays based on current values.
Conclusion
If you’re new to Unity, even the most essential of Unity classes can seem like a daunting list to learn. But as the old saying goes, practice makes perfect. This list attempts to order the classes based off what’s easiest to learn and most important to know for any given project, which should hopefully help any newcomers among us who are interested in using the engine for their projects. Of course, feel free to use this as a quick guide. As for experienced Unity users, one may be surprised at the things they think have figured out, only to discover a whole new way of doing things that’s far more efficient. For those users, the hope is there’s still some new knowledge you’ve obtained here. Whether you’re a hobbyist or a member of a large team working on a Unity project, these essential classes will likely become useful to you during your project’s development.
become useful to you during your project’s development.
Load comments