Clock
Create a new Unity Project
Create a new Unity Project
In the hierarchy create an empty game object to hold the components of our clock.
In the hierarchy create an empty game object to hold the components of our clock.
Rename the game object and ensure that the scale is uniform and position and rotation are zeroed out.
Rename the game object and ensure that the scale is uniform and position and rotation are zeroed out.
Create the clock face
Create the clock face
Add a Cylinder object to our Clock object. Ensure that it's transform values are the same as those for the clock.
Add a Cylinder object to our Clock object. Ensure that it's transform values are the same as those for the clock.
Turn the cylinder into a clock face by flattening the Y scale and make the face bigger by increasing the X and Y scale.
Turn the cylinder into a clock face by flattening the Y scale and make the face bigger by increasing the X and Y scale.
We want the clock to be upright as if hanging on a wall.
We want the clock to be upright as if hanging on a wall.
In Unity the X axis points right, the Y axis points up, and the Z axis points forward.
In Unity the X axis points right, the Y axis points up, and the Z axis points forward.
Set the cylinder's X rotation to 90 and adjust the scene view so the clock's front is visible, so the blue Z arrow of the move tool points away from you, into the screen.
Set the cylinder's X rotation to 90 and adjust the scene view so the clock's front is visible, so the blue Z arrow of the move tool points away from you, into the screen.
Change the name of the cylinder game object to Face, as it is one component of the clock, it is a child of the clock.
Change the name of the cylinder game object to Face, as it is one component of the clock, it is a child of the clock.
We need to create the hour positions on the clock.
We need to create the hour positions on the clock.
A clock is a circle which has 360 degrees.
A clock is a circle which has 360 degrees.
There are 12 hour positions.
There are 12 hour positions.
Therefore 360 divided by 12 gives us 30 degrees.
Therefore 360 divided by 12 gives us 30 degrees.
30 degrees between each hour position.
30 degrees between each hour position.
Add a cube object to the scene, name it Hour Indicator 12,
Add a cube object to the scene, name it Hour Indicator 12,
and also make it a child of Clock.
and also make it a child of Clock.
Set its X scale to 0.5, Y scale to 1, and Z scale to 0.1 so it becomes a narrow flat long block. Then set its X position to 0, Y position to 4, and Z position to −0.25. That places it on top of the face to indicate hour 12. Also remove its BoxCollider component.
Set its X scale to 0.5, Y scale to 1, and Z scale to 0.1 so it becomes a narrow flat long block. Then set its X position to 0, Y position to 4, and Z position to −0.25. That places it on top of the face to indicate hour 12. Also remove its BoxCollider component.
Create a new material for the 12th hour indicator.
Create a new material for the 12th hour indicator.
Change the Albedo.
Change the Albedo.
Add the material to the hour indicator.
Add the material to the hour indicator.
Albedo is a Latin word which means whiteness. It's the color of something when illuminated by white light.
Albedo is a Latin word which means whiteness. It's the color of something when illuminated by white light.
We need an indicator for every hour but we will start with the cardinal points.
We need an indicator for every hour but we will start with the cardinal points.
Begin by orienting the scene view camera so we look straight down the Z axis. You can do this by clicking on the axis cones of the view camera gizmo at the top right of the scene view. You can also change the axis of the scene grid to Z via the grid toolbar button.
Begin by orienting the scene view camera so we look straight down the Z axis. You can do this by clicking on the axis cones of the view camera gizmo at the top right of the scene view. You can also change the axis of the scene grid to Z via the grid toolbar button.
Duplicate the Hour Indicator 12 object.
Duplicate the Hour Indicator 12 object.
Rename it to Hour Indicator 6.
Rename it to Hour Indicator 6.
Now Negate it's Y position.
Now Negate it's Y position.
Do the same for indicators 3 and 9
Do the same for indicators 3 and 9
Their X positions should be 4 and -4 with 0 Ys.
Their X positions should be 4 and -4 with 0 Ys.
Also their Z rotation should be 90 degrees.
Also their Z rotation should be 90 degrees.
Then create another duplicate of Hour Indicator 12, this time for hour 1.
Then create another duplicate of Hour Indicator 12, this time for hour 1.
Set its X position to 2, its Y position to 3.464, and its Z rotation to −30.
Set its X position to 2, its Y position to 3.464, and its Z rotation to −30.
Then duplicate that one for hour 2, swap its X and Y positions, and double its Z rotation to −60.
Then duplicate that one for hour 2, swap its X and Y positions, and double its Z rotation to −60.
How are the positions calculated?
How are the positions calculated?
Duplicate these two indicators and negate their Y positions and their rotations to create the indicators for hours 4 and 5.
Duplicate these two indicators and negate their Y positions and their rotations to create the indicators for hours 4 and 5.
Then use the same trick on hours 1, 2, 4, and 5 to create the remaining indicators, this time negating their X positions and again their rotations.
Then use the same trick on hours 1, 2, 4, and 5 to create the remaining indicators, this time negating their X positions and again their rotations.
Creating the Arms
Creating the Arms
To create the hour hand duplicate hour indicator 12 and rename it to Hours Arm.
To create the hour hand duplicate hour indicator 12 and rename it to Hours Arm.
Create a Clock Arm material, I chose solid black, use it on the arm.
Create a Clock Arm material, I chose solid black, use it on the arm.
Decrease the arms X scale to 0.3
Decrease the arms X scale to 0.3
Increase the arms Y scale to 2.5
Increase the arms Y scale to 2.5
Change the Y position to 0.75 so that it points up to 12 but is offset slightly downwards.
Change the Y position to 0.75 so that it points up to 12 but is offset slightly downwards.
ROTATION
ROTATION
The arm needs to rotate around the CENTER of the clock
The arm needs to rotate around the CENTER of the clock
PROBLEM: If we change its Z rotation the arm rotates around its own center!
PROBLEM: If we change its Z rotation the arm rotates around its own center!
SOLUTION: Create a PIVOT object and rotate that instead.
SOLUTION: Create a PIVOT object and rotate that instead.
Create an empty game object
Create an empty game object
Make it a child of Clock
Make it a child of Clock
Name the object Hours Arm Pivot
Name the object Hours Arm Pivot
Ensure its position and roation is 0 and its scale is 1
Ensure its position and roation is 0 and its scale is 1
Make Hours Arm a child of the Pivot
Make Hours Arm a child of the Pivot
Duplicate Hours Arm Pivot twice to create a Minutes Arm Pivot and a Seconds Arm Pivot. Rename them accordingly, including the duplicated arm child objects.
Duplicate Hours Arm Pivot twice to create a Minutes Arm Pivot and a Seconds Arm Pivot. Rename them accordingly, including the duplicated arm child objects.
Minutes Arm should be narrower and longer than Hours Arm
Minutes Arm should be narrower and longer than Hours Arm
set its X scale to 0.2 and Y scale to 4, then increase its Y position to 1.
set its X scale to 0.2 and Y scale to 4, then increase its Y position to 1.
Change its Z position to −0.35 so it sits on top of the hours arm.
Change its Z position to −0.35 so it sits on top of the hours arm.
Note that this is for the arm, not its pivot!
Note that this is for the arm, not its pivot!
Adjust Seconds Arm as well.
Adjust Seconds Arm as well.
This time use 0.1 and 5 for the XY scale
This time use 0.1 and 5 for the XY scale
and 1.25 and −0.45 for the YZ position.
and 1.25 and −0.45 for the YZ position.
The seconds arm should stand out.
The seconds arm should stand out.
Make a different material for it using a bright colour.
Make a different material for it using a bright colour.
Animating the clock
Animating the clock
Add a new script asset to the project via Assets / Create / C# Script and name it Clock.
Add a new script asset to the project via Assets / Create / C# Script and name it Clock.
Open the Clock Script in your code editor by double clicking it.
Open the Clock Script in your code editor by double clicking it.
Delete all code in the Clock Script.
Delete all code in the Clock Script.
An empty file defines nothing. It must contain the definition of our clock component.
An empty file defines nothing. It must contain the definition of our clock component.
We define the general class or type known as Clock.
We define the general class or type known as Clock.
Once that's established, we could create multiple such components in Unity, even though we'll limit ourselves to a single clock in this tutorial.
Once that's established, we could create multiple such components in Unity, even though we'll limit ourselves to a single clock in this tutorial.
In C#, we define the Clock type by first stating that we're defining a class, followed by its name.
In C#, we define the Clock type by first stating that we're defining a class, followed by its name.
Because we don't want to restrict which code has access to our Clock type, it is good form to prefix it with the public access modifier.
Because we don't want to restrict which code has access to our Clock type, it is good form to prefix it with the public access modifier.
At this point we don't have valid C# syntax yet. If you were to save the file and go back to the Unity editor then compilation errors will get logged in its console window.
At this point we don't have valid C# syntax yet. If you were to save the file and go back to the Unity editor then compilation errors will get logged in its console window.
We indicated that we're defining a type, so we must actually implement it. That's done by a block of code that follows the declaration. The boundaries of a code block are indicated with curly brackets. We're leaving it empty for now, so just write {}.
We indicated that we're defining a type, so we must actually implement it. That's done by a block of code that follows the declaration. The boundaries of a code block are indicated with curly brackets. We're leaving it empty for now, so just write {}.
Our code is now valid. Save the file and switch back to Unity.
Our code is now valid. Save the file and switch back to Unity.
The Unity editor will detect that the script asset has changed and triggers a recompilation.
The Unity editor will detect that the script asset has changed and triggers a recompilation.
After that is done, select our script.
After that is done, select our script.
The inspector will inform us that the asset does not contain a MonoBehaviour script.
The inspector will inform us that the asset does not contain a MonoBehaviour script.
What this means is that we cannot use this script to create components in Unity.
What this means is that we cannot use this script to create components in Unity.
At this point, our Clock defines a basic C# object type.
At this point, our Clock defines a basic C# object type.
Our custom component type must extend Unity's MonoBehaviour type, inheriting its data and functionality.
Our custom component type must extend Unity's MonoBehaviour type, inheriting its data and functionality.
What does mono-behavior mean?
What does mono-behavior mean?
The idea is that we can program our own components to add custom behavior to game objects. That's what the behavior part refers to.
The idea is that we can program our own components to add custom behavior to game objects. That's what the behavior part refers to.
The mono part refers to the way in which support for custom code was added to Unity.
The mono part refers to the way in which support for custom code was added to Unity.
It used the Mono project, which is a multi-platform implementation of the .NET framework.
It used the Mono project, which is a multi-platform implementation of the .NET framework.
Hence, MonoBehaviour. It's an old name that we're stuck with due to backwards-compatibility.
Hence, MonoBehaviour. It's an old name that we're stuck with due to backwards-compatibility.
We must make Clock into a subtype of MonoBehaviour.
We must make Clock into a subtype of MonoBehaviour.
Change the type declaration so that it extends that type, which is done with a colon after our type name, followed by what it extends.
Change the type declaration so that it extends that type, which is done with a colon after our type name, followed by what it extends.
This makes Clock inherit everything of the MonoBehaviour class type.
This makes Clock inherit everything of the MonoBehaviour class type.
However, this will result in an error after compilation.
However, this will result in an error after compilation.
The compiler cannot find the MonoBehaviour type.
The compiler cannot find the MonoBehaviour type.
This happens because the type is contained in a namespace, which is UnityEngine.
This happens because the type is contained in a namespace, which is UnityEngine.
To access it, we have to use its fully-qualified name, UnityEngine.MonoBehaviour.
To access it, we have to use its fully-qualified name, UnityEngine.MonoBehaviour.
It is inconvenient to always have to include the UnityEngine prefix when accessing Unity types. Fortunately we can declare that the namespace should be searched automatically to complete type names in the C# file.
It is inconvenient to always have to include the UnityEngine prefix when accessing Unity types. Fortunately we can declare that the namespace should be searched automatically to complete type names in the C# file.
This is done by adding using UnityEngine; at the top of the file.
This is done by adding using UnityEngine; at the top of the file.
The semicolon is required to mark the end of the statement.
The semicolon is required to mark the end of the statement.
Now add the custom component to the Clock game object in Unity.
Now add the custom component to the Clock game object in Unity.
This can be done either by dragging the script asset onto the object,
This can be done either by dragging the script asset onto the object,
or via the Add Component button at the bottom of the object's inspector.
or via the Add Component button at the bottom of the object's inspector.
To rotate the arms, Clock objects need to know about them.
To rotate the arms, Clock objects need to know about them.
Start with the hours arm.
Start with the hours arm.
Like all game objects, it can be rotated by adjusting its Transform component.
Like all game objects, it can be rotated by adjusting its Transform component.
So we have to add knowledge of the arm pivot's Transform component to Clock.
So we have to add knowledge of the arm pivot's Transform component to Clock.
This can be done by adding a data field inside its code block.
This can be done by adding a data field inside its code block.
Defined as a name followed by a semicolon.
Defined as a name followed by a semicolon.
The name, hours pivot would be appropriate for the field. However, names have to be single words.
The name, hours pivot would be appropriate for the field. However, names have to be single words.
The convention is to make the first word of a field name lowercase and capitalize all other words, then stick them together.
The convention is to make the first word of a field name lowercase and capitalize all other words, then stick them together.
Name it hoursPivot.
Name it hoursPivot.
We also have to declare the type of the field, which in this case is UnityEngine.Transform.
We also have to declare the type of the field, which in this case is UnityEngine.Transform.
It has to be written in front of the field's name.
It has to be written in front of the field's name.
Our class now defines a field that can hold a reference to another object, whose type has to be Transform.
Our class now defines a field that can hold a reference to another object, whose type has to be Transform.
We have to make sure that it holds a reference to the Transform component of the hours arm pivot.
We have to make sure that it holds a reference to the Transform component of the hours arm pivot.
Fields are private by default, which means that they can only be accessed by the code belonging to Clock.
Fields are private by default, which means that they can only be accessed by the code belonging to Clock.
But the class doesn't know about our Unity scene, so there's no direct way to associate the field with the correct object.
But the class doesn't know about our Unity scene, so there's no direct way to associate the field with the correct object.
We can change that by declaring the field as serializable.
We can change that by declaring the field as serializable.
This means that it should be included in the scene's data when Unity saves the scene, which it does by putting all data in a sequence—serializing it—and writing it to a file.
This means that it should be included in the scene's data when Unity saves the scene, which it does by putting all data in a sequence—serializing it—and writing it to a file.
Marking a field as serializable is done by attaching an attribute to it, in this case SerializeField.
Marking a field as serializable is done by attaching an attribute to it, in this case SerializeField.
It's written in front of the field declaration between square brackets, typically on the line above it but can also be placed on the same line.
It's written in front of the field declaration between square brackets, typically on the line above it but can also be placed on the same line.
Once the field is serializable Unity will detect this
Once the field is serializable Unity will detect this
and display it in the inspector window of the Clock.
and display it in the inspector window of the Clock.
To make the proper connection, drag the Hours Arm Pivot from the hierarchy onto the Hours Pivot field. Alternatively, use the circular button at the right of the field and search for the pivot in the list.
To make the proper connection, drag the Hours Arm Pivot from the hierarchy onto the Hours Pivot field. Alternatively, use the circular button at the right of the field and search for the pivot in the list.
In both cases the Unity editor grabs the Transform component of Hours Arm Pivot and puts a reference to it in our field.
In both cases the Unity editor grabs the Transform component of Hours Arm Pivot and puts a reference to it in our field.
Accessing all three arms
Accessing all three arms
Add two more serializable Transform fields to Clock with appropriate names.
Add two more serializable Transform fields to Clock with appropriate names.
Because these fields are all the same data type, we can make this code much shorter:
Because these fields are all the same data type, we can make this code much shorter:
Now connect the other two arms in the editor.
Now connect the other two arms in the editor.
Moving the arms
Moving the arms
We will rotate the arms as the components awaken. We will do this in the Awake method.
We will rotate the arms as the components awaken. We will do this in the Awake method.
Methods are somewhat like mathematical functions, for example:
Methods are somewhat like mathematical functions, for example:
That function takes a number—represented by the variable parameter x —doubles it, then adds three.
That function takes a number—represented by the variable parameter x —doubles it, then adds three.
It operates on a single number, and its result is a single number as well.
It operates on a single number, and its result is a single number as well.
In the case of a method, it's more like
In the case of a method, it's more like
where p represents input parameters and c represents whatever code it executes.
where p represents input parameters and c represents whatever code it executes.
Like a mathematical function a method can produce a result, but this isn't required.
Like a mathematical function a method can produce a result, but this isn't required.
We have to declare the type of the result—as if it were a field—or write void to indicate that there is no result.
We have to declare the type of the result—as if it were a field—or write void to indicate that there is no result.
In our case, we just want to execute some code without providing a resulting value, so we use void.
In our case, we just want to execute some code without providing a resulting value, so we use void.
We also don't need any input data.
We also don't need any input data.
However, we still have to define the method's parameters, as a comma-separated list between round brackets.
However, we still have to define the method's parameters, as a comma-separated list between round brackets.
It's just an empty list in our case.
It's just an empty list in our case.
We now have a valid method, although it doesn't do anything yet.
We now have a valid method, although it doesn't do anything yet.
Just like Unity detected our fields, it also detects this Awake method.
Just like Unity detected our fields, it also detects this Awake method.
When a component has an Awake method, Unity will invoke that method on the component when it awakens.
When a component has an Awake method, Unity will invoke that method on the component when it awakens.
This happens after it's been created or loaded while in play mode.
This happens after it's been created or loaded while in play mode.
We're currently in edit mode, so this doesn't happen yet.
We're currently in edit mode, so this doesn't happen yet.
To rotate the arms we have to create a new rotation.
To rotate the arms we have to create a new rotation.
We can change the rotation of a Transform by assigning a new one to its localRotation property.
We can change the rotation of a Transform by assigning a new one to its localRotation property.
Although the rotation of a Transform component is defined with Euler angles in degrees per axis in the inspector, in code we have to do it with a quaternion.
Although the rotation of a Transform component is defined with Euler angles in degrees per axis in the inspector, in code we have to do it with a quaternion.
We can create a quaternion based on Euler angles by invoking the Quaternion.Euler method.
We can create a quaternion based on Euler angles by invoking the Quaternion.Euler method.
Do this by writing it in Awake, followed by a semicolon to end the statement.
Do this by writing it in Awake, followed by a semicolon to end the statement.
The method has parameters used to describe the desired rotation.
The method has parameters used to describe the desired rotation.
In this case we'll provide a comma-separated list of containing three arguments, all between round brackets, after the method name.
In this case we'll provide a comma-separated list of containing three arguments, all between round brackets, after the method name.
We supply three numbers for the X, Y, and Z rotations.
We supply three numbers for the X, Y, and Z rotations.
Use zero for the first two and −30 for the Z rotation.
Use zero for the first two and −30 for the Z rotation.
If you enter play mode now
If you enter play mode now
you will see that the clock is set to 1pm.
you will see that the clock is set to 1pm.
Getting the current time.
Getting the current time.
We can use the DateTime struct to access the system time of the device we're running on.
We can use the DateTime struct to access the system time of the device we're running on.
DateTime isn't a Unity type, it is found in the System namespace.
DateTime isn't a Unity type, it is found in the System namespace.
It is part of the core functionality of the .NET framework, which is what Unity uses to support scripting.
It is part of the core functionality of the .NET framework, which is what Unity uses to support scripting.
DateTime has a Now property that produces a DateTime value containing the current system date and time.
DateTime has a Now property that produces a DateTime value containing the current system date and time.
To check whether it's correct we'll log it to the console at the start of Awake.
To check whether it's correct we'll log it to the console at the start of Awake.
We can do that by passing it to the Debug.Log method.
We can do that by passing it to the Debug.Log method.
Now we get a timestamp logged each time we enter play mode.
Now we get a timestamp logged each time we enter play mode.
You can see it both in the console window and in the status bar at the bottom of the editor window.
You can see it both in the console window and in the status bar at the bottom of the editor window.