Python: Castle Clash
Python: Castle Clash
CASTLE CLASH
CASTLE CLASH
WORKBOOK
Copyright © 2021 by PixelPAD
Second Edition
EDITORS
Jamie Chang
Ivo van der Marel
Arthur Teles
Rochelle Magnaye
DESIGNERS
Fernando Medrano
Kenneth Chui
Prateeba Perumal
Emily Chow
www.pixelpad.io
www.underthegui.com
CONTENTS
COURSE GOALS &
LEARNING OUTCOMES / 07
C HAPTER 01. C H AP T ER 0 5.
/ 09 • Setting Up / 41 • Health and Attack Damage
• UI/UX Philosophy • Healthbars
• Base Health and Healthbars
C HAPTER 02. C H AP T ER 0 6.
/ 12 • Creating our Map / 51 • Game Over Screens
• Bases
• Bridges
C HAPTER 03 . C H AP T ER 0 7 .
/ 20 • Unit Cards / 60 • More Units
• Creating our First Unit • Spawning Other Enemy
• Spawning Units Units
C HAPTER 04 .
/ 30 • Unit AI
• Teams
• Enemy Spawning
EXTRA ACTIVITIES / 68
GLOSSARY / 70
CHALLENGE QUESTION / 86
COURSE GOALS &
LEARNING OUTCOMES
Students will be able to adjust features, change their sprites and re-skin
their game to make it uniquely their own. In addition, they will be able to
create custom units, AI behaviour and multiple worlds.
7
COURSE GOALS
LEARNING OUTCOMES
Creativity
ű Students understand how balancing of characters, their features and their
value can improve the gameplay
Construction
ű Students understand and can create games of any type, for multiple
platforms in PixelPAD
SAMPLE PROJECT
GAME GUIDE I PREFACE
https://pixelpad.io/app/juuorqvgkib/?edit=1
8
01.
C H A P TER
SETTING UP
In this workbook we’ll be making Castle Clash, a top down strategy game
that is targeted at mobile platforms!
The goal of the game is to spawn as many units as you can overwhelm the
enemy base while defending your own!
10
Upon the creation of your project, you should be greeted by the PixelPAD
Development Environment.
UI/UX PHILOSOPHY
For this particular game we are “targeting” mobile platforms. This means
that we are designing and creating our game in a way that will be easy to
use on phones or other touchscreen devices!
The concept of making things easy to use and interact with is called User
Interface/ User Experience Design. UI/UX for short.
For some people, their whole job is to make things easy to use and interact
with. A few examples of some fields that heavily involve UI/UX are games,
websites, public signage,maps, and much much more.
For our game, this means thinking about what makes our game mechanics
and design easy to use for a mobile player.
11
02.
CHA P TER
The first thing we are going to add to our game is our game terrain.. For
this workbook example, we’re going to create a map that has a river that
runs through the middle.
1
Create a new Class
Classes
GAME GUIDE I CHAPTER TWO
self.field = Background()
Here we simply add a fruit object to our game and naming it “field”.
The self attributes the created object to the class this code is written in, in
this case, the Game class.
In plain english, this line would look like this: “field” is my object from the
class Background.
SAVE PLAY
You’ll notice that all you can see is a box that says “Empty Image”
13
2
Let’s add a sprite
Sprites
14
Click on the Background
Class, then click on the
Start Tab of your editor.
Background Start
self.sprite = sprite(“background.png”)
SAVE PLAY
You should now have your background object placed in the game.
15
BASES
Now that we have a background for our game, let’s add bases. These
buildings will be where units from both sides will spawn from.
For our workbook example, we’re going to be placing bases at the left and
right of our game, directly in the middle.
base
Sprites
base.png
16
Click on the Base Class,
then click on the Start Tab
of your editor.
Base Start
self.sprite = sprite(“base.png”)
Here we are simply assigning the Base object the base sprite.
SAVE PLAY
Now that we have our base objects, let’s add them to our game’s map.
self.field = Background()
self.playerBase = Base()
self.playerBase.x = -500
self.enemyBase = Base()
self.enemyBase.x = 500
Here we add two base objects to our game, named playerBase and
enemyBase.
Then we set their positions to top the left and right of our game map. Feel
free to change the x and y position of your bases to anywhere in your
map.
SAVE PLAY
You should now see two bases in your game, we’ll come back to them later
to add spawning and health.
17
BRIDGES
Next, we’re going to make some bridges to span the river. We’re going to
be directing our AI units to cross only where the bridges are.
bridge
18
Bridge Start
self.sprite = sprite(“bridge.png”)
Here we are simply assigning the Bridge object the bridge sprite.
SAVE PLAY
Now that we have our object, let’s add it into our game.
Game Start
self.field = Background()
self.playerBase = Base()
self.playerBase.x = -500
self.enemyBase = Base()
self.enemyBase.x = 500
self.bridgeTop = Bridge()
self.bridgeTop.y = 200
self.bridgeBottom = Bridge()
self.bridgeBottom.y = -200
Here we add two bridge objects to our game, we then manually set their
y positions to span the river. Feel free to adjust or change where your
bridges are placed!
SAVE PLAY
You should now be able to see two bridges going across your river!
19
03.
C H A P TER
UNIT CARDS
Next we’re going to create a way to spawn units from our bases!
Since this game will be played on mobile, we are going to design around
the fact that the player will most likely not have access to a physical
keyboard.
To let the player spawn units easily, we’re going to create “card” buttons for
our player to tap.
GAME GUIDE I CHAPTER THREE
20
1 Create a new Class
2 Now let’s add a sprite
Classes Sprites
slimeCard
21
Card Start
self.sprite = sprite(“slimeCard.png”)
SAVE PLAY
Now that we have the card object, let’s add it to our game.
Game Loop
self.field = Background()
self.playerBase = Base()
self.playerBase.x = -500
self.enemyBase = Base()
self.enemyBase.x = 500
self.bridgeTop = Bridge()
self.bridgeTop.y = 200
self.bridgeBottom = Bridge()
self.bridgeBottom.y = -200
self.slimeCard = Card()
self.slimeCard.x = -550
self.slimeCard.y = -270
Here we create the new slimeCard object as well as place it at the bottom
left of our game by directly changing its x, y position. This will let our
player tap the card without obstructing gameplay information!
SAVE PLAY
GAME GUIDE I CHAPTER THREE
22
Your spawning card should now show up in the bottom left corner of your
game!
Although we have our spawning card, we need to make the unit that will be
created whenever we tap on the card.
23
1 Create a new Class
2 Now let’s add a sprite
Classes Sprites
slimeUnit
24
We should first create the sprite sheet and slice it into smaller sprites. To
slice it, we have to know how many rows and columns this sprite has. Let’s
open the slimeUnit.png sprite.
Sprites
slimeUnit.png
As you can see, our slime animation has 8 images. They are divided into 1
row and 8 columns. That’s all the information we need from this sprite for
now.
Unit Start
spriteSheet = sprite(“slimeUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(self, anim)
Here we are creating a new sprite sheet from our ‘slimeUnit.png’ sprite. In
our example, our sprite has 1 row and 8 columns.
SAVE PLAY
25
SPAWNING UNITS
Now that we have a spawning card as well as a unit to spawn, we now need
to make sure that card creates unit objects.
To detect if we are tapping the card, we are going to need an object that
keeps following our mouse. Once we tap on the screen, we check if this
object is colliding with a card. If it is, we then spawn a unit.
mouseCollider
GAME GUIDE I CHAPTER THREE
Colliders are what make our objects collide with each other. In pixelPAD,
colliders are simply sprites. We are going to use this to check where the
player is clicking by checking collisions between our Cursor class and other
objects in our game, like cards.
26
Cursor Start
self.sprite = sprite(“mouseCollider.png”)
Now that we have created our Cursor object, let’s add it to our game.
Game Loop
...
self.bridgeBottom = Bridge()
self.bridgeBottom.y = -200
self.slimeCard = Card()
self.slimeCard.x = -550
self.slimeCard.y = -270
self.pointer = Cursor()
Here we are simply adding a Cursor object to our game, and naming it
“pointer”.
SAVE PLAY
You should now be able to see your cursor’s collider right in the middle of
your screen. However, as this object is what is going to detect where we are
clicking, it should always be in the same position of our mouse.
27
Cursor Loop
self.x = mouse_x()
self.y = mouse_y()
SAVE PLAY
Your cursor collider should now be following your mouse around the game
screen. Now, the last step is to be able to detect clicks on the card and
spawn a unit if that happens.
Cursor Loop
self.x = mouse_x()
self.y = mouse_y()
if mouse_was_pressed(“left”):
cardClicked = get_collision(self, “Card”)
if cardClicked:
slime = Unit()
slime.x = game.playerBase.x
slime.y = game.playerBase.y
Next, we use the variable cardClicked to store the return value of the
function get_collision
The get_collision function will return the first Card object colliding with the
Cursor. If there is no collision, then the function will return false.
If it is colliding with a card, then we create a new slime object, and set its
position to the playerBase location.
SAVE PLAY
GAME GUIDE I CHAPTER THREE
You should now notice that your card spawns units at your player’s base!
28
Now that we’ve made sure that our cursor is working correctly, we can hide
it from the game so we don’t have a flying red dot following our mouse. We
can simply make it invisible by setting it’s visible variable to false.
Cursor Start
self.sprite = sprite(“mouseCollider.png”)
self.visible = False
SAVE PLAY
29
04.
CHA P TER
UNIT AI
Next, we’re going to need to make our units move on their own. However,
we can’t simply move them up. We’re going to need them to cross bridges
to get to the enemy base as well as pick a bridge to cross.
Unit Start
spriteSheet = sprite(“slimeUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(self, anim)
GAME GUIDE I CHAPTER FOUR
self.destinationX = 0
self.destinationY = 0
Here we are creating variables that can store our x and y position of the
destination the unit should move to.
SAVE PLAY
30
Next, we need to use this variable to determine how we are going to move
the unit.
Unit Loop
First we check if the current x position of the unit is less than the
destinationX position.
If it is, then we increase the x position of the unit, making it move to the
right.
We repeat the same code afterwards, except now we are comparing the
current y position of the unit to the destinationY position.
SAVE PLAY
You will notice that your units are walking to the right, but they stop at the
middle of your game. That’s because destinationX and destinationY are
equal to 0. Let’s go ahead and make our unit randomly choose one of the
two bridges as its first destination.
31
Unit Start
import random
spriteSheet = sprite(“slimeUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(self, anim)
self.destinationX = 0
self.destinationY = 0
self.bridgeSelected = random.randint(1,2)
if self.bridgeSelected == 1:
self.destinationX = game.bridgeTop.x
self.destinationY = game.bridgeTop.y
else:
self.destinationX = game.bridgeBottom.x
self.destinationY = game.bridgeBottom.y
SAVE PLAY
32
As you can see in our example, our slimes are moving backwards. We can
easily flip their sprites by setting their scaleX value to -1. You might not
need to do it in your project if your object’s sprite is already facing the right
direction.
Every object has default scaleX and scaleY values set to 1. If we invert the
scaleY value, we flip the object vertically, and if we invert the scaleX value,
we flip it horizontally.
Cursor Loop
self.x = mouse_x()
self.y = mouse_y()
if mouse_was_pressed(“left”):
cardClicked = get_collision(self, “Card”)
if cardClicked:
slime = Unit()
slime.x = game.playerBase.x
slime.y = game.playerBase.y
slime.scaleX = -1
Here we simply flip the slime horizontally by setting its scaleX value to -1
after its creation.
SAVE PLAY
33
TEAMS
When you play your game, you may notice that your units bunch up on the
bridges.
This is because once your units arrive at their destination, they don’t assign
themselves a base to go to!
Unit Start
...
if self.bridgeSelected == 1:
self.destinationX = game.bridgeTop.x
self.destinationY = game.bridgeTop.y
else:
self.destinationX = game.bridgeBottom.x
self.destinationY = game.bridgeBottom.y
self.team = None
Here we are creating a variable to store the team of the unit. At the
moment we assign this value to none, but using the bases, we can assign
this externally.
SAVE PLAY
Cursor Loop
self.x = mouse_x()
self.y = mouse_y()
if mouse_was_pressed(“left”):
GAME GUIDE I CHAPTER FOUR
Now, when we create a new slime object, we assign its team as well.
SAVE PLAY
34
Next, we need to head back into the Unit class to make use of this new
variable.
Unit Loop
Here we are checking if the current position of the unit is the same as the
destination’s position.
If it is, we then check what team the unit is on. If the unit’s team is ‘player’,
then we set the destination to be the enemy base.
If the team isn’t ‘player’, then we set the destination to the player’s base.
SAVE PLAY
You should now notice that your units move to the bridge, and then to the
enemy base!
35
ENEMY SPAWNING
Now that our units move to the enemy base, we need to let our enemy AI
retaliate as well.
First let’s handle how the enemy will assign teams. We need to create a
team variable for the bases so that they can assign the right team to its
units.
Base Start
self.sprite = sprite(“base.png”)
self.team = None
SAVE PLAY
Game Start
self.field = Background()
self.playerBase = Base()
self.playerBase.x = -500
self.playerBase.team = “player”
self.enemyBase = Base()
self.enemyBase.x = 500
self.enemyBase.team = “enemy”
self.bridgeTop = Bridge()
self.bridgeTop.y = 200
self.bridgeBottom = Bridge()
GAME GUIDE I CHAPTER FOUR
self.bridgeBottom.y = -200
self.slimeCard = Card()
self.slimeCard.x = -550
self.slimeCard.y = -270
self.pointer = Cursor()
For each of the base objects in our scene we assign the team variable to
either ‘player’ or ‘enemy’.
36
SAVE PLAY
Now that these bases have teams associated with them, let’s let bases
automatically spawn units.
First, we need to find a sprite to represent our enemy’s units. For our
example we are going to be using ghosts as enemies!
ghostUnit
37
Next, we’ll need to create a spawnTimer in our Base class, so that our base
doesn’t spawn a unit every frame.
Base Start
self.sprite = sprite(“base.png”)
self.team = None
self.spawnTimer = 0
Here we simply create a variable that stores the amount of frames that
have passed. We’ll use this variable to delay spawning a new unit.
SAVE PLAY
Base Loop
if self.team == “enemy”:
self.spawnTimer = self.spawnTimer + 1
if self.spawnTimer >= 60:
enemyUnit= Unit()
enemyUnit.team = self.team
spriteSheet = sprite(“ghostUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(enemyUnit, anim)
enemyUnit.x = self.x
enemyUnit.y = self.y
self.spawnTimer = 0
First, we check if this base is from the team ‘enemy’. If so, we add one to
the spawnTimer’s current value every frame.
If the condition is true, we then create a new unit named enemyUnit, set
its team to the current team of the base, set the ghost animation on it, and
position it on top of the base.
Next, we set the spawnTimer back to 0 so that we can repeat this code in
another 30 frames.
SAVE PLAY
38
You should now notice that your enemy base spawns units back towards
you! However, both the player’s and enemy’s units change their direction as
soon as they reach the middle of the bridge, instead of finishing crossing
the bridge first. That makes them walk on the water.
To fix that, we can check if our units have walked enough to cross the
bridge before changing their destination to the other base. We can look at
the units’ x position to get that information.
The units should walk 250 pixels past the middle of the screen to cross the
bridge. For the player’s units, we can check if their position is greater than
250. However, for the enemy’s units, their position has to be smaller than
-250 as they’re moving to the left.
39
Unit Loop
...
Here we are modifying our if statements to check if the player unit is at the
x position of 250 before changing its destination to the enemy base. If it
isn’t, we set it’s destinationX to 250.
We then do the same with the enemy units, but checking for the position
-250, as they’re moving to the left.
SAVE PLAY
You should now notice that all units cross the bridge entirely before moving
to the other base!
GAME GUIDE I CHAPTER FOUR
40
05.
C H A P TER
Now that we can have units going back and forth, we need to add health to
our units so the balance of power can shift!
Unit Start
...
if self.bridgeSelected == 1:
self.destinationX = game.bridgeTop.x
self.destinationY = game.bridgeTop.y
else:
self.destinationX = game.bridgeBottom.x
self.destinationY = game.bridgeBottom.y
self.team = None
self.health = 100
self.attack = 5
Here we simply create a variable for health and give it a base value of 100
Later we create another variable for attack and give it a base value of 5.
SAVE PLAY
41
Now that we have a value for health, we’re going to need some way to
check for collisions between different units. Let’s handle collisions between
units.
Unit Loop
...
First we check for collisions between the current Unit and all other “Unit”
objects.
Next, we have a for each loop. The for each goes through every object that
is returned by collision_check_all and checks the team of each object.
Then, the next step is to deal damage to the other unit by decreasing their
health values with our attack value.
SAVE PLAY
42
Although we’ve created a health system, our units don’t despawn once they
hit 0 health!
Unit Loop
...
if self.health <= 0:
destroy(self)
If the unit’s health has fallen below 0, then we destroy the unit.
SAVE PLAY
You should notice that your units start disappearing once their health hits 0!
43
HEALTHBARS
Although we have health for our units, it would be really useful to see the
health of our units at a glance. To do this, we’re going to create healthbars.
healthBar
44
HealthBar Start
self.sprite = sprite(“healthBar.png”)
SAVE PLAY
Now that we have a HealthBar object, we’re going to need to give every
unit we spawn a HealthBar, as well as scale it to the current health of the
unit.
Unit Start
...
if self.bridgeSelected == 1:
self.destinationX = game.bridgeTop.x
self.destinationY = game.bridgeTop.y
else:
self.destinationX = game.bridgeBottom.x
self.destinationY = game.bridgeBottom.y
self.team = None
self.health = 100
self.maxHealth = self.health
self.attack = 5
self.healthBar = HealthBar()
First we create a new healthBar object. This is our solid red rectangle from
earlier.
Next we have a variable that stores the maximum value of health. Since
this code is in start, we can simply set it to health.
SAVE PLAY
45
Now that we have our variables, let’s edit this code in Loop.
Unit Loop
...
self.healthBar.y = self.y - 40
self.healthBar.x = self.x
self.healthBar.scaleX = self.health / self.maxHealth
if self.health <= 0:
destroy(self.healthBar)
destroy(self)
Here we set the position of the healthBar slightly below the unit on every
frame.
Next, we set the scaleX of the sprite to a fraction of the original health.
Finally, we make the unit also destroy its lifebar if it’s life reaches zero.
SAVE PLAY
46
You might now have health bars appearing in the middle of your game
right when a unit is spawned in game. That happens because it takes some
time from when the health bar is created till when it is moved to below
the unit. We can simply solve that problem by making the health bar to be
created outside of the screen.
HealthBar Start
self.sprite = sprite(“healthBar.png”)
self.x = 999
Here we make the health bar to spawn outside of the game screen by
changing its x position.
SAVE PLAY
You should now see that health bars of units shrink until they are destroyed!
47
BASE HEALTH AND HEALTHBARS
Let’s add some health and health bars to our bases now!
Base Start
self.sprite = sprite(“base.png”)
self.team = None
self.spawnTimer = 0
self.health = 100
self.maxHealth = self.health
self.healthBar = HealthBar()
After that, just like the unit, we create a new healthBar object.
SAVE PLAY
Base Loop
if self.team == “enemy”:
self.spawnTimer = self.spawnTimer + 1
if self.spawnTimer >= 60:
slime = Unit()
slime.team = self.team
slime.sprite = sprite(“slimeUnit.png”)
slime.x = self.x
slime.y = self.y
self.spawnTimer = 0
self.healthBar.x = self.x
self.healthBar.scaleX = self.health / self.maxHealth
Here we’re placing the healthBar slightly below the base just like our units.
SAVE PLAY
48
You may have noticed that your base health hasn’t gone down despite your
units colliding with it!
That’s because we haven’t added the collision check to our units. Let’s do
that now.
Unit Loop
...
self.healthBar.y = self.y - 40
self.healthBar.x = self.x
self.healthBar.scaleX = self.health / self.maxHealth
if self.health <= 0:
destroy(self.healthBar)
destroy(self)
Here we add a get_collision code that checks for collisions between the
unit and any “Base” object.
If there is a collision with a base that does not have the same team as the
unit, deal damage to the base. We also set this unit’s life to 0 so it gets
destroyed.
SAVE PLAY
49
Your bases should now decrease health and be destroyed after their health
reaches 0!
GAME GUIDE I CHAPTER FIVE
50
06.
C H A P TER
Now that our bases are already taking damage from units, we need to
create game over screens for when the player wins and loses the game.
51
YouWin Start
Here we first create a text object that says “You Win”, and change its x
position to -440 and y position to 130.
SAVE PLAY
YouLose Start
self.youLoseText.color = “red”
self.youLoseText.fontSize = 250
Here we first create a text object that says “You Lose”, and change its x
position to -500 and y position to 130.
52
Now that we have both rooms created, we are going to redirect the player
to one of them in case he wins or loses the game.
Base Start
if self.team == “enemy”:
self.spawnTimer = self.spawnTimer + 1
if self.spawnTimer >= 60:
slime = Unit()
slime.team = self.team
slime.sprite = sprite(“slimeUnit.png”)
slime.x = self.x
slime.y = self.y
self.spawnTimer = 0
if self.health <= 0:
if self.team == “enemy”:
set_room(“YouWin”)
else:
set_room(“YouLose”)
Next, we check if the base’s team is ‘enemy’. If so, we set the YouWin room.
If not, we set the “YouLose” room.
SAVE PLAY
You should now get different screens in case you win or lose the game. Feel
free to style these screens as you like!
53
Now that we have our game over rooms, we should be able to replay our
game without stopping and playing our game through PixelPAD. That can
be done by running our game in a Room instead of running it straight on
the Game class. Then, we can just reload the room to play the game again.
Highlight all the code with your mouse, and copy it with Ctrl+C
Paste your code that you copied back in the Game class with Ctrl+V
GAME GUIDE I CHAPTER SIX
Game Start
We copied all the code from here into the Play room. Then, we deleted all
the code from the Game class.
54
Play Start
self.field = Background()
self.playerBase = Base()
self.playerBase.x = -500
self.playerBase.team = “player”
self.enemyBase = Base()
self.enemyBase.x = 500
self.enemyBase.team = “enemy”
self.bridgeTop = Bridge()
self.bridgeTop.y = 200
self.bridgeBottom = Bridge()
self.bridgeBottom.y = -200
self.slimeCard = Card()
self.slimeCard.x = -550
self.slimeCard.y = -270
self.pointer = Cursor()
We copied all the code from the Game class into here. Then, we deleted
all the code from the Game class.
SAVE PLAY
Your game screen should now be all black since we don’t have any code
inside our Game class anymore. Now, we want to tell our game to run the
Play room as soon as the game starts.
Game Start
set_room(“Play”)
Here, we are telling our game to set the Play room as the current room.
SAVE PLAY
55
Now your game will show up on the screen. However, after a couple
seconds it causes many errors.
As we’ve copied all our code from the Game class into the Play room,
objects like our bridges, bases, and cards are now owned by the room. That
causes problems when we try to access them.
For example, our units need to know both bridges and bases in order to
move. When they look for those objects in the game class (with game.
bridgeTop for example), they can’t find it.
To solve this problem, we have to tell our game that all those objects in the
room are actually owned by the Game class, and not by the room. To do
that, we just have to change the self for game.
GAME GUIDE I CHAPTER SIX
56
Play Start
game.field = Background()
game.playerBase = Base()
game.playerBase.x = -500
game.playerBase.team = “player”
game.enemyBase = Base()
game.enemyBase.x = 500
game.enemyBase.team = “enemy”
game.bridgeTop = Bridge()
game.bridgeTop.y = 200
game.bridgeBottom = Bridge()
game.bridgeBottom.y = -200
game.slimeCard = Card()
game.slimeCard.x = -550
game.slimeCard.y = -270
game.pointer = Cursor()
Here, we are specifying that all these objects created are owned by the
Game class.
SAVE PLAY
Your game should now be working again. However, nothing has changed
yet. Now, we are going to add some code on YouLose and YouWin rooms
to reload the game if we press the left mouse button (or tap on the screen
for mobile platforms).
57
YouLose Start
Here we first create a text object that says “Click to play again”, and change
its x position to -200 and y position to -250.
SAVE PLAY
YouLose Loop
if mouse_was_pressed(“left”):
set_room(“Play”)
Here we first check if the left mouse button was pressed. If so, we then set
the Play room as the current room, which will restart the game.
SAVE PLAY
You should now be able to replay your game by pressing the Space key in
the YouLose room. Now you will have to repeat the same process for your
YouWin room!
GAME GUIDE I CHAPTER SIX
58
YouWin Start
Here we first create a text object that says “Click to play again”, and change
its x position to -200 and y position to -250.
SAVE PLAY
YouWin Loop
if mouse_was_pressed(“left”):
set_room(“Play”)
Here we first check if the left mouse button was pressed. If so, we then set
the Play room as the current room, which will restart the game.
SAVE PLAY
59
07.
C H A P TER
MORE UNITS
Now that we have a functioning push/pull gameplay system with our units,
it’s time to diversify our unit roster by adding different units!
For our example, we’re going to be adding another unit. However, you can
change the name, sprite or gameplay behaviour of your unit.
GAME GUIDE I CHAPTER SEVEN
60
1 Let’s add a sprite
2 Let’s add another sprite
Sprites Sprites
Find and select the sprite for Find and select a sprite for
your Card your Unit
61
Play Start
game.field = Background()
game.playerBase = Base()
game.playerBase.x = -500
game.playerBase.team = “player”
game.enemyBase = Base()
game.enemyBase.x = 500
game.enemyBase.team = “enemy”
game.bridgeTop = Bridge()
game.bridgeTop.y = 200
game.bridgeBottom = Bridge()
game.bridgeBottom.y = -200
game.slimeCard = Card()
game.slimeCard.x = -550
game.slimeCard.y = -270
game.golemCard = Card()
game.golemCard.sprite = sprite(“golemCard.png”)
game.golemCard.x = -400
game.golemCard.y = -270
game.pointer = Cursor()
Here we simply create the golemCard object from the Card class, change
its sprite to the golemCard.png sprite we just added, and move it to the
right of the slime card
SAVE PLAY
GAME GUIDE I CHAPTER SEVEN
62
Card Start
self.sprite = sprite(“slimeCard.png”)
self.spawns = None
Here we simply create the golemCard object from the Card class, change
its sprite to the golemCard.png sprite we just added, and move it to the
right of the slime card.
SAVE PLAY
Play Start
...
game.bridgeBottom = Bridge()
game.bridgeBottom.y = -200
game.slimeCard = Card()
game.slimeCard.x = -550
game.slimeCard.y = -270
game.golemCard = Card()
game.golemCard.sprite = sprite(“golemCard.png”)
game.golemCard.x = -400
game.golemCard.y = -270
game.pointer = Cursor()
Here we simply create the golemCard object from the Card class, change
its sprite to the golemCard.png sprite we just added, and move it to the
right of the slime card.
Next, we need to spawn the correct unit depending on the spawns variable
assigned to the card.
63
Cursor Loop
self.x = mouse_x()
self.y = mouse_y()
if mouse_was_pressed(“left”):
cardClicked = get_collision(self, “Card”)
if cardClicked:
if cardClicked.spawns == “slime”:
slime = Unit()
slime.x = game.playerBase.x
slime.y = game.playerBase.y
slime.team = “player”
slime.scaleX = -1
elif cardClicked.spawns == “golem”:
golem = Unit()
spriteSheet = sprite(“golemUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(golem, anim)
golem.x = game.playerBase.x
golem.y = game.playerBase.y
golem.team = “player”
golem.scaleX = -1
Here, after checking if we’ve clicked on a card, we then check what this
card spawns. If it spawns a slime, then we just spawn the slime unit as we
were already doing before.
If the card spawns a golem, we create another Unit object called golem,
set the golem’s animation to the object, position the golem on the player
base, set it to the player team, and flip its sprite horizontally so it faces
right.
SAVE PLAY
Now, by tapping or clicking your new unit card, you can spawn another
type of unit from your base!
GAME GUIDE I CHAPTER SEVEN
64
SPAWNING OTHER ENEMY UNITS
Now that you can add other units, we can also incorporate these new units
into our enemy base spawner.
batUnit
65
Base Loop
import random
if self.team == “enemy”:
self.spawnTimer = self.spawnTimer + 1
if self.spawnTimer >= 60:
enemyUnit = Unit()
enemyUnit.team = self.team
spriteSheet = sprite(“ghostUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(enemyUnit, anim)
enemyUnit.x = self.x
enemyUnit.y = self.y
unitSelected = random.randint(1,2)
if unitSelected == 1:
spriteSheet = sprite(“ghostUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(enemyUnit, anim)
else:
spriteSheet = sprite(“batUnit.png”,1,8)
anim = animation(spriteSheet, 20, 0, 7)
animation_set(enemyUnit, anim)
self.spawnTimer = 0
if self.health <= 0:
if self.team == “enemy”:
set_room(“YouWin”)
else:
set_room(“YouLose”)
Next, we removed the old animation code we had and created a variable
that is assigned to a random number.
Based off the result of the random number we either spawn a slimeUnit or
GAME GUIDE I CHAPTER SEVEN
batUnit.
SAVE PLAY
Your enemy base should now spawn your new unit as well! If you ever want
to spawn more units from your enemy base, increase the maximum number
that can be generated by random.randint() and add a new if statement
checking for that number. In that if statement simply create a new object!
66
Congratulations! You’ve created a top-down RTS targeted at mobile
platforms!
Feel free to add more units with different behaviour or destinations, change
the layout of your map, whatever you want!
67
EXTRA ACTIVITIES
The following activities are optional and should be added to your current
game. Most of them can be added during your game’s development, but
some might require your game to be already completed. You can check the
prerequisite chapters beside the activities to know if you are able or not to
do it at the stage you are now in the course.
# Prerequisite Activity
to 120 health
ű Ghosts and bats can have from 100
to 150 health
69
GLOSSARY
WHAT IS PIXELPAD?
PixelPAD is an online platform we will be using to create our own apps or
games!
ASSETS: Your assets are where you can add and access your classes and
sprites. Classes are step-by-step instructions that are unique to the object.
For example, the instructions for how your player moves will be different
from the way your asteroid moves! Sprites is another word for image, and
these images give your objects an appearance!
CODE: In this section, you will write instructions for your game. To write
your code, click within the black box and on the line you want to type on. To
make a new line, click the end of the previous line and then press “Enter”
GAME GUIDE I GLOSSARY
on your keyboard.
STAGE: The stage is where your game will show up after you write your
code and click Play (or Stop and then Play). Don’t forget to click save after
you make changes to your code!
CONSOLE: Your console is where you will see messages when there are
errors in your code, and also where you can have messages from your
game show up such as the score, or instructions on how to play your game.
70
SCRIPTS
Scripts and Assets
Two of the asset types, rooms and classes, are script assets. Script assets (or
scripts) are assets that have code inside them. Sprites are not considered
scripts, because they do not contain any code.
Creating Scripts and Assets: To Create an asset, you start by clicking the +
next to “Rooms”, “Classes” or “Sprites”
Then type in any name you’d like. My particular convention looks like this:
Classes and rooms (scripts) should follow the “TitleCase” standard, where
all words are capitalized. For sprites and sounds (assets) we use the
“camelCase” standard, where the first word is lowercase, and every word
that follows is capitalized. This isn’t necessary, but keeps your code neat
and readable.
71
DEFAULT OBJECT PROPERTIES
Sprite, scaleX and scaleY
Every class inherits default properties when created in PixelPAD. The First of
these few properties you should learn about are:
.sprite is the image of the object. The value of .sprite is an image object
which we will get to later. .scaleY takes a float between 0 and 1 and
stretches the sprite of the object lengthwise. .scaleX takes a float between 0
and 1 and stretches the sprite of the object widthwise.
X, Y and Z Coordinates
The position of an object is where the object is. In programming, we usually
describe an object’s position using a pair of numbers: its X coordinate and
its Y coordinate.
ű .x takes the value of the x position of the object. The higher .x is, the
farther to the right it is.
ű .y takes the value of the y position of the object. The higher .y is, the
higher up the object is.
ű .z takes the value of the z position of the object. The higher .z is, the
closer to you the object is.
The ‘s tells you who you’re talking about. In code instead of using
apostrophes we use dots to talk about ownership.
72
The “Self” Property
Self refers to whichever class you are currently in.
So if you’re typing code inside the Spaceship class, saying “self” refers to
the Spaceship class itself.
Examples of Self
The code would make Spaceship move to the right by 50px, up by 30px,
and reduce its image size in half.
COLLISIONS
What Are Collisions?
Think of collision as checking whenever two objects touch. In Mario,
whenever he collides with a coin, it runs the code to add a score.
In PixelPAD, it’s when the “bounding boxes” of sprites touch. This includes
the transparent areas of the sprite as well!
We will use collisions in our game to determine when our ship is hit by
obstacles, when we’ve collected a power-up or health refill, and when we’ve
managed to shoot down an asteroid.
And here they are again, with their bounding boxes shown:
DESTROYING OBJECTS
The destroy Function
When two objects collide, we generally would like to destroy at least one
of them. Destroying an object removes it from the game. When an object
is destroyed, it no longer exists, and trying to use it could make your game
behave strangely or crash.
player object:
74
THE START AND THE LOOP TABS
Start and Loop
Say you decide to go for a run. You put on your runners and then you run.
Running would be the loop because it repeats (one foot in front of the
other), and putting runners on would be the start because it only happens
in the beginning.
Loops can happen every day, or they can repeat a specific number of times.
For example, a programmer can code a robot to jump 100 times, or code
the robot to keep jumping forever!
Video games are built around a game loop. Specifically for PixelPAD, our
game loop runs the code 60 times every second!
The loop starts when we click the Play button, and stops when we click the
Stop button. It goes around and around for as long as the game is playing,
updating each of our objects a little bit at a time.
75
How Do We Use The Game Loop?
When we write code for our objects, we can choose to place it in one of
two sections: the Start Section or the Loop Section. Code placed in the Start
Section is executed as soon as we create the object. Code placed in the
Loop Section, however, is added to the game loop, which means it will be
executed over and over until the game stops.
CONDITIONALS
Conditions
So far, whenever we’ve written any code, all we’ve done is give the
computer a list of commands to do one after the other. Using conditions,
we can tell the computer to make a decision between doing one thing or
another.
If Statements
The way we write conditions in our code is by using if statements. Here is an
example of a simple if statement:
spaces to the left of the code. Indentation is important for two reasons:
76
For example:
We can see clearly that the statements only run if the condition is met. E.g.
the player presses the SPACE button.
It is very important that all of the code in the same body be indented using
the same number of spaces on every line.
KEY PRESS
Keyboard Input
One kind of condition we can use is a keyboard check. Keyboard checks
can be used to determine whether a keyboard key is being pressed or
not. We can use keyboard checks to make things happen when the player
presses or releases a keyboard key.
if key_is_pressed(“W”):
print(“You are pressing the W key!”)
77
The code inside the apostrophes is the name of a keyboard key. Most keys
are named the same as the letter or word on their keyboard key. A few keys
have specific names:
COMPARISONS
Comparisons
If we have code like: x > 300, this is a specific kind of condition called
a comparison. Comparisons are true/false questions we can ask the
computer about pairs of numbers. There are six main kinds of comparisons,
each with its own operator (special symbol). This table shows an example of
each kind of comparison:
There are other kinds of conditions, but comparisons are the kind that we
will be using most often.
78
In our Player class’ Loop Section, add this new code at the bottom:
COMMENTS
Explaining Code with Comments
Computer code is complicated. That’s why, a long time ago, some very
smart programmers invented code comments.
Comments are like little notes that you can leave for yourself in your
programs. The computer completely ignores comments in your code. You
can write whatever you want inside of a comment.
Comments are an extremely useful tool, and you should get in the habit
of writing them. Comments help us remember what our code does, help
others understand our code, and help us keep our code organized.
79
TYPES OF BAD COMMENTS
Misleading Comments
It’s important to remember that comments are notes. The computer doesn’t
read our comments when it’s deciding what to do next. Because of this,
comments can sometimes be inaccurate. We should always read the code,
even if it is commented, to make sure it does what we think it is doing.
The comment says that this code makes the player move upwards, but
when we read the code, it actually makes them move downwards. If we just
read the comment without checking it against the code, we would have no
idea why our game wasn’t working properly.
Obvious Comments
Another type of bad comment is an obvious comment. Obvious comments
don’t add any meaningful information to your code; they usually just re-
state what the code is saying in plain English. Here is an example of an
obvious comment:
Obvious comments clutter up our code and can slowly turn into misleading
comments if we’re not careful. If a comment doesn’t add anything
meaningful to our code, it’s best to just delete it.
Vague Comments
Vague comments are comments that don’t actually explain anything. Vague
comments are usually written without much thought, or because the author
of the comment was told to comment on their code. Here is an example of
a vague comment:
GAME GUIDE I GLOSSARY
80
The comment at the top of this code just says “Grab it.” It doesn’t say what
the code does, or how it works. Some of this code doesn’t even have
anything obvious to do with grabbing. Similar to obvious comments, vague
comments clutter up our code and can slowly become misleading as we
work on our project. It is better to just delete any vague comments you find
in your code.
Games use frames, too. Every time the code in an object’s Loop Section
runs, the game is drawing a new frame based on where our objects are and
what sprites we have attached to those objects.
Every frame in our game lasts exactly the same amount of time: 1/60th of a
second. That means that there are 60 frames in a second.
Timers
A timer is a number that counts time. For example, if we were watching a
clock, and counted up by one every time the clock’s second hand moved,
we would be timing seconds.
Since each frame in our games lasts the same amount of time, we can build
a timer that counts frames by counting up by one whenever our game’s
Loop Section is run.
Why are timers useful? Timers let us schedule things. For example, if we
wanted an asteroid to appear at the top of the screen every second, we
could use a timer that counted to 60 (since each frame lasts 1/60th of a
second).
81
RANDOM & IMPORT
Random Numbers
Most video games use some kind of randomness to change what happens
in the game each time we play, to stop the game from getting boring. We
can add randomness to our games using random numbers.
Random Positions
For example, whenever we create an asteroid, we’ve been using code like
this:
asteroid = Asteroid()
asteroid.x = 0
asteroid.y = 300
This code makes asteroids appear at the top of our screen, right in the
middle. When we play the game, every asteroid will appear in exactly the
same place. We can change this by asking for random numbers when we
set the asteroid’s position:
asteroid = Asteroid()
asteroid.x = random.randint(-600, 600)
asteroid.y = 300
Random Function
random.randint is a special command which asks for a random number. The
numbers between the parentheses are the smallest and largest values you
want to get. For example, if we were writing a dice-rolling game, we could
use random.randint(1, 6) to perform a dice roll.
Probability
Random numbers can be used to affect the probability that something will
happen in your program.
For example, in the code below we’re only creating an asteroid only half the
time we used to by adding the random.randint(1,2) == 1 conditional.
asteroid_frames = 0
if random.randint(1, 2) == 1:
asteroid = Asteroid()
82
Modules
When we want to use random numbers, we have to write another special
command at the very beginning of our program. This is the command:
import random
Recall that when we want to change rooms, we use the room_set command.
This command does two things:
1. It automatically destroys every object that was part of the previous room
2. It runs the Start Section of the new room, which should create all the
objects that are part of the new room
Because each room controls all of the objects that are part of that room,
each room can be used to create an independent section of our game.
Persistent Objects
Persistent objects do not belong to any room, and are never automatically
destroyed by the room_set command. Persistent objects can be useful,
but we have to be extremely careful to clean them up with the destroy
command when we don’t need them any more.
To turn an object persistent you can use the code self.persistent = True in
the Start tab of the class
83
ERRORS
TYPES OF ERRORS
Compile-time Errors
Sometimes, we make mistakes when we write code. We mean to type x, but
accidentally type y. We accidentally write If instead of if. These are called
programmer errors.
Compile-time errors are generally easy to find and fix, because they tend to
produce detailed error messages with line numbers and file names.
Runtime Errors
On the other hand, sometimes we’ve written our code in the correct way,
but it doesn’t do what we expect it to do. For example, we could expect
an object to move in one direction, but it ends up moving in the opposite
direction. These are called runtime errors, and are much harder to debug.
Runtime errors occur when code is written without mistakes, but does not
behave correctly.
The easiest way to find and fix runtime errors is to use the debug loop.
Many problems are caused by incorrect assumptions, so make sure to
always reread your code thoroughly to make sure it is doing what you think
it is doing.
DEBUGGING
Debugging
Debugging is when we find and fix problems in our programs. Debugging
is very important, because it’s very easy to make mistakes when we write
code. Even the very best programmers need to debug their code every
day.
random until our program behaves the way we want it to. If we’re persistent,
we can fix problems this way, but it’s not a very fast (or easy!) way to work.
84
A better way to debug is to use the debug loop. The debug loop is a simple
process that we repeat until our program works properly. This is what it
looks like:
1. First, we Run our code. Running our code will let us observe it,
which will show us whether there are any errors or other problems. If
everything is working properly, we can stop debugging.
2. Next, we Read our code. Using what we learned from running our
code, we look for specific commands that might be causing problems.
Sometimes, an error message will tell us exactly where to look by giving
us a line number and file name (for example, error in Player.Loop() on
line 4 means that the 4th line of code in the Player class’ loop tab is
wrong). When we don’t have an error message, we have to look for the
problem ourselves.
3. Lastly, we Change our code a tiny little bit. Once we think we’ve found
the source of a bug, we can change our code to either make it give us
more information (this is called tracing), or we can try to fix the problem.
It is important to change only a small amount of code in this step,
because whenever we change our code, we risk adding new bugs to
our program.
LOGGING IN
Logging onto PixelPAD
We will access PixelPAD using an internet browser such as Google Chrome,
Firefox, or Safari. This way you can play and create your game from any
computer! Go onto https://www.pixelpad.io
2. Your username and password will be provided for you! If you don’t have
a username, please speak to one of your facilitators!
3. Click on Learn, and select the Game Tutorial you’d like to work on. This
will create a blank project of a game with the tutorial open to get you
started.
85
CHALLENGE
QUESTIONS
CHAPTER 1
What is the difference between the Learn and MyPAD section on PixelPAD?
CHAPTER 2
GAME GUIDE I CHALLENGE QUESTIONS
If our game is running at 60 Frames per Second, how many frames is:
a) One Minute?
b) One Hour?
86
CHAPTER 3
For each of the following pieces of code, in what direction is the object
moving in?
self.y = self.y + 3
a) Up
b) Down
c) Right
self.x = self.x - 7
a) Up
b) Right
c) Left
self.x = self.x - 3
self.y = self.y + 4
a) Up and Right
b) Up and Left
CHAPTER 4
c) On a Sprite
87
CHAPTER 5
If you create a variable in the game script, where can you access it from?
CHAPTER 6
If you had to add one more feature to your game, what would it be?
88
89