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
- Spaceship - Sprite Creation, Key Inputs, Sprite Movement
- Spaceship User Interface - Health - Textbox, Sprite manipulation
- Asteroid - From the edge of the screen, then from in and out. - Sprite manipulation, Game Loop
- Visual cues - where the asteroid is going - Sprite manipulation
- 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.
- Sprite creation
- 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 StartPlayer()Player Startself.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:
- Objects - Sprite manipulation, position, etc.
- Inputs - Listening to key inputs; notice that it’s used in
loopand notstart(understand why this is the case). - Conditionals - Read this page on if-statements.
Solution - WASD
Non-exclusive movements - Option 1
Player Loopif 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 -= 10Exclusive 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 Loopif 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 -= 10Spaceship 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
startcannot containifstatements - B. Because
looprepeatedly checks the keyboard while the game is running - C. Because
startonly 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 += 10and
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 += 10What 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 -= 10Which statements are true?
- A.
aanddcannot both cause horizontal movement during the same loop iteration - B.
wandscannot 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
apreventswfrom 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 Startasteroid = 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
Obstacleto 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 calleddirectionin 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 Startasteroid = 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/Startself.greeting = "hello"RandomClass/Startprint(game.greeting)RandomClass2/Startprint(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 Startself.clock = 0Note 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/Loopself.clock += 1/60
# Equivalent: self.clock = self.clock + 1/60Now, 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/StartUI()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/Loopself.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_obstacledef 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
passProgramming 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
passQuestion: Form the rest of the if-statements including north and south.
Solution - if-statements for all directions
Obstacle/Loopif 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"
passThe only thing left is to fill out the action after you’ve finished the condition.
Solution - Full
Obstacle/Startself.speed = 10Obstacle/Loopif 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
passDirection 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.