Space Obstacles Avoidance

This is a game where you command a spaceship by WASD and avoid obstacles that that can come from the edge of the screen up, down, left, right, and also out from the screen. Unlike normal “car-and-highway” game, the asteroids will be coming from different directions.

Activity - Game Design

From a simple imagination of the game, the student is tasked with creating a list of features they have to implement and as well as the order that they should do these tasks.

Activity Prompt: List out the game features you would like to make. Then, choose the game features you would really want to add. Next, organize them in the order you can get them done. Finally, for each of these features, list of the skills and knowledge you know and do not know yet in order to code these features out.

Solution - Game Plan, Learning List
  1. Spaceship - Sprite Creation, Key Inputs, Sprite Movement
  2. Spaceship User Interface - Health - Textbox, Sprite manipulation
  3. Asteroid - From the edge of the screen, then from in and out. - Sprite manipulation, Game Loop
  4. Visual cues - where the asteroid is going - Sprite manipulation
  5. Shop - Items that can be used to survive harder stages and a currency - sprite manipulation

How to Best Use the Solution

The solution is here as a guide. The student is encouraged to modify and add more than what’s on here.

Activity - Making the Spaceship

We’re going to be making the spaceship, which the player can press WASD to move the spaceship around. First, if you haven’t already, list out the pixelPAD concepts that are going to be needed.

What We’re Training For

We’re training our problem solving skills as part of the engineering process. We have identified what we would like to make in our game. Now, we’re making a list of “tools” we have learned. We’re simply then separating this list of “tools” to concepts we know and concepts we do not know yet.

Solutions - Concepts Needed Example

In order to make this spaceship that can be controlled with WASD, I as the student need to learn where I mark concepts that I understand already.

  1. Sprite creation
  2. Handling keyboard inputs

Sprite Creation

In pixelPAD, we know that objects are created from calling their classes. Each object can hold one sprite. The sprite can move on the screen through the manipulation of its built-in object variables.

Don’t Rush It - Be sure to know prior concepts

If you don’t understand this sentence, it’s best that you do the three activities: documentation, text box creation, and sprite creation at /programming/teaching/pixelpad.

If you have already gone through the activities, or do not feel like repeating them. Check out the relevant document page on this topic at pixelpad.io/docs.

First, create the class for the sprite object and add a image for the sprite object.

Solution - Sprite Creation

The class for the sprite object will be named Player. Any image is fine but I decide to change its name inside the project to spaceship.png.

Game Start
Player()
Player Start
self.sprite = sprite("spaceship.png")

Now, you should see a spaceship in the middle of your screen.

Image Could Not Load - Potential Technical Issue

Sometimes, pixelPAD cannot load the image from its own server because of network connectivity issues. If you look at the output, you can see the error message.

Listening to Key Inputs

We’re adding code so that the spaceship moves up, left, down, right through the key presses WASD.

If a student doesn’t understand this concept yet, they are advised to practice reading the documentation on the following two web pages and code his own solution before looking at the solution:

  1. Objects - Sprite manipulation, position, etc.
  2. Inputs - Listening to key inputs; notice that it’s used in loop and not start (understand why this is the case).
  3. Conditionals - Read this page on if-statements.
Solution - WASD

Non-exclusive movements - Option 1

Player Loop
if key_is_pressed("a"):
    self.x = self.x - 10
if key_is_pressed("d"):
    self.x += 10
    # Equivalent to self.x = self.x + 10
if key_is_pressed("w"):
    self.y += 10
if key_is_pressed("s"):
    self.y -= 10

Exclusive movements - Option 2

We use else-ifs or elif in Python to establish an Exclusive OR relationship. A player cannot press a or d at the same time and have their horizontal movement canceled.

Player Loop
if key_is_pressed("a"):
    self.x = self.x - 10
elif key_is_pressed("d"):
    self.x += 10
    # Equivalent to self.x = self.x + 10

if key_is_pressed("w"):
    self.y += 10
elif key_is_pressed("s"):
    self.y -= 10

Spaceship Debrief

Question 1 — Multiple Choice

What is the main purpose of listing the concepts needed before building the spaceship?

  • A. To memorize all of pixelPAD’s documentation
  • B. To identify which programming tools are already known and which still need to be learned
  • C. To determine which spaceship image looks best
  • D. To avoid writing code until every pixelPAD concept is mastered
Answer

B. This is part of the engineering problem-solving process. Identify what you want to build, list the required concepts, and separate what you already understand from what you still need to learn.

Question 2 — Multiple Choice

How is an object normally created in pixelPAD?

  • A. By assigning an image directly to the screen
  • B. By calling its class
  • C. By pressing a keyboard key
  • D. By creating a variable named sprite
Answer

B. For example:

Player()

This creates an object from the Player class.

Question 3 — Multiple Choice

How many sprites can one pixelPAD object hold according to the material?

  • A. Zero
  • B. One
  • C. Two
  • D. Any number
Answer

B. Each object can hold one sprite.

Question 4 — Multiple Choice

Which line assigns the spaceship image to the player’s sprite?

  • A. Player("spaceship.png")
  • B. self.image = "spaceship.png"
  • C. self.sprite = sprite("spaceship.png")
  • D. sprite = Player()
Answer

C.

self.sprite = sprite("spaceship.png")

This creates the sprite using the image named spaceship.png and assigns it to the object’s sprite variable.

Question 5 — Select All That Apply

Which concepts or documentation topics are useful when implementing WASD movement?

  • A. Objects and sprite position
  • B. Keyboard input
  • C. Conditionals
  • D. File compression
  • E. Database queries
Answer

A, B, C. The movement system requires changing the object’s position, detecting keyboard input, and deciding what action to perform using conditionals.

Question 6 — Multiple Choice

Why should key_is_pressed() normally be checked inside loop instead of start?

  • A. Because start cannot contain if statements
  • B. Because loop repeatedly checks the keyboard while the game is running
  • C. Because start only works with mouse input
  • D. Because key_is_pressed() automatically creates a loop
Answer

B. Keyboard state can change continuously while the game is running. The game therefore needs to check the keyboard repeatedly rather than only once when the object starts.

Question 7 — Multiple Choice

What happens when the player presses d in the following code?

if key_is_pressed("d"):
    self.x += 10
  • A. The player’s x-coordinate decreases by 10
  • B. The player’s y-coordinate increases by 10
  • C. The player’s x-coordinate increases by 10
  • D. The player’s sprite becomes 10 pixels larger
Answer

C. The statement self.x + 10= is equivalent to self.x = self.x + 10.

Question 8 — Multiple Choice

What is the important difference between these two structures?

if key_is_pressed("a"):
    self.x -= 10
if key_is_pressed("d"):
    self.x += 10

and

if key_is_pressed("a"):
    self.x -= 10
elif key_is_pressed("d"):
    self.x += 10
  • A. The first allows both conditions to execute during the same loop iteration, while the second allows at most one branch to execute
  • B. The first only works in start
  • C. The second makes the spaceship move twice as fast
  • D. There is no behavioral difference
Answer

A. With two independent if statements, both conditions are checked and both bodies can run. With if followed by elif, once the first condition is true, the elif branch is skipped.

Question 9 — Multiple Choice

Suppose both a and d are pressed using the non-exclusive version:

if key_is_pressed("a"):
    self.x -= 10
if key_is_pressed("d"):
    self.x += 10

What is the net change in self.x during that loop iteration?

  • A. -20
  • B. -10
  • C. 0
  • D. +20
Answer

C. The first statement subtracts 10, while the second adds 10. Therefore, the horizontal movements cancel each other.

Question 10 — Select All That Apply

Consider the following movement code:

if key_is_pressed("a"):
    self.x -= 10
elif key_is_pressed("d"):
    self.x += 10
if key_is_pressed("w"):
    self.y += 10
elif key_is_pressed("s"):
    self.y -= 10

Which statements are true?

  • A. a and d cannot both cause horizontal movement during the same loop iteration
  • B. w and s cannot both cause vertical movement during the same loop iteration
  • C. The player can still move diagonally by pressing one horizontal key and one vertical key
  • D. Pressing a prevents w from being checked
  • E. Horizontal and vertical movement use separate conditional chains
Answer

A, B, C, E. The horizontal if/elif chain and vertical if/elif chain are separate. Therefore, the player can perform one horizontal movement and one vertical movement during the same loop iteration.

Making Obstacles (Asteroids)

Whenever you want to import a sprite into your game, you have to create a class that contains the instructions of how to setup the object that contains the sprite. Remember that every object created by a PixelPad class can hold a sprite that can be held on the screen.

So, we’ll first create our class Obstacle and at the same time import the image for your asteroid.

In terms of logic, the asteroid should spawn and then move in a determined direction. The direction should be set by the scope (Class/Game) that invoked the class to create the sprite object.

Here’s an example showing a creation of a single Asteroid.

Game Start
asteroid = Obstacle()
asteroid.sprite = sprite("Asteroid.png")
asteroid.x = 0
asteroid.y = 400
asteroid.direction = "South"

In this example an obstacle should be spawned and immediately it will be moving to the south. So the key idea is that depending on the value of the object variable direction, the asteroid’s x and y value should be changed. For now, only work on asteroids coming from the edge of the screen. You’ll be practicing working with a class’s Loop in this activity.

Making Our First Function

In pixelPAD, you have been using a lot of functions already. They are functions that were created by the developers of pixelPAD. In fact, every line of code that you put into any class’s start and loop including that of the Game, you’re in fact coding a function.

The functions that are the game start and game loop are coded by you and are used by pixelPAD’s existing code you do not see.

The function that we’re making is called create_obstacle. Its purpose is to create any obstacle in the game. Think back on how the asteroid is created, what information do you need to provide to our function in order for it to work?

Ask: What does the computer need to know to make the asteroid?

The asteroid is an obstacle object so we use the class Obstacle to create the asteroid object. Then, we have to change the object’s image. After that, we set the x-y position, and lastly we modify our custom object variable called direction in order to set the direction the asteroid will be moving in.

There should be four values that pixelPAD needs. What are the types of these four values?

Hints
  • What do you call a collection of characters?
  • What is the data type for a number that has a fractional part?
Answer - Types of Data

Previously, this is the code to create the asteroid.

Game Start
asteroid = Obstacle()
asteroid.sprite = sprite("Asteroid.png")
asteroid.x = 0
asteroid.y = 400
asteroid.direction = "South"

We need to provide it the name of the sprite e.g. "Asteroid.png" , the x and y coordinates e.g. 0, 4, and as well as the direction the asteroid should be moving to e.g. "South".

These are two str and 2 int.

Making Our Timer

We need a way to keep track of time for game mechanics such as spawning the asteroid every x amount of seconds. There’s a way to create and save information in pixelPAD that will be accessible in every single class in pixelPAD. You do this by creating a variable inside the game object inside Game Start.

Here’s an example:

Game/Start
self.greeting = "hello"
RandomClass/Start
print(game.greeting)
RandomClass2/Start
print(game.greeting + ". My name is John Doe.")

Logically, you’re making two variables. The first variable keeps track of the actual time that has elapsed since the beginning of the game in seconds. The second variable will keep track of the text box object that is you create from the function text (or new_text as it was known as).

First, make the first variable that keeps track of the actual time. You should start it at 0. And of course, this variable should be created by the class Game inside Start.

Solution - Creating Variable clock
Game Start
self.clock = 0

Note that the Loop section of every pixelPAD class will run the code about 60 times per second. This essentially makes it so that every pixelPAD game runs at 60 frames per second. So now, think of a way to change the number that the first variable refers to so that it gains 1.0 per second.

Solution - changing first variable
Game/Loop
self.clock += 1/60
# Equivalent: self.clock = self.clock + 1/60

Now, create the second variable. This variable should refer to a newly created text object. You can create this variable inside a class called UI. You should also make the object’s appearance as a sprite invisible on the screen.

Solution - creating second variable
Game/Start
UI()
UI/Start
# You should replace the arguments for x and y coordinates
self.clock_text = text("Time: 0", 250, 250)

Now, change the variable text of the object that the clock object that the second variable refers to a new string every game loop.

Solution - updating text
UI/Loop
self.clock_text.text = "Time: " + str(int(game.clock))

Making the Asteroid

When an object is created by the class Obstacle, it should be moving the asteroid in the direction tracked by the object variable direction. Previously, our create_obstacle function sets the object variable direction.

create_obstacle
def create_obstacle(direction: str, ...):
    obstacle = Obstacle()
    obstacle.direction = direction
    ...

Direction From North, South, East, West

Question: How do we move the obstacle by some x or y amount constantly until the asteroid move out of the screen? Where do we put this code? What class? Start or Loop?

Solution - Where To Put the Code

We should put the code in the Loop section of the Obstacle class.

Always Refer to the Documentation

You should look at the page about the Start/Loop and as well as the page about Object which shows the variables you could modify.

Question: What are if-statements? What does an if-statement look like in Python? What are boolean expressions?

Solution - if-statements

An if-statement is a condition-based control flow structure. Every if-statement requires a condition and an action.

import random

random_decimal_symbol = random.randint(1, 9)
even_decimal_symbols = []
odd_decimal_symbols = []

# condition
if random_decimal_symbol % 2 == 0:
    # action
    even_decimal_symbols.append(random_decimal_symbol)
elif random_decimal_symbol != 9:
    # alternative action through using else
    odd_decimal_symbols.append(random_decimal_symbol)
else:
    # Do nothing just skip 9
    pass

Programming Fundamentals - Conditionals

Checkout the programming fundamental concepts page on conditionals.

Control flow structures are lines of code that change the order of how lines are run instead of running from top-to-bottom.

Question: How would you create an if-statement for east and west

Solution - if-statement for east and west
if self.direction == "east":
    # Nothing here yet
    pass
elif self.direction == "west":
    # Nothing here yet
    pass

Question: Form the rest of the if-statements including north and south.

Solution - if-statements for all directions
Obstacle/Loop
if self.direction == "east":
      # Nothing here yet
      pass
elif self.direction == "west":
    # Nothing here yet
      pass
elif self.direction == "north":
      # Nothing here yet
      pass
elif self.direction == "south":
      # Nothing here yet
      pass
else:
      # The only left is "out of the screen"
      pass

The only thing left is to fill out the action after you’ve finished the condition.

Solution - Full
Obstacle/Start
self.speed = 10
Obstacle/Loop
if self.direction == "east":
      # Nothing here yet
      self.x -= self.speed
elif self.direction == "west":
      # Nothing here yet
      self.x += self.speed
elif self.direction == "north":
      # Nothing here yet
      self.y += self.speed
elif self.direction == "south":
      # Nothing here yet
      self.y -= self.speed
else:
      # The only left is "out of the screen"
      # TODO: this is what we're doing next
      pass

Direction Below the Spaceship (Work in Progress)

Question: What property should we modify if the obstacle is moving from below the spaceship (not x or y).

Solution - Property to Modify

The property we should modify is the size of the obstacle.