Termbank
  1. C
    1. Course Schedule
  2. L
    1. Lecture Slides
      Reading
  3. O
    1. Oculus Best Practices Guide
      Reading
  4. U
    1. Unity
      Platform
  5. V
    1. Virtual Reality, LaValle 2016
      Reading
  6. Y
    1. YouTube Lectures: VR Systems and Humans
      Content
Completed: / exercises

The Room: A Unity Step-by-Step Tutorial

This tutorial will guide you on starting a project in Unity from scratch. Once you are done, you will have created a basic room with some simple functionalities and scripts.

Provided Files

We provide some of the materials for you to download and use:

Creating a Unity Project

Create a Project in Unity. To create a new project, click the New Project button on the top right. If you are using university computers, pay attention where the project is saved. All saves on PCs are deleted when they are rebooted.
We will use the 3D template:
Get acquainted with Unity’s basic features and the interface: Interface Tutorial.

Part 1. Setting Up the Room and Unity UI Basics

1.1 The Room

Build a cubic room out of six planes. Make sure to orient these planes so the visible sides face inwards, and ensure that the player cannot walk through any of them. The room should be 15x15x15 Unity units (aka meters). Create a plane:
GameObject → 3D Object → Plane.
Unity’s default plane size is 10×10(X×Z) units. In order to make your room 15 units wide, you have to scale the plane. On the right side (in the default editor layout) you will find the Inspector window. This window provides details about the currently selected object. Select the plane in the Scene view, and the Inspector will fill with information and settings for said plane. Find the Scale option, and set the X and Z values to 1.5 to make your plane 15 units wide and long. Build the rest of the walls by adding new planes or duplicating the existing and adjusting scale, position, and rotation. The plane has no thickness, so the value in Y can be any positive integer and remember your plane is now 15 units/meters wide.
Note: The plane also rotated its axes, when you adjust rotation so the local axes change. Make sure to account for that when rotating and moving objects!
Note: By default, your scene has a directional light in it, which illuminates your entire scene from a specified angle, from very far away, much like the sun. You’ll notice that your planes do not block this light. That’s because planes only block light (and render) from one side. Make sure that in your room, all six planes face inwards, and bear this in mind when creating objects in Unity in the future! For now, just delete the directional light. You will add more lights later.

1.2 Player Controller in VR

Note: If you do not yet have access to an HMD, you should still be able to follow these steps, you will just be unable to test the results properly.
To get started with setting up VR for your project, open Package Manager (right next to Asset Store as seen in the screenshot above). In Package Manager, look for packages from Unity Registry (top left in the window), search for OpenXR, select it and click Install. See screenshot below for how to navigate the Package Manager window. In addition, import the controller sample, which will appear in the window after installation. That will allow you to test your controllers, see how they work and also use it as an example.
You may then see a warning about an interaction profile. Next, we should navigate to Edit → Project Settings → XR Plug-in Management and select OpenXR. Although you probably have an Oculus device, OpenXR is a more universal solution, allowing your project to run on other devices as well, so we will use that.
After that, select OpenXR below XR Plug-in Management and add an interaction profile. For most of you, Oculus Touch is the correct profile, so add that. If you have different controllers, choose a suitable option. Note that even when a specific profile is selected, many actions are shared between different controllers, so it will work with different controllers too.
After that, all the packages and settings should be in order! Try opening the sample scene that was imported earlier (Assets/Samples/OpenXR Plugin/{version number}/Controller/ControllerSample.unity) and run it. If everything is working correctly, you should see the scene in your HMD and your controller inputs should be visible on screen. There should even be vibration. PlayerRig is the object in the scene where the tracking and inputs are set up. We will go into more detail later, but for now you may wish to look at what it consists of.
The easiest way to set this up for your own scene is to create a prefab of the PlayerRig by clicking and dragging it to your Project → Assets in the Unity window. You may wish to remove some of the unnecessary components, but for now it’s fine to leave them there. Place the prefab into your scene, at (0,1,0), and delete the main camera object.

1.3 Lighting

Create a point light GameObject → Light → Point Light, and place it at (0,15,0). The Inspector tab should have a Light component like below:
Of primary importance are the range (the radius of your light), color, and intensity values. Set the shadow type to soft shadows, and the mode to Realtime. Set your range and intensity so that your room is brightly lit.
We recommend reading the Unity manuals for general lighting, shadows, and lighting modes. In later homework, and your course project, these settings can impact performance significantly.

1.4 Planet and Moon

Create two spheres GameObject → 3D → Sphere. Scale the first sphere to 2 in all directions, and place it in the center of your room. In the Hierarchy view, drag the second sphere onto the first. The result should look like this:
Now, the second sphere is a child of the first sphere. When you change the position, rotation, or size of the parent sphere, its child will also undergo the same movement, rotation, or scaling. The (0,0,0) origin position of the child is now its parent’s position, not the global (0,0,0) origin. That is, the child’s position is an offset from the parent’s position. Finally, if the parent rotates, then the child will rotate about its parent’s axes, not its own axes (this will make more sense later). See the Unity hierarchy manual for more information.
Set the position of the child sphere to be (2,0,0), which is four units from the parent sphere on the X-axis. For why this is, think about the scaling of the parent sphere.

1.5 Text

Check out the Unity tutorial on Creating Worldspace UIs. Create a canvas GameObject → UI → Canvas and then create a text object under it GameObject → UI → Text. If you create a text object directly, it will be parented under an existing canvas, which may not be desired since our PlayerRig prefab uses its own canvases. The canvas should look like this in the Inspector tab:
First, change the Render Mode from Screen Space - Overlay, to World Space. This changes the canvas from a UI element glued to the camera, to an object that is stationary in the world. Traditional UIs do not work well in VR, and we strongly discourage sticking any UI elements to the camera in your future Assignments. Always attach UI elements to something in the world (see this).
Now that the canvas is a world space object, we can make it a more reasonable size. However, since the Rect Transform’s width and height are in units of pixels, not world units, we must first set the resolution of the canvas. Set the width and height to 1000 (that is 1000×1000 pixels). Now, shrink the canvas by setting the scale. Multiplying the canvas width and height by the scale factors gives the actual size of the canvas in world space. For example, since we set the canvas to be of size 1000×1000 pixels, using scaling factors of 0.01 would make the canvas 1000∗0.01=10 units large in world space. Adjust your text’s Rect Transform (position and with/height) to be the same as on the parent canvas, but leave the scale as it is, 1. Now, you can place your canvas against one of the walls.
Offset it a small amount (like, 0.001) off of the wall it is against to avoid Z-fighting, which happens when two objects have the same depth, and Unity can’t figure out which one to render. Below is an example of Z-fighting:
Now, you can set your text color, size, font, width, whether it wraps or overflows, etc. You can check how your project looks in VR by just clicking play on top of your Scene view.
Later when we start scripting, make sure your text has the controls for your game. Make sure the text is big enough for us to read. If the text appears blurry or jagged, then increase the width and height of the canvas and text (to increase the resolution), and scale them down further.

1.6 Material

Read the Unity materials, shaders and textures manual, focusing mainly on the materials, for now. A base texture (tile.png), and a normal map (the weird purplish image tile-normal.png) can be found from the Provided Files (materials.zip). To create a material, go to Assets → Create → Material.
This will generate a default material, which should show up in the Inspector tab like so:
Drag the tile.png image to the box labeled Albedo. Now, drag this material from the Assets folder onto one of your walls in the Scene view. It probably doesn’t look too good. Don’t worry, it’ll get better. Drag the tile-normal.png image to the box labeled Normal Map. Notice how it changes the visual perception of the material. A normal map is a trick used to give the illusion of depth on a flat surface, by telling the engine to reflect light as if there were these little bumps and pits in the material. Apply this material to a wall by dragging it onto a wall.
Create a new material, called Wall 2, apply the same albedo and normal maps as you just did for Wall 1, and apply it to a different wall. Right below the Secondary Maps subheading is the Tiling option, which has an option for X and Y. Tiling causes a material to repeat itself on the same object, rather than covering the whole thing. So, changing tiling X to 2, means that the material will repeat once, and show up twice, in the X direction on the wall. Play with the tiling until you like the look of it. Below is an example of non tiled and tiled walls side by side.
In the Inspector tab, right below the Albedo option, there are Metallic and Smoothness sliders. Play around with these, and see how they affect the material. The Metallic slider adjusts the reflections, and Smoothness helps to enhance or subdue the normal map. Modify the metallicity and smoothness of the two wall materials so that they are clearly visually different.
Finally, create a material that has no albedo or normal map. Next to the Albedo option there is a small color box. Since this material has no albedo, the material will be this flat reflection color. Try and see what happens when you change the color of a material with an albedo. Apply this flat color onto a third wall in the room.

1.7 Adding Skybox

Follow the instructions in the Unity skybox manual to set up the skybox. Since the room is currently enclosed, you won’t be able to see the skybox from the room. We will remedy that in the scripting section below.
The skybox is from mgsvevo.

Part 2. Scripting in C#

2.1 Basic Scripting

Unity scripts use the C# language. If you are unfamiliar with programming, you can check out this C# tutorial. You’ll only need the basics of objects, classes, and variables for now.
We recommend reading the Unity scripts manual and the script object API.
Unity’s Scripting API Reference is a useful source of information for when you are scripting new objects. For the first script, we will detail the functions that we recommend, but for the others, we expect that you will refer to the API reference if needed.
If you have access to VR HMD and controllers, use those controllers as the input device. You may test the following scripts with keyboard inputs until you get access to VR hardware. However, the returned assignment should function with the motion controllers of a VR device.
For testing without VR hardware, you may also find it useful to use the Scene view. When you run the scene in the Unity editor, the view defaults to Game, but it can be switched to Scene right after, allowing you to move the rig, simulate movement, or otherwise manipulate the scene. You can also monitor the whole scene this way and ensure everything is functioning as intended.
Some documentation about input systems is linked below. However, you may find it sufficient to simply take example from the controller sample scene.

2.2 Quit Key

Create a new script using the Assets menu (Create → C# script). To attach the script to an object, select the object, then drag the script from the Assets tab to the Inspector tab. (Alternatively, you can create and attach a script in one step using Add Component → New Script.) For this script, the exact object attached is unimportant.
When a Unity script is attached to a GameObject, that script will run when the game is started. Furthermore, the this reference in the script will refer to the object to which the script is attached.
By mimicking the input code in the controller sample scene, we can make it something like this:
In addition, remember to set the desired action in the Unity editor. The controller sample should come with all the necessary actions to use all the input options of your controllers, and you can find and select these by pressing the circle icon on the right, but if you want to add other actions or edit them in some way, you can find the input actions file by clicking on the text after selecting an action, or you could just right click on the Project Assets window, Create →Input Actions.
This documentation may also help you understand what action corresponds to what button.
Application.Quit() quits a Unity application, but it will not stop a game running in the editor. Thus, we use an #if and a Unity-specific preprocessor directive that adjusts its behavior depending on whether the project is within the editor, or a standalone executable.

2.3 Light Switch

Make a script and attach it to the point light. Our first step is to get the Light component of our point light GameObject. Read the Controlling GameObjects using Components tutorial, then add these lines to your script:
The Start function runs as the game initially starts. This saves a reference to the Light component for later use.
Alternatively, you can declare public GameObjectvariables. Save your script, then navigate to the script in the Inspector pane. The variable will show up in the Inspector pane, and you can assign GameObject to it by dragging them from the hierarchy tab into the variable slot! This works similarly for other variable types. You can read more about this in the Variables and the Inspector tutorial.
To change the light, write something similar to the following, but remember to do it according to the input system you are using rather than just copying the code below.
For the actual light color, you can either create a new color using the new Color(red, green, blue) constructor, or one of the predefined colors. How you change the light is up to you, but pressing the controller button you designate must visibly change the light color.

2.4 Orbiting Moon

Make the moon orbit the planet. The easiest way to do this is to have the planet object constantly rotate. Since the moon object is a child of the planet object, it will also rotate around the planet. You can control the rotation and position of a GameObject with the transform object variable, an instance of the aptly named Transform class. Use the functions of this class to rotate the planet system around the Y-axis. Keep in mind that the Update function runs every frame, but frames often vary in real time length. Thus, using static rotation amounts with the Transform class will make the apparent rotation amount depend on framerate. This is generally not desirable. To use real time frame times instead, use the Time.deltaTime variable.

2.4.1 Break Out

Set up a button on the controllers to switch the player’s position between an external viewing point and the room.
Like any GameObject, the player object has a Transform. With that in mind, make a script that switches the player between a room and a new external viewing point, e.g. a small new plane a moderate distance from the room. The player should still start within the room. Pressing the button for the first time must move the player to the external viewing point. After that, pressing the button must alternate between the two locations.
You can control the rotation and position of a GameObject with the Transform object variable, an instance of the aptly named Transform class. Use the functions of this class to rotate the planet system around the Y-axis.

Part 3. A More Interesting Room

Instead of the cube room we laid out above, use a modeling tool to create more complicated room geometry, like a curved roof, slanted windows, multiple levels, et cetera. Please write down what you created. Your creation still needs to have the other features of the room we described above.
Blender is a free and powerful 3D modeling software, but you may also use something else if you prefer.
The default Unity modeling tools are extremely limited, so we highly recommend you familiarize yourself with another tool. It will assist greatly in your final project. You can use the room as a basis of your course project.

Authors

Nathaniel Myren, Shantanu Tulshibagwale, and Elmeri Uotila.
?
Lectures and labs schedule for the VR Systems and Humans course, Fall 2023
Lecture slides, VR Systems and Humans course, University of Oulu, 2023
Informative Guides to Help You Design, Develop, and Distribute Your VR App.
The platform for interactive, real-time, 3D content creation.
Virtual Reality, LaValle 2016 is the free online textbook we use in this course.
YouTube Channel: VR Systems and Humans, Fall 2022
University of Oulu