Dana Vrajitoru
I355/C490/C590 3D Games Programming

I355/C490/C590 Lab 2 / Homework 2 Unity Version

Date: Wednesday, September 11, 2024. To be turned in by Wednesday, September 18. Here is a snapshot of the game:

In this lab, we will build a small platformer game with a capsule for the player, and we'll see how we can have the camera follow the player.

Lab Part

Ex. 1. In this lab we'll create a simple 3D platformer game.

Environment

Open Unity Hub and and create a new project called 3DPlatformer, of type 3D Core.

Rename the scene as Level.

Ground

Add an object of type Plane and call it Ground. The default size of such a plane is 10 x 10, so leave it like that for now.

Camera Setup

Select the Main Camera object in the hierarchy. Set the Y coordinate to 2 and the Z coordinate to -5.

Project Settings

In the top menu, click on the File menu and then on Build Settings. While you are here, click to add the open scene to the build. Then in the bottom left corner, click on Player Settings. This adds another dialog with a bunch of things we can configure.

With the Player selected on the left, scroll down to the Resolution and switch from Fullscreen Window to Maximized Window. Further down, under Shadows, next to Shadow Resolution you can select Medium Resolution. You can switch these back to higher values if the game doesn't look right to you. You can close the Build Settings panel.

Player and Scene

We will now create an object for the player and turn it into a prefab. We do that sometimes when we want to be able to create copies of it more easily.

Using the + button in the Hierarchy, add a new object to the scene of type Capsule and rename it Player.

This object comes with a pre-made collider, but since it's going to be moving, let's add a Rigidbody component to it from the Physics category. Change the collision detection in this component from Discrete to Continuous. Below that, expand the Constraints and check the boxes to freeze the rotation on all 3 coordinates.

Set the position of this object in the inspector at the top as 0 x 2 x -2. Run the program to see what happens. The capsule should simply fall to the ground and rest there. This is the right behavior so far. To be able to control its movement, we need to add a script to it.

Script. With the Player still selected, click on Add Component in the inspector and select a New Script at the bottom of the menu. Name this script PlayerControl and save it. Then in the project browser, create a folder called Scripts and move it inside. Now double-click on it to open it for editing.

At the top, declare a class variable to hold a reference to the rigid body component:

Rigidbody rbody;

Then in the function Start, assign to this variable a reference to the component:

rbody = GetComponent<Rigidbody>();

We need to make this assignment in the function Start and not at the top of the class because the function Start is only called after the object is created, so the rigid body we're referencing actually exists.

To make referencing the velocity components easier, add the following function to the class:

float vx()
{
    return rbody.velocity.x;
}

and similar functions for Y and Z.

Input Configuration

Let's configure what keys to use in the project to move the player. At the top, from the Edit menu select Project Settings. Click on Input Manager on the left. Expand Axes and the Horizontal. We see here that the left and right keys are connected to this axis, with A and D as the alternative options. So both of them should work. Similarly, the Vertical uses the up and down arrow keys as well as the W and S keys. We may also use the Jump later.

Player Movement

At the top of the class, declare the following variables:

public float speed = 6f; 
public float jumpForce = 5f;

Then go back to Unity. These variables should have been added as attributes in the script component of the Player. You can leave the speed and the jump force as they are for now, but this lets us calibrate these values more easily later.

The add the following code in the function Update:

float horizontalInput = Input.GetAxis("Horizontal");
float verticalInput = Input.GetAxis("Vertical");
rbody.velocity = new Vector3(horizontalInput * speed, vy(), verticalInput * speed);

This uses the horizontal input to apply the speed of the object to the X axis, and the vertical one to the Z axis. We keep the current vertical velocity. Note that in Unity, we cannot modify individual components of the velocity, we can only assign a full vector to it.

Jump. Let's make the player also jump when we press the spacebar. First, add the following function checking if the object is on the ground. For that, add a bool variable called isGrounded and set it as false in the function Start.

Then add another component to the Player object from Physics, or type SphereCollider. Set the Radius of this sphere as 0.1 and the coordinates of the Center as 0 x -1 x 0.

Check the box Is Trigger for this collider. This means that the collider will not prevent objects from passing through it, but it will let us know when it collides with anything.

Now this is a small sphere placed at the bottom of the object. Then add the following functions to the script:

void OnTriggerEnter(Collider other)
{
    isGrounded = true;
}
void OnTriggerExit(Collider other)
{
    isGrounded = false;
}

Then add the following function applying a vertical velocity to the object:

public void Jump()
{
    rbody.velocity = new Vector3(vx(), jumpForce, vz());
}

Now add the following code to the function Update:

if (Input.GetButtonDown("Jump") && isGrounded)
{
    Jump();
}

If you've set the attributes properly in Unity, the jump should also be working.

Camera Tracking Player

Let's have the camera track the player and rotate with as as the player is moving around.

Click on the Player object to select it. Click on the + and add a new node of type Create Empty Child. Rename this node TwistPivot. Set its position as 0 x 1 x 0. Add a child of this node and call it PitchPivot. Set its position at 0 and the rotation over X as 10 (degrees).

With this, in the hierarchy, move the Main Camera object and make it a child of the PitchPivot node by dragging it and dropping it over this object. Now the camera will move with the player subtree and be affected by transformations applied to the two pivots. Set the camera position to 0 x 0 x -3.

Save the scene and test the program. Now the camera should be moving with the player.

Mouse Control

We would like to make the mouse invisible on screen and confined to the application window while playing. However, when the player hits the ESC key, the mouse should become visible again and unconfined. If we look at the project settings, input manager, we see an option called Cancel that uses the escape key.

To be able to show and hide the mouse in the game, add a bool variable to the class mouseHidden initialized as false. add the following two functions in the script:

void HideMouse()
{
    Cursor.visible = false;
    Cursor.lockState = CursorLockMode.Confined;
    mouseHidden = true;
}
void ShowMouse()
{
    Cursor.visible = true;
    Cursor.lockState = CursorLockMode.None;
    mouseHidden = false;
}

Now call HideMouse in the function Start. Then add the following

if (Input.GetButtonDown("Cancel"))
    ShowMouse()

To test this, when playing the game you need to click on the game play window first to see the mouse disappear. When you hit the Escape key, it should appear again.

Then, we want to capture mouse movement in our scene. First, at the top of the class, add the following variables:

public float mouseSensitivity = 1f;
public GameObject twistRef;
public GameObject pitchRef;

The latter will hold references to the two pivot objects. Going back to Unity, check that these were added as attributes to the Player in the script component in the inspector. Then click on the circle next to None for each of them and select the corresponding objects from the list. Now the script has access to these objects.

Add the following code to the function Update:

if (mouseHidden)
{
    float mouseX = Input.GetAxis("Mouse X");
    float mouseY = Input.GetAxis("Mouse Y");
    if (mouseX != 0 || mouseY != 0)
    {
        float twistInput = -mouseX * mouseSensitivity;
        float pitchInput = -mouseY * mouseSensitivity;
        twistRef.transform.Rotate(0, twistInput, 0);
        pitchRef.transform.Rotate(pitchInput, 0, 0);
    }
}

Now when you play the game and click in the Game window, moving the mouse should rotate the camera proportionally. If we don't want the camera to rotate too much when we move the mouse, let's force the rotations to [-30o, 30o]. Add the following code inside the inner conditional above:

float twistY = twistRef.transform.eulerAngles.y;
if (twistY < -30)
    twistRef.transform.Rotate(0, -30 -twistY, 0);
else if (twistY > 180 && twistY < 330)
    twistRef.transform.Rotate(0, 330-twistY, 0);
else if (twistY > 30 && twistY <= 180)
    twistRef.transform.Rotate(0, 30-twistY, 0);

Perform a similar changes to pivotRef applying it to the X rotation. Then turn the entire code dealing with the mouse into a function called MouseMove and make a call to this function in Update.

Forward direction

Now, we'd like the player to move forward in the direction of the camera, and not in the global direction. To do this, replace the lines defining the velocity of the rbody with the following (but keep the input axes probing):

Matrix4x4 direction = twistRef.transform.localToWorldMatrix;
Vector3 motion = new Vector3(horizontalInput * speed, vy(), verticalInput * speed);
rbody.velocity = direction.MultiplyVector(motion);

This gets the transformation matrix of the twist pivot and applies it to the motion direction vector. Test the program to see how well it works. This is the end of the lab.

Homework Part

Create a few other plane or box objects places around the ground one , accessible to the player by jumping, to create a more complex level. Apply some materials to the objects to make the game look better.

Add a few collectible objects that the player can look for. You can make them yellow cylinders with a small height, and lay them on the side to look like coins. Add a counter to them and increment it every time there is a collision. Set them with a tag such as "coin" that you can check from the code.

You will need to add a function OnCollisionEnter(Collision colInfo) to the player control script to handle the collision. In this function, when the tag of the object colInfo is equal to the tag you added to them, delete the object by doing

colInfo.collider.gameObject.SetActive(false);

That's it for the homework. Submit a Windows executable from it as a zip file, as well as the files Level.unity and PlayerControl.cs, and a screenshot of the program (png or jpg image), in a single zip file.

When you create the executable for Windows, make sure to create a new folder for it, so that you don't submit files that you don't need to. This folder should contain:

You can add the scripts and the scene to the same zip file.

Homework Submission

Submit the zip file containing the Windows executable, level and script files, and the screenshot of the game to Canvas, Assignments - Homework 2.