Script Manipulations
Set the camera position to the origin and rotate it to point in the direction of the blue arrow (the Z-axis) by modifying its transform component accordingly: set the position to X=0, Y=0, Z=0
and the rotation to X=0, Y=90, Z=0
.
Create a First Object
Let's add a cube to the scene.
While you can go to the main menu under GameObject and select what you want to create, a more precise method is to right-click on an empty part of the hierarchy and select 3D Object → Cube. This will create a basic GameObject cube inside our scene, with a default transform. If we had right-clicked on an existing object in the scene instead of an empty space, we would have created a child object that would be linked to its parent, which is not what we want in this case.
The cube is positioned by default according to where you look in the "scene view", let's reset its position to X=0, Y=0, Z=0
.
If you double-click on the new Cube object in the hierarchy, the scene view will zoom in on it. If you select our camera object again, its view (or game view) will not show the cube because the camera is now inside the cube — in the way most 3D engines work, objects are transparent from the inside.
Let's move the box to a position where the camera can see it, and also try to make it smaller by setting its transform component to position X=2, Y=0, Z=0
and scale X=0.2, Y=0.2, Z=0.2
.
By selecting our camera again, we can now see a small box appear in the center of its view. If the cube has disappeared from our scene view, simply zoom out a bit or double-click on it again in the hierarchy.
You can continue to play with the cube's transform component to move and resize it, or use the handles in the scene view: dragging one of the three arrows or the squares in the center will also change the transform accordingly.
More on Positioning
There are many other ways to modify the transform with the mouse. You can change the transform modes by pressing W, E, R, or by selecting the different icons in the small floating window in the scene view to move, rotate, or scale an object, respectively. Check the Unity Documentation for more details on this.
First Animation
Everything we have done so far is not very different from using basic 3D creation software. Where a game engine like Unity stands out is in its ability to script the objects we place in the scenes, using a programming language (C#).
Create a First Script File
Let's try this by creating a script that will constantly rotate our cube. The simplest way to do this is to click the Add Component button at the bottom of the Inspector and start typing the name of the script we want to create in the search field, let's call it Rotating
. It will automatically understand that we want to create a new script, so select that option and confirm by pressing Create and Add.
After a short wait, the Unity editor will have created a new file in its Assets folder, which you can see in the Project view by clicking on the new script component in the cube's inspector.
New Script — C#!
A double-click on the Script field in the Script component of the cube, or on the script icon in the Project view will open a text editor (probably Visual Studio) with the script file, named Rotating.cs
, already open. With the editor, you can now make changes to the script! The Unity editor will take into account all changes whenever you save your changes in the text editor and return to the Unity editor.
Each script you create this way will be a C# script, or C Sharp, as indicated by its ".cs" extension. Unity will always create a new script file with a pre-written basic structure, looking like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotating : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
The first three lines (starting with using
) indicate to Unity which software libraries to load before executing the code, allowing it to access, for example, Unity's built-in features and data types, or any other elements you might want to include.
The following block, enclosed by the { }
braces of public class Rotating : MonoBehaviour
, encompasses all the functionality of this script, in the simplest case. It tells Unity that this block defines a MonoBehaviour
object, which is the type of script object that can be attached to GameObjects in the Unity hierarchy.
Inside the braces, there are already the outlines of two standard functions that many scripts use: void Start()
and void Update()
. As explained by the comments above them (all lines that start with //
are ignored by Unity and are only there for the programmer to read), the code inside the braces of Start()
will be executed only once, at the very beginning of each "game" execution, while the content of Update()
will be called every time the screen refreshes. The empty parentheses ( )
following the names of these functions mean that they do not accept any input variables, and the keyword void
before their names means that they do not return anything either.
Executing the Script
Since all this code is still fundamentally empty, executing it will change nothing — you can try this by pressing the Play button at the top center of the Unity editor, which will compile all the scripts you may have modified, switch to the game view (the view from our main camera), and execute all the scripts that are attached to the GameObjects in the hierarchy.
As expected, the game view shows the perspective of the main camera on a static cube. You can still access the cube's inspector by clicking on it in the hierarchy, where you can make changes to its transform by clicking and dragging the desired coordinates, for example.
Exiting Play mode by pressing the Play button again will reset all the changes we made in the hierarchy and inspector to their original state, and will display the default scene view again.
Modify the Script — Constant Rotation Around a Fixed Axis
Let's modify the Rotating.cs
script so that it actually does something when the game runs.
What we want is to change something regarding the cube's transform every time the screen refreshes. This can be accomplished by adding a line of code inside the given void Update()
function, so that it looks like this:
Update()
now calls the Rotate()
function of the transform component of the GameObject with the parameters Vector3.up for the rotation axis, and 30.0f * Time.deltaTime for the rotation angle — for each frame that the game executes. If you type this yourself in the code editor instead of copying and pasting, you might see code completion trigger, which offers suggestions and explanations for each of these fields.
Vector3.up
is a shorthand for a three-dimensional vector that points directly upward, and Time.deltaTime
is the time that has elapsed since the last frame before the current one. 30.0f
is simply a convention for writing floating-point numbers, as opposed to integers. If we save this script file, return to Unity, and press the Play button, we will see the camera view in the game view again, with the cube rotating steadily around its vertical axis at 30 degrees per second.
Make the Script Interactive
For now, this rotation is fixed — although you can still manually modify the transform in the game view as above, the cube continues to rotate. You can try moving it or rotating it around the X or Z axes to see its automatic rotation change direction relative to the world, but remain constant relative to the cube.
To make the automatic rotation more interactive, we can modify our script to include variables, which should be placed outside the Update()
method but still inside the braces of the Rotating
class. Let's modify these parts of the script to look like this:
public class Rotating : MonoBehaviour
{
[Tooltip("Rotation angle per second")] public float speed = 30.0f;
[Tooltip("Vector of the rotation axis")] public Vector3 axis = Vector3.up;
// Update is called once per frame
void Update() {
transform.Rotate(axis, speed * Time.deltaTime);
}
}
Variables are introduced by first indicating their type, then their name, like float speed
— a floating-point number that we reference by the name "speed". If we want to make a variable accessible to scripts outside of its scope (the MonoBehaviour file "Rotating" in this case), we need to prefix them with public
. This way, we can also directly see their current value in the Unity inspector, as parameters of the cube's script component.
It is often necessary, and generally a good practice, to initialize new variables with a certain default value, which is assigned with the equal sign =
followed by a value. Here, we can give them exactly what we had previously put in the parameters of the Rotate()
function: 30.0f
for speed
, and Vector3.up
for axis
.
Finally, we need to replace the fixed values in the call to transform.Rotate()
with our new variables: transform.Rotate(axis, speed * Time.deltaTime);
does that. Optionally, we can make these parameters, which are now exposed in the inspector, even more readable by adding tooltips in the format shown above, before the public
definitions of the variables.
Try it: save the script, return to the Unity editor, press Play. You see the same rotation happening, but you can now directly modify the rotation parameters in the same way you can interact with the cube's transformation!
Online/Offline Editing of Values
As before, changes in the inspector that occur while the game is running will be reset to their default values when you stop, but will remain if you make them while the game is not running. If you change the rotation axis or speed while the game is stopped, the new values will overwrite the initial values you have in the code. If you return to the code and set new initial values and save, these will overwrite anything currently set in the inspector.
Warning
Always ensure that the game is not running when you make changes that you want to keep, especially when adding or removing objects and components!
Combining Animations
A Second Script to Move It
To further practice animating an object, let's create a new script for the cube as we did previously, by adding a new script component and naming it Moving
. As before, we can keep the pre-written structure and simply add elements to it.
With the new script, we want to move the cube between two specified points in space, at a certain speed. Let's add these variables to the Moving
MonoBehaviour, similar to what we did above:
public class Moving : MonoBehaviour
{
[Tooltip("Units per second")] public float speed = 1.0f;
[Tooltip("Starting position in 3D space")] public Vector3 startPoint = new Vector3(2, 0, -1);
[Tooltip("End position in 3D space")] public Vector3 endPoint = new Vector3(2, 0, 1);
// Update is called once per frame
void Update() {
}
}
startPoint
and endPoint
: we initialize them with their coordinates. Save this code and ensure that this new component appears in the cube's inspector with the correct values we provided in the code. If not, you can change the speed and points to their values directly there.
Interpolation
To move between these two points, we need to interpolate between them as time passes. For this, let's add another variable that will track the intermediate position between the two points where the cube should be. This time, we will keep it private
— it will not be accessible by external scripts, as we only want to modify it internally. However, we can display its value in the inspector by adding a line above its declaration to make it a "serialized field:"
public class Moving : MonoBehaviour
{
[Tooltip("Units per second")] public float speed = 1.0f;
[Tooltip("Starting position in 3D space")] public Vector3 startPoint = new Vector3(2, 0, -1);
[Tooltip("End position in 3D space")] public Vector3 endPoint = new Vector3(2, 0, 1);
[SerializeField]
private float interpolator = -1.0f;
// Update is called once per frame
void Update() {
}
}
We initially define it as -1
and want to constantly increase it. This is accomplished by the +=
operation in the Update()
loop: we take its current value and add the time elapsed since the last frame multiplied by the speed, as before.
When it reaches or exceeds 1
, we will reset it to -1
, so that it loops indefinitely between these values. This is done by testing it inside an if()
statement: if the content of its parentheses ()
is true (is the interpolator greater than or equal to one?), then the content of its braces {}
is executed: (reset the interpolator to -1).
public class Moving : MonoBehaviour
{
[Tooltip("Units per second")] public float speed = 1.0f;
[Tooltip("Starting position in 3D space")] public Vector3 startPoint = new Vector3(2, 0, -1);
[Tooltip("End position in 3D space")] public Vector3 endPoint = new Vector3(2, 0, 1);
[SerializeField]
private float interpolator = -1.0f;
// Update is called once per frame
void Update() {
interpolator += speed * Time.deltaTime;
if (interpolator >= 1)
{
interpolator = -1;
}
}
}
To translate this into movement for the cube, we need this interpolator to affect the position
part of its transform. For this, we can use the Lerp() function of Vector3
: it takes a starting point and an endpoint, as well as a value between zero and one that allows it to calculate a new point between the two.
Since our interpolator goes from -1 to 1, we need to slightly modify it before passing it to this Lerp()
function, which we can do directly within its parameter field:
public class Moving : MonoBehaviour
{
[Tooltip("Units per second")] public float speed = 1.0f;
[Tooltip("Starting position in 3D space")] public Vector3 startPoint = new Vector3(2, 0, -1);
[Tooltip("End position in 3D space")] public Vector3 endPoint = new Vector3(2, 0, 1);
[SerializeField]
private float interpolator = -1.0f;
// Update is called once per frame
void Update() {
interpolator += speed * Time.deltaTime;
if (interpolator >= 1)
{
interpolator = -1;
}
transform.position = Vector3.Lerp(startPoint, endPoint, interpolator < 0 ? -interpolator : interpolator);
}
?: or Ternary Conditional Operator
The structure in the form x < y ? a : b
is a handy shortcut: we test if x
is less than y
(or any other comparison we need here), and return a
if true and b
if false.
Here, we give the Lerp()
function the negative of interpolator
if it is less than zero, thus making it positive again. If interpolator
is already positive, it is passed directly to Lerp()
. Enter this code, save it, and run the game:
Interplay
You can now see the cube moving back and forth while continuing to rotate thanks to its Rotating
script! As before, you can play with the parameters to change the speed (both of rotation and translation) as well as the starting and ending points. You can also disable either of the two scripts at any time during the game by clicking their checkbox, which will freeze their execution and prevent them from influencing the cube.
Connecting Objects
Instead of this crazy rotation, let's make sure this cube is always oriented towards the camera.
Look At
First, disable the Rotating
component while out of play mode, so that this change is taken into account. Then, let's add two lines to our Moving
script:
public class Moving : MonoBehaviour
{
[Tooltip("Units per second")] public float speed = 1.0f;
[Tooltip("Starting position in 3D space")] public Vector3 startPoint = new Vector3(2, 0, -1);
[Tooltip("End position in 3D space")] public Vector3 endPoint = new Vector3(2, 0, 1);
[Tooltip("Object to face")] public Transform targetObject;
[SerializeField]
private float interpolator = -1.0f;
// Update is called once per frame
void Update() {
interpolator += speed * Time.deltaTime;
if (interpolator >= 1)
{
interpolator = -1;
}
transform.position = Vector3.Lerp(startPoint, endPoint, interpolator < 0 ? -interpolator : interpolator);
transform.LookAt(targetObject);
}
}
We have now given it a new variable (targetObject
), this time a Transform
. This allows us to reference the transform of any other GameObject, and thus, for example, its position. The new last line in the Update()
loop uses the LookAt() method of the transform
class, which will orient the transform of the current game object to face the direction of another transform that we provide (looking in its direction...).
Referencing Objects
Note that we have not initialized this variable in the code; we will instead assign it from the inspector. One way to do this is to click on the target icon that is now in the new variable field in the cube's inspector and select the Main Camera object from the list that appears, or simply drag the Main Camera from the hierarchy into the field:
We can see the cube moving back and forth and always facing the camera if we run the game, but to display this new behavior more clearly, we can arrange our Unity editor to show both the scene and game views at the same time to have an external view. Changing the starting and ending points, or the position of the camera will not prevent the LookAt
method from correctly adjusting the position of the cube:
If everything works and you have understood each part, you should now master the basics of Unity, congratulations!
Remember, if something is unclear or not working: feel free to ask us for help.