Dana Vrajitoru
I355/C490/C590 3D Games Programming

I355/C490/C590 Lab 9 Godot Version

Date: Wednesday, November 30, 2024. To be turned in by Wednesday, November 6.

In this homework, we will implement a first person shooter game. Here is a snapshot of the game:

Ex. 1. Scene Creation

a. Setup

Create a new Godot project called Drone Attack. In the res:// folder, create a folders called Materials, Scenes, Scripts, and Models.

Add a 3D Scene node called Root. Add the sun and the environment to the scene like before, as well as a Camera 3D object.

Download the following file and extract the files in a local folder:

lab9.zip

Save the scene as game.tscn into the Scenes folder. Run the program and set this scene as the main scene.

Add a StaticBody3D object called Ground. Add a child of it of type MeshInstance3D. Set its Mesh as a New PlaneMesh, then click on the plane in the inspector and set its dimensions as 80 x 80.

Choose one of the ground images and drag it to the Materials folder in the project to import it. Do the same with the brick images. Then drag the ground image and drop it over the ground to apply it to this object. Now to have it repeated over the floor and not just placed there once, in the inspector, expand Geometry. Click on the Material Overload to expand its properties. Scroll down to UV1 and set the scale to 10 in all 3 dimensions. This will repeat the image 10 times in both dimensions.

Add a CollisionShape3D child of the Ground and set its space as a new box. Adjust the dimensions accordingly.

We'll reuse the scripts player.gd from Homework 6 and game_master.gd (former gd) from Homework 8. Download these files and add them to the Scripts folder. Some modifications were made to remove the PageUp PageDown input. Create a folder Scripts in the project under res:// and drag the scripts into it.

Drag the file drone.fbx to the folder Models, then add it to the scene. Set its coordinates as 0 x 0 x 0. Set its scale to 0.3 in all dimensions and its rotation over y to 180.

Add a CharacterBody3D object called Player and make the drone its child. Set the coordinates of this object as 0 x 1 x 0. Change the motion Mode to Floating. Then under Axis Lock, lock it linear movement over y and its angular movement over x and z.

b. Camera and Movement

Add the script player/gd as a component of the player object. For that, click to add a script, and instead of selecting a new script, click Load and select the script player.gd. If you get an error because the bullet scene and object are not yet defined, comment out those lines for now and declare the constant and variable with the value 0. Don't try the fire (spacebar) until you have created those resources.

Move the camera up until it's above the drone and towards the back a little bit. Rotate it a little towards the ground. In the preview, it should show some of the front of the drone. Play with the fov until you're happy with the preview. Then make it a child of the Player.

c. Arena Setup

To reuse the maze creation from Homework 8, first we need a brick. Add a StaticBody3D object called Brick. Add a child of it of type MeshInstance3D. Set its Mesh as a New BoxMesh. Set its dimensions as 2 x 3 x 0.5 and its coordinates as 1 x 1.5 x 0. Then drag and drop the brick image over it to create a material for it.

Add an object of type CollisionShape3D as child of the Brick and set its shape as a new box. Adjust the dimensions to fix the brick well. Now right-click on the Brick object and select Save Branch as Scene. Delete the Brick node from the Hierarchy.

Add an empty object to the scene called GameMaster. Attach the script game_master.gd to it as a component.

Drag the file baloon.fbx to the scene and set its position to 0 x 0 x 0 and the scale to 0.5. Right-click on it and select Make Local. Now you should see a mesh as its child. Expand the mesh in the inspector, then expand Surface Material Override. Assign it a new StandardMaterial3D and click it to expand it. Set the Albedo color to something like bright red.

Right-click on the baloon object and choose Change Type. Select a StaticBody3D. Since the baloon node already has a mesh for a child, we can change it to this type of node. Add another child of the baloon of type CollisionShape3D and set its shape as a new sphere, then adjust its position and dimension to fit the baloon well. Then save the baloon branch as scene as well. Now you can delete it from the scene.

The script also has a function make_baloons that uses the same technique we used for adding the flowers in Homework 6. It needs the baloon scene to be created in order to work.

d. Adding the Bullet

Add an object of type CharacterBody3D called Bullet. Add one child of type MeshInstance3D and set a mesh of type Capsule. Adjust the size of the mesh so that it's small enough but still well visible. Add another child of the Bullet object of type CollisionShape3D and set its shape as a Capsule. Adjust the dimensions to match the mesh. Set its color however you like. Set its coordinates as 0 x 0 x 0 and save the branch as a scene. 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 player.gd, there is a variable called bullet_start initialized in _ready as the bullet's offset with respect to the player's position. There is also a constant that references the bullet scene and a variable that references the bullet object. Make sure both of them have the correct path and object or scene name.

The fire function as it works now just places the existing bullet in front of the drone based on the initial position where you've placed it and rotates it in the direction the player is facing.

In the function _ready, after initializing the variable bullet_start, delete the bullet object by calling bullet.queue_free().

In the function fire, assign to the bullet variable a value instantiating the constant BULLET before it is positioned. 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 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 delta 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 script game_master.gd:

var hood =  [[-1, 0], [1, 0], [0, -1], [0, 1]]
var rotations = [0, 0, 90, 90]
var offsets = [[-2, 0], [0, 0], [0, 0], [0, 2]]

Then add the following functions to the same script:

func on_table(i, j):
    if i >= 0 and i < maze_width and j >= 0 and j < maze_height:
        return true
    else:
        return false
            
func make_brick(i, j):
    var br = []
    var b
    for k in len(hood):
        var c = hood[k][0]
        var r = hood[k][1]
        if on_table(i+c, j+r) and maze[i+c][j+r] == WALL:
            b = BRICK.instantiate()
            add_child(b)
            b.position = Vector3(col2X(i) + offsets[k][0], brickY, row2Z(j) + offsets[k][1])
            b.rotation = Vector3(0, deg_to_rad(rotations[k]), 0)
            br.append(b)
    if br == []: # didn't get any neighboring walls
        b = BRICK.instantiate() # add the basic one
        add_child(b)
        b.position = Vector3(col2X(i)-1, brickY, row2Z(j))
        b.find_child("MeshInstance3D").scale = Vector3(1, 1, 6) # and make it bigger
        br.append(b)
    return br

Finally, in the function build_maze, replace the brick instance in the assignment to brick[i, j] with a call to make_brick(i, j) and delete the next two lines in that function.

Homework Part

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 baloon 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.

Homework Submission

Create a Windows executable like you did for Homework 5. Create a zip 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.