Dana Vrajitoru
I355/C490/C590 3D Games Programming

I355/C490/C590 Lab 3

Date: Wednesday, September 18, 2024. To be turned in by Wednesday, September 25 as part of the homework.

In this lab we will start writing a game of Skittles, which you will complete in the homework. Before you start, you will need to have Blender installed on your computer for the 3D objects to work. If you haven't done it yet, you can install it from www.blender.org.

Here is an example of the finished game (http://www.cs.iusb.edu/~dvrajito/teach/i355/skitGL/). Note: I added one spotlight to the game area. The big pink rectangle is the New Game button.

Project

Create a new Godot project called Skittles. Download the following resources a local folder:
bump.mp3
push.mp3
peg.blend
spinner.blend

In Godot, create the folders in the /res: Art, Scenes, Sounds, and Scripts. From the file explorer, drag the the blend files to the Art folder and the mp3 files into Sounds. Add a Node3D node as the root of the scene and save the scene as skittles in the Scenes folder you created.

When you add the Blender files to the project, it will ask you to identify a Blender executable. On Windows, that is normally under C:\ -> Program Files -> Blender Foundation -> Blender #.#. Once in that folder, identify Blender.exe. On MacOS, the path is probably different.

Here is the configuration for the game:

Add a Camera3D object, as well as the sun and the world environment just like in the previous projects.

Click on the camera object. In the Inspector, switch it from Perspective to Orthographic. Then for the WorldEnvironment node, in the Inspector, click on the Environment box on the right to open its attributes. Expand Background and change the Mode, from Sky to Custom Color. Click on the black box next to the Color below and select a color of your choice. In the top left corner of the scene window, click on the 3 dots next to Perspective and switch to Orthogonal.

Box

From the + menu, add a MeshInstance3D object to the scene, then set the mesh as a New QuadMesh. Call the object floor. Set the size of the object as 15 x 8. Further down, next to Material, assign it a New StandardMaterial3D. Set the Albedo of this material as dark red or some other color of your choice.

You can close the material and the shape, and then in the Transform, set the rotation over X to -90o. If you play the game now, it will have disappeared. This is because the camera is looking at it sideways and a quad does not have any depth.

Camera

To be able to see the floor, let's re-orient the camera so that it faces it at a different angle. Set the rotation of the camera over x to 10 degrees. You should see a little of the floor now. Set the x rotation to -75 x 5 x -5 degrees and the coordinates to (0, 11, 2.5). Set the Size of the camera to 9. You should see most of the floor now. Make sure that the position of the floor object is (0, 0, 0). You can find better settings for the camera once you have some objects inside.

Add a child to the floor of type StaticBody3D and then a collision shape as child to this node, where the collision shape is a concave polygon.

Add 4 walls to the box by using quads and give them appropriate names. The front and back ones should have dimensions 15 x 4, and positioned at 0 x 2 x -4 and 0 x 2 x 4 respectively. You don't need to rotate the front and back walls. Create collision shapes for them the same way, but set them as thin boxes.

The left and right walls will need to be rotated by 90 over y. They should have dimensions 8 x 4 and positioned at -7.5 and 7.5 over x, 2 over y, and 0 over z. Check with the Game tab and if any of your walls is not visible, change the sign of its rotation over y (only the front facing side of the quad is visible).

Create a material for the walls in a lighter color. When you set the color for it, set the value of Alpha (transparency) to something around 100 (over 255). You also need to set the Transparency property to Alpha. You can adjust this later to look better.

In case your box coordinate and dimensions don't quite work, here are some settings I've used; these actually work well with the camera using a Perspective projection:
Object Position Rotation Scale
Camera (0, 7,22, -4.43) (60, 0, 0) (1, 1, 1)
floor (0, 0, 0) (90, 0, 0) (15, 8, 1)
front wall (0, 1.5, 4) (0, 0, 0) (15, 3, 1)
back wall (0, 1.5, -4) (0, 0, 0) (15, 3, 1)
left wall (-7.5, 1.5, 0) (0, 90, 0) (8, 3, 1)
right wall (7.5, 1.5, 0) (0, 90, 0) (8, 3, 1)

On your own: add the smaller 3 quads for the interior walls in the overall figure of the box above. Make sure that they have colliders and are visible on the screen.

Visible face
Quads have a single visible face. Once you have all your objects in place, if some of your quads are not visible from the camera position you chose, duplicate those quads (Edit menu or Ctrl-D) and then change the sign of their rotation around y or add 180 to it. For example, if the angle was 0, replace it with 180. If it's 90, you can replace it with -90 and the other way around.

Peg

Drag the peg object from the Arts folder to the scene. To set its color, click on the movie icon next to its name in the hierarchy, that says "Open in Editor" when you hover over it. It will ask if you want to create a derived scene, and you can accept. Click on the Sphere child of the peg node and you will see that it has a Blender shape defined, similar to a MeshInstance3D object. Further below, next to Surface Material Override, click on the arrow next to <empty> and set it as a New StandardMaterial3D. Set the Albedo of this material with any color you want, something visible.

Click on the peg object in the hierarchy and change its scale to 0.4 on all 3 axes. Then save the scene in the Scenes folder as peg.tscn.

Going back to the skittles scene, add a RigidBody3D as child of the root node and call it pegBody. Then drag the peg object over it to make it its child. Set the mass of the body to 0.03.

Then add a CollisionShape3D child of the peg body object and add a box collider shape to it. Switch to a view facing the front of the peg, then edit the box to overlap the object as well as it can. Then switch to the top and adjust the red dots from there. Leave the peg floating a little above the floor, then play the game. You should see it fall to the floor and rest there.

Add a script component to the pegBody object called peg_body.gd and for now just declare a public attribute called standing and set it to true.

Go back to the 3D view and reset the vertical coordinate of the pegBody to 0 in the skittles scene. Set the coordinates of the peg object that is its child to 0 x 0 x 0. Also, reset the coordinates of the collision shape to 0 x 0 x 0 and then pull it up to align it vertically with the object.

Let's create a scene out of the subtree with root pegBody so that we can easily create more instances. Right-click on this node in the hierarchy, then select Save Branch as Scene, and save it under the name it offers in the folder Scenes.

After that, drag and drop this scene from the folder into the skittles scene to create more copies of the peg and place them approximately in the locations marked in the scheme above. You can also duplicate the first peg you have to create more copies so that you don't have to move them far.

Audio Stream Objects

Create two objects of type AudioStreamPlayer3D called bumpPlay and pushPlay. Drag and drop the mp3 files over their Stream property to set them up to use these files. be each of the two mp3 sounds respectively. Add references to them in the peg_body script as:

@onready var bump_play: AudioStreamPlayer3D = $"../bumpPlay"
@onready var push_play: AudioStreamPlayer3D = $"../pushPlay"

Now we'll be able to play these sounds by just calling a function Play on these objects.

Spinner

Add the spinner object to the scene. Scale it down and set its color any way you want. Add a RigidBody3D object as child of the root and then drag the spinner over it to make it its child. Name it spinnerBody. Add a CollisionShape3D sibling of the spinner (child of the spinner body) and set the shape as a new CapsuleShape and adjust its dimensions to fit the object as well as you can.

With the spinner body object selected, in the inspector under PhysicsBody3D, lock the rotation over X and Z (check the Angular boxes) and also lock the linear velocity over y. Then under RigidBody3D, assign a new PhysicsMaterial to the object. Set the bounciness to 0.8 and friction to 0.1.

Under the RigidBody3D category, expand the Solver and turn on the Continuous CD and the Contact Monitor with Max Contacts as 20.

Switch over to the peg_body scene and add a physics material to its rigid body (pegBody object) too. Set its bounciness as 0.6 and friction as 0.4.

Spinning

Add a script component to the spinner and use the offered name. Add references to the push audio stream object like in the other script. Declare a class variable rand and assign it RandomNumberGenerator.new() in the function _ready. Declare a class variable called spin and initialize it as 20.

Then add the following function to this class and call it from the function _ready:

func roll_spin():
    push_play.play()
    var force = Vector3(rand.randf_range(-speed, speed), 0, 
                        rand.randf_range(-speed, speed))
    apply_impulse(force)

Now the spinner should be randomly spinning at the beginning of the run.

Sound

Let's use the bump sound when the spinner collides with a peg. In the peg_body scene, select the root of the scene. In the Inspector, expand Solver, then turn on Continuous CD and Contact Monitor. Set the number of contacts to 10.

Then in the Inspector click on the Node tab, and on the body_entered(...) signal. Click Connect at the bottom and connect it to the peg_body object. Leave the function name as it is. Add the following to the body of this function:

if body.name == "spinnerBody":
    bump_play.play()

Text Label and Font.

Look up a free font on a page such as
www.1001fonts.com
http://www.fontsquirrel.com/

Download a font you like. If it comes in a zip file, extract the files first. Then locate a file with the extension .ttf. Drag this file into Godot, into the Art folder. Now the font should be ready to use.

Create an object of type RichTextLabel with the name showCount. We'll use this one to display the number of moves. Set the text as "Move Count: 0" in the inspector. Then further down, below Control, expand Theme and set a new theme. Then expand it and drag and drop the font file one the empty spot next to Default Font. Place the object around the top center of the screen. You can also set the Default Font Size to something like 24 or another value that works for you.

Add a reference to it in the spinner_body class. Note that the editor has switched to 2D. This is because this object belongs to the user interface part of the application, which is two dimensional. However, when you play the game, it should be well visible on top of the 3D scene.

New Game Button

Let's add a button to start a new game. Create a new node as child of the root of type Button. Set the text as "New Game" and the name NewGameButton. Place it in the top right corner or the window or wherever you think might look good.

Then in the Inspector, click on Node, select the pressed() signal, and click Connect. Select the spinner object to connect it to, and leave the name of the function as it suggests. This function will be created in the spinner body script and the editor will switch to it. In this function, add the line:

owner.get_tree().reload_current_scene()

This will be continued in the homework.