In this homework, we will implement a first person shooter game. Here is a snapshot of the game:
a. Setup
Create a new Unity project called Drone Attack, of type 3D Core. In the Assets, create a folder called Models and another one called Materials. Rename the sample scene as game.
Add a plane from 3D objects. Color it the way you want (use a material). Call it ground. Set its scale as 8 x 8 x 8.
b. Objects
Download the following file and extract the files in a local folder:
We'll reuse the file GameMaster.cs from Homework 8 and PlayerControl.cs from Homework 6. Some modifications were made to remove the Fly axis and move the creation of the flower array from the player to the game master and turn it into balloons. Create a folder Scripts in Unity under Assets and drag these scripts and drop them inside it.
Drag the file drone.fbx to the folder Models, then add it to the scene. Place it a little above the ground and add the script PlayerControl.cs to it. Add a Rigidbody component to it and turn off gravity and drag, and make the collision detection continuous. Freeze its position over Y and its rotation over X and Z.
Drag the VertexColorShader.shader file to the Materials folder. Create a new material and call it vertexShader. In the inspector at the top, in the menu next to Shader, select Custom - VertexColorsStandard. Increase the Metallic and the Smoothness properties of this material. Then drag this material and drop it on the drone object. The colors of the object should be visible now. Increase the Metallic property of this material.
Drag one of the ground images and one of the brick images to the Materials folder. Drag the ground image from that folder and drop it over the ground object. This will create a material too.
With the Ground object selected, expand the material in the Inspector and under Emission, set the tiling to 10 x 10. Now the texture should be replicated 10 times in each dimension.
b. Camera and Movement
Rename the drone Player and add a Player tag to it. Add the PlayerControl.cs script as a component to it. Set the speed to 1 and the mouse sensitivity to 1. Feel free to change the controls to something you like better. Set the coordinates of the drone object to 0 on X and Z to begin with. Reduce the scale of the object to 30 x 30 x 30. Also add a box collider component to the player and adjust the dimensions.
Bring the camera close to the drone and place it a little above it. We aim for a first person view, but the drone should not occupy too much of the screen. Make the camera a child of the player. Now it should be moving around with it.
c. Arena Setup
To reuse the maze creation from Homework 8, first we need a brick. Add a cube 3D object to the scene and place it close to the drone. Scale it to 2 x 4 x 0.5 and set its coordinates to 1 x 2x 0. Drag and drop the brick image over it to create a material for it. Drag and drop the brick object into the Prefab folder to create a prefab from it.
Note that this will only be half of a brick covering a tile in the arena. Because these bricks have a thin side, the idea is to align the bricks of the neighboring tiles to create a continuous looking structure.
Add an empty object to the scene called GameMaster. Attach the script GameMaster.cs to it as a component.
Back in Unity, drag and drop the Brick prefab over the Brick Ref property of the GameMaster object. Set the maze width and height to 20. You should be able to run the program and see the maze. It looks like the walls are sparse for now, but we will fix that.
Next, drag both files baloon.obj and baloon.mtl to the project. Add the baloon object to the scene. Set its coordinates to 0 x 0 x 0 and its scale to 0.5 in all dimensions. Create a red material and apply it to the baloon. Add a sphere collider to it and adjust it to cover the baloon well. Then create a prefab from the object and then delete it from the hierarchy.
Select the GameMaster object and drag the baloon prefab onto its Baloon Ref property. Set the Baloon Nr to 10. Run the program to see if the baloons were placed properly.
d. Adding the Bullet
Add a 3D object of type Capsule and call it Bullet. Apply a 90 rotation over x to make it parallel to the drone. Add a Rigidbody to it with a small mass and not using gravity, and no drag. Make it small enough but still visible. It should already have a collider. Set its color however you like. Set its coordinates to 0 x 0 x 0, then create a prefab from it. That way, bullets will be instantiated at position 0.
Now, we want to set the initial position assigned to bullets relative to the drone. Place the bullet in front of the drone facing forward.
In the script PlayerControl.cs, add a public class variable of type GameObject called firstBullet, a variable of type Vector3 called bulletStart, and another public variable of type GameObject called bulletRef. In Unity, drag the bullet prefab onto the Bullet Ref property of the player, and the bullet object onto the First Bullet property.
In the function Start, assign to the variable bulletStart the current position of the first bullet object minus the position of the player (transform.position).
Add a public class variable of type KeyCode called fireKey. Come back to Unity and assign it the Space in the inspector.
Add the following function to the PlayerControl class:
void Fire() { Vector3 dir = Quaternion.Euler(0, twistAngle, 0) * bulletStart; firstBullet.transform.position = transform.position + dir; firstBullet.transform.rotation = Quaternion.Euler(90, twistAngle, 0); }
In the function Update, add the following code:
if (Input.GetKey(fireKey)) Fire();
Test that the functionality works and that when you press the spacebar, the bullet's position and orientation are adjusted to the player's.
Next, in the function Start, after assigning a value to the variable bulletStart, you should should delete the bullet object by calling Destroy(firstBullet). Then in the function Fire, replace firstBullet with a local variable bullet declare with type GameObject and initialized by instantiating the buletRef variable. After it's positioned and rotated, give it a velocity based on the direction vector multiplied by some speed. The bullet should be ready to go.
Add a cooldown to the fire function so that it doesn't let us fire too often. For that, declare two class variables of type float called coolDown and coolTime. Assign to the first one the value 0 and to the second, the number of seconds you want to wait, like 0.5.
In the function Update, before firing, check if coolDown is greater than 0, and if it is, subtract Time.deltaTime from it.
In the function Fire, add a conditional where you check if coolDown is <= 0 and move the rest of the code inside of it. Then after all the operations on the bullet, reset coolDown with the value of coolTime.
e. Maze Components
Let's get complete the maze walls based on neighboring cells for the tiles. Add the following variables to the GameMaster script:
public int [, ] hood = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; float [] rotations = {0, 0, 90, 90}; float [, ] offsets = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
Change the definition of the bricks array to:
public GameObject[,][] bricks; and its initialization in BuildMaze to
bricks = new GameObject [mazeWidth, mazeHeight][];
Then add the following functions to the class:
// Checks if the cell [i, ] is in the table maze bool OnTable(int i, int j) { if (i >= 0 && i < mazeWidth && j >= 0 && j < mazeHeight) return true; else return false; } // Create a composite brick for cell [i, j] based on neighboring walls GameObject [] MakeBrick(int i, int j) { GameObject [] br = new GameObject[4]; GameObject b; int bi = 0; for (int k = 0; k < hood.Length/2; k++) { int c = hood[k, 0]; int r = hood[k, 1]; if (OnTable(i + c, j + r) && maze[i + c, j + r] == CellType.wall) { b = Instantiate(brickRef) as GameObject; b.transform.position = new Vector3(Col2X(i) + offsets[k, 0], brickY, Row2Z(j) + offsets[k, 1]); b.transform.rotation = Quaternion.Euler(0, rotations[k], 0); br[bi++] = b; } } if (bi == 0) // didn't get any neighboring walls { b = Instantiate(brickRef) as GameObject; // add the basic one b.transform.position = new Vector3(Col2X(i), brickY, Row2Z(j)); b.transform.localScale = new Vector3(3f, 4f, 3f); // and make it bigger br[bi++] = b; } return br; }
Then in the function BuildMaze replace the expression assigned to bricks[i, j] with a call to MakeBrick(i, j) and delete the line following the assignment. It should now work to create the appropriate maze.
a. Interface
Add a button that starts a new game and one to quit the application. Also add the key 'n' for starting a new game and 'q' to quit the program.
Add a score keeping feature and a text box that displays it. Increase the score every time a bullet hits a baloon.
Add a sound for when a bullet is fired and when it hits a baloon, and any other sound you may think appropriate.
b. Maze Walls
Complete the function adding the wall sections based on the neighboring walls.
c. Opponents
Add a feature where if the player collides with a balloon directly, the game is over.
Optional (up to 5 extra credit points). You can add different types of opponents, even some that can shoot bullets themselves, and have the game being lost if the player collides with a bullet. You can also have the player and/or opponents have a number of hit points to begin with, and have them lose a hit point when they get hit by a bullet, and only disappear or lose the game when the hit points get to 0.
Please make a comment when submitting the assignment letting me know what optional features you implemented.
Create a Windows executable and a zip file of the build folder, and add the script files to it. Also take a screenshot of the running game and add it to the zip file. Make sure that the zip file contains:
Submit the zip file containing the Windows executable and the script file to Canvas, Assignments - Homework 9.