《一起打怪兽吧》——自制一款Python小游戏

news/2025/2/26 3:12:07

《一起消灭怪兽吧》——在深夜的屏幕前,你是指引光明的勇者。键盘化作利剑,用方向键在像素战场游走,发射吧,每次击杀都有代码绽放的烟火。这款由Python与Pygame铸就的小游戏,让0与1的世界生长出童真的浪漫。

文章目录

一、概要

二、游戏界面

三、游戏介绍

1)游戏特性:

2)游戏控制:

四、整体架构流程

1)游戏采用典型的三层架构:

2)核心类结构:

五、技术细节

1)资源管理

2)游戏逻辑

3)碰撞检测

4)怪物特性

5)状态管理

6)渲染优化

7)输入处理

六、需要的文件

七、完整代码

八、打包成exe

1)准备工作:

2)打包步骤:

九、总结

1)技术亮点

2)扩展性分析

一、概要

本游戏是基于Pygame框架开发的2D射击类游戏,采用经典街机游戏设计范式。游戏实现了玩家控制、怪物生成、碰撞检测、音效系统、UI界面等核心功能,并包含难度升级机制和游戏状态管理系统。项目代码量约400行,采用面向对象编程范式,通过精灵类(Sprite)实现游戏元素的模块化管理,具有较好的可扩展性。

二、游戏界面

三、游戏介绍

1)游戏特性:

三种不同类型的怪兽(普通怪,快速怪,boss)
随分数提升自动升级,每级提升飞机子弹攻速,并增加怪物数量
显示生命值、分数和等级
爆炸效果和音效
游戏结束后可重新开始

2)游戏控制:

上下左右方向键控制飞机移动
自动发射子弹
鼠标点击也可以发射子弹

四、整体架构流程

1)游戏采用典型的三层架构:

  1. 表现层:Pygame的Surface对象渲染系统

  2. 逻辑层

    • 精灵管理系统(Sprites)

    • 碰撞检测系统

    • 游戏状态机(运行/暂停/结束)

  3. 数据层

    • 图像/音频资源加载

    • 游戏状态持久化(分数/生命值)

2)核心类结构:

classDiagram
    class Plane
    class Bullet
    class Enemy
    class FastEnemy
    class BossEnemy
    class Explosion
    
    Plane --> Bullet : 生成
    Enemy <|-- FastEnemy
    Enemy <|-- BossEnemy
    Bullet --> Enemy : 碰撞
    Plane --> Enemy : 碰撞
    Enemy --> Explosion : 触发

五、技术细节

1)资源管理

  • 图像加载

  • 采用PIL库进行动态尺寸调整(Image.resize()),通过pygame.image.fromstring()实现跨库图像转换,支持alpha通道处理(convert_alpha()

  • 音频系统

    • 背景音乐循环播放(pygame.mixer.music.play(-1)

    • 音效异常处理机制(try-except块)

  • 资源回退

  • 当资源加载失败时自动生成红色占位Surface,保证游戏可运行性

2)游戏逻辑

  • 怪物生成算法

enemy_type = random.random()
if enemy_type < 0.7:  # 70%普通敌机
    enemy = Enemy()
elif enemy_type < 0.9:  # 20%快速敌机
    enemy = FastEnemy()
else:  # 10%BOSS敌机
    enemy = BossEnemy()

  • 难度升级机制
    每累计1000分提升玩家等级,增强子弹速度(+0.2px/frame)和移动速度(+1px/frame),曾加生命值10(生命值满100则不增加),增加敌人数量0.3倍。

3)碰撞检测

采用Pygame内置碰撞检测方法:

  • 子弹-怪物碰撞:groupcollide()

  • 玩家-怪物碰撞:spritecollide()

  • 分层碰撞处理(BOSS需被击中5次)

4)怪物特性

为不同类型的怪物设置了不同的移动速度:
普通怪物:2-3
快速怪物:4-6
BOSS:1-2


你可以通过调整以下参数来改变游戏难度:

enemy_spawn_rate:值越小,怪物生成越频繁
各种敌机的speed范围:值越大,怪物下落越快
bullet_interval:值越小,子弹发射越频繁

5)状态管理

  • 游戏状态机

if not paused and not game_over:
    # 游戏逻辑
elif game_over:
    # 结束菜单处理
  • 生命值系统
    使用全局变量life管理,碰撞扣除10点,BOSS逃脱扣除20点

6)渲染优化

  • 使用clock.tick(60)保持60FPS帧率

  • 分层绘制策略:

    1. 背景清除

    2. 精灵组批量绘制(all_sprites.draw()

    3. UI叠加渲染

7)输入处理

  • 键盘事件:方向键控制移动,P键暂停

  • 鼠标事件:左键射击

  • 菜单导航:上下方向键选择,回车确认

六、需要的文件

1. 需要的资源文件:

player.png(玩家飞机)
enemy.png(普通怪物)
fast_enemy.png(快速怪物)
boss.png(BOSS)
explosion1.png, explosion2.png, explosion3.png(爆炸效果)
background.mp3(背景音乐)
shoot.wav(射击音效)
explosion.wav(爆炸音效)
game_over.wav(游戏结束音效)

2. 图片要求:

建议使用PNG格式的图片,支持透明背景
参考尺寸:
普通怪物:50x50像素
快速怪物:40x40像素
BOSS:100x100像素

* 如果缺少某些资源文件,游戏会使用替代的简单图形,不会影响游戏的正常运行。

* 有需要的话提供百度网盘地址可以下载图片和音频:

链接: https://pan.baidu.com/s/1CSCgSLswcwXSvocHzZ031A?pwd=ce7t提取码: ce7t

七、完整代码

import pygame
import random
import os
from PIL import Image

# 初始化Pygame
pygame.init()

# 设置屏幕尺寸
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

# 设置标题
pygame.display.set_caption("打怪兽")

# 定义颜色
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
GRAY = (128, 128, 128)

# 修改后的加载图片函数
def load_image(name, size=None):
    try:
        fullname = os.path.join(os.path.dirname(__file__), name)

        # 使用PIL打开和处理图片
        pil_image = Image.open(fullname)
        if size:
            pil_image = pil_image.resize(size, Image.Resampling.LANCZOS)

        # 转换为pygame surface
        mode = pil_image.mode
        size = pil_image.size
        data = pil_image.tobytes()

        py_image = pygame.image.fromstring(data, size, mode)
        py_image = py_image.convert_alpha()

        print(f"图片加载成功: {name}")
        return py_image
    except Exception as e:
        print(f"图片 {name} 加载失败: {e}")
        print(f"当前工作目录: {os.getcwd()}")
        surface = pygame.Surface(size if size else (50, 50), pygame.SRCALPHA)
        surface.fill((255, 0, 0, 128))
        return surface

# 加载音乐和音效
pygame.mixer.init()
try:
    bg_music = pygame.mixer.music.load("background.mp3")
    pygame.mixer.music.play(-1)
except:
    print("背景音乐加载失败,继续使用无音乐模式。")

try:
    shoot_sound = pygame.mixer.Sound("shoot.wav")
except:
    shoot_sound = None

try:
    explosion_sound = pygame.mixer.Sound("explosion.wav")
except:
    explosion_sound = None

try:
    game_over_sound = pygame.mixer.Sound("game_over.wav")
except:
    game_over_sound = None

# 定义飞机类
class Plane(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = load_image("player.png", (50, 50))
        self.rect = self.image.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT - 100))
        self.speed = 8
        self.upgrade_level = 1
        self.bullet_speed = 10
        self.bullet_frequency = 10  # 初始子弹发射间隔

    def update(self):
        keys = pygame.key.get_pressed()
        # 左右移动
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT] and self.rect.right < SCREEN_WIDTH:
            self.rect.x += self.speed
        # 上下移动
        if keys[pygame.K_UP] and self.rect.top > 0:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN] and self.rect.bottom < SCREEN_HEIGHT:
            self.rect.y += self.speed

# 定义子弹类
class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y, speed):
        super().__init__()
        self.image = pygame.Surface((5, 10))
        self.image.fill(RED)
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = speed
        if shoot_sound:
            shoot_sound.play()

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

# 定义敌机基类
class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = load_image("enemy.png", (50, 50))
        self.rect = self.image.get_rect()
        self.rect.x = random.randint(0, SCREEN_WIDTH - self.rect.width)
        self.rect.y = -self.rect.height
        self.speed = random.randint(2, 3)
        self.score = 10

    def update(self):
        global life
        self.rect.y += self.speed
        if self.rect.top > SCREEN_HEIGHT:
            if life > 0:
                life -= 10
            self.kill()

# 定义快速敌机类
class FastEnemy(Enemy):
    def __init__(self):
        super().__init__()
        self.image = load_image("fast_enemy.png", (40, 40))
        self.rect = self.image.get_rect()
        self.rect.x = random.randint(0, SCREEN_WIDTH - self.rect.width)
        self.rect.y = -self.rect.height
        self.speed = random.randint(4, 5)
        self.score = 20

# 定义BOSS敌机类
class BossEnemy(Enemy):
    def __init__(self):
        super().__init__()
        self.image = load_image("boss.png", (100, 100))
        self.rect = self.image.get_rect()
        self.rect.x = random.randint(0, SCREEN_WIDTH - self.rect.width)
        self.rect.y = -self.rect.height
        self.speed = random.randint(1, 2)
        self.score = 50
        self.hp = 5

    def update(self):
        global life
        self.rect.y += self.speed
        if self.rect.top > SCREEN_HEIGHT:
            if life > 0:
                life -= 20
            self.kill()


# 定义爆炸效果类
class Explosion(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.images = []
        for i in range(1, 4):
            try:
                self.images.append(load_image(f"explosion{i}.png", (50, 50)))
            except:
                surface = pygame.Surface((50, 50))
                surface.fill(RED)
                self.images.append(surface)
        self.image = self.images[0]
        self.rect = self.image.get_rect(center=(x, y))
        self.frame = 0
        self.frame_rate = 0
        if explosion_sound:
            explosion_sound.play()

    def update(self):
        self.frame_rate += 1
        if self.frame_rate >= 10:
            self.frame += 1
            if self.frame < len(self.images):
                self.image = self.images[self.frame]
            else:
                self.kill()
            self.frame_rate = 0


# 添加游戏重置函数
def reset_game():
    global life, score, plane, bullets, enemies, explosions, all_sprites, enemy_spawn_multiplier
    life = 100
    score = 0
    enemy_spawn_multiplier = 1.0
    bullets.empty()
    enemies.empty()
    explosions.empty()
    all_sprites.empty()
    plane = Plane()
    all_sprites.add(plane)


# 开始界面绘制函数
def draw_start_screen():
    title_text = font.render("Fight Monsters", True, WHITE)
    start_text = font.render("Start Game", True, WHITE if menu_selection == 0 else GRAY)
    quit_text = font.render("Quit Game", True, WHITE if menu_selection == 1 else GRAY)

    if menu_selection == 0:
        start_text = font.render("> Start Game", True, WHITE)
        quit_text = font.render("  Quit Game", True, GRAY)
    else:
        start_text = font.render("  Start Game", True, GRAY)
        quit_text = font.render("> Quit Game", True, WHITE)

    title_rect = title_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 3))
    start_rect = start_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2))
    quit_rect = quit_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 50))

    screen.blit(title_text, title_rect)
    screen.blit(start_text, start_rect)
    screen.blit(quit_text, quit_rect)


# 创建精灵组
plane = Plane()
bullets = pygame.sprite.Group()
enemies = pygame.sprite.Group()
explosions = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(plane)

# 设置时钟
clock = pygame.time.Clock()

# 游戏变量
bullet_interval = 0
enemy_interval = 0
enemy_spawn_rate = 60
enemy_spawn_multiplier = 1.0  # 敌人生成倍率
life = 100
score = 0
paused = False
menu_selection = 0
game_state = "START"

# 设置字体
font = pygame.font.Font(None, 36)

# 主游戏循环
running = True
while running:
    # 处理事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.KEYDOWN:
            if game_state == "START":
                if event.key == pygame.K_UP:
                    menu_selection = 0
                elif event.key == pygame.K_DOWN:
                    menu_selection = 1
                elif event.key == pygame.K_RETURN:
                    if menu_selection == 0:
                        game_state = "PLAYING"
                    else:
                        running = False

            elif game_state == "PLAYING":
                if event.key == pygame.K_p:
                    paused = not paused

            elif game_state == "OVER":
                if event.key == pygame.K_UP:
                    menu_selection = 0
                elif event.key == pygame.K_DOWN:
                    menu_selection = 1
                elif event.key == pygame.K_RETURN:
                    if menu_selection == 0:
                        reset_game()
                        game_state = "PLAYING"
                    else:
                        running = False

    screen.fill(BLACK)

    if game_state == "START":
        draw_start_screen()

    elif game_state == "PLAYING":
        if not paused:
            # 发射子弹
            bullet_interval += 1
            if bullet_interval >= plane.bullet_frequency:
                bullets.add(Bullet(plane.rect.centerx, plane.rect.top, plane.bullet_speed))
                bullet_interval = 0

            # 生成敌机
            enemy_interval += 1
            if enemy_interval >= enemy_spawn_rate:
                # 根据倍率生成多个敌人
                enemies_to_spawn = max(1, int(enemy_spawn_multiplier))
                for _ in range(enemies_to_spawn):
                    enemy_type = random.random()
                    if enemy_type < 0.7:
                        enemy = Enemy()
                    elif enemy_type < 0.9:
                        enemy = FastEnemy()
                    else:
                        enemy = BossEnemy()
                    enemies.add(enemy)
                    all_sprites.add(enemy)
                enemy_interval = 0

            # 更新精灵
            all_sprites.update()
            bullets.update()
            enemies.update()
            explosions.update()

            # 碰撞检测
            hits = pygame.sprite.groupcollide(bullets, enemies, True, False)
            for bullet, enemies_hit in hits.items():
                for enemy in enemies_hit:
                    if isinstance(enemy, BossEnemy):
                        enemy.hp -= 1
                        if enemy.hp <= 0:
                            enemy.kill()
                            score += enemy.score
                            explosions.add(Explosion(enemy.rect.centerx, enemy.rect.centery))
                    else:
                        enemy.kill()
                        score += enemy.score
                        explosions.add(Explosion(enemy.rect.centerx, enemy.rect.centery))

            # 敌机击中飞机
            hits = pygame.sprite.spritecollide(plane, enemies, True)
            if hits:
                life = max(0, life - 10)
                explosions.add(Explosion(plane.rect.centerx, plane.rect.centery))

            # 检查升级
            if score >= plane.upgrade_level * 1000:
                plane.upgrade_level += 1
                plane.bullet_speed += 2
                plane.speed += 1
                # 子弹频率随等级提升而减少(发射更密集)
                plane.bullet_frequency = max(1.2, 10 // plane.upgrade_level)
                # 增加生命值
                life = min(100, life + 10)  # 限制最大生命值为100
                # 增加敌人生成倍率
                enemy_spawn_multiplier += 0.3

        # 绘制游戏画面
        all_sprites.draw(screen)
        bullets.draw(screen)
        enemies.draw(screen)
        explosions.draw(screen)

        # 显示游戏信息
        life_text = font.render(f"Life: {life}", True, WHITE)
        score_text = font.render(f"Score: {score}", True, WHITE)
        level_text = font.render(f"Level: {plane.upgrade_level}", True, WHITE)
        screen.blit(life_text, (10, 10))
        screen.blit(score_text, (10, 50))
        screen.blit(level_text, (10, 90))

        if paused:
            pause_text = font.render("Paused", True, WHITE)
            screen.blit(pause_text, (SCREEN_WIDTH // 2 - 50, SCREEN_HEIGHT // 2))

    elif game_state == "OVER":
        game_over_text = font.render("Game Over", True, RED)
        restart_text = font.render("Restart", True, WHITE if menu_selection == 0 else GRAY)
        quit_text = font.render("Quit", True, WHITE if menu_selection == 1 else GRAY)
        final_score_text = font.render(f"Final Score: {score}", True, WHITE)

        if menu_selection == 0:
            restart_text = font.render("> Restart", True, WHITE)
            quit_text = font.render("  Quit", True, GRAY)
        else:
            restart_text = font.render("  Restart", True, GRAY)
            quit_text = font.render("> Quit", True, WHITE)

        screen.blit(game_over_text, (SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 - 75))
        screen.blit(restart_text, (SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 - 25))
        screen.blit(quit_text, (SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 + 25))
        screen.blit(final_score_text, (SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 + 75))

    # 检查游戏结束
    if life <= 0 and game_state == "PLAYING":
        if game_over_sound:
            game_over_sound.play()
        game_state = "OVER"

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

# 退出游戏
pygame.quit()

八、打包成exe

1)准备工作:

确保已安装Python环境(建议3.6+版本)

安装打包工具(推荐使用PyInstaller):pip install pyinstaller

2)打包步骤:

1. 定位到项目目录
打开命令行(CMD/PowerShell/终端)
使用cd命令进入游戏代码所在目录:
cd C:\path\to\your\game

2. 基础打包命令
pyinstaller --onefile --windowed game.py
--onefile:生成单个exe文件
--windowed:隐藏控制台窗口(适用于图形界面游戏)
将game.py替换为你的主程序文件名

3. 处理资源文件(如图片/音效等)
如果游戏包含外部资源文件,需要:修改代码中的资源路径为临时路径

import sys
import os

def resource_path(relative_path):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)

# 使用示例
image = pygame.image.load(resource_path("images/monster.png"))

4. 获取exe文件
打包完成后,在项目目录下会生成:
dist/文件夹 ➔ 包含最终exe文件
build/文件夹 ➔ 临时文件(可删除)

5. 进阶选项

参数说明
--icon=game.ico添加exe图标
--noconsole完全隐藏控制台
--add-data "src;dest"添加额外文件
--upx-dir UPX_DIR使用UPX压缩exe体积

九、总结

1)技术亮点

  1. 采用多态继承实现敌机类型扩展

  2. 异常处理机制保障程序健壮性

  3. 动态难度调整提升游戏可玩性

  4. 使用PIL实现跨分辨率适配

2)扩展性分析

当前架构支持以下扩展:

  • 新怪物类型:继承Enemy类并重写属性

  • 武器系统:在Plane类中添加武器类型属性

  • 成就系统:通过装饰器模式跟踪游戏事件

游戏实现经典射击游戏的核心机制,通过Pygame框架在2D游戏开发中的高效性、模块化设计和面向对象方法,使代码具备良好的可维护性和扩展性。

祝你和孩子玩的开心!如果喜欢这款小游戏的话记得点赞收藏加关注哦!


http://www.niftyadmin.cn/n/5867113.html

相关文章

Open WebUI 是什么

Open WebUI 是什么 Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离线运行。它支持各种 LLM 运行器,如 Ollama 和 OpenAI 兼容的 API,并内置了 RAG 推理引擎,使其成为强大的 AI 部署解决方案。 https://github.com/open-webui/open-webui 🚀 …

Spring Boot + JSqlParser:全面解析数据隔离最佳实践

Spring Boot JSqlParser&#xff1a;全面解析数据隔离最佳实践 在构建多租户系统或需要进行数据权限控制的应用时&#xff0c;数据隔离是一个至关重要的课题。不同租户之间的数据隔离不仅能够确保数据的安全性&#xff0c;还能提高系统的灵活性和可维护性。随着业务的扩展和需…

<tauri><rust><GUI><PLC>基于tauri,编写一个串口485调试助手

前言 本文是基于rust和tauri,由于tauri是前、后端结合的GUI框架,既可以直接生成包含前端代码的文件,也可以在已有的前端项目上集成tauri框架,将前端页面化为桌面GUI。 环境配置 系统:windows 10平台:visual studio code语言:rust、javascript库:tauri2.0概述 本文基…

51单片机-AT24CXX存储器工作原理

1、AT24CXX存储器工作原理 1.1、特点&#xff1a; 与400KHz&#xff0c;I2C总线兼容1.8到6.0伏工作电压范围低功耗CMOS技术写保护功能当WP为高电平时进入写保护状态页写缓冲器自定时擦写周期100万次编程/擦除周期可保存数据100年8脚DIP SOIC或TSSOP封装温度范围商业级和工业级…

Git-速查

Git 安装 Git 之后&#xff0c;你可以… 配置全局用户信息&#xff08;推荐&#xff09; 全局设置&#xff0c;创建本地仓库时默认分支名称为 main&#xff08;你需要什么名称就该什么名称&#xff09;【推荐配置为 main 】 git config --global init.defaultBranch main全…

[Linux]从零开始的STM32MP157 U-Boot网络命令讲解及相关配置

一、前言 在上一次的STM32MP157的教程中&#xff0c;教大家STM32MP157基础命令的使用&#xff0c;同时也验证了我们STM32MP157U-Boot的部分功能。因为U-Boot的网络配置部分比较复杂&#xff0c;所以在上一次的教程中并没有涉及。那么本次教程&#xff0c;我会为大家详细的讲解U…

面试八股文--数据库基础知识总结(1)

1、数据库的定义 数据库&#xff08;DataBase&#xff0c;DB&#xff09;简单来说就是数据的集合数据库管理系统&#xff08;Database Management System&#xff0c;DBMS&#xff09;是一种操纵和管理数据库的大型软件&#xff0c;通常用于建立、使用和维护数据库。数据库系统…

在scss中使用for循环生成头像/icon

在业务中有时会出现相同样式不同icon的情况出现&#xff0c;如果我们使用scss中的for循环的话这个问题很好解决 先列出标签结构 <div v-for"item in function_data" :key"item.id" class"signal_btn"><div :class"btn_avatar (…