Pygame 大鱼吃小鱼

发布于:2025-06-23 ⋅ 阅读:(18) ⋅ 点赞:(0)

【Pygame 大鱼吃小鱼】是一款基于Python编程语言和Pygame库开发的趣味游戏。Pygame是Python中一个广泛用于开发2D游戏的开源模块集合,它提供了丰富的功能,如窗口管理器、事件处理、图形绘制等,使得初学者也能快速上手创建游戏。

   这段 Python 代码使用Pygame库实现了一个经典的 “大鱼吃小鱼” 游戏。游戏中,玩家控制一条粉色的鱼,通过方向键移动,吃掉比自己小的鱼和食物来增长大小和分数,同时要避开比自己大的鱼。当分数达到 1200 时,游戏挑战成功。


下面展示一下界面图:(我设置的是分数达到1200自动结束游戏,因为后期如果小鱼吃的太多会膨胀整个屏幕)

后期我还将持续完善这个项目,比如添加音效等等效果,达到锦上添花的效果。


1. 初始化部分

功能:导入必要的库,初始化pygame和字体模块,设置游戏窗口的大小、标题和帧率,定义颜色常量和字体对象。

import pygame
import random
import sys
import math

# 初始化pygame库,为后续使用pygame的各种功能做准备
pygame.init()

# 初始化pygame的字体模块,确保后续能正常使用字体来显示文字
pygame.font.init()
# 尝试查找系统中的宋体或黑体字体,用于显示中文
font_path = pygame.font.match_font('simsun') or pygame.font.match_font('simhei')
# 如果未找到中文字体,则使用系统默认字体
if not font_path:
    font_path = pygame.font.get_default_font()
# 创建不同大小的字体对象,用于游戏中的不同文本显示
game_font = pygame.font.Font(font_path, 24)
title_font = pygame.font.Font(font_path, 48)
small_font = pygame.font.Font(font_path, 16)

# 游戏相关的设置
SCREEN_WIDTH = 1200  # 游戏窗口的宽度
SCREEN_HEIGHT = 800  # 游戏窗口的高度
FPS = 60  # 游戏的帧率,即每秒刷新的帧数
WHITE = (255, 255, 255)  # 白色,用于文字显示等
BLACK = (0, 0, 0)  # 黑色,用于背景填充等
PINK = (255, 192, 203)  # 粉色,玩家鱼的颜色
RED = (255, 0, 0)  # 红色
GREEN = (0, 255, 0)  # 绿色
BLUE = (0, 0, 255)  # 蓝色
YELLOW = (255, 255, 0)  # 黄色,食物的颜色
COLORS = [RED, GREEN, BLUE]  # 敌人鱼的颜色列表

# 创建游戏窗口,设置窗口的大小和标题(传入参数)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("大鱼吃小鱼")
# 创建一个时钟对象,用于控制游戏的帧率.游戏主循环中,通常会调用 clock.tick(FPS)
clock = pygame.time.Clock()

2. 类定义部分

功能:定义了FishFood两个类,分别表示游戏中的鱼和食物。Fish类包含了鱼的属性和方法,如移动、旋转、生长等;Food类用于创建食物对象。

# 定义Fish类,继承自pygame的Sprite类,用于表示游戏中的鱼
class Fish(pygame.sprite.Sprite):
    def __init__(self, x, y, size, color, is_player=False):
        # 调用父类的构造函数
        super().__init__()
        self.size = size  # 鱼的大小
        # 创建一个透明的Surface对象,SRCALPHA让鱼的图像边缘不会有难看的黑边,能和游戏背景自然融合。
        self.original_image = pygame.Surface((size * 2, size), pygame.SRCALPHA)
        # 绘制鱼的身体,使用椭圆表示
        pygame.draw.ellipse(self.original_image, color, (0, 0, size * 2, size))
        # 绘制鱼的眼睛,白色外圈和黑色内圈
        pygame.draw.circle(self.original_image, WHITE, (int(size * 1.5), int(size / 2)), int(size / 8))
        pygame.draw.circle(self.original_image, BLACK, (int(size * 1.5), int(size / 2)), int(size / 16))
        # 绘制鱼尾,使用多边形表示。下面的函数可以绘制任意边数的封闭图形
        points = [(0, size // 2), (size // 2, 0), (size // 2, size)]
        pygame.draw.polygon(self.original_image, color, points)

        self.image = self.original_image  # 当前显示的鱼的图像
        self.rect = self.image.get_rect(center=(x, y))  # 鱼的矩形区域,用于碰撞检测和位置管理

        # 使用浮点数来存储鱼的精确位置,避免整数运算带来的精度损失
        self.float_x = x
        self.float_y = y

        self.angle = 0  # 鱼当前的朝向角度
        self.target_angle = 0  # 平滑转向的目标角度
        self.is_player = is_player  # 标记这条鱼是否为玩家控制的鱼
        # 根据鱼的大小和是否为玩家鱼来计算鱼的速度
        self.speed = self.calculate_speed(size, is_player)
        self.score = size  # 鱼的分数,与大小相关

        # AI行为参数
        self.direction_change_timer = 0  # 方向改变计时器
        # 随机设置方向改变的间隔帧数,范围在60到120帧之间
        self.direction_change_interval = random.randint(60, 120)

    def calculate_speed(self, size, is_player):
        # 玩家鱼的基础速度为5,敌人鱼的基础速度为1到3之间的随机值
        base_speed = 5 if is_player else random.randint(1, 3)
        # 鱼的速度随着大小的增加而减小,确保大鱼速度慢,小鱼速度快
        return max(1, base_speed * (20 / (size + 10)))

    def update(self):
        if self.is_player:
            # 如果是玩家鱼,处理玩家的输入 L108
            self.handle_player_input()
        else:
            # 如果是敌人鱼,进行平滑移动 L134
            self.move_smoothly()

        # 将浮点数位置转换为整数位置,更新矩形区域的位置
        self.rect.centerx = int(self.float_x)
        self.rect.centery = int(self.float_y)

        # 边界检查,确保鱼不会移出游戏窗口
        if self.rect.left < 0:
            self.rect.left = 0
            self.float_x = self.rect.centerx
        if self.rect.right > SCREEN_WIDTH:
            self.rect.right = SCREEN_WIDTH
            self.float_x = self.rect.centerx
        if self.rect.top < 0:
            self.rect.top = 0
            self.float_y = self.rect.centery
        if self.rect.bottom > SCREEN_HEIGHT:
            self.rect.bottom = SCREEN_HEIGHT
            self.float_y = self.rect.centery

    def handle_player_input(self):
        # 获取当前所有按键的状态
        keys = pygame.key.get_pressed()
        dx = 0  # x轴方向的移动量
        dy = 0  # y轴方向的移动量

        # 根据按键状态更新移动量
        if keys[pygame.K_LEFT]:
            dx = -self.speed
        if keys[pygame.K_RIGHT]:
            dx = self.speed
        if keys[pygame.K_UP]:
            dy = -self.speed
        if keys[pygame.K_DOWN]:
            dy = self.speed

        # 更新浮点数位置
        self.float_x += dx
        self.float_y += dy

        # 如果有移动,计算鱼的朝向角度。让鱼的朝向跟着移动方向走
        if dx != 0 or dy != 0:
            self.target_angle = math.degrees(math.atan2(dy, dx))
            # 进行平滑旋转,转向更丝滑。
            self.smooth_rotate()

    def move_smoothly(self):
        # 增加方向改变计时器的值
        self.direction_change_timer += 1
        # 当计时器达到间隔帧数时,进行方向改变
        if self.direction_change_timer >= self.direction_change_interval:
            self.direction_change_timer = 0
            # 随机设置下一次方向改变的间隔帧数
            self.direction_change_interval = random.randint(60, 120)

            # 随机选择移动模式(1-4)
            move_pattern = random.randint(1, 4)

            if move_pattern == 1:
                # 直线运动,随机选择一个方向
                self.target_angle = random.uniform(0, 360)
            elif move_pattern == 2:
                # 追逐玩家,有50%的概率触发
                if player and random.random() < 0.5:
                    # 计算玩家与当前鱼的位置差
                    dx = player.rect.centerx - self.float_x
                    dy = player.rect.centery - self.float_y
                    if dx != 0 or dy != 0:
                        # 计算追逐的目标角度
                        self.target_angle = math.degrees(math.atan2(dy, dx))
            elif move_pattern == 3:
                # 远离玩家,有30%的概率触发
                if player and random.random() < 0.3:
                    # 计算当前鱼与玩家的位置差
                    dx = self.float_x - player.rect.centerx
                    dy = self.float_y - player.rect.centery
                    if dx != 0 or dy != 0:
                        # 计算远离的目标角度
                        self.target_angle = math.degrees(math.atan2(dy, dx))
        # 进行平滑旋转
        self.smooth_rotate()

        # 将角度转换为速度分量
        rad_angle = math.radians(self.angle)
        dx = math.cos(rad_angle) * self.speed
        dy = math.sin(rad_angle) * self.speed

        # 更新浮点数位置
        self.float_x += dx
        self.float_y += dy

    # 平滑旋转,让鱼转向时避免 “瞬间跳转”,而是逐帧旋转,更丝滑
    def smooth_rotate(self):
        # 计算当前角度与目标角度的差值,确保差值在-180到180度之间,选择最短路径旋转。
        angle_diff = (self.target_angle - self.angle) % 360
        if angle_diff > 180:
            angle_diff -= 360

        # 每帧最多旋转5度,避免角度突变。abs()计算数值的绝对值。
        max_rotation = 5
        if abs(angle_diff) > max_rotation:
            # 根据角度差的正负,决定旋转方向
            self.angle += max_rotation if angle_diff > 0 else -max_rotation
        else:
            # 角度差很小,直接对齐目标角度
            self.angle = self.target_angle

        # 旋转鱼的图像,使其朝向正确的方向
        self.image = pygame.transform.rotate(self.original_image,-self.angle)
        # 更新矩形区域的中心位置,确保旋转后位置不变
        self.rect = self.image.get_rect(center=self.rect.center)

    # 用于控制游戏中鱼的有序运动模式
    def move_orderly(self):
        # 随机选择移动模式
        move_pattern = random.randint(1, 4)

        # 根据模式移动
        if move_pattern == 1:
            # 直线运动
            rad_angle = math.radians(self.angle)
            dx = math.cos(rad_angle) * self.speed
            dy = math.sin(rad_angle) * self.speed
        elif move_pattern == 2:
            # 圆周运动
            angle_rad = math.radians(self.rect.x * 2)
            dx = self.speed
            dy = math.sin(angle_rad) * self.speed * 0.5
        elif move_pattern == 3:
            # 波浪运动
            angle_rad = math.radians(self.rect.x * 0.5)
            dx = self.speed
            dy = math.sin(angle_rad) * self.speed * 0.3
        else:
            # 追逐玩家
            if player:  # 确保玩家存在
                dx = player.rect.centerx - self.rect.centerx
                dy = player.rect.centery - self.rect.centery
                distance = max(1, math.sqrt(dx * dx + dy * dy))  # 避免除以零
                dx = (dx / distance) * self.speed * 0.7
                dy = (dy / distance) * self.speed * 0.7

        # 移动鱼
        self.rect.x += int(dx)  # 取整以减少抖动
        self.rect.y += int(dy)  # 取整以减少抖动

        # 旋转鱼的图像以匹配移动方向
        if dx != 0 or dy != 0:
            # math.degrees() 将弧度转换为角度(范围:[-180°, 180°])
            # math.atan2(dy, dx) 返回向量的弧度值(范围:[-π, π])
            self.angle = math.degrees(math.atan2(dy, dx))
            # 调用rotate方法,根据新角度旋转鱼的图像
            self.rotate()

        # 边界反弹(左边界<0或者右边界等于宽度)
        if self.rect.left <= 0 or self.rect.right >= SCREEN_WIDTH:
            self.angle = 180 - self.angle
        if self.rect.top  <= 0 or self.rect.bottom >= SCREEN_HEIGHT:
            self.angle = -self.angle

    def rotate(self):
        # 旋转鱼的图像
        self.image = pygame.transform.rotate(self.original_image, -self.angle)
        self.rect = self.image.get_rect(center=self.rect.center)

    def grow(self, amount):
        # 鱼的大小增加指定的量
        self.size += amount
        # 更新鱼的图像
        self.original_image = pygame.Surface((self.size * 2, self.size), pygame.SRCALPHA)
        # 根据是否为玩家鱼,选择不同的颜色绘制鱼的身体
        pygame.draw.ellipse(self.original_image, PINK if self.is_player else BLUE, (0, 0, self.size * 2, self.size))
        # 绘制鱼的眼睛
        pygame.draw.circle(self.original_image, WHITE, (int(self.size * 1.5), int(self.size / 2)), int(self.size / 8))
        pygame.draw.circle(self.original_image, BLACK, (int(self.size * 1.5), int(self.size / 2)), int(self.size / 16))
        # 绘制鱼尾:polygon多边形
        points = [(0, self.size // 2), (self.size // 2, 0), (self.size // 2, self.size)]
        pygame.draw.polygon(self.original_image, PINK if self.is_player else BLUE, points)
        self.image = self.original_image  # 更新当前显示的图像
        self.rect = self.image.get_rect(center=self.rect.center)  # 更新矩形区域的位置
        self.score = self.size  # 更新分数
        # 根据新的大小重新计算鱼的速度
        self.speed = self.calculate_speed(self.size, self.is_player)

# 定义Food类,继承自pygame的Sprite类,用于表示游戏中的食物
class Food(pygame.sprite.Sprite):
    def __init__(self, x, y):
        # 调用父类的构造函数
        super().__init__()
        # 随机生成食物的大小,范围在5到10之间
        self.size = random.randint(5, 10)
        # 创建一个透明的Surface对象,用于绘制食物的图像
        self.image = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA)
        # 绘制食物,使用黄色的圆形表示
        pygame.draw.circle(self.image, YELLOW, (self.size, self.size), self.size)
        self.rect = self.image.get_rect(center=(x, y))  # 食物的矩形区域,用于碰撞检测和位置管理

3. 辅助函数部分

功能:定义了一些辅助函数,用于创建鱼和食物对象、在屏幕上绘制文本、显示游戏开始和结束屏幕。

# 创建鱼的函数,随机生成鱼的位置、大小和颜色
def create_fish():
    x = random.randint(50, SCREEN_WIDTH - 50)
    y = random.randint(50, SCREEN_HEIGHT - 50)
    size = random.randint(10, 40)
    color = random.choice(COLORS)
    return Fish(x, y, size, color)

# 创建食物的函数,随机生成食物的位置
def create_food():
    x = random.randint(20, SCREEN_WIDTH - 20)
    y = random.randint(20, SCREEN_HEIGHT - 20)
    return Food(x, y)

# 在指定的Surface上绘制文本的函数
def draw_text(surface, text, font, color, x, y):
    # 渲染文本,返回一个包含文本的Surface对象
    text_surface = font.render(text, True, color)
    # 获取文本Surface的矩形区域,并将其中心位置设置为指定的坐标
    text_rect = text_surface.get_rect(center=(x, y))
    # 将文本Surface绘制到指定的Surface上
    surface.blit(text_surface, text_rect)

# 显示游戏开始屏幕的函数
def show_start_screen():
    screen.fill(BLACK)  # 填充屏幕为黑色
    # 绘制游戏标题
    draw_text(screen, "大鱼吃小鱼", title_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 4)
    # 绘制游戏说明
    draw_text(screen, "使用方向键移动你的鱼", game_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
    draw_text(screen, "吃掉比你小的鱼,避开比你大的鱼", game_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 50)
    draw_text(screen, "分数达到1200时挑战成功", game_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 100)
    draw_text(screen, "按任意键开始游戏", game_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT * 3 / 4)
    pygame.display.flip()  # 更新整个屏幕的显示

    waiting = True
    while waiting:
        clock.tick(FPS)  # 控制帧率
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # 如果用户关闭窗口,退出游戏
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYUP:
                # 如果用户按下任意键,结束等待,开始游戏
                waiting = False

# 显示游戏结束屏幕的函数
def show_game_over_screen(score, is_success=False):
    screen.fill(BLACK)  # 填充屏幕为黑色
    if is_success:
        # 如果游戏成功,显示成功信息和最终得分
        draw_text(screen, "挑战成功!", title_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 4)
        draw_text(screen, f"你的最终得分: {score}", game_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
    else:
        # 如果游戏失败,显示游戏结束信息和得分
        draw_text(screen, "游戏结束", title_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 4)
        draw_text(screen, f"你的得分: {score}", game_font, WHITE, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
    pygame.display.flip()  # 更新整个屏幕的显示

4. 游戏主函数部分

功能:游戏的主函数,负责初始化游戏状态、创建精灵组、处理游戏事件、检测碰撞、更新精灵状态、渲染游戏画面,并根据游戏结果显示相应的屏幕。

# 游戏主函数
def main():
    global player  # 使玩家鱼对象在全局范围内可访问

    # 游戏主循环
    running = True
    game_over = False
    score = 0
    success_score = 1200  # 成功分数阈值

    # 游戏状态初始化
    while running:
        # 创建玩家鱼,初始位置在屏幕中心,大小为20,颜色为粉色
        player = Fish(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 20, PINK, True)

        # 创建精灵组,用于管理所有的精灵对象
        all_sprites = pygame.sprite.Group()
        fish_sprites = pygame.sprite.Group()
        food_sprites = pygame.sprite.Group()

        all_sprites.add(player)  # 将玩家鱼添加到所有精灵组中

        # 初始化游戏,创建10条敌人鱼
        for i in range(10):
            fish = create_fish()
            all_sprites.add(fish)
            fish_sprites.add(fish)

        # 初始化游戏,创建20个食物
        for i in range(20):
            food = create_food()
            all_sprites.add(food)
            food_sprites.add(food)

        # 显示游戏开始屏幕
        show_start_screen()

        # 游戏主循环
        score = 0
        game_over = False
        success = False
        success_time = 0

        while running and not game_over and not success:
            clock.tick(FPS)  # 控制帧率

            # 事件处理
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    # 如果用户关闭窗口,结束游戏
                    running = False

            # 更新所有精灵的状态
            all_sprites.update()

            # 检测玩家与食物的碰撞
            food_collisions = pygame.sprite.spritecollide(player, food_sprites, True)
            for food in food_collisions:
                player.grow(1)  # 玩家鱼大小增加1
                score += food.size  # 增加分数
                # 生成新的食物
                new_food = create_food()
                all_sprites.add(new_food)
                food_sprites.add(new_food)

            # 检测玩家与其他鱼的碰撞
            fish_collisions = pygame.sprite.spritecollide(player, fish_sprites, False)
            for fish in fish_collisions:
                if player.size > fish.size:
                    # 如果玩家鱼比其他鱼大,吃掉这条鱼
                    player.grow(fish.size // 5)  # 玩家鱼大小增加被吃的鱼的大小的五分之一
                    score += fish.score  # 增加分数
                    fish.kill()  # 移除被吃的鱼
                    # 生成新的鱼
                    new_fish = create_fish()
                    all_sprites.add(new_fish)
                    fish_sprites.add(new_fish)
                elif player.size < fish.size:
                    # 如果玩家鱼比其他鱼小,游戏结束
                    game_over = True

            # 检查是否达到成功分数
            if score >= success_score:
                success = True
                success_time = pygame.time.get_ticks()  # 记录成功的时间

            # 渲染游戏,填充屏幕为蓝色背景
            screen.fill((100, 180, 255))
            # 绘制所有精灵
            all_sprites.draw(screen)

            # 显示分数和玩家鱼的大小
            draw_text(screen, f"分数: {score}/{success_score}", small_font, WHITE, 60, 20)
            draw_text(screen, f"大小: {player.size}", small_font, WHITE, 60, 40)

            pygame.display.flip()  # 更新整个屏幕的显示

        # 游戏结束,显示结果
        if running:
            if success:
                # 如果游戏成功,显示成功屏幕,并等待5秒
                show_game_over_screen(score, success)
                while pygame.time.get_ticks() - success_time < 5000:
                    clock.tick(FPS)
                    for event in pygame.event.get():
                        if event.type == pygame.QUIT:
                            running = False
                running = False
            else:
                # 如果游戏失败,显示游戏结束屏幕,等待用户操作
                show_game_over_screen(score, success)
                waiting = True
                while waiting:
                    clock.tick(FPS)
                    for event in pygame.event.get():
                        if event.type == pygame.QUIT:
                            running = False
                            waiting = False

    pygame.quit()  # 退出pygame库
    sys.exit()  # 退出程序

if __name__ == "__main__":
    main()  # 程序入口,调用主函数开始游戏

模块化处理总结

  • 初始化模块:负责导入库、初始化pygame和字体模块、设置游戏窗口和帧率等。
  • 类定义模块:定义FishFood类,封装鱼和食物的属性和方法。
  • 辅助函数模块:包含创建鱼和食物对象、绘制文本、显示游戏开始和结束屏幕的函数。
  • 游戏主函数模块:控制游戏的主循环,处理游戏事件、碰撞检测、状态更新和画面渲染。


网站公告

今日签到

点亮在社区的每一天
去签到