Learning Python with a Nostalgic Twist: Building the ‘Satellite Game’ from Jak 3
This is a non data-related article. However, learning Python might be helpful to master for future data projects, especially when it comes to Data Science. Python has great data related libraries such as Pandas, Seaborn and Matplotlib. They can be used in tools such as Power BI, and I already noticed the advantages of knowing a bit of Data Science for Business Intelligence projects.
I wanted to learn Python with a fun project that no one ever did. It’s a game from my childhood. Here it is.
Table of Contents
The “Satellite Game”
This project comes from a mini-game in a PlayStation 2 game called “Jak 3”. This mini-game is called “Satellite Game” and you can find videos in YouTube if you search this: “Jak 3 Satellite Game”. The game is quite simple: at the start, there are 4 blue circles swinging from the center of screen to the 4 edges. Shapes are falling from the edges to the center of the screen:
- Green triangles from the top
- Pink squares from left
- Red circles from right
- Blue crosses from the bottom
The player scores points whenever he clicks on the right controller button when the corresponding shapes overlaps with the circles. When the player hits the shape, it disappears. There are some rules for scoring points:
- 1 shape at a time = 1 point
- 2 shapes at a time = 5 points
- 3 shapes at a time = 10 points
When the player clicks on a controller button by mistake, meaning when there is no corresponding shape overlapping a circle, it increases by 1 the number of allowed “Misses”. When a shape reaches the center of the screen and the player couldn’t hit it in time, it counts as a miss too. The goal on the left of this screenshot is simply the number of points to reach to win the game. The game gets harder through time; more shapes are falling, and faster.
Here is how it looked, in 2004!
Organizing my code with GitHub
The game I coded is available on GitHub : https://github.com/PratsNathan/Jak-3-Satellite-Game.
I’ll go through the different commits I created. I didn’t code the game in 3 hours… I just reorganized my work with GitHub afterwards.
Moving Circles
The first step is to draw 4 circles and to swing them from the middle of the screen to the 4 edges.
Draw Circles
To draw circles on a screen, I used a Python library called Pygame. Its documentation is here : https://www.pygame.org/docs/.
First, we have to define what a circle is. I created a class to store this object.
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
From what I understood, a class stores a kind of object. I considered that a circle is an object. Later on, we’ll see that I created another class for falling shapes. You define what the object is in the __init__ definition. I gave the object Circle the following variables:
- X
- Y
- Radius
- Thick
- Color
- Speed
- Position
In Pygame, a circle is actually a rectangle object: https://www.pygame.org/docs/ref/rect.html
- Rect(left, top, width, height)
That’s why I have this line: self.rect = pygame.Rect(0, 0, 2*radius, 2*radius). The width and height of the circle is 2 times its radius. I define the position of the circles using X and Y. The Rect object has several virtual attributes which can be used to move and align the Rect. I used centerx and centery.
Regarding the draw definition, I followed the Pygame documentation on how to draw a circle :
- pygame.draw.circle(surface, color, center, radius, width=0)
So I added a draw definition in the circle class:
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
To draw the 4 circles perfectly in the middle of screen, I created the list of circles in the main() definition. I added and subtracted 2 times the circle radius on X and Y axis to make this happen. We’ll see later that when these circles will swing from the center to the edges of the screen, this allow me to have a synchronized swing. Going back to the code where I create the circles, it’s important to understand how Pygame coordinates work. The following image sums it up.
Swing Circles
Now that we have our 4 circles, we have to make them swing from the center to the edges. I added a constant called SPEED and modified the Circle class, then the variable speed in __init__ and added a definition called swing. I also added an attribute called direction and directionX. If the speed is greater than 0, the direction = “Right” and the direction(Y) = “Up”, otherwise, the directions are left and down. The swing definition is ugly but I didn’t find better alternative. It does the job though. *
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 420 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
The principle is simple and we’ll take the example for the circle located at “Top” at the start. As we want the circle to move to the top at start, we write “self.rect.y -= self.speed”. Therefore, going up means Y decreases. When the circle reaches the top of the screen, we want it to change direction and go back to its initial position. That’s why we have:
The speed is going to be positive (going down), so we set the direction to down. Now that the circle is going down, we have to change its direction again when it reaches its initial position. The rest of the swing definition is simply the same principle applied for the other 3 circles.
Putting this all together, this code draws 4 circles and swing them perfectly from their initial position to the 4 edges of the screen:
# ---------- Packages and Inits ----------
import pygame
from pygame.locals import *
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FPS = 60
CIRCLE_RADIUS = 70
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN)
clock = pygame.time.Clock()
SPEED = 2
# ---------- Resources (Colors) ----------
BLACK = (000,000,000)
WHITE = (255,255,255)
# ---------- Classes ----------
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 420 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
# ---------- Main ----------
def main():
# Settings
screen_rect = screen.get_rect()
game_over = False
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.fill(WHITE)
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
pygame.display.update()
clock.tick(FPS)
main()
Dropping Enemies
Now that we have our 4 circles swinging properly, the next step is to drop the shapes I mentioned at the beginning:
- Green triangles from the top
- Pink squares from left
- Red circles from right
- Blue crosses from the bottom
These shapes have to drop from the 4 different edges of the screen, with the following rules to begin with:
- Once an enemy reaches the middle, it disappears
- Enemies are created every 400 ms
- Enemies position must be random
- On the screen, there should be a maximum of 4 enemies
Later, we’ll modify these variables based on the game difficulty. As the game gets harder, there will be more enemies at the same time and they’ll drop faster.
For this I added:
- a constant called ENEMY_SIZE
- a class called Enemies
- a for loop in the main() definition
- images of the 4 different shapes
Putting this all together, the following code will show 4 circles swinging from the middle to the edges, and some shapes falling from the 4 edges to the middle of the screen.
# ---------- Packages and Inits ----------
import pygame, random, math, sys, os, pickle, time
from tkinter import *
from pygame.locals import *
from random import choice
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FPS = 60
CIRCLE_RADIUS = 70
ENEMY_SIZE = 40
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN)
clock = pygame.time.Clock()
SPEED = 2
# ---------- Resources (Images & Sounds) ----------
if getattr(sys, 'frozen', False):
current_path = os.path.dirname(sys.executable)
else:
current_path = os.path.dirname(os.path.realpath(__file__))
# PS2 images
button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert()
button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert()
button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert()
button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert()
# ---------- Resources (Colors) ----------
BLACK = (000,000,000)
WHITE = (255,255,255)
# ---------- Classes ----------
class Enemies:
def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"):
self.rect = button_circle_red_tiny.get_rect()
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw( self, screen):
if self.position == "right":
screen.blit(button_circle_red_tiny, self.rect )
elif self.position == "left":
screen.blit(button_square_pink_tiny, self.rect )
elif self.position == "top":
screen.blit(button_triangle_green_tiny, self.rect )
else:
screen.blit(button_cross_blue_tiny, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 420
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH - 420
self.position= "right"
self.calcDirection()
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 420 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
# ---------- Main ----------
def main():
# Settings
screen_rect = screen.get_rect()
game_over = False
interval = 400
simultaneity = 4
pygame.time.set_timer(USEREVENT+1, interval)
# We create an empty list of enemies, as we want them to drop randomly
all_enemies = []
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.fill(WHITE)
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
# Enemies
for e in all_enemies:
e.draw(screen) # Place enemies on the screen
e.update() # Move enemies from the edges of the screen towards the center
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated
all_enemies.remove(e)
# Scoring and lifepoints systems
for event in pygame.event.get():
if event.type == USEREVENT+1 and len(all_enemies) < simultaneity:
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = BLACK, position = "top"))
appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top
for e in appended_enemies:
e.randomise()
pygame.display.update()
clock.tick(FPS)
main()
Scoring System and Lives
# ---------- Packages and Inits ----------
import pygame, random, math, sys, os, pickle, time
from tkinter import *
from pygame.locals import *
from random import choice
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FPS = 60
CIRCLE_RADIUS = 70
ENEMY_SIZE = 40
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN)
clock = pygame.time.Clock()
myFont = pygame.font.SysFont("monospace", 25 , bold = True)
SPEED = 2
# ---------- Resources (Images & Sounds) ----------
if getattr(sys, 'frozen', False):
current_path = os.path.dirname(sys.executable)
else:
current_path = os.path.dirname(os.path.realpath(__file__))
# PS2 images
button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert()
button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert()
button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert()
button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert()
# ---------- Resources (Colors) ----------
RED = (255,000,000)
BLUE = (000,000,255)
YELLOW = (255,255,000)
GREEN = (000,128,000)
BLACK = (000,000,000)
WHITE = (255,255,255)
LIGHT_GREEN = (144,238,144)
LIGHT_RED = (255,0,128)
# ---------- Classes ----------
class Enemies:
def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"):
self.rect = button_circle_red_tiny.get_rect()
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw( self, screen):
if self.position == "right":
screen.blit(button_circle_red_tiny, self.rect )
elif self.position == "left":
screen.blit(button_square_pink_tiny, self.rect )
elif self.position == "top":
screen.blit(button_triangle_green_tiny, self.rect )
else:
screen.blit(button_cross_blue_tiny, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 420
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH - 420
self.position= "right"
self.calcDirection()
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 420 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
def isCollision(self, enemyX, enemyY, circleX, circleY):
distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2)))
if distance < (CIRCLE_RADIUS+ENEMY_SIZE//2):
return True
else:
return False
# ---------- Main ----------
def main():
# Settings
screen_rect = screen.get_rect()
game_over = False
score = 0
lifes = 5
interval = 400
simultaneity = 4
pygame.time.set_timer(USEREVENT+1, interval)
# We create an empty list of enemies, as we want them to drop randomly
all_enemies = []
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.fill(WHITE)
# Variables
interval = 400
simultaneity = 4
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
# Enemies
for e in all_enemies:
e.draw(screen) # Place enemies on the screen
e.update() # Move enemies from the edges of the screen towards the center
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated
lifes -=1
all_enemies.remove(e)
# Scoring and lifepoints systems
for event in pygame.event.get():
# Cheats
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_g:
lifes += 5
if event.key == pygame.K_t:
score -= 100
if event.type == USEREVENT+1 and len(all_enemies) < simultaneity:
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top"))
appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top
for e in appended_enemies:
e.randomise()
for c in all_circles:
if event.type == pygame.KEYDOWN:
# LEFT
if event.key == pygame.K_LEFT and c.position == "left":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# RIGHT
if event.key == pygame.K_RIGHT and c.position == "right":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# TOP
if event.key == pygame.K_UP and c.position == "top":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# BOT
if event.key == pygame.K_DOWN and c.position == "bot":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# Game Over condition
if lifes < 0:
game_over = True
# Score / Lifes
place_text = SCREEN_WIDTH-250
print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, BLACK)
screen.blit(print_lifes, (place_text, 10))
print_score = myFont.render("Score:" + str(round(score)), 1, BLACK)
screen.blit(print_score, (place_text, 50))
pygame.display.update()
clock.tick(FPS)
main()
Levels, Menus and Sounds
# ---------- Packages and Inits ----------
import pygame, random, math, sys, os, pickle, time
from tkinter import *
from pygame.locals import *
from random import choice
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FPS = 60
SPEED = 2
CIRCLE_RADIUS = 70
ENEMY_SIZE = 40
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN)
clock = pygame.time.Clock()
pause = False
over = False
myFont = pygame.font.SysFont("monospace", 25 , bold = True)
# ---------- Resources (Images & Sounds) ----------
if getattr(sys, 'frozen', False):
current_path = os.path.dirname(sys.executable)
else:
current_path = os.path.dirname(os.path.realpath(__file__))
# Background Images
background_image_Start = pygame.image.load(os.path.join(current_path,"Start.jpg")).convert()
background_image_Pause = pygame.image.load(os.path.join(current_path,"Pause.jpg")).convert()
background_image_Game_Over = pygame.image.load(os.path.join(current_path,"Over.jpg")).convert()
background_image_In_Game = pygame.image.load(os.path.join(current_path,"InGame.png")).convert()
# Sounds
Re5HealingSoundEffect = pygame.mixer.Sound('Re5HealingSoundEffect.wav')
Halo3Deaths1 = pygame.mixer.Sound('Halo3Deaths1.wav')
# PS2 images
button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert()
button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert()
button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert()
button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert()
# ---------- Resources (Colors) ----------
RED = (255,000,000)
BLUE = (000,000,255)
YELLOW = (255,255,000)
GREEN = (000,128,000)
BLACK = (000,000,000)
WHITE = (255,255,255)
LIGHT_GREEN = (144,238,144)
LIGHT_RED = (255,0,128)
# ---------- Classes ----------
class Enemies:
def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"):
self.rect = button_circle_red_tiny.get_rect()
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw( self, screen):
if self.position == "right":
screen.blit(button_circle_red_tiny, self.rect )
elif self.position == "left":
screen.blit(button_square_pink_tiny, self.rect )
elif self.position == "top":
screen.blit(button_triangle_green_tiny, self.rect )
else:
screen.blit(button_cross_blue_tiny, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 420
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH - 420
self.position= "right"
self.calcDirection()
def set_speed( self, difficulty):
if difficulty == 1 : self.speed = 2
elif difficulty == 2 : self.speed = 2.25
elif difficulty == 3 : self.speed = 2.5
elif difficulty == 4 : self.speed = 2.75
elif difficulty == 5 : self.speed = 3
elif difficulty == 6 : self.speed = 3.25
return self.speed
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 420 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
def isCollision(self, enemyX, enemyY, circleX, circleY):
distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2)))
if distance < (CIRCLE_RADIUS+ENEMY_SIZE//2):
return True
else:
return False
# ---------- Other Functions ----------
""" Difficulty sets 4 variables :
enemy speed (in enemy class)
interval between enemy drops
maximum number of enemies drop in a row from the same lane
simultaneity (drops at the same time from different lanes)"""
def set_difficulty(score):
if score < 25 : difficulty = 1
elif score < 50 : difficulty = 2
elif score < 100: difficulty = 3
elif score < 250: difficulty = 4
elif score < 500: difficulty = 5
else: difficulty = 6
return difficulty
def set_interval(difficulty):
if difficulty == 1 : interval = 400
elif difficulty == 2 : interval = 250
elif difficulty == 3 : interval = 200
elif difficulty == 4 : interval = 150
elif difficulty == 5 : interval = 100
elif difficulty == 6 : interval = 75
return interval
def set_simultaneity(difficulty):
if difficulty == 1 : simultaneity = 3
elif difficulty == 2 : simultaneity = 4
elif difficulty == 3 : simultaneity = 5
else : simultaneity = 6
return simultaneity
# ---------- Welcome screen / Pause / Game Over ----------
def text_objects(text, font):
textSurface = font.render(text, True, WHITE)
return textSurface, textSurface.get_rect()
def button(msg,x,y,w,h,ic,ac,action=None):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if x+w > mouse[0] > x and y+h > mouse[1] > y:
pygame.draw.rect(screen, ac,(x,y,w,h))
if click[0] == 1 and action != None:
action()
else:
pygame.draw.rect(screen, ic,(x,y,w,h))
smallText = pygame.font.SysFont("comicsansms",20)
textSurf, textRect = text_objects(msg, smallText)
textRect.center = ( (x+(w/2)), (y+(h/2)) )
screen.blit(textSurf, textRect)
def game_intro():
intro = True
# Music
pygame.mixer.music.load('in_game.ogg')
pygame.mixer.music.play()
over == False
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
screen.blit(background_image_Start, [0, 0])
button("Go !",700,700,100,50,GREEN,LIGHT_GREEN,main)
button("Quit",1200,700,100,50,RED,LIGHT_RED,quit_game)
print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE)
screen.blit(print_myself, (10, 1050))
pygame.display.update()
clock.tick(15)
def unpause():
global pause
pygame.mixer.music.unpause()
pause = False
def paused():
pygame.mixer.music.pause()
screen.blit(background_image_Pause, [0, 0])
largeText = pygame.font.SysFont("comicsansms",50)
TextSurf, TextRect = text_objects("Paused", largeText)
TextRect.center = (380,200)
screen.blit(TextSurf, TextRect)
while pause:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
unpause()
if event.key == pygame.K_a:
pygame.quit()
button("Continue",150,300,100,50,GREEN,LIGHT_GREEN,unpause)
button("Quit",500,300,100,50,RED,LIGHT_RED,quit_game)
pygame.display.update()
clock.tick(15)
def quit_game():
pygame.quit()
# ---------- Main ----------
def main():
global pause
# Settings
screen_rect = screen.get_rect()
game_over = False
score = 0
lifes = 5
difficulty = set_difficulty(score)
interval = set_interval(difficulty)
pygame.time.set_timer(USEREVENT+1, interval)
# We create an empty list of enemies, as we want them to drop randomly
all_enemies = []
# Music
music_end = pygame.USEREVENT+2
pygame.mixer.music.set_endevent(music_end)
pygame.mixer.music.load('in_game.ogg')
pygame.mixer.music.play()
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.blit(background_image_In_Game, [0, 0])
# Variables
difficulty = set_difficulty(score)
interval = set_interval(difficulty)
simultaneity = set_simultaneity(difficulty)
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
# Enemies
for e in all_enemies:
e.draw(screen) # Place enemies on the screen
e.set_speed(difficulty) # Set speed difficulty for enemies
e.update() # Move enemies from the edges of the screen towards the center
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated
lifes -=1
all_enemies.remove(e)
# Scoring and lifepoints systems
for event in pygame.event.get():
# Music
if event.type == music_end:
pygame.mixer.music.load('Halo3OneFinalEffort.mp3')
pygame.mixer.music.play()
# Allow Pause and Quit
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
pause = True
paused()
if event.key == pygame.K_a:
quit_game()
# Cheats
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_g:
pygame.mixer.Sound.play(Re5HealingSoundEffect)
lifes += 5
if event.key == pygame.K_t:
score -= 100
if event.type == USEREVENT+1 and len(all_enemies) < simultaneity:
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top"))
appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top
for e in appended_enemies:
e.randomise()
for c in all_circles:
if event.type == pygame.KEYDOWN:
# LEFT
if event.key == pygame.K_LEFT and c.position == "left":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# RIGHT
if event.key == pygame.K_RIGHT and c.position == "right":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# TOP
if event.key == pygame.K_UP and c.position == "top":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# BOT
if event.key == pygame.K_DOWN and c.position == "bot":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# Game Over condition
if lifes < 0:
pygame.mixer.Sound.play(Halo3Deaths1)
you_lose()
next_level = 25
if difficulty == 2 : next_level = 50
elif difficulty == 3 : next_level = 100
elif difficulty == 4 : next_level = 250
elif difficulty == 5 : next_level = 500
elif difficulty == 6 : next_level = 1000
# Score / Lifes / Number of Enemies / Next Level
place_text = SCREEN_WIDTH-250
print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, BLACK)
screen.blit(print_lifes, (place_text, 10))
print_score = myFont.render("Score:" + str(round(score)), 1, BLACK)
screen.blit(print_score, (place_text, 50))
print_next_level = myFont.render("Next Level:" + str(round(next_level)), 1, BLACK)
screen.blit(print_next_level, (place_text, 90))
pygame.display.update()
clock.tick(FPS)
def you_lose():
global over
over = True
pygame.mixer.music.load(os.path.join(current_path,"Re5DreamyLoops.mp3"))
pygame.mixer.music.play(-1)
while over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
screen.blit(background_image_Game_Over, [0, 0])
y = SCREEN_HEIGHT-100
button("Retry !",1200,y,100,50,GREEN,LIGHT_GREEN,main)
button("Menu",1350,y,100,50,BLUE,LIGHT_GREEN,game_intro)
button("Quit",1500,y,100,50,RED,LIGHT_RED,quit_game)
print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE)
screen.blit(print_myself, (10, 1050))
pygame.display.update()
clock.tick(15)
game_intro()
Scores Leaderboard
This is the last step of this project. I wanted to add the ability to score your scores. Now, when you launch the game, you’re prompted your name. There is a scores file that will store the score of each player.
With this being done, the code is now complete. I ended up with this python file to run the entire game.
# ---------- Packages and Inits ----------
import pygame, random, math, sys, os, pickle, time
from tkinter import *
from pygame.locals import *
from random import choice
pygame.init()
# ---------- Users & Scores ----------
"""
This section allows me to :
Prompt for a user name
Display his best score
Save his score if he beats his current best score
"""
scores_file = "scores"
def get_scores():
if os.path.exists(scores_file):
file_scores = open(scores_file, "rb")
my_depickler = pickle.Unpickler(file_scores)
scores = my_depickler.load()
file_scores.close()
else:
scores = {}
return scores
def save_scores(scores):
file_scores = open(scores_file, "wb")
my_pickler = pickle.Pickler(file_scores)
my_pickler.dump(scores)
file_scores.close()
def get_user():
my_user = input("Write your Name: ")
my_user = my_user.capitalize()
if not my_user.isalnum() or len(my_user)<4:
print("This name is invalid.")
return get_user()
else:
return my_user
scores = get_scores()
utilisateur = get_user()
if utilisateur not in scores.keys():
scores[utilisateur] = 0
# ---------- Settings ----------
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FPS = 60
SPEED = 2
CIRCLE_RADIUS = 70
ENEMY_SIZE = 40
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN)
clock = pygame.time.Clock()
pause = False
over = False
myFont = pygame.font.SysFont("monospace", 25 , bold = True)
last_score = 0
# ---------- Resources (Images & Sounds) ----------
if getattr(sys, 'frozen', False):
current_path = os.path.dirname(sys.executable)
else:
current_path = os.path.dirname(os.path.realpath(__file__))
# Background Images
background_image_Start = pygame.image.load(os.path.join(current_path,"Start.jpg")).convert()
background_image_Pause = pygame.image.load(os.path.join(current_path,"Pause.jpg")).convert()
background_image_Game_Over = pygame.image.load(os.path.join(current_path,"Over.jpg")).convert()
background_image_In_Game = pygame.image.load(os.path.join(current_path,"InGame.png")).convert()
# Sounds
Re5HealingSoundEffect = pygame.mixer.Sound('Re5HealingSoundEffect.wav')
Halo3Deaths1 = pygame.mixer.Sound('Halo3Deaths1.wav')
# PS2 images
button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert()
button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert()
button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert()
button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert()
# ---------- Resources (Colors) ----------
RED = (255,000,000)
BLUE = (000,000,255)
YELLOW = (255,255,000)
GREEN = (000,128,000)
BLACK = (000,000,000)
WHITE = (255,255,255)
LIGHT_GREEN = (144,238,144)
LIGHT_RED = (255,0,128)
# ---------- Classes ----------
class Enemies:
def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"):
self.rect = button_circle_red_tiny.get_rect()
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw( self, screen):
if self.position == "right":
screen.blit(button_circle_red_tiny, self.rect )
elif self.position == "left":
screen.blit(button_square_pink_tiny, self.rect )
elif self.position == "top":
screen.blit(button_triangle_green_tiny, self.rect )
else:
screen.blit(button_cross_blue_tiny, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 420
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH - 420
self.position= "right"
self.calcDirection()
def set_speed( self, difficulty):
if difficulty == 1 : self.speed = 2
elif difficulty == 2 : self.speed = 2.25
elif difficulty == 3 : self.speed = 2.5
elif difficulty == 4 : self.speed = 2.75
elif difficulty == 5 : self.speed = 3
elif difficulty == 6 : self.speed = 3.25
return self.speed
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 420 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
def isCollision(self, enemyX, enemyY, circleX, circleY):
distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2)))
if distance < (CIRCLE_RADIUS+ENEMY_SIZE//2):
return True
else:
return False
# ---------- Other Functions ----------
""" Difficulty sets 4 variables :
enemy speed (in enemy class)
interval between enemy drops
maximum number of enemies drop in a row from the same lane
simultaneity (drops at the same time from different lanes)"""
def set_difficulty(score):
if score < 25 : difficulty = 1
elif score < 50 : difficulty = 2
elif score < 100: difficulty = 3
elif score < 250: difficulty = 4
elif score < 500: difficulty = 5
else: difficulty = 6
return difficulty
def set_interval(difficulty):
if difficulty == 1 : interval = 400
elif difficulty == 2 : interval = 250
elif difficulty == 3 : interval = 200
elif difficulty == 4 : interval = 150
elif difficulty == 5 : interval = 100
elif difficulty == 6 : interval = 75
return interval
def set_simultaneity(difficulty):
if difficulty == 1 : simultaneity = 3
elif difficulty == 2 : simultaneity = 4
elif difficulty == 3 : simultaneity = 5
else : simultaneity = 6
return simultaneity
# ---------- Welcome screen / Pause / Game Over ----------
def text_objects(text, font):
textSurface = font.render(text, True, WHITE)
return textSurface, textSurface.get_rect()
def button(msg,x,y,w,h,ic,ac,action=None):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if x+w > mouse[0] > x and y+h > mouse[1] > y:
pygame.draw.rect(screen, ac,(x,y,w,h))
if click[0] == 1 and action != None:
action()
else:
pygame.draw.rect(screen, ic,(x,y,w,h))
smallText = pygame.font.SysFont("comicsansms",20)
textSurf, textRect = text_objects(msg, smallText)
textRect.center = ( (x+(w/2)), (y+(h/2)) )
screen.blit(textSurf, textRect)
def game_intro():
intro = True
# Music
pygame.mixer.music.load('in_game.ogg')
pygame.mixer.music.play()
over == False
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
screen.blit(background_image_Start, [0, 0])
button("Go !",700,700,100,50,GREEN,LIGHT_GREEN,main)
button("Quit",1200,700,100,50,RED,LIGHT_RED,quit_game)
# Show the user best score
print_score = myFont.render("Your best score : {0}".format(round(scores[utilisateur])), 1, WHITE)
screen.blit(print_score, (10, 50))
print_user = myFont.render("Current Player : " + utilisateur, 1, WHITE)
screen.blit(print_user, (10, 10))
print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE)
screen.blit(print_myself, (10, 1050))
pygame.display.update()
clock.tick(15)
def unpause():
global pause
pygame.mixer.music.unpause()
pause = False
def paused():
pygame.mixer.music.pause()
screen.blit(background_image_Pause, [0, 0])
largeText = pygame.font.SysFont("comicsansms",50)
TextSurf, TextRect = text_objects("Paused", largeText)
TextRect.center = (380,200)
screen.blit(TextSurf, TextRect)
while pause:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
unpause()
if event.key == pygame.K_a:
pygame.quit()
button("Continue",150,300,100,50,GREEN,LIGHT_GREEN,unpause)
button("Quit",500,300,100,50,RED,LIGHT_RED,quit_game)
pygame.display.update()
clock.tick(15)
def quit_game():
pygame.quit()
# ---------- Main ----------
def main():
global pause
global last_score
# Settings
screen_rect = screen.get_rect()
game_over = False
score = 0
lifes = 5
difficulty = set_difficulty(score)
interval = set_interval(difficulty)
pygame.time.set_timer(USEREVENT+1, interval)
# We create an empty list of enemies, as we want them to drop randomly
all_enemies = []
# Music
music_end = pygame.USEREVENT+2
pygame.mixer.music.set_endevent(music_end)
pygame.mixer.music.load('in_game.ogg')
pygame.mixer.music.play()
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.blit(background_image_In_Game, [0, 0])
# Variables
difficulty = set_difficulty(score)
interval = set_interval(difficulty)
simultaneity = set_simultaneity(difficulty)
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
# Enemies
for e in all_enemies:
e.draw(screen) # Place enemies on the screen
e.set_speed(difficulty) # Set speed difficulty for enemies
e.update() # Move enemies from the edges of the screen towards the center
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated
lifes -=1
all_enemies.remove(e)
# Scoring and lifepoints systems
for event in pygame.event.get():
# Music
if event.type == music_end:
pygame.mixer.music.load('Halo3OneFinalEffort.mp3')
pygame.mixer.music.play()
# Allow Pause and Quit
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
pause = True
paused()
if event.key == pygame.K_a:
quit_game()
# Cheats
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_g:
pygame.mixer.Sound.play(Re5HealingSoundEffect)
lifes += 5
if event.key == pygame.K_t:
score -= 100
if event.type == USEREVENT+1 and len(all_enemies) < simultaneity:
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top"))
appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top
for e in appended_enemies:
e.randomise()
for c in all_circles:
if event.type == pygame.KEYDOWN:
# LEFT
if event.key == pygame.K_LEFT and c.position == "left":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# RIGHT
if event.key == pygame.K_RIGHT and c.position == "right":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# TOP
if event.key == pygame.K_UP and c.position == "top":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# BOT
if event.key == pygame.K_DOWN and c.position == "bot":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
all_enemies.remove(e)
# Game Over condition
if lifes < 0:
pygame.mixer.Sound.play(Halo3Deaths1)
if score > scores[utilisateur]: # Populate best score
scores[utilisateur] = score
last_score = score
save_scores(scores)
game_over = True
you_lose()
next_level = 25
if difficulty == 2 : next_level = 50
elif difficulty == 3 : next_level = 100
elif difficulty == 4 : next_level = 250
elif difficulty == 5 : next_level = 500
elif difficulty == 6 : next_level = 1000
# Score / Lifes / Number of Enemies / Next Level
place_text = SCREEN_WIDTH-250
print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, BLACK)
screen.blit(print_lifes, (place_text, 10))
print_score = myFont.render("Score:" + str(round(score)), 1, BLACK)
screen.blit(print_score, (place_text, 50))
print_next_level = myFont.render("Next Level:" + str(round(next_level)), 1, BLACK)
screen.blit(print_next_level, (place_text, 90))
pygame.display.update()
clock.tick(FPS)
def you_lose():
global over
over = True
pygame.mixer.music.load(os.path.join(current_path,"Re5DreamyLoops.mp3"))
pygame.mixer.music.play(-1)
while over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
screen.blit(background_image_Game_Over, [0, 0])
y = SCREEN_HEIGHT-100
button("Retry !",1200,y,100,50,GREEN,LIGHT_GREEN,main)
button("Menu",1350,y,100,50,BLUE,LIGHT_GREEN,game_intro)
button("Quit",1500,y,100,50,RED,LIGHT_RED,quit_game)
# Show the user best score
print_score = myFont.render("Your best score : {0}".format(round(scores[utilisateur])), 1, WHITE)
screen.blit(print_score, (10, 50))
print_user = myFont.render("Current Player : " + utilisateur, 1, WHITE)
screen.blit(print_user, (10, 10))
print_last_score = myFont.render("You scored : " + str(round(last_score)), 1, WHITE)
screen.blit(print_last_score, (10, 90))
print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE)
screen.blit(print_myself, (10, 1050))
pygame.display.update()
clock.tick(15)
game_intro()
Creating the Executable
Now that the game is working properly, I needed a way to share it. I used a Python library called cx_Freeze to build it, which turned out to work well.
In the end I didn’t bother adding all files in the script. I added them manually later on the right folders.
I created a Setup.py file.
from cx_Freeze import setup, Executable
includefiles = [
'scores',
'InGame.png',
'Pause.jpg',
'Over.jpg',
'Start.jpg',
'in_game.ogg'
]
includes = []
packages = ['pygame','random', 'math', 'sys','os','pickle']
target = Executable(
script="SatelliteGame.py",
icon="Icon.ico"
)
setup(
name = "Jak 3 - Satellite Game",
author = 'Nathan PRATS',
options = {'build_exe': {'includes':includes,'packages':packages,'include_files':includefiles}},
executables = [target]
)
Then I ran a Windows Command: python Setup.py bdist_msi and migrated all resources to the build folder (audio, pictures) as it was tedious to write them in the Setup.py file.
At this point, I have now a folder with the executable. It’s available in the GitHub project. The game works well on any PC with a 1080p screen.