The University of Melbourne
In this workshop you will be learning how to apply linear transformations to objects, as well as how to utilise basic user input to control game objects. You'll also be exploring how a variable framerate impacts physical simulations, where object transforms change over time. There are two files present in the assets folder:
- MainScene.unity – The Unity scene to open and modify in this workshop.
- XAxisSpin.cs – A component which spins the object it is attached to about its local x-axis.
Note
Some of the tasks today require basic knowledge of Newtonian motion (nothing more than highschool level physics). If you end up finding this aspect of the workshop challenging, make a note to do a bit of revision at some point. While we normally use the Unity physics engine to assist with complex physical simulations, basic theoretical knowledge is still important to use it correctly. Sometimes we also still need to control game objects "manually".
Open MainScene.unity
in Unity. Press the ‘Play’ button and take a look at the cube object – currently it will be spinning about its x-axis. Open the script XAxisSpin.cs
to examine how this done.
In particular, note the use of the Update()
method to incrementally rotate the cube.
This is called by the Unity engine on every frame of the game, before rendering of the scene
is performed. In previous workshops you've already seen the Start()
method,
which is called once before the first Update()
call. Often this is
used for initialisation of variables and/or initial loading of resources
required by the respective component throughout its lifespan. It's best to keep logic
in the Update()
method as efficient as possible, since it has a direct
impact on the simulation frame rate.
Note
There are two other "update" methods in the scripting lifecycle:LateUpdate()
andFixedUpdate()
. Similarly, there is aStart()
method "alternative" calledAwake()
. There are subtle, but important differences between these. At some point it's worth reading this page in the Unity docs to better understand the full Unity scripting lifecycle, because sometimes execution order matters. However, for the remainder of this workshop we'll stick withStart()
andUpdate()
to keep things simple.
In an ideal world, each frame would be rendered in fixed intervals so that the speed of animated objects is consistent. However, there is usually significant variance in the specs of end-user machines, which means the rendering framerate will vary accordingly. Even on identical machines, other variables may impact performance, e.g., use of multiple programs simultaneously. Who knows, some users might even deliberately attempt to reduce the framerate to "cheat" by slowing down the pace of the game!
To address this issue, we can "normalise" object transformations with respect to time.
In the x-axis spin script, Time.deltaTime
is used to adjust the amount of rotation
in a given frame according to how much time has elapsed since the previous one. This
is just your classic equations of motion in
action, e.g., displacement = velocity * time
. It might be worth getting out your highschool physics textbook!
Note
Use of theFixedUpdate()
method is another approach for addressing this problem, by allowing us to separate "physics" updates from regular frame updates. This will be discussed in more detail when we look at the Unity physics engine in a few weeks' time.
Instead of rotating the cube incrementally based on the change in time since the last update,
we could instead re-compute the exact rotation
of the cube on every frame by using the total time elapsed since the start of the
game. Let's try this out. Open XAxisSpin.cs
and change Time.deltaTime
to Time.time
(read more
about the Unity Time
class here).
You then need to make a
further (tiny) modification to make the applied rotation absolute instead of relative. When this
is working correctly, there should be no observable difference in play mode in terms of the rotation
speed.
Practically speaking is there any difference between the two approaches? Think about the potential pros and pitfalls of each. To get you started, here are a few questions:
- Would the cube have the same "angle" if it was created sometime after the game started?
- How does floating point precision and variable framerate (potentially) impact either approach?
- Which approach is simpler if the cube should be rotated only if the user holds down a key?
If you're unsure about any of these, complete the workshop first and revisit them later. An important takeaway from today's workshop should be to always consider how physical simulations are impacted by a variable framerate, or more generally, a finite series of time steps. Things can get especially unpredictable when objects are able to collide with each other, or move very fast, hence why we usually opt to use the Unity physics engine when things get complex. But we'll save that for another workshop.
Your next task is to create five additional cubes, replicating the movements shown below as closely as possible. You should aim to maximise script re-use, that is, make use of the scene hierarchy (parent-child relationships) and strategic attachment of components to avoid writing a separate script for every cube. It's possible to create cubes A-D with the x-axis spin script, and just one additional script!
- Cube A: Moves back and forth in the z-axis
- Cube B: Moves back and forth in the z-axis whilst rotating about the local x-axis
- Cube C: Orbits about the x-axis
- Cube D: Orbits about the x-axis while locally rotating about the x-axis
- Cube E: Performs a ‘figure 8’ motion (challenge)
Adjust the camera's transform so that it looks vertically downwards at the cubes. All the cubes should be visible from the ‘Game’ tab when the simulation is running.
Write a component that allows you to move the original cube with the arrow keys on the
keyboard (in the X-Z plane). You will need to use the Unity engine Input
class within the script. Like always, you can (and should) check out the API for the Input
class in the Unity docs.
At this point it's probably worth adding a static object to serve as the "ground",
or an arbitrary reference point that doesn't move. We've made
this a green plane in the example below.
Write a similar component that allows you to accelerate
the original cube
with the arrow keys on the keyboard (in the X-Z plane). For example,
pressing the right arrow will increase the velocity of the cube in the
positive x-axis. Remember to disable the previous movement script
component from the cube before attaching this one.
Hint: In your component, you'll probably want to create a private Vector3
structure to hold the object's
current velocity
. Every frame, you should update this variable based on user
input instead of directly changing the object's position ("displacement" in physics talk).
However, you'll also need to use this variable to update the object's position every frame!
Write a component which switches the cube between using the movement script and the acceleration script when the ‘s’ key is pressed. To do this the component should keep/get references to the two motion components and enable/disable them accordingly. Additionally, switch the material of the cube respectively, such that it is blue when the movement script is enabled, and red when the acceleration script is enabled.
Currently the cube can move outside the bounds of the camera view. Create a component, which is to be attached to the camera game object, that tracks the cube by rotating to "look at" it. The script should theoretically allow any game object to be tracked, not specifically the cube, so make sure you make the target game object a serialized/public field.
Note
You aren't expected to manually calculate the camera's rotation, as this would be a fairly complex exercise. Instead, you may useQuaternion.LookRotation()
or other Unity engine helper methods to compute the respective rotation for you.