Game #1: Pong

Overall, Pong was not that difficult to complete. I've already had a little bit of experience with the Godot engine before so most of the time spent working on this game was just refamiliarizing myself with the various nodes and functions.

Arena


To set up the main game scene, I added a ColorRect node and colored it black, then set the "Anchors Preset" property to "Full Rect" so that the background would take up the whole game screen.

For the wall scene, I set up a StaticBody2D with a rectangular CollisionShape2D that extended the full length of the viewport window. The sprites were simple white bars I designed in Aseprite.

Finally, the divider is a Sprite2D of a dotted line that I placed in the main game scene.

Paddles


The paddle is a CharacterBody2D that is set up so that P1 uses WASD and P2 uses the arrow keys. To check for these inputs, I exported a variable called player that gets set in the main scene for both paddles. In _physics_process(), the inputs are checked via string formatting so that when W or S are pressed, the conditional will only evaluate to true for P1 (and likewise for P2):


                    if Input.is_action_pressed("p%s_move_down" % player) and not is_ai:
                        velocity.y = 1 * SPEED
                    elif Input.is_action_pressed("p%s_move_up" % player) and not is_ai:
                        velocity.y = -1 * SPEED
                    else:
                        velocity.y = move_toward(velocity.y, 0, SPEED)

                    move_and_slide()
                

Ball


The ball took a few attempts to get right. Originally, I tried using a RigidBody2D but quickly realized that collisions would be very messy to handle due to the node's physics, so I switched to an Area2D. Unfortunately, vector reflection on collisions started getting confusing with that node, so I ultimately ended up using a CharacterBody2D to fully control motion and have collision baked in.

In _ready(), I initialize the ball's X and Y velocities to random values between 25 and 75. Then in _physics_process(), I simply mode_and_collide() the ball. When a collision is detected, the velocity vector is updated to bounce off of the normal of the collided surface. This accomplishes a very barebones collision system for now.


                    func _physics_process(delta):
                        var collision_info = move_and_collide(velocity*delta)
                        if collision_info:
                            var normal = collision_info.get_normal()
                            velocity = velocity.bounce(normal)
                

After implementing the ball, a new problem arose during testing where if the ball struck a paddle from below, the paddle would treat the ball like a moving platform and "glue" itself to the object, then get carried away with the ball's movement. This was fixed by setting the motion mode to "floating" for the paddles.

Scores and Rounds


I added a VisibleOnScreenNotifier2D to the ball for detecting when the ball goes off screen with the screen_exited() signal. To the ball's code, I added a signal match_over(winner)

The winner of a match is determined by the ball's X-velocity when it exits the screen. If the ball is traveling to the right, then P1 wins the round; if the ball is traveling to the left, then P2 wins. When the screen_exited() signal is detected, this winner is calculated and the match_over() signal is emitted. Finally, queue_free() is called to delete the ball instance.

In the main scene, match_over() is detected and a new variable score is updated for the player that won.

To handle respawning the ball, I removed the ball from the main scene and rewrote code to instantiate balls in main via code. The spawn_ball() function instantiates a ball_scene, sets its position, and connects the match_over signal to _on_ball_match_over() in main. This function is called in _ready() and _on_ball_match_over() to endlessly loop spawning balls.

Displaying Scores