FruitNinja

FruitNinja

介绍

FruitNinja 是一个使用 Pygame 实现的水果忍者风格的小游戏。在游戏中,玩家通过切割屏幕上的水果来获得分数,同时避免炸弹。游戏包括了水果的扔出、碰撞检测、得分系统以及生命值管理等功能。

源码:

!!!!!!!!!!!!!可访问源码仓库查看源码以及资源!!!!!!!!!!!!!!!!

实现效果预览:

image-20240823152542870

image-20240823153658836

image-20240823153022469

image-20240823153059933

软件架构
  1. FruitNinjaGame: 游戏的主类,负责游戏的主要循环、事件处理、画面绘制和游戏状态管理。
  2. SetImages: 负责加载和管理游戏中使用的各种图像资源。
  3. Fruit: 表示水果对象,负责水果的创建、绘制和碰撞检测。
  4. Obstacle: 表示障碍物(如炸弹),负责障碍物的创建、绘制和碰撞检测。
  5. BgElement: 表示背景元素,负责背景的绘制。
  6. ThrowFruits: 管理水果的扔出和运动。
  7. Knife: 负责刀具的绘制和移动。
  8. HalfFruit: 表示切割后的半个水果,负责半个水果的绘制和运动。
  9. SoundSource: 负责游戏音效的管理和播放。

主架构代码

import pygame
import sys
import random

from SoundSource import SoundSource
from SetImages import SetImages
from Fruit import Fruit
from Obstacle import Obstacle
from BgElement import BgElement
from ThrowFruits import ThrowFruits
from Knife import Knife
from HalfFruit import HalfFruit


# 1. 水果忍者游戏主类
class FruitNinjaGame(object):
    def __init__(self):
        # 1.1 初始化 Pygame
        pygame.init()

        # 1.2 设置游戏窗口大小
        self.screen = pygame.display.set_mode((925, 700))

        # 1.3 加载游戏图像资源
        self.gameImages = SetImages()

        # 1.4 设置初始游戏状态,(0:表示主菜单 1:游戏开始)
        self.gameStatus = 0

        # 1.5 初始化背景图上的旋转体元素
        self.bg_element = [
            BgElement(self.screen, self.gameImages.bg[5], 200, 350),
            BgElement(self.screen, self.gameImages.bg[6], 600, 500),
            Fruit(self.screen, self.gameImages.fruits[3], 250, 405),
            Obstacle(self.screen, self.gameImages.obstacles[0], 640, 540)
        ]

        # 1.6 创建时钟对象控制游戏帧率
        self.clock = pygame.time.Clock()

        # 1.7 初始化水果列表
        self.fruits = []

        # 1.8 生成水果的数量并初始化水果数量
        self.throw_fruit_number = 1
        self.init_fruits(self.throw_fruit_number)

        # 1.9 创建一个 SoundSource 实例用于管理游戏音效
        self.sound_source = SoundSource()
        # 1.9.1当前播放的音乐状态
        self.current_music = 'menu'
        # 1.9.2播放菜单音乐
        self.sound_source.play_sound(self.current_music, -1)

        # 1.10 创建字体对象
        self.font = pygame.font.Font(None, 36)  # 36 是字体大小

        # 1.11 初始化水果扔出
        self.throw_fruits = ThrowFruits()

        # 1.12 初始化斩果刀
        self.knife = Knife(self.screen, pygame.transform.scale(self.gameImages.knife[1], (30, 30)))

        # 1.13 初始化鼠标位置和状态
        self.mouse_positions = []
        self.mouse_pressed = False

        # 1.14 待检测碰撞物体
        self.collision_menu_objects = []
        self.collision_objects = []
        # 1.14.1 初始化碰撞物体
        self.collision_menu_objects.append(self.bg_element[2])
        self.collision_menu_objects.append(self.bg_element[3])

        # 1.15 玩家生命值
        self.life = 3

        # 1.16 自定义声音延迟播放时间
        self.START_SOUND_EVENT = pygame.USEREVENT + 1

        # 1.17初始化分数
        self.score = -10

        # 1.18 一半水果
        self.half_fruits = []

    # 2. 初始化水果对象
    def init_fruits(self, i):
        # 2.1 初始化水果和炸弹的数量
        num_fruits = i
        num_obstacles = i // 3  # 假设炸弹的数量是水果数量的四分之一,依据实际需求调整

        # 2.1.2 创建水果和炸弹列表
        all_objects = []
        # 2.1.2.1 添加随机水果
        for _ in range(num_fruits):
            image = self.gameImages.fruits[random.randint(0, len(self.gameImages.fruits) - 1)]
            fruit = Fruit(self.screen, image, random.randint(image.get_width(), 925 - image.get_width()), 700)
            all_objects.append(fruit)
        # 2.1.2.2 添加随机炸弹
        for _ in range(num_obstacles):
            image = self.gameImages.obstacles[random.randint(0, len(self.gameImages.obstacles) - 1)]
            obstacle = Obstacle(self.screen, image, random.randint(image.get_width(), 925 - image.get_width()), 700)
            all_objects.append(obstacle)

        # 2.1.3 随机打乱列表
        random.shuffle(all_objects)

        # 2.1.4 将打乱后的水果和炸弹添加到列表中
        self.fruits = all_objects

    # 3. 游戏主循环
    def frame(self):
        # 3.1 设置窗口标题
        pygame.display.set_caption("水果忍者")
        while True:
            # 3.2 处理游戏事件和状态
            self.action()

            # 3.3 绘制游戏画面
            self.paint()
            # 3.4 更新显示
            pygame.display.update()

            # 3.5 控制游戏帧率为 60 FPS
            self.clock.tick(60)

    # 4. 处理游戏事件和状态
    def action(self):
        # 4.1 监听所有事件
        for event in pygame.event.get():
            # 4.1.1 处理退出事件
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            # 4.1.2 处理鼠标按下事件
            if event.type == pygame.MOUSEBUTTONDOWN:
                self.mouse_pressed = True
                self.mouse_positions.append(pygame.mouse.get_pos())

            # 4.1.3 处理鼠标松开事件
            if event.type == pygame.MOUSEBUTTONUP:
                self.mouse_pressed = False
                self.mouse_positions.clear()

            # 4.1.4 处理鼠标移动事件
            if event.type == pygame.MOUSEMOTION and self.mouse_pressed:
                self.mouse_positions.append(pygame.mouse.get_pos())
                #4.1.4.1 游戏开始(滑到主菜单的水果开始游戏)
                if len(self.collision_menu_objects) > 0 and self.check_fruit_collision(self.collision_menu_objects[0]):
                    # 4.1.4.1.1 更新游戏状态(跳转页面,游戏开始)
                    self.gameStatus = 1
                    # 4.1.4.1.2.1 设置当前音乐为开始音乐
                    self.current_music = 'start'
                    # 4.1.4.1.2.2 播放新的音乐
                    pygame.time.set_timer(self.START_SOUND_EVENT, 100)
                    # 4.1.4.1.3 将初始化碰撞物体删去
                    self.collision_menu_objects.clear()
                #4.1.4.2 退出游戏(滑到主菜单的炸弹)
                elif len(self.collision_menu_objects) > 1 and self.check_fruit_collision(
                        self.collision_menu_objects[1]):
                    pygame.quit()
                    sys.exit()

            # 4.1.5 处理键盘按键事件
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_j:
                    self.gameStatus = 2
                    self.life -= 1

            # 4.1.6 处理自定义事件(声音延迟)
            if event.type == self.START_SOUND_EVENT:
                self.sound_source.play_sound(self.current_music, 0)
                # 取消计时器,防止重复触发
                pygame.time.set_timer(self.START_SOUND_EVENT, 0)

        # 4.2 监听游戏开始状态事件
        if self.gameStatus == 1:
            # 4.2.1 生成水果(根据难调整不同速度)
            if self.score < 50:
                self.throw_fruits.throw(self.fruits, 0)
            elif 50 <= self.score < 100:
                self.throw_fruits.throw(self.fruits, 1)
            else:
                self.throw_fruits.throw(self.fruits, 2)

            # 4.2.2 检查扔出的水果是否被切到
            for fruit in self.fruits:
                # 4.2.2.1检测是否在可切范围内
                if fruit.whether_cut:
                    # 4.2.2.1.1 检查水果是否被切到
                    if self.check_fruit_collision(fruit) is not None:
                        fruit.current_cut = True
                        #4.2.2.1.1.1 添加切成两半的水果
                        if fruit.image == self.gameImages.fruits[0]:
                            half_apple1 = HalfFruit(self.screen, self.gameImages.half_fruits[0], fruit.rect.x,
                                                    fruit.rect.y, 0 )
                            half_apple2 = HalfFruit(self.screen, self.gameImages.half_fruits[1], fruit.rect.x,
                                                    fruit.rect.y, 1)
                            self.half_fruits.append(half_apple1)
                            self.half_fruits.append(half_apple2)
                        if fruit.image == self.gameImages.fruits[1]:
                            half_banana1 = HalfFruit(self.screen, self.gameImages.half_fruits[2], fruit.rect.x,
                                                     fruit.rect.y, 0)
                            half_banana2 = HalfFruit(self.screen, self.gameImages.half_fruits[3], fruit.rect.x,
                                                     fruit.rect.y, 1)
                            self.half_fruits.append(half_banana1)
                            self.half_fruits.append(half_banana2)
                        if fruit.image == self.gameImages.fruits[2]:
                            half_peach1 = HalfFruit(self.screen, self.gameImages.half_fruits[4], fruit.rect.x,
                                                    fruit.rect.y, 0)
                            half_peach2 = HalfFruit(self.screen, self.gameImages.half_fruits[5], fruit.rect.x,
                                                    fruit.rect.y, 1)
                            self.half_fruits.append(half_peach1)
                            self.half_fruits.append(half_peach2)
                        if fruit.image == self.gameImages.fruits[3]:
                            half_watermelon1 = HalfFruit(self.screen, self.gameImages.half_fruits[6], fruit.rect.x,
                                                         fruit.rect.y, 0)
                            half_watermelon2 = HalfFruit(self.screen, self.gameImages.half_fruits[7], fruit.rect.x,
                                                         fruit.rect.y, 1)
                            self.half_fruits.append(half_watermelon1)
                            self.half_fruits.append(half_watermelon2)
                        if fruit.image == self.gameImages.fruits[4]:
                            half_strawberry1 = HalfFruit(self.screen, self.gameImages.half_fruits[8], fruit.rect.x,
                                                         fruit.rect.y, 0)
                            half_strawberry2 = HalfFruit(self.screen, self.gameImages.half_fruits[9], fruit.rect.x,
                                                         fruit.rect.y, 1)
                            self.half_fruits.append(half_strawberry1)
                            self.half_fruits.append(half_strawberry2)
                        self.fruits.remove(fruit)

                # 4.2.2.2 如果没有被切到,看他是超出屏幕范围
                elif fruit.show_cross and not fruit.current_cut and fruit.type == 0:
                    self.life -= 1
                    if self.score > 0:
                        self.score -= 10
                    self.fruits.remove(fruit)

                # 4.2.2.3 其他情况(如果没有被切到,是炸弹的情况)
                else:
                    self.fruits.remove(fruit)

                # 4.2.2.4 处理被切半水果的平抛运动
                self.throw_fruits.throw_half_fruit(self.half_fruits)

            # 4.2.3 监听屏幕中的水果数量事件
            # 4.2.3.1 如果屏幕中的水果数量小于1,则重新初始化水果,抛出水果
            if len(self.fruits) < 1:
                if self.score < 50:
                    self.throw_fruit_number = 1
                elif 50 <= self.score < 200:
                    self.throw_fruit_number = 2
                elif 200 <= self.score < 400:
                    self.throw_fruit_number = 3
                elif 400 <= self.score < 500:
                    self.throw_fruit_number = 4
                elif 500 <= self.score < 700:
                    self.throw_fruit_number = 5
                else:
                    self.throw_fruit_number = 15
                self.init_fruits(self.throw_fruit_number)

    # 5. 绘制游戏画面
    def paint(self):
        # 5.1 有游戏主菜单页面绘制
        if self.gameStatus == 0:
            # 5.1.1 绘制主菜单背景
            self.paint_background()

        # 5.2 游戏开始页面绘制
        if self.gameStatus == 1:
            # 5.2.1 绘制游戏开始背景
            self.paint_background_start()
            # 5.2.2 绘制所有水果
            for fruit in self.fruits:
                fruit.draw()

            # 5.2.3 绘制被切半的水果
            for half in self.half_fruits:
                half.draw()

        # 5.3 游戏结束页面绘制
        if self.gameStatus == 2:
            # 5.3.1 清空屏幕
            self.half_fruits.clear()
            self.fruits.clear()
            # 5.3.2 绘制游戏结束背景
            self.paint_background_start()
            self.paint_background_end()

        # 5.4 绘制刀光效果
        if self.mouse_pressed:
            for pos in self.mouse_positions:
                self.knife.show_flash(pos[0] - self.knife.image.get_width() // 2,
                                      pos[1] - self.knife.image.get_height() // 2)

    #6. 绘制主菜单背景页面
    def paint_background(self):
        # 6.1 绘制静态元素
        self.screen.blit(pygame.transform.scale(self.gameImages.bg[0], (925, 700)), (0, 0))
        self.screen.blit(pygame.transform.scale(self.gameImages.bg[1], (925, 250)), (0, 0))
        self.screen.blit(self.gameImages.bg[2], (50, 10))
        self.screen.blit(self.gameImages.bg[3], (360, 50))
        self.screen.blit(pygame.transform.scale(self.gameImages.bg[7], (288, 144)), (600, 50))
        self.screen.blit(self.gameImages.bg[4], (50, 150))
        self.screen.blit(self.gameImages.bg[8], (345, 335))
        # 6.2 绘制背景上旋转体元素
        self.bg_element[0].draw()
        self.bg_element[1].draw()
        self.bg_element[2].draw()
        self.bg_element[3].draw()

    # 7. 绘制游戏开始背景页面
    def paint_background_start(self):
        # 7.1 绘制静态元素
        self.screen.blit(pygame.transform.scale(self.gameImages.bg[0], (925, 700)), (0, 0))
        self.screen.blit(pygame.transform.scale(self.gameImages.start_bg[0], (60, 60)), (10, 10))
        self.paint_life()
        self.draw_score()

    # 8. 绘制游戏结束背景页面
    def paint_background_end(self):
        self.screen.blit(self.gameImages.start_bg[1],
                         ((self.screen.get_width() - self.gameImages.start_bg[1].get_width()) / 2,
                          (self.screen.get_height() - self.gameImages.start_bg[1].get_height()) / 2))

    # 9. 绘制生命
    def paint_life(self):
        # 9.1 满生命值
        if self.life == 3:
            self.screen.blit(pygame.transform.scale(self.gameImages.life[0], (40, 40)), (770, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life[1], (50, 50)), (810, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life[2], (60, 60)), (860, 10))

        # 9.2 生命值减一
        if self.life == 2:
            self.screen.blit(pygame.transform.scale(self.gameImages.life_over[0], (40, 40)), (770, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life[1], (50, 50)), (810, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life[2], (60, 60)), (860, 10))

        # 9.3 生命值减二
        if self.life == 1:
            self.screen.blit(pygame.transform.scale(self.gameImages.life_over[0], (40, 40)), (770, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life_over[1], (50, 50)), (810, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life[2], (60, 60)), (860, 10))

        # 9.4 生命值清零
        if self.life == 0:
            self.screen.blit(pygame.transform.scale(self.gameImages.life_over[0], (40, 40)), (770, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life_over[1], (50, 50)), (810, 10))
            self.screen.blit(pygame.transform.scale(self.gameImages.life_over[2], (60, 60)), (860, 10))
            self.gameStatus = 2

    # 10. 绘制分数
    def draw_score(self):
        # 生成分数文本
        score_text = self.font.render(f"Score: {self.score}", True, (0, 255, 0))
        # 绘制分数文本到屏幕
        self.screen.blit(score_text, (75, 35))  # 可以调整位置

    # 11. 碰撞检测
    def check_fruit_collision(self, collision_object):
        # 11.1 遍历刀光的路径
        for i in range(len(self.mouse_positions) - 1):
            start_pos = self.mouse_positions[i]
            end_pos = self.mouse_positions[i + 1]
            # 11.1.1 检测路径与碰撞对象的矩形是否相交(碰撞检测)
            if self.line_rect_collision(start_pos, end_pos, collision_object.rect) and collision_object.whether_cut:
                # 11.1.1.1 切到水果
                if collision_object.type == 0:
                    # 11.1.1.1.1设置碰撞水果时的声音
                    self.sound_source.stop_sound()
                    # 设置当前音乐为切水果音乐
                    self.current_music = 'splatter'
                    # 播放新的音乐
                    self.sound_source.play_sound(self.current_music, 0)
                    # 11.1.1.1.2 切到水果增加分数
                    self.score += 10
                    # 11.1.1.1.3 设置水果不可被切下落
                    collision_object.whether_cut = False
                # 11.1.1.2 切到炸弹
                if collision_object.type == 1:
                    self.sound_source.stop_sound()
                    self.current_music = 'boom'
                    self.sound_source.play_sound(self.current_music, 0)
                    self.gameStatus = 2
                return collision_object

    # 12. 检测线段与矩形是否相交
    def line_rect_collision(self, start_pos, end_pos, rect):
        # 12.1 获取碰撞物体矩形的四个角
        rect_points = [
            (rect.left, rect.top),
            (rect.right, rect.top),
            (rect.right, rect.bottom),
            (rect.left, rect.bottom)
        ]
        # 12.2检测线段与矩形的每一边是否相交
        for i in range(4):
            rect_start = rect_points[i]
            rect_end = rect_points[(i + 1) % 4]
            if self.lines_intersect(start_pos, end_pos, rect_start, rect_end):
                return True
        return False

    # 13. 向量叉积检测
    @staticmethod
    def lines_intersect(p1, p2, p3, p4):
        # 13.1 检测两条线段是否相交
        def ccw(a, b, c):
            return (c[1] - a[1]) * (b[0] - a[0]) > (b[1] - a[1]) * (c[0] - a[0])
        return ccw(p1, p3, p4) != ccw(p2, p3, p4) and ccw(p1, p2, p3) != ccw(p1, p2, p4)


# 14. 程序入口
if __name__ == '__main__':
    # 14.1 启动游戏
    FruitNinjaGame().frame()
安装教程
  1. 确保已经安装 Python 3.x。

  2. 使用以下命令安装 Pygame:

    pip install pygame
    
  3. 克隆游戏仓库到本地:

    git clone https://gitee.com/Victorzl/fruit-ninja.git
    
  4. 进入项目目录并运行游戏:

    python FruitNinjaGame.py
    
使用说明
  1. 启动游戏: 运行 FruitNinjaGame.py 文件,游戏窗口将会打开。
  2. 游戏玩法:
    • 使用鼠标左键切割屏幕上的水果。
    • 避免触碰炸弹,炸弹会减少玩家的生命值。
  3. 查看分数和生命值: 游戏窗口的左上角会显示当前分数和剩余生命值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Victor ZL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值