Python 游戏开发

本笔记包含三个使用 pygame 开发的2D游戏项目:赛车游戏、打飞船游戏和俄罗斯方块游戏。

相关笔记: python-GUI-tkinter


赛车2D游戏开发

import io,sys
#导入 Python 的两个内置模块
#io:负责"输入输出"(input/output),也就是读写文件、控制台输出等。
#sys:访问与 Python 解释器相关的系统参数,比如输入输出流(stdin、stdout)、命令行参数等。
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
#sys.stdout这是 Python 的"标准输出流",就是你平时用 print() 输出的目标。输出的结果会在控制台显示。
#但 sys.stdout 其实也可以变成一个"字节流",这就是 sys.stdout.buffer,它处理的是数据的二进制格式,不涉及直接的字符编码。
#io.TextIOWrapper 就是将这个"字节流"转换回"文本流",而且让它知道要使用 UTF-8 编码,这样就能正确处理中文字符了。

import tkinter as tk
#tkinter 是 Python 标准库中的一个图形用户界面(GUI)库,用于开发桌面应用程序。它提供了丰富的控件(如按钮、文本框、标签等)来构建用户界面。
#tk 是对 tkinter 模块的简写,方便在代码中调用。使用 tk,你可以创建窗口、按钮、文本框等,来设计桌面应用的界面。
from tkinter import messagebox
#messagebox 是 tkinter 库中的一个模块,用于弹出消息框(对话框),用来提示用户信息或错误。你可以用它来显示信息、警告、错误提示等。
import pygame
#pygame 是一个用于开发 2D 游戏和多媒体应用的第三方库,提供了处理图形、声音、事件和其他游戏相关功能的工具。
#你可以用 pygame 来处理图像、音频、用户输入(键盘、鼠标)、碰撞检测等,常用于游戏开发。你可以使用 pygame 来加载图片、播放音效、处理动画等。
import random
#random 是 Python 的标准库模块,提供了生成随机数的功能。它可以生成随机数、随机选择列表中的元素等。
# 它在游戏中常用于生成随机事件,比如随机生成敌人位置、掉落物品、随机数决定的游戏元素。
import os
#os 是 Python 的标准库模块,用于与操作系统进行交互。它提供了文件和目录操作、环境变量管理、进程管理等功能。
#使用 os,你可以创建、删除、移动文件或文件夹,获取操作系统的信息等。

class RacingGame:
#定义一个类,用来设置游戏基本属性和参数的,类似于是游戏全部功能的集合

    def __init__(self):
    #定义一个init构造函数,在面向对象编程中,__init__ 是类的构造函数,负责在创建类的实例时对对象进行初始化操作
    #这个方法会在你创建类实例时自动调用,目的是为新创建的对象设置一些初始值,通常用于设置对象的属性和初始化一些必要的资源。

        pygame.init()
        #初始化 pygame 库,为游戏设置一些环境。pygame 是一个用于开发2D游戏的库,包括图像、声音、事件、游戏循环等功能。
        pygame.mixer.init()
        #初始化 pygame库下的音频模块(mixer),用于处理游戏中的音效和背景音乐。

        #设置游戏窗口的大小,宽度 800 像素,高度 600 像素。
        self.screen_width, self.screen_height = 800, 600
        #通过pygame库创建游戏窗口,大小为 800x600 像素。
        self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
        #设置窗口的标题为 "竞速赛车游戏"。
        pygame.display.set_caption("竞速赛车游戏")
        #创建一个时钟对象,用于控制游戏的帧率。通过控制帧率,可以让游戏运行得更加流畅。时钟也帮助控制游戏循环的速度。
        self.clock = pygame.time.Clock()
        #游戏中的每一帧都是一幅静止的图像。当这些帧以足够快的速度连续播放时,人眼就会产生画面在连续运动的错觉
        #帧率越高,意味着在单位时间内显示的画面数量越多,画面的连贯性就越好,给人的视觉感受就越流畅。

        #对游戏属性进行初始化
        self.player_speed = 5 #  设置玩家汽车的移动速度,5像素/帧
        self.enemy_base_speed = 3  #  设置敌人汽车的初始速度

        #设置游戏的运行状态为 False,表示游戏没有开始。
        self.running = False
        #初始距离为0
        self.distance = 0

        # 指定图片路径
        self.player_car_image = "D:/player_car.png"  # 玩家汽车图片
        self.enemy_car_image = "D:/enemy_car.png"    # 机器自带的赛车图片
        self.background_music = "D:/background_music.ogg"  # 背景音乐
        self.collision_sound = "D:/collision_sound.wav"    # 碰撞音效

        # 加载图像
        self.player_car = pygame.image.load(self.player_car_image) #  加载玩家车辆图像
        self.player_car = pygame.transform.scale(self.player_car, (50, 100)) #将玩家车辆图像缩放到50x100像素
        self.enemy_car = pygame.image.load(self.enemy_car_image) #加载敌人车辆图像
        self.enemy_car = pygame.transform.scale(self.enemy_car, (50, 100)) #将敌人车辆图像缩放到50x100像素

        # 设置玩家初始位置
        self.player_x = self.screen_width // 2 - 25  #设置玩家车辆的水平位置位于屏幕中心
        self.player_y = self.screen_height - 120 #玩家汽车的垂直位置是距离屏幕底部 120 像素
        self.road_y = 0 #初始化背景滚动位置为0,表示背景尚未开始滚动

        self.enemies = [] #  初始化敌人车辆列表为空
        self.spawn_multiple_enemies() #调用函数生成多个敌人车辆

        # 加载背景音乐
        self.load_background_music()

        # 加载碰撞音效
        self.load_collision_sound()

    def load_background_music(self):
        """加载背景音乐"""
        if os.path.exists(self.background_music):
            try:
                pygame.mixer.music.load(self.background_music)
                pygame.mixer.music.play(-1)  # 循环播放背景音乐,-1表示无限循环
            except pygame.error as e:
                print("背景音乐加载失败:", e)
        else:
            print("背景音乐文件未找到:", self.background_music)

    def load_collision_sound(self):
        """加载碰撞音效"""
        if os.path.exists(self.collision_sound):
            try:
                self.collision_sound_effect = pygame.mixer.Sound(self.collision_sound)
            except pygame.error as e:
                print("碰撞音效加载失败:", e)
                self.collision_sound_effect = None
        else:
            print("碰撞音效文件未找到:", self.collision_sound)
            self.collision_sound_effect = None

    def play_collision_sound(self):
        """播放碰撞音效"""
        if self.collision_sound_effect:
            self.collision_sound_effect.play()

    def spawn_multiple_enemies(self, count=5):
        """生成多个敌人汽车位置"""
        self.enemies = []
        for _ in range(count):
            self.spawn_enemy()

    def spawn_enemy(self):
        """随机生成一个敌人汽车位置和速度"""
        enemy_x = random.randint(200, self.screen_width - 250)
        enemy_y = random.randint(-600, -100)
        enemy_speed = self.enemy_base_speed + random.random() * 2
        self.enemies.append([enemy_x, enemy_y, enemy_speed])

    def run_game(self):
        """游戏主循环"""
        self.running = True
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    pygame.quit()
                    sys.exit()

            # 玩家逻辑,玩家左右和加速减速控制
            keys = pygame.key.get_pressed()
            if keys[pygame.K_LEFT] and self.player_x > 200:
                self.player_x -= 8
            if keys[pygame.K_RIGHT] and self.player_x < self.screen_width - 250:
                self.player_x += 8
            if keys[pygame.K_UP]:
                self.player_speed += 0.1
            if keys[pygame.K_DOWN] and self.player_speed > 1:
                self.player_speed -= 0.1

            # 背景滚动控制
            self.road_y += self.player_speed / 2
            if self.road_y >= self.screen_height:
                self.road_y = 0

            # 移动敌人车辆
            self.move_enemies()

            # 计算赛程距离和动态加速
            self.distance += self.player_speed / 10
            if int(self.distance) % 100 == 0:
                self.enemy_base_speed += 0.1

            # 检查碰撞
            if self.is_collision():
                self.play_collision_sound()
                pygame.mixer.music.stop()
                self.game_over()

            # 绘制背景
            self.draw_background()
            # 显示玩家车、敌人车、当前赛程距离
            self.screen.blit(self.player_car, (self.player_x, self.player_y))
            for enemy_x, enemy_y, _ in self.enemies:
                self.screen.blit(self.enemy_car, (enemy_x, enemy_y))
            # 显示赛程距离
            font = pygame.font.Font("C:/Windows/Fonts/msyh.ttc", 28)
            distance_text = font.render(f"赛程: {int(self.distance)} 米", True, (255, 255, 255))
            self.screen.blit(distance_text, (10, 10))
            pygame.display.flip()
            self.clock.tick(30)

    def draw_background(self):
        """绘制赛道背景"""
        self.screen.fill((0, 128, 0))
        pygame.draw.rect(self.screen, (50, 50, 50), (200, 0, self.screen_width - 400, self.screen_height))
        for i in range(0, self.screen_height, 60):
            pygame.draw.line(self.screen, (255, 255, 255),
                             (self.screen_width // 2 - 5, i + self.road_y),
                             (self.screen_width // 2 + 5, i + 40 + self.road_y), 5)

    def move_enemies(self):
        """控制敌人车辆在屏幕上移动并清理屏幕外的敌人"""
        for enemy in self.enemies:
            enemy[1] += enemy[2]
        self.enemies = [enemy for enemy in self.enemies if enemy[1] < self.screen_height]
        if len(self.enemies) < 5:
            self.spawn_enemy()

    def is_collision(self):
        """检查玩家和敌人之间的碰撞(AABB碰撞检测)"""
        for enemy_x, enemy_y, _ in self.enemies:
            if (self.player_x < enemy_x + 50 and
                self.player_x + 50 > enemy_x and
                self.player_y < enemy_y + 100 and
                self.player_y + 100 > enemy_y):
                return True
        return False

    def game_over(self):
        """处理游戏结束逻辑"""
        choice = messagebox.askyesno("游戏结束", f"你已行驶 {int(self.distance)} 米!\n是否重新开始游戏?")
        if choice:
            self.reset_game()
            self.run_game()
        else:
            pygame.quit()
            sys.exit()

    def reset_game(self):
        """重置游戏状态"""
        self.player_speed = 5
        self.enemy_base_speed = 3
        self.distance = 0
        self.road_y = 0
        self.player_x = self.screen_width // 2 - 25
        self.enemies.clear()
        self.spawn_multiple_enemies()

# 使用 tkinter 创建主界面
class RacingApp:
    def __init__(self, root):
        self.root = root
        self.root.title("竞速赛车游戏")
        self.root.geometry("900x700")

        button_font = ("Microsoft YaHei", 18)

        # 启动游戏按钮
        self.start_button = tk.Button(self.root, text="开始游戏", command=self.start_game,font=button_font)
        self.start_button.pack(pady=20)

        # 退出按钮
        self.exit_button = tk.Button(self.root, text="退出", command=self.root.quit,font=button_font)
        self.exit_button.pack(pady=10)

    def start_game(self):
        self.root.withdraw()
        game = RacingGame()
        game.run_game()
        self.root.deiconify()

# 创建主窗口并启动应用
if __name__ == "__main__":
    root = tk.Tk()
    app = RacingApp(root)
    root.mainloop()

打飞船2D游戏开发

游戏功能

  • 玩家控制飞船左右移动,按空格键射击
  • W 键切换武器(普通枪、散弹枪、火箭筒)
  • P 键暂停/继续游戏
  • R 键在游戏结束后重新开始
  • 碰撞敌人3次后游戏结束
import sys,io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

import pygame
import random

# 初始化 Pygame
pygame.init()

width, height = 800, 600
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("射击美国飞船")

# 颜色定义
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
blue = (0, 0, 255)

# 加载中文字体
try:
    font_path = "C:\Windows\Fonts\STXIHEI.TTF"
    font = pygame.font.Font(font_path, 20)
except FileNotFoundError:
    font = pygame.font.SysFont(None, 20)

# 玩家类
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((30, 50), pygame.SRCALPHA)
        pygame.draw.polygon(self.image, blue, [(15, 0), (30, 50), (0, 50)])
        self.rect = self.image.get_rect(center=(width // 2, height - 50))
        self.lives = 3
        self.hit_count = 0
        self.is_game_over = False
        self.weapon = RegularGun()
        self.fire_message = "按下空格键射击"
        self.switch_message = "按下 W 切换武器"

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= 5
        if keys[pygame.K_RIGHT] and self.rect.right < width:
            self.rect.x += 5

    def switch_weapon(self):
        if isinstance(self.weapon, RegularGun):
            self.weapon = Shotgun()
            self.switch_message = "切换为散弹枪"
        elif isinstance(self.weapon, Shotgun):
            self.weapon = RocketLauncher()
            self.switch_message = "切换为火箭筒"
        else:
            self.weapon = RegularGun()
            self.switch_message = "切换为普通枪"

    def fire(self):
        bullets = self.weapon.fire(self.rect.centerx, self.rect.top)
        if isinstance(bullets, list):
            for bullet in bullets:
                all_sprites.add(bullet)
                bullets_group.add(bullet)
        elif bullets is not None:
            all_sprites.add(bullets)
            bullets_group.add(bullets)

    def respawn(self):
        self.rect.center = (width // 2, height - 50)
        self.hit_count += 1
        if self.hit_count >= 3:
            self.is_game_over = True

    def reset_game(self):
        self.hit_count = 0
        self.lives = 3
        self.is_game_over = False
        self.rect.center = (width // 2, height - 50)
        bullets_group.empty()
        enemies_group.empty()
        all_sprites.add(self)

# 武器基类
class Weapon:
    def fire(self, x, y):
        raise NotImplementedError("This method should be overridden in subclasses.")

# 普通枪类
class RegularGun(Weapon):
    def fire(self, x, y):
        return Bullet(x, y)

# 子弹类
class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((5, 10))
        self.image.fill(red)
        self.rect = self.image.get_rect(center=(x, y))

    def update(self):
        self.rect.y -= 10
        if self.rect.bottom < 0:
            self.kill()

# 散弹枪类
class Shotgun(Weapon):
    def fire(self, x, y):
        bullets = []
        for offset in [-10, 0, 10]:
            bullet = Bullet(x + offset, y)
            bullets.append(bullet)
        return bullets

# 火箭筒类
class RocketLauncher(Weapon):
    def fire(self, x, y):
        return Rocket(x, y)

# 火箭类
class Rocket(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((10, 20))
        self.image.fill(blue)
        self.rect = self.image.get_rect(center=(x, y))

    def update(self):
        self.rect.y -= 15
        if self.rect.bottom < 0:
            self.kill()

# 敌人类
class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.image.load(r"C:\代码\python公开课\ameri.png")  # 加载敌人图像
        self.image = pygame.transform.scale(self.image, (100, 30))
        self.rect = self.image.get_rect(center=(random.randint(0, width), random.randint(-100, -40)))
    def update(self):
        self.rect.y += 5
        if self.rect.top > height:
            self.kill()

# 创建精灵组
player = Player()
bullets_group = pygame.sprite.Group()
enemies_group = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)

# 游戏状态
running = True
paused = False
score = 0

# 游戏主循环
clock = pygame.time.Clock()
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and not paused:
                player.fire()
                player.fire_message = "发射成功!"
            elif event.key == pygame.K_p:
                paused = not paused
            elif event.key == pygame.K_r:
                if player.hit_count >= 3:
                    player.reset_game()
                    player.fire_message = "按下空格键射击"
                    player.switch_message = "按下 W 切换武器"
            elif event.key == pygame.K_w:
                player.switch_weapon()

    if not paused and not player.is_game_over:
        all_sprites.update()

        # 随机生成敌人
        if random.randint(1, 20) == 1:
            enemy = Enemy()
            all_sprites.add(enemy)
            enemies_group.add(enemy)

        # 碰撞检测:子弹击中敌人
        for bullet in bullets_group:
            hits = pygame.sprite.spritecollide(bullet, enemies_group, True)
            for hit in hits:
                bullet.kill()
                score += 1

        # 碰撞检测:玩家碰到敌人
        if pygame.sprite.spritecollideany(player, enemies_group):
            player.respawn()

        # 屏幕绘制
        screen.fill(black)
        all_sprites.draw(screen)

        # 显示得分和生命
        score_text = font.render(f'得分: {score}', True, white)
        lives_text = font.render(f'生命: {player.lives}', True, white)
        hit_count_text = font.render(f'击中次数: {player.hit_count}', True, white)
        weapon_text = font.render(f'当前武器: {type(player.weapon).__name__}', True, white)
        fire_text = font.render(player.fire_message, True, white)
        switch_text = font.render(player.switch_message, True, white)
        screen.blit(score_text, (10, 10))
        screen.blit(lives_text, (10, 40))
        screen.blit(hit_count_text, (10, 70))
        screen.blit(weapon_text, (10, 100))
        screen.blit(fire_text, (10, 130))
        screen.blit(switch_text, (10, 160))

    elif player.is_game_over:
        screen.fill(black)
        game_over_text = font.render('游戏结束!按R键重新开始', True, white)
        screen.blit(game_over_text, (width // 2 - 200, height // 2))

    elif paused:
        screen.fill(black)
        pause_text = font.render('游戏暂停,按 P 继续', True, white)
        screen.blit(pause_text, (width // 2 - 200, height // 2))

    pygame.display.flip()
    clock.tick(50)

pygame.quit()
sys.exit()
pyinstaller --onefile --windowed .\game.py

俄罗斯方块游戏开发

游戏操作

  • 左右方向键: 移动方块
  • 上方向键: 旋转方块
  • 下方向键: 加速下落
  • 消除满行得 100分
import pygame
import random

# 游戏窗口的宽度和高度
SCREEN_WIDTH = 300
SCREEN_HEIGHT = 600
BLOCK_SIZE = 30

# 定义颜色
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
CYAN = (0, 255, 255)

# 用二维数组定义五种俄罗斯方块的形状
SHAPES = [
    [[1, 1, 1, 1]],   # 一字形(I形)
    [[1, 1, 1], [0, 1, 0]],  # T形
    [[1, 1], [1, 1]],  # 方块(O形)
    [[0, 1, 1], [1, 1, 0]],   # Z形
    [[1, 1, 0], [0, 1, 1]]    # 反Z形(S形)
]

class Tetris:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        pygame.display.set_caption("俄罗斯方块")
        self.clock = pygame.time.Clock()
        self.running = True
        self.board = [[0 for _ in range(SCREEN_WIDTH // BLOCK_SIZE)] for _ in range(SCREEN_HEIGHT // BLOCK_SIZE)]

        # 初始化当前方块和下一个方块
        self.current_piece = self.new_piece()
        self.next_piece = self.new_piece()

        # 方块初始位置(居中)
        self.piece_x = SCREEN_WIDTH // BLOCK_SIZE // 2 - len(self.current_piece[0]) // 2
        self.piece_y = 0

        # 下落速度控制变量
        self.fall_speed = 500  # 每500毫秒下落一次
        self.last_fall_time = pygame.time.get_ticks()

        # 游戏得分
        self.score = 0

    def new_piece(self):
        """随机生成一个俄罗斯方块形状"""
        return random.choice(SHAPES)

    def draw_board(self):
        """绘制游戏界面"""
        self.screen.fill(BLACK)
        # 绘制已固定的方块
        for y in range(len(self.board)):
            for x in range(len(self.board[y])):
                if self.board[y][x]:
                    pygame.draw.rect(self.screen, WHITE, (x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))

        # 绘制当前正在下落的方块
        for y in range(len(self.current_piece)):
            for x in range(len(self.current_piece[y])):
                if self.current_piece[y][x]:
                    pygame.draw.rect(self.screen, CYAN, ((self.piece_x + x) * BLOCK_SIZE, (self.piece_y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))

        # 显示分数
        font = pygame.font.Font(None, 36)
        score_text = font.render(f'Score: {self.score}', True, WHITE)
        self.screen.blit(score_text, (10, 10))

        pygame.display.flip()

    def run(self):
        """游戏主循环"""
        while self.running:
            self.handle_events()
            self.update()
            self.draw_board()
            pygame.display.flip()
            self.clock.tick(30)

    def handle_events(self):
        """处理用户输入"""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    self.piece_x -= 1
                    if self.collision():
                        self.piece_x += 1
                elif event.key == pygame.K_RIGHT:
                    self.piece_x += 1
                    if self.collision():
                        self.piece_x -= 1
                elif event.key == pygame.K_DOWN:
                    self.piece_y += 1
                    if self.collision():
                        self.piece_y -= 1
                elif event.key == pygame.K_UP:
                    self.current_piece = self.rotate(self.current_piece)
                    if self.collision():
                        self.current_piece = self.rotate(self.current_piece, -1)

    def rotate(self, piece, direction=1):
        """旋转方块"""
        if direction == 1:
            # 顺时针旋转:将方块转置并反转每行
            return [list(row) for row in zip(*piece[::-1])]
        else:
            # 逆时针旋转:转置后上下翻转
            return [list(row) for row in zip(*piece)][::-1]

    def update(self):
        """更新方块下落状态"""
        self.fall_time = pygame.time.get_ticks()
        if self.fall_time - self.last_fall_time > self.fall_speed:
            self.piece_y += 1
            if self.collision():
                self.piece_y -= 1
                self.lock_piece()
                self.clear_lines()
                self.current_piece = self.next_piece
                self.next_piece = self.new_piece()
                self.piece_x = SCREEN_WIDTH // BLOCK_SIZE // 2 - len(self.current_piece[0]) // 2
                self.piece_y = 0
                if self.collision():
                    self.running = False
            self.last_fall_time = self.fall_time

    def collision(self):
        """检查碰撞"""
        for y in range(len(self.current_piece)):
            for x in range(len(self.current_piece[y])):
                if self.current_piece[y][x]:
                    board_x = self.piece_x + x
                    board_y = self.piece_y + y
                    if board_x < 0 or board_x >= SCREEN_WIDTH // BLOCK_SIZE or board_y >= SCREEN_HEIGHT // BLOCK_SIZE or self.board[board_y][board_x]:
                        return True
        return False

    def lock_piece(self):
        """将当前方块锁定到棋盘上"""
        for y in range(len(self.current_piece)):
            for x in range(len(self.current_piece[y])):
                if self.current_piece[y][x]:
                    self.board[self.piece_y + y][self.piece_x + x] = 1

    def clear_lines(self):
        """清除已填满的行并更新分数"""
        for y in range(len(self.board) - 1, -1, -1):
            if all(self.board[y]):
                self.score += 100
                del self.board[y]
                self.board.insert(0, [0 for _ in range(SCREEN_WIDTH // BLOCK_SIZE)])

# 创建并运行游戏
game = Tetris()
game.run()
pygame.quit()