Prg2/flappy dot

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
This is part of Programming 2 2563

Overview

You will work with another student (or more). You would work alone shortly in the beginning to get some feature done, then we would like to ask you to form a team of 2 or 3 students to complete the game, while collaborating with git (and github).

Objectives

In this small project, you will learn:

  • how to use git (with branches) to work individually and with teammates
  • how to use github project to board to plan and track your work
  • basic game programming
  • how to apply object-oriented programming concepts to game development

Task breakdown

Before we start, it is useful to think about incremental steps you needed to complete the game. You can think of this as a list of features (probably built on top of one another).

When you get your list, please see the steps that we plan to take here.

  • The player can jump and fall. (Implement player physics)
  • The player jumps with keyboard control
  • Show a single pillar pair.
  • Move the pillar pair across the screen.
  • Let the pillar pair reappear.
  • Check for player-pillar collision.
  • Make the game with one pillar pair.
  • Show more than one pillar pairs.

Getting started

We have provided the starter template for you at https://github.com/jittat/flappydot-starter-template. Go to the repository and then click "Use this template" to create a new repository.

Prg2-flappy-template.png

To start working, you should clone the project to your local machine. The starting code only shows a single dot on the screen. The template also provides two other images for the pillars (one for the top part and the other for the bottom part) in images folder. You should try to run the code to see if everything works fine.

Project board

Create the project board for your repository. Use the 3-column format: To do, In progresss, Done. You can also use the Basic Kanban template when create the project.

Prg2-flappy-project-board.png

Use this project board to plan your project. Add a few steps from the list above as notes, then convert them to Github issues so that you can associate your commits with them. Don't forget the order the cards by their priority, i.e., the issues that you want to work on the most should be at the top.

Prg2-flappy-project-starting-items.png

When you are ready, just move the first card (the player jump with gravity) to the "In progress" column.

Changes in gamelib

If you watch the clip from the class where we work on the Monkey game, the game library for this project has been updated slightly. Here are the summary of the changes.

  • New initialization methods: init_element for Sprite and Text, and init_game for GameApp
  • GameApp update methods: pre_update and post_update which would be called before the updates of all elements are called and after they are called. These two methods will be very useful when we want to handle global events such as collisions.

Prg2-flappy-new-gamelib.png

Basic player physics

This is the part where you would work as a single developer.

Review of physics

You might forget all these, but if you want objects in your game to look and act a bit like real objects, you might have to recall stuffs you learned from mechanics.

Let's look at the basics. An object has a position, its position changes if it has non-zero velocity.

How can you change the player's position? We can set its x and y attribute on the sprite.

If you want to apply the velocity, you can change the player position based on the velocity.

If there is an acceleration, the object's velocity also changes. The Dot currently does not have velocity as its attribute, so we will add it. Now, you can update the velocity based on the acceleration.

These attributes (the position, the velocity, and the acceleration) all have directions. Sometimes, you see negative velocity; this means the object is moving in an opposite direction as the positive direction. We shall follow the standard co-ordinate system for tkinter, i.e., for the y-axis, we think of the direction as going downwards, i.e., the y-coordinate increases as you go down.

While in Physics, everything is continuous, but when writing games, we don't really need exact physics, so we can move objects in discrete steps.

So the usual pseudo code for physics is as follows.

pos = pos + velocity;
velocity = velocity + acceleration

Falling dot

To simulate the player falls, we should maintain the player's current velocity, so that we can make it falls as close as the real object.

Let's add this line that initialize property vy in Dot's initialization code:

class Dot(Sprite):
    def init_element(self):
        self.vy = -30

Notes: recall that we have changed the initialization method for sprites to init_element (previously we called it init_sprite).

You may wonder why we put -30 here. First, it should be negative because we want to move up. But why -30? It is just pure guess at this point. However, when you write games, you might want to try various possible values and pick the best one (i.e., the one that make the game fun). You can adjust both the starting velocity and the GRAVITY constant (currently 2.5).

The update method changes the player's position

class Dot(Sprite):
    # ....

    def update(self):
        self.y += self.vy
        self.vy += GRAVITY

Note that we update self.vy at the end of update. Try to run the program. You should see the player falling.

We are using -30 as a magic number. Let's try to get rid of the magic numbers first, by defining them explicitly. Add STARTING_VELOCITY constant after GRAVITY, and change init_element to use the constant instead of using -30.

GRAVITY = 2.5                    # this line is already in the file
STARTING_VELOCITY = -30

class Dot(Sprite):
    def init_element(self):
        self.vy = STARTING_VELOCITY

    # ...

Test your code again. If everything looks fine, you should commit your code by calling:

git commit -a

and enter the commit message. You can also commit inside VS Code. In the commit message, don't forget to refer to issue #1 in your project board. To tell github that you have resolved the issue, include the message "Resolve #1" in the commit message.

Then you should push your work back to github:

git push

After that don't forget to move the card to the Done column.

Prg2-flappy-done-card.png

Teamwork

You will work with other students. Form a team of 2 students (preferably). A team of 3 students is also OK.

Choose one student as the owner of the repository and add the other members as collaborator. Then invite the other student as collaborator.

First go to "Settings" (top menu) and "Manage access" (side menu). Then click "Invite a collaborator".

Prg2-github-invite1.png

Choose the username.

Prg2-github-invite2.png

The status shows that github is waiting for the user's response.

Prg2-github-invite3.png

The invited user will get an email. Clicking on that would lead to a confirmation.

Prg2-github-invite4.png

After the user has confirmed, you will the updated status as below.

Prg2-github-invite5.png

Quick review of git commands

In this assignment, we assume that you have some experience using very basic git commands. If you have trouble navigating git, please call the instructor or the TA on discord.

Here are a few commands that we might use.

  • Working with remote repository
    • git pull
    • git push
    • git fetch
  • Working with branches
    • git branch --- lists local branches (git branch -a lists all local and remote branches)
    • git branch BRANCHNAME --- create a new branch
    • git checkout BRANCHNAME --- switch branch
  • Merge
    • git merge SRC --- merge from SRC to the current branch

How to split work for Flappy Dot

When working in a team, you need enough work to ensure everyone has something to do. Each member should work on a different task. These tasks should be somewhat independent, i.e., each member can work fairly independently without having to consult everyone in the team.

Many features of Flappy Dot can be implemented independently so when you work with your friends you can split the work. The following tables give a few possibilities.

For 2-member team:

Member 1 Member 2
  • The player jumps when key pressed
  • Player falls off the screen
  • Show a single pillar pair
  • Move the pillar pair across the screen
  • The player dies after falls off the screen
  • Pillars reappear
  • Pillars with random heights
  • Collision
  • Pillar movement starts when game starts

For 3-member team:

Member 1 Member 2 Member 3
  • The player jumps when key pressed
  • Player falls off the screen
  • Show a single pillar pair
  • Move the pillar pair across the screen
  • Show score (with text)
  • The player dies after falls off the screen
  • Pillars reappear
  • Pillars with random heights
  • Collision

Since this is a small game, it would be hard to split the work in later steps. In that case, you can split the repository to work individually, or you can do pair programming to get the game done.

Git branches

When working in a team, try to start your own work in a separate development branch (recall the fun-with-branch activity). You should merge your work to main branch when it is done.

However, when working on features with many dependencies, it would be useful to talk with your teammate to get your (non-breaking) codes to be pulled to the main branch a little more often.

Developer 1 - Keyboard control

Notes: one person in the team should work on this feature in a feature branch, another person should attempt on the pillar movement work.

You should start working on this feature in a new branch (to be merged to main when you are done).

git branch keyboard-control
git checkout keyboard-control

You should also associate your work with the issues in Github. So don't forget to move the card "The player jumps..." in Github project to the "In Progress" column.

Prg2-flappy-card-start-key.png

Game and Dot's states

We will add states to MonkeyGame and Dot.

We add is_started to Dot. Note that we also add method start to Dot to make it easier to update its state.

File: flappydot.py
class Dot(Sprite):
    def init_element(self):
        # ... 
        self.is_started = False

    def update(self):
        if self.is_started:
            self.y += self.vy
            self.vy += GRAVITY

    def start(self):
        self.is_started = True

We also add is_started to MonkeyGame.

File: flappydot.py
class MonkeyGame(GameApp):
    # ...

    def init_game(self):
        # ...

        self.is_started = False

At this point, if you run the game, the Dot should not move.

Your task: Implement on_key_pressed in MonkeyGame to start the Dot and update state is_started when the user presses Spacebar.

You should test your work. If everything is fine, you should commit your work.

Gitmark.png Commit your work regularly. Don't forget to work in a branch.

Dot jumps

We will also make the Dot jumps when Spacebar is hit. First add the jump method to Dot. Note that we also introduce another constant JUMP_VELOCITY. We can use STARTING_VELOCITY here, but we use another constant to be more specific and this allows us to use different velocity for starting and for jumping. Here, we use smaller velocity for jumps.

File: flappydot.py
JUMP_VELOCITY = -20

class Dot(Sprite):
    # ...

    def jump(self):
        self.vu = JUMP_VELOCITY

To implement the jump we need to, again, edit the on_key_pressed method.

Your task: Implement on_key_pressed in MonkeyGame so that the Dot jump. You should make sure that when the user hit Spacebar after the game starts, the Dot should use JUMP_VELOCITY not STARTING_VELOCITY. This would give a finer control for the player.

If this works correctly, you should be able to play empty jumping game with your Dot.

This completes the feature, so you should also resolve the issue in Github. Therefore, you should include a text "Resolve #xxx" with the correct associate issue number so that Github would close the issue for you after you push the changes.

Gitmark.png Commit your work if it looks OK.

You are done with the feature. It's time to merge your work to the main branch. Make sure you have committed your work. Then you can use the following command:

git checkout main

to check out the main branch

git pull

to update your local copy to the latest version in github, and then

git merge keyboard-control

to merge your work into main.

If everything works, you should push your work to github. If you find a conflict, try to resolve it then commit and push. If you have trouble resolving the conflict, please call the TAs. Don't do "--force" unless you understand what you are doing.

When you finishing merging, don't forget to move the card in the project to "Done".

Developer 2 - Pillar movement

Notes: one person in the team should work on this feature in a feature branch, another person should attempt on the player control work.

You should start working on this feature in a new branch (to be merged to main when you are done).

git branch pillar-movement
git checkout pillar-movement

You should also associate your work with the issues in Github. So don't forget to move the card "Show a single pillar pair" in Github project to the "In Progress" column.

Prg2-flappy-start-pillar.png

The pillar pair

Let's create PillarPair class.

File: flappydot.py
class PillarPair(Sprite):
    pass

And create it in the game.

File: flappydot.py
class MonkeyGame(GameApp):
    def create_sprites(self):
        # ...

        self.pillar_pair = PillarPair(self, 'images/pillar-pair.png', CANVAS_WIDTH, CANVAS_HEIGHT // 2)
        self.elements.append(self.pillar_pair)

Try to run the code to see if the pillar pair appears at the right end of the screen. This completes the issue, so we should commit and include "Resolve #xxx" in the commit message with the associate issue number. When we merge the branch, the issue will be closed automatically.

Gitmark.png Commit your work.

Before we continue, move the card in the project board to the "Done" column.

Movement

Let's make the pillar pair move. Before we start, move the card "Move the pillar pair across..." to the "In progress" column.

We add update method.

File: flappydot.py
class PillarPair(Sprite):
    def update(self):
        # your code here

Your task: Implement update in PillarPair above so that the pillars move. It would be good to use another constant PILLAR_SPEED for the pillar speed. Adjust the constant so that the pillar movement looks OK.

You should test your work. If everything is fine, you should commit your work. Again this completes the issue, so we should commit and include "Resolve #xxx" in the commit message with the associate issue number.


Gitmark.png Commit your work regularly. Don't forget to work in a branch.

You are done with the feature. It's time to merge your work to the main branch. Make sure you have committed your work. Then you can use the following command:

git checkout main

to check out the main branch

git pull

to update your local copy to the latest version in github, and then

git merge pillar-movement

to merge your work into main.

If everything works, you should push your work to github. If you find a conflict, try to resolve it then commit and push. If you have trouble resolving the conflict, please call the TAs. Don't do "--force" unless you understand what you are doing.

When you finishing merging, don't forget to move the card in the project to "Done".

Merge your work

At this point, you should merge your work into the main branch. Since both of you work on the same file, it can be the case that git would find conflicts in your edits. Don't worry about that, because it is fact of life.

If you encounter merge conflicts, look at the conflict closely. It might be just that you edit different things at the same place and you can just accept all changes.

When you finishing manually merging changes, you should commit your work. Don't forget to "add" the conflicted files before commits. If you can't work things out, please call the TAs. Don't call "--force" to get things done because you might be likely to destroy your own work.

Prg2-flappy-merge-conflict.png

Developer 1 - Player falls off the screen

We would provide only guidelines for working on this feature. Don't forget to

  • Create associated cards and issues in Github project
  • Move the cards you work on to "In progess" column and move them to "Done" column when you are done
  • Start working in a branch, commit regularly, and merge your work to the main branch when you are done

You should implement more game controls. First, you should check if the Dot falls of the screen by going too high or being under the screen. In that case, the game should stop. (You may want to implement restart later.)

Implementation tips:

  • Add method is_out_of_screen in Dot so that it checks its own location
  • Call this method to check the Dot position in GameApp's post_update
  • Add more game states for "game-over" state
  • Make sure everything stops when the game is over

Developer 2 - More pillar movement

We would provide only guidelines for working on this feature. Don't forget to

  • Create associated cards and issues in Github project
  • Move the cards you work on to "In progess" column and move them to "Done" column when you are done
  • Start working in a branch, commit regularly, and merge your work to the main branch when you are done

You should implement more pillar movement and controls. First, you should let the pillar reappear after it falls off to the left side of the screen. Another thing to do is to let the pillar have random heights.

Implementation tips for making re-appearing pillars:

  • Add method is_out_of_screen in Pillar so that it checks its own location
  • Add method reset_position in Pillar so that is moves back to the right side of the screen. You should place the pillars slightly out of the screen so that it would reenter smoothly
  • Call these methods in GameApp's post_update to check pillars' position and make them reappear

Implementation tips for pillar with random heights:

  • Add method random_height to random the pillar's y coordinate.
  • Call this method initially before the pillars are placed in the screen and when the pillars reappear.

Collisions and game control

We would provide only guidelines for working on this feature. Don't forget to

  • Create associated cards and issues in Github project
  • Move the cards you work on to "In progess" column and move them to "Done" column when you are done
  • Start working in a branch, commit regularly, and merge your work to the main branch when you are done

This is probably the hardest part of this project, and your team should work together closely to implement this feature.

Up to this point, the works for the Dot and the Pillars are quite separate and independent. Now you have to make them work together.

Implementation tips for collision detection:

  • Add method is_hit in Dot that takes the pillar pair and checks if the Dot hits the top or the bottom pillars.
    • It would be easier to check the x-coordinate and the y-coordinate separately.
    • The position x and y of the pillar pair is at the empty center of the pair. The pillar width is 80 pixels and the empty space in the middle is of height 200 pixels.
    • The Dot is of size 40 x 40.
  • You might check the collision in FlappyApp post_update and stop the game when the collision occurs

Towards a complete game

These are more features that you might want to implement:

  • The game should have more than one pillar pairs
  • Restart the game
  • Scoring