game.py
import pygame
from settings import HEIGHT, WIDTH
pygame.font.init()
class Game:
def __init__(self, screen):
self.screen = screen
self.font = pygame.font.SysFont("impact", 70)
self.message_color = pygame.Color("darkorange")
# if player ran out of life or fell below the platform
def _game_lose(self, player):
player.game_over = True
message = self.font.render('You Lose...', True, self.message_color)
self.screen.blit(message,(WIDTH // 3 + 70, 70))
# if player reach the goal
def _game_win(self, player):
player.game_over = True
player.win = True
message = self.font.render('You Win!!', True, self.message_color)
self.screen.blit(message,(WIDTH // 3, 70))
# checks if the game is over or not, and if win or lose
def game_state(self, player, goal):
if player.life <= 0 or player.rect.y >= HEIGHT:
self._game_lose(player)
elif player.rect.colliderect(goal.rect):
self._game_win(player)
else:
None
def show_life(self, player):
life_size = 30
img_path = "assets/life/life.png"
life_image = pygame.image.load(img_path)
life_image = pygame.transform.scale(life_image, (life_size, life_size))
# life_rect = life_image.get_rect(topleft = pos)
indent = 0
for life in range(player.life):
indent += life_size
self.screen.blit(life_image, (indent, life_size))
player.py
import pygame
from support import import_sprite
class Player(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self._import_character_assets()
self.frame_index = 0
self.animation_speed = 0.15
self.image = self.animations["idle"][self.frame_index]
self.rect = self.image.get_rect(topleft = pos)
self.mask = pygame.mask.from_surface(self.image)
# player movement
self.direction = pygame.math.Vector2(0, 0)
self.speed = 5
self.jump_move = -16
# player status
self.life = 5
self.game_over = False
self.win = False
self.status = "idle"
self.facing_right = True
self.on_ground = False
self.on_ceiling = False
self.on_left = False
self.on_right = False
# gets all the image needed for animating specific player action
def _import_character_assets(self):
character_path = "assets/player/"
self.animations = {
"idle": [],
"walk": [],
"jump": [],
"fall": [],
"lose": [],
"win": []
}
for animation in self.animations.keys():
full_path = character_path + animation
self.animations[animation] = import_sprite(full_path)
# animates the player actions
def _animate(self):
animation = self.animations[self.status]
# loop over frame index
self.frame_index += self.animation_speed
if self.frame_index >= len(animation):
self.frame_index = 0
image = animation[int(self.frame_index)]
image = pygame.transform.scale(image, (35, 50))
if self.facing_right:
self.image = image
else:
flipped_image = pygame.transform.flip(image, True, False)
self.image = flipped_image
# set the rect
if self.on_ground and self.on_right:
self.rect = self.image.get_rect(bottomright = self.rect.bottomright)
elif self.on_ground and self.on_left:
self.rect = self.image.get_rect(bottomleft = self.rect.bottomleft)
elif self.on_ground:
self.rect = self.image.get_rect(midbottom = self.rect.midbottom)
elif self.on_ceiling and self.on_right:
self.rect = self.image.get_rect(topright = self.rect.topright)
elif self.on_ceiling and self.on_left:
self.rect = self.image.get_rect(bottomleft = self.rect.topleft)
elif self.on_ceiling:
self.rect = self.image.get_rect(midtop = self.rect.midtop)
# checks if the player is moving towards left or right or not moving
def _get_input(self, player_event):
if player_event != False:
if player_event == "right":
self.direction.x = 1
self.facing_right = True
elif player_event == "left":
self.direction.x = -1
self.facing_right = False
else:
self.direction.x = 0
def _jump(self):
self.direction.y = self.jump_move
# identifies player action
def _get_status(self):
if self.direction.y < 0:
self.status = "jump"
elif self.direction.y > 1:
self.status = "fall"
elif self.direction.x != 0:
self.status = "walk"
else:
self.status = "idle"
# update the player's state
def update(self, player_event):
self._get_status()
if self.life > 0 and not self.game_over:
if player_event == "space" and self.on_ground:
self._jump()
else:
self._get_input(player_event)
elif self.game_over and self.win:
self.direction.x = 0
self.status = "win"
else:
self.direction.x = 0
self.status = "lose"
self._animate()
world.py
import pygame
from settings import tile_size, WIDTH
from tile import Tile
from trap import Trap
from goal import Goal
from player import Player
from game import Game
class World:
def __init__(self, world_data, screen):
self.screen = screen
self.world_data = world_data
self._setup_world(world_data)
self.world_shift = 0
self.current_x = 0
self.gravity = 0.7
self.game = Game(self.screen)
# generates the world
def _setup_world(self, layout):
self.tiles = pygame.sprite.Group()
self.traps = pygame.sprite.Group()
self.player = pygame.sprite.GroupSingle()
self.goal = pygame.sprite.GroupSingle()
for row_index, row in enumerate(layout):
for col_index, cell in enumerate(row):
x, y = col_index * tile_size, row_index * tile_size
if cell == "X":
tile = Tile((x, y), tile_size)
self.tiles.add(tile)
elif cell == "t":
tile = Trap((x + (tile_size // 4), y + (tile_size // 4)), tile_size // 2)
self.traps.add(tile)
elif cell == "P":
player_sprite = Player((x, y))
self.player.add(player_sprite)
elif cell == "G":
goal_sprite = Goal((x, y), tile_size)
self.goal.add(goal_sprite)
# world scroll when the player is walking towards left/right
def _scroll_x(self):
player = self.player.sprite
player_x = player.rect.centerx
direction_x = player.direction.x
if player_x < WIDTH // 3 and direction_x < 0:
self.world_shift = 8
player.speed = 0
elif player_x > WIDTH - (WIDTH // 3) and direction_x > 0:
self.world_shift = -8
player.speed = 0
else:
self.world_shift = 0
player.speed = 3
# add gravity for player to fall
def _apply_gravity(self, player):
player.direction.y += self.gravity
player.rect.y += player.direction.y
# prevents player to pass through objects horizontally
def _horizontal_movement_collision(self):
player = self.player.sprite
player.rect.x += player.direction.x * player.speed
for sprite in self.tiles.sprites():
if sprite.rect.colliderect(player.rect):
# checks if moving towards left
if player.direction.x < 0:
player.rect.left = sprite.rect.right
player.on_left = True
self.current_x = player.rect.left
# checks if moving towards right
elif player.direction.x > 0:
player.rect.right = sprite.rect.left
player.on_right = True
self.current_x = player.rect.right
if player.on_left and (player.rect.left < self.current_x or player.direction.x >= 0):
player.on_left = False
if player.on_right and (player.rect.right > self.current_x or player.direction.x <= 0):
player.on_right = False
# prevents player to pass through objects vertically
def _vertical_movement_collision(self):
player = self.player.sprite
self._apply_gravity(player)
for sprite in self.tiles.sprites():
if sprite.rect.colliderect(player.rect):
# checks if moving towards bottom
if player.direction.y > 0:
player.rect.bottom = sprite.rect.top
player.direction.y = 0
player.on_ground = True
# checks if moving towards up
elif player.direction.y < 0:
player.rect.top = sprite.rect.bottom
player.direction.y = 0
player.on_ceiling = True
if player.on_ground and player.direction.y < 0 or player.direction.y > 1:
player.on_ground = False
if player.on_ceiling and player.direction.y > 0:
player.on_ceiling = False
# add consequences when player run through traps
def _handle_traps(self):
player = self.player.sprite
for sprite in self.traps.sprites():
if sprite.rect.colliderect(player.rect):
if player.direction.x < 0 or player.direction.y > 0:
player.rect.x += tile_size
elif player.direction.x > 0 or player.direction.y > 0:
player.rect.x -= tile_size
player.life -= 1
# updating the game world from all changes commited
def update(self, player_event):
# for tile
self.tiles.update(self.world_shift)
self.tiles.draw(self.screen)
# for trap
self.traps.update(self.world_shift)
self.traps.draw(self.screen)
# for goal
self.goal.update(self.world_shift)
self.goal.draw(self.screen)
self._scroll_x()
# for player
self._horizontal_movement_collision()
self._vertical_movement_collision()
self._handle_traps()
self.player.update(player_event)
self.game.show_life(self.player.sprite)
self.player.draw(self.screen)
self.game.game_state(self.player.sprite, self.goal.sprite)
settings.py
world_map = [
' ',
' ',
' t t ',
' X XXXXXXXXXs XX X ',
' tXXXt XX XX XXXX tt XX ',
' XX XX XXXXX ',
' Xt t t t X G ',
' XXXXXX XXXXs XXXXXXXXXXX XX tt t XXX',
' P XX X XX X X XXXt X XX XX XXX XXXXXXXXs XXXXXX ',
'XXXXXXX X X X X XXXXXXXXX XX XX XXX XX XX XXXXXXX X ',
]
tile_size = 50
WIDTH, HEIGHT = 1000, len(world_map) * tile_size
trap.py
import pygame
from support import import_sprite
class Trap(pygame.sprite.Sprite):
def __init__(self, pos, size):
super().__init__()
self.blade_img = import_sprite("assets/trap/blade")
self.frame_index = 0
self.animation_delay = 3
self.image = self.blade_img[self.frame_index]
self.image = pygame.transform.scale(self.image, (size, size))
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft = pos)
# adds the spinning effect to the Blade trap
def _animate(self):
sprites = self.blade_img
sprite_index = (self.frame_index // self.animation_delay) % len(sprites)
self.image = sprites[sprite_index]
self.frame_index += 1
self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))
self.mask = pygame.mask.from_surface(self.image)
if self.frame_index // self.animation_delay > len(sprites):
self.frame_index = 0
# update object position due to world scroll
def update(self, x_shift):
self._animate()
self.rect.x += x_shift
goal.py
import pygame
class Goal(pygame.sprite.Sprite):
def __init__(self, pos, size):
super().__init__()
img_path = 'assets/goal/gate.png'
self.image = pygame.image.load(img_path)
self.image = pygame.transform.scale(self.image, (size, size))
self.rect = self.image.get_rect(topleft = pos)
# update object position due to world scroll
def update(self, x_shift):
self.rect.x += x_shift
tile.py
import pygame
class Tile(pygame.sprite.Sprite):
def __init__(self, pos, size):
super().__init__()
img_path = 'assets/terrain/stone.jpg'
self.image = pygame.image.load(img_path)
self.image = pygame.transform.scale(self.image, (size, size))
self.rect = self.image.get_rect(topleft = pos)
# update object position due to world scroll
def update(self, x_shift):
self.rect.x += x_shift
main.py
import pygame, sys
from settings import *
from world import World
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Platformer")
class Platformer:
def __init__(self, screen, width, height):
self.screen = screen
self.clock = pygame.time.Clock()
self.player_event = False
self.bg_img = pygame.image.load('assets/terrain/bg.jpg')
self.bg_img = pygame.transform.scale(self.bg_img, (width, height))
def main(self):
world = World(world_map, self.screen)
while True:
self.screen.blit(self.bg_img, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.player_event = "left"
if event.key == pygame.K_RIGHT:
self.player_event = "right"
if event.key == pygame.K_SPACE:
self.player_event = "space"
elif event.type == pygame.KEYUP:
self.player_event = False
world.update(self.player_event)
pygame.display.update()
self.clock.tick(60)
if __name__ == "__main__":
play = Platformer(screen, WIDTH, HEIGHT)
play.main()
support.py
from os import walk
import pygame
def import_sprite(path):
surface_list = []
for _, __, img_file in walk(path):
for image in img_file:
full_path = f"{path}/{image}"
img_surface = pygame.image.load(full_path).convert_alpha()
surface_list.append(img_surface)
return surface_list