Dana Vrajitoru
I254/C297 2D Games Programming

I254/C297 Lab 6 / Homework 6

Date: Wednesday, February 21, 2024.
Due date: Wednesday, February 28, 2024.

In this lab and homework, we will create a game with top-down character movement similar to Agar.io or Tasty Planet. Here is a screenshot of the possible result.

Lab Part

Ex. 1. In this lab we'll create a project using top-down movement.

Create a project called Blobber and a scene called main inside. Create a Node2D object called Root in the scene. Switch the scene editor to 2D.

a. Background

Download the file (source: https://www.pexels.com/photo/starry-sky-998641/)

sky.png

Drag it from the file explorer to the File System in your Godot project. Create a node of type TextureRect and drag and drop the sky image over its Texture property in the inspector at the top.

Place the image over the viewport and make sure it covers it entirely.

b. Blob Objects

Download the file

blobs.zip

Extract all the files from it, then drag them all over to the file system in Godot to import them.

Create a node of type CharacterBody2D and call it Blob. Choose one of the images of blobs with eyes and add it as a Sprite2D child of this node with the top corner set at point (0, 0). Name this object Image.

In the Transform are in the Inspector, set the scale of this object to .25, then set the position of the object as (25, 25). This should make the object smaller and aligned with the top left corner of the image.

Add another child node to the Blob of type CollisionShape2D. Set the Shape of this object as a new CapsuleShape2D. Zoom in on the object and edit the capsule points to fit over the blob tightly.

Add a script to the blob object of type CharacterBody2D. Delete the variable gravity and the test using it at the beginning of the function _physics_process. Delete the test for being on the floor from the jumping part of the code.

Add a variable at the top of the class called is_player and initialize it as true.

Turn the code currently in the function _physics_process into a function called player_move(delta). Then in _physics_process, add a test for is_player being true where you call the function player_move(delta).

Add two variables in the class called speed initialized as 300.0 and angular_speed initialized as 5.0.

Let's change the movement so that the left and right keys rotate the player, and the spacebar moves it forward in the direction where it's facing. In addition, the up and down keys will move it up and down with respect to the direction it's facing. Replace the code in the function player_move with the following:

var rotate_direction = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
rotation += rotate_direction * angular_speed * delta

# Handle movement
if Input.is_action_pressed("ui_accept"):
    velocity.x = speed
    velocity.y = 0
    velocity = velocity.rotated(rotation)
elif Input.is_action_pressed("ui_down"):
    velocity.x = 0
    velocity.y = speed
    velocity = velocity.rotated(rotation)
elif Input.is_action_pressed("ui_up"):
    velocity.x = 0
    velocity.y = -speed
    velocity = velocity.rotated(rotation)
else:
    velocity.x = move_toward(velocity.x, 0, abs(0.1*velocity.x));
    velocity.y = move_toward(velocity.y, 0, abs(0.1*velocity.y));
move_and_slide()

c. World Constraint

We would like to constraint the characters to the viewport. We will do this by creating a collider box made of line colliders around the world.

For this, add a new node that is a child of the Root of type StaticBody2D. Add a child node of type CollisionShape2D. Add a shape to it of type SegmentShape2D. Edit the nodes to make it go from the top left corner of the viewport to the bottom left corner. This will be the left border of the world. Call this collider Wall1. Repeat the procedure for each of the other 3 walls of the world.

To make things more interesting, let's make the player bounce against these walls when it runs into them.

Add the following code to the function player_move right before the call to move_and_slide:

var collision_info = move_and_collide(velocity*delta, true)
if collision_info:
    if collision_info.get_collider().name.contains("Wall"):
        velocity = velocity.bounce(collision_info.get_normal())

Note that this doesn't change the direction the player is facing, so if you keep pushing it in the same direction, it will keep bouncing against the same wall.

If you want the bouncing to send the blob further away from the wall, you can multiply the velocity above by a factor like 1.5.

d. Player vs NPCs

Create another move function called npc_move(delta). Copy the part of the code from the player move function from the last else (without the else) together with the move_and_slide call. Add a

At this point, right-click on the Blob object and select Save Branch as Scene. The name blob.tscn is fine. Change the name of the Blob object that is child of the Root to Player.

Add another blob object to the scene and call it Blob0. Right-click on it and turn on Editable Children. This will let us modify the attached nodes. Change the texture of the Image child of this object to one of the round shapes with no eyes, in a color different from the player.

In the script, change the variable is_player at the top to false. Then add the following function:

func _ready():
    if name == "Player":
        is_player = true

Make sure first that your player object's name is spelled exactly like in this function. Now even though you have two objects in the scene, you should be able to move only one of them.

Next, let's give the NPC a random velocity at the start. Declare a class variable rand at the top:

static var rand = RandomNumberGenerator.new()

Define two class variables maxX and maxY that are set with the maximum x and y. Add functions that randomizes the blob's speed and position:

func random_place():
    position.x = rand.randi_range(25, maxX-25)
    position.y = rand.randi_range(25, maxY-25)

func random_speed():
    velocity.x = rand.randf_range(-1, 1) * speed
    velocity.y = rand.randf_range(-1, 1) * speed

In the function _ready, add an else to the conditional and inside, add a call to the function randomize.

Now when you start the program, the blob will move in a random direction for a little while. To make it move for a while longer, in the function npc_move, change the slowing down factor from 0.1 to 0.01.

But then we don't want the blob to just stop moving. Let's check when its velocity is less than 10, and randomize the speed again. In the function npc_move, just before the collision variable is declared, add a test for velocity.length() < 10 and if true, call the function randomize_speed.

Add a few more blobs around the scene so that there are a good number of enemies but not too many.

e. Size and Scale

Add an attribute in the class for a size of the blob. Then in the function read. set the scale of the object over x and over y as the size/100.

Then in the function move_player, when there is a collision, check if the size of the player is greater than or equal to that of the blob. If that's the case, then add the size of the blob to the player and resize the player. Randomize the blob by calling the functions to randomize its position and speed.

f. Play a Sound

Download the following file and import it into your Godot project.

eat.mp3

Then add a child node of the root of type AudioStreamPlayer2D. Drag and drop the sound onto the Stream field of this object in the inspector. Rename it EatSound. Then drag to the top of the script and hold control just before dropping it to create a reference to it in the script.

Then in the function player_move, when the player collides with the blob, add the line

eat_sound.play()

This is the end of the lab part. You will finish the game in the homework.

Homework Part

Ex. 2 a. New Game Button

Add a button to the scene as a child of the root with the caption New Game where you call a function from a script added to the root doing

get_tree().reload_current_scene()

b. Random Size

When the player collides with a blob and "eats" it, the blob is being randomized to a different place and with a different speed. Add a feature to also randomize the size of the blob in a range that you find suitable.

c. Gameplay Adjustment

Adjust the speed and other elements to make the gameplay nice.

If the player collides with a blob larger than it, the game should be lost. In this case, hide the player blob and display some message that the game is lost, and find an appropriate sound to play.

Figure out a way to define when the game is won and also display a message and/or play a sound.

Homework Submission

Take a screen shot of your running program, showing the content of the screen while the game is running, and save it as png or jpg. Create a zip file of the project folder (the screenshot can be inside). Submit both of them to Canvas, Assignments - Homework 6.