成果展示
config.py
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600
FPS = 60 # frame per second
main.py
:
import pygame
import config
from game_manager import GameManager
from utils.draw_text import draw_text
pygame.init()
pygame.mixer.init() # initialize the sound
screen = pygame.display.set_mode((config.SCREEN_WIDTH, config.SCREEN_HEIGHT))
clock = pygame.time.Clock()
game_manager = GameManager(screen)
ico = pygame.image.load("static/images/maze.png").convert()
pygame.display.set_icon(ico)
pygame.display.set_caption("Car Maze")
pygame.mixer.music.load("static/sound/bgm.wav")
pygame.mixer.music.set_volume(0.1)
pygame.mixer.music.play(-1) # -1 means loop playback
running = True
success_time = -1 # -1 means no win, otherwise it means the current time
success_finished = False # Whether the game has been completed.
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# If the game has been completed and any key is pressed, exit the program
elif success_finished and event.type == pygame.KEYDOWN:
running = False
if success_finished:
screen.fill("black")
draw_text(screen, "Win!!!", 200, config.SCREEN_WIDTH / 2, config.SCREEN_HEIGHT / 2)
else:
if success_time > 0:
if pygame.time.get_ticks() - success_time > 3000:
has_next = game_manager.next_level()
if not has_next:
success_finished = True
continue
success_time = -1 # refresh the success timestamp
# draw the screen first and then draw the car
screen.fill("black")
if game_manager.update():
success_time = pygame.time.get_ticks()
pygame.display.flip()
clock.tick(config.FPS)
pygame.quit()
game_manager.py
import os.path
import pygame
from player import Player
from star import Star
from wall import Wall
from target import Target
from utils.collide import collided_rect, collided_circle
class GameManager:
def __init__(self, screen, level=1):
self.screen = screen
self.level = level
self.player = None
self.stars_cnt = 0
self.walls = pygame.sprite.Group()
self.stars = pygame.sprite.Group()
self.targets = pygame.sprite.Group()
self.eat_stars_sound = pygame.mixer.Sound("static/sound/eat_stars.wav")
self.eat_stars_sound.set_volume(0.3)
self.success_sound = pygame.mixer.Sound("static/sound/success.wav")
self.success_sound.set_volume(0.3)
self.load()
def load_walls(self, walls):
self.walls.empty()
for x, y, width, height in walls:
wall = Wall(x, y, width, height)
wall.add(self.walls)
def load_stars(self, stars):
self.stars.empty()
for x, y in stars:
star = Star(x, y)
self.stars_cnt += 1
star.add(self.stars)
def load_targets(self, targets):
self.targets.empty()
for x, y in targets:
target = Target(x, y)
target.add(self.targets)
def load_player(self, center_x, center_y, forward_angle):
if self.player:
self.player.kill()
self.player = Player(center_x, center_y, forward_angle)
def load(self):
with open("static/maps/level%d.txt" % self.level, 'r') as fin:
walls_count = int(fin.readline())
walls = []
for i in range(walls_count):
x, y, width, height = map(int, fin.readline().split())
walls.append((x, y, width, height))
self.load_walls(walls)
stars_count = int(fin.readline())
stars = []
for i in range(stars_count):
x, y = map(int, fin.readline().split())
stars.append((x, y))
self.load_stars(stars)
targets_count = int(fin.readline())
targets = []
for i in range(targets_count):
x, y = map(int, fin.readline().split())
targets.append((x, y))
self.load_targets(targets)
center_x, center_y, forward_angle = map(int, fin.readline().split())
self.load_player(center_x, center_y, forward_angle)
def next_level(self):
self.level += 1
if not os.path.isfile("static/maps/level%d.txt" % self.level):
return False
self.load()
return True
def check_collide(self): # Detecting Collisions
if pygame.sprite.spritecollide(self.player, self.walls, False, collided_rect):
self.player.crash()
if pygame.sprite.spritecollide(self.player, self.stars, True, collided_circle):
self.eat_stars_sound.play()
self.stars_cnt -= 1
if self.stars_cnt == 0:
if pygame.sprite.spritecollide(self.player, self.targets, True, collided_circle):
self.success_sound.play()
return True
return False
def update(self):
self.player.update()
self.stars.update()
self.stars.draw(self.screen)
self.targets.update()
self.targets.draw(self.screen)
success = self.check_collide()
self.screen.blit(self.player.image, self.player.rect) # put the image in the rect
self.walls.update()
self.walls.draw(self.screen)
return success
player.py
import pygame
import config
import math
class Player(pygame.sprite.Sprite):
def __init__(self, center_x, center_y, forward_angle):
super().__init__()
self.width = 100
self.height = 50
self.forward_angle = forward_angle # the angle in game coordinate
# load the car image
self.image_source = pygame.image.load("static/images/car.png").convert()
# image represent the stuff we want to draw
self.image = pygame.transform.scale(self.image_source, (self.width, self.height))
self.image = pygame.transform.rotate(self.image, -self.forward_angle)
self.image.set_colorkey("black")
# our image can be deployed in a rectangle
# the coordinate of the rect
self.rect = self.image.get_rect()
self.rect.center = (center_x, center_y)
self.last_time = pygame.time.get_ticks() # get the timestamp of now in milliseconds
self.delta_time = 0 # the time interval between two adjacent frames
self.move_velocity_limit = 300
self.move_velocity = 0 # current speed
self.move_acc = 600 # increase speed by 600 per second
self.friction = 0.9 # friction
self.rotate_velocity_limit = 140 # Angular velocity limit
self.rotate_velocity = 0
self.crash_sound = pygame.mixer.Sound("static/sound/crash.mp3")
self.crash_sound.set_volume(0.1)
self.move_sound = pygame.mixer.Sound("static/sound/move.mp3")
self.move_sound.set_volume(0.3)
self.move_voice_channel = pygame.mixer.Channel(7)
def update_delta_time(self):
cur_time = pygame.time.get_ticks()
self.delta_time = (cur_time - self.last_time) / 1000 # convert the milliseconds to seconds
self.last_time = cur_time
def input(self):
key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_w]:
self.move_velocity += self.move_acc * self.delta_time
self.move_velocity = min(self.move_velocity_limit, self.move_velocity)
if not self.move_voice_channel.get_busy():
self.move_voice_channel.play(self.move_sound)
elif key_pressed[pygame.K_s]:
self.move_velocity -= self.move_acc * self.delta_time
self.move_velocity = max(-self.move_velocity_limit, self.move_velocity)
if not self.move_voice_channel.get_busy():
self.move_voice_channel.play(self.move_sound)
else:
self.move_velocity = int(self.move_velocity * self.friction)
if self.move_voice_channel.get_busy():
self.move_voice_channel.stop()
sign = 1
if self.move_velocity < 0:
sign = -1
if key_pressed[pygame.K_d]:
self.rotate_velocity = self.rotate_velocity_limit * sign
elif key_pressed[pygame.K_a]:
self.rotate_velocity = -self.rotate_velocity_limit * sign
else:
self.rotate_velocity = 0
def rotate(self, direction=1):
self.forward_angle += self.rotate_velocity * self.delta_time * direction
self.image = pygame.transform.scale(self.image_source, (self.width, self.height))
self.image = pygame.transform.rotate(self.image, -self.forward_angle)
self.image.set_colorkey("black")
center = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = center
def move(self, direction=1):
if direction == 1 and abs(self.move_velocity) > 50:
self.rotate(direction) # only when car's velocity greater than 50, there is a rotate velocity.
vx = self.move_velocity * math.cos(math.pi * self.forward_angle / 180) * direction
vy = self.move_velocity * math.sin(math.pi * self.forward_angle / 180) * direction
self.rect.x += vx * self.delta_time
self.rect.y += vy * self.delta_time
if direction == -1 and abs(self.move_velocity) > 50:
self.rotate(direction)
def crash(self):
self.crash_sound.play()
self.move(-1)
if self.move_velocity >=0 :
self.move_velocity = min(-100, -self.move_velocity)
else:
self.move_velocity = max(100, -self.move_velocity)
self.rotate_velocity *= -1
def update(self):
self.update_delta_time()
self.input()
self.move()
wall.py
import pygame
class Wall(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill((106, 182, 209))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
target.py
import pygame
class Target(pygame.sprite.Sprite):
def __init__(self, center_x, center_y):
super().__init__()
self.image_source = pygame.image.load("static/images/target.png").convert()
self.image = pygame.transform.scale(self.image_source, (100, 100))
self.image.set_colorkey("black")
# our image can be deployed in a rectangle
# the coordinate of the rect
self.rect = self.image.get_rect()
self.rect.center = (center_x, center_y)
self.scale = 1
self.delta_scale = 0.01
def update(self):
self.scale += self.delta_scale
if self.scale > 1.1 or self.scale < 0.9:
self.delta_scale = -self.delta_scale
self.image = pygame.transform.scale(self.image_source, (100 * self.scale, 100 * self.scale))
center = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = center
star.py
import pygame
class Star(pygame.sprite.Sprite):
def __init__(self, center_x, center_y):
super().__init__()
self.image_source = pygame.image.load("static/images/star.png").convert()
self.image = pygame.transform.scale(self.image_source, (50, 50))
self.image.set_colorkey("black")
# our image can be deployed in a rectangle
# the coordinate of the rect
self.rect = self.image.get_rect()
self.rect.center = (center_x, center_y)
self.scale = 1
self.delta_scale = 0.01
def update(self):
self.scale += self.delta_scale
if self.scale > 1.1 or self.scale < 0.9:
self.delta_scale = -self.delta_scale
self.image = pygame.transform.scale(self.image_source, (50 * self.scale, 50 * self.scale))
center = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = center
draw_text.py
import pygame
def draw_text(screen, text, size, x, y):
font = pygame.font.SysFont(pygame.font.get_default_font(), size)
image = font.render(text, True, "white")
rect = image.get_rect()
rect.center = (x, y)
screen.blit(image, rect)
collide.py
import math
import pygame
def collided_rect(a, b):
p = []
for i, j in [(1, -1), (1, 1), (-1, 1), (-1, -1)]:
t = pygame.Vector2(i * a.width / 2 * 0.8, j * a.height / 2 * 0.8).rotate(a.forward_angle)
p.append(t + a.rect.center)
for i in range(4):
x = p[i]
y = p[(i + 1) % 4]
if b.rect.clipline(x, y):
return True
p.clear()
for i, j in [(1, -1), (1, 1), (-1, 1), (-1, -1)]:
t = pygame.Vector2(i * a.width / 2, j * a.height / 2 * 0.2).rotate(a.forward_angle)
p.append(t + a.rect.center)
for i in range(4):
x = p[i]
y = p[(i + 1) % 4]
if b.rect.clipline(x, y):
return True
return False
def collided_circle(a, b):
x1, y1 = a.rect.center
x2, y2 = b.rect.center
dx, dy = x2 - x1, y2 - y1
if math.sqrt(dx * dx + dy * dy) < 50:
return True
return False