Dana Vrajitoru
I254/C297 2D Games Programming

I254/C297 Lab 7 / Homework 7

Date: Wednesday, February 28, 2024.
Due date: Wednesday, March 6, 2024.

In this lab and homework, we will create a game similar to Breakout. We will explore TileMap objects some more and dynamically change such objects in the code. Here is a screenshot of the possible result.

Lab Part

Ex. 1. In this lab we'll create a Breakout-style game.

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

a. Layout

Download the file

hw7.zip

Extract the files, then choose one of the wallpaper images and drag it to the project, as well as one of the paddle objects and the file tiles.png, as well as the two soundsbytes.

In Project Settings - Window, set the size of the window as 1024x832, or something smaller if it doesn't fit on your screen.

Create a TextureRect object and set the texture for it with the wallpaper you've chosen. Scale the object so that it covers the entire viewport, then set the Stretch Mode as Tile. You can also leave it as stretched if you think that looks better.

Create a TileMap object and set the Tile Set as a new TileSet. Click on the TileSet on the right, then click on the tabTileSet at the bottom of the window, and drag the file tiles.png over the dark grey square to set it as the tile set. It will ask if you want to recreate the atlas. You can say yes. Note that this image has some extra empty space left intentionally in case you want to add some elements of your own.

Before painting with the tile set, in the Inspector, under Node2D - Transform, set the scale of the tile set to 4x4. Or you can set it to 3x3 and set the window size to 1048x816. Then under Physics Layers click to add an element. Then in the bottom panels, click on the TileSet tab, then on the Paint button, then under Paint Properties, select Physics Layer 0, which is the collision layer. Paint over all the border tiles (black and white) and over the brick tiles.

Click on the TileMap tab at the bottom to start creating the layout in the scene. Start by using the black/white tiles to create a border around the viewport. This will prevent the ball from getting out of the arena.

Then select one of the bricks, and notice that it's made of two tiles. Click on the first one in the tile set, then hold the Shift and click on the second one. You will use them together to paint the bricks. Create 3 rows of bricks close to the top of the window. You can mix and match them if you like.

b. Paddle and Ball

Create a Character2D node called Paddle. Add the paddle image as a Sprite2D node and make it a child of the Paddle. Then add another CollisionShape2D object as a child of the Paddle, and set its shape as a capsule. Adjust the collider shape to fit the paddle well. You will need to use the rotation button to rotate it by 90 degrees, then you should be able to fit it over the paddle pretty well. Add a script to the paddle object using the CharacterBody2D template. Save the paddle as a scene to be able to move it as one object.

Repeat the operation with the ball, including a script. Set the scale of the ball to .7, or whatever value makes it smaller than one TileMap tile.

Let's move the paddle with the arrow keys. Remove all the gravitation and jumping from the paddle movement code. The remainder of the code should be just what we need.

Let's edit the movement of the ball. Remove all the code from the function _physics_process except for the call to move_and_slide. We want the ball to move at constant speed, but we also want it to bounce against anything it runs into.So add the following code seen in the last homework 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())

Create two objects of type AudioStreamPlayer2D and set their Stream property to be each of the two clicks respectively. Add references to them in the Ball script by dragging and Ctrl-dropping them at the top of the script.

Then modify the code by moving the second conditional after setting the velocity, but still inside the check for collision info. Change the string to look for in the name to "Paddle". In the true case, play the click1 sound, and on the else, play the click2 sound.

We still need to give the ball an initial velocity before we can test all of this code. Add a random number generator to the script and intialize it at the top. Add a function _ready to the script and in it, set the horizontal velocity as a random number between -100 and +100, and the vertical velocity as sqrt(SPEED*SPEED-velocity.x*velocity.x).

To prevent some artifacts of the interaction of the ball and paddle, set the paddle's vertical velocity to 0 just before move_and_slide. Then add a function _ready to the script where you store the initial vertical position of the paddle in a class variable called paddle_level. Then after move_and_slide, reset the y component of the position to the stored value.

Now we can add some code to make the ball be lost when it goes below the paddle level. In the ball script, add a reference to the paddle object at the top. Then after the ball is moved, check if its value is larger than the value of paddle_level, then multiply the velocity of the ball by 0. That should sopt its movement. We'll deal with what else should happen later.

c. Brick Breaking

The next thing to deal with is being able to break the bricks when the ball intersects with them.

Add the following function to the ball script:

func break_brick(pos):
    var local_position = tile_map.to_local(pos)
    var tile_position = tile_map.local_to_map(local_position)
    var tile_coords = tile_map.get_cell_atlas_coords(0, tile_position)
    print(tile_coords)

Then in the function _physics_process, after move_and_slide, add the call

break_brick(collision_info.get_position() - velocity*delta)

Observe the values being printed and try to figure out the logic of how they are mapped to the tile map.

It looks like the coordinates of the briks in the tile map atlas are between (2, 0) and (5, 2). This may change if you add your own. So we can add a test for that:

if tile_coords.x >= 2 and tile_coords.x <= 5 and tile_coords.y >= 0 and tile_coords.y <= 2:
    tile_map.erase_cell(0, tile_position)

This works, except that it only breaks half of the bricks at a time. That part will be completed in the homework.

d. Keyboard Input

Let's add some functionality to the game so that the ball is static in the game until we press a space to launch it.

In the ball script, add a class variable called playing initialized as false. Add another variable in the class called initial_pos and assign to it the value of position in the function _ready.

Add a function called launch. Assign to position the value of initial_pos, and move the part of the code randomizing the velocity from the function _ready into this function. Then add the check if Input.is_action_pressed("ui_accept") before randomizing the velocity. Also set playing to true.

Add a call to this function in the function _physics_process at the top.

Homework Part

Ex. 2. a. New Game Key

Add keyboard input to the game in the class ball so that if we press "n" or "N", we restart the game. You will need to make the call from the owner of the node to reload the current scene.

owner.get_tree().reload_current_scene()

b. Breaking Whole Bricks

To break a whole brick when the ball hits it, you need to check if tile_coords.x is an even or odd number. If it's even, you need to create another tile position as a Vector2i with coordinates tile_position.x+1 and tile_position.y, and also erase that cell from the map. If it's odd, then you would do the same thing but where you subtract 1 from x.

c. Display Information

Add a label at the top displaying "Space - start, N - new game".

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