
开学练手项目最怕两件事要么代码太简单跑完没收获要么依赖太重环境配了半天连窗口都打不开。这次我们来看一个用 Python 写“拳皇格斗”对战小游戏的实践项目。它不涉及复杂的 3D 引擎也不需要调用任何在线服务核心就是 pygame 加面向对象思路玩家控制、攻击判定、血量计算、胜负判断、简单 AI全部自己写出来。如果你是在校生准备交 Python 课程设计或者刚学完基础语法想找一个能放到简历里的项目实战这个题材很合适。它比“图书管理系统”更有辨识度比“俄罗斯方块”更能体现逻辑设计而且开发周期可控一个人用两三个晚上就能做出一个可以演示的版本。本文会带你把环境准备、pygame 安装、角色类设计、攻击判定、AI 控制和打包发布全部走通代码框架可以直接复制改造成自己的作品。这个项目还有一个好处硬件门槛极低。普通笔记本 CPU 就能流畅跑不需要 GPU不需要训练模型不涉及 API 调用也不依赖 50 系或 40 系之类的显卡。只要能安装 Python 3.8 以上环境就能完成本地开发。下面直接进入正题。1. 核心能力速览项目类型Python 小游戏实战面向初学者的 pygame 格斗游戏建议语言版本Python 3.8 及以上64 位优先核心依赖pygame用 pip 安装即可主要功能双人对战、简单 AI 对战、攻击与防御、血量条、胜负判定、重新开局硬件要求普通 CPU 即可无需显卡启动方式命令行运行python main.py是否支持 API不支持本地桌面程序是否支持批量任务不涉及适合场景Python 课程设计、毕业设计预演、pygame 入门练习、面向对象编程训练扩展方向连招系统、角色选择、技能特效、音效、关卡模式、数据保存这里要特别说清楚项目本身是“仿拳皇”的游戏机制不是商业游戏源码。真实的拳皇角色形象、场景素材和音乐都属于版权范围个人学习时不要直接搬运商业素材。后面的代码示例全部使用自定义色块和占位素材你有余力的话可以自己画像素角色或者使用明确允许免费商用的游戏素材。2. 适用场景与使用边界这个项目适合三类人刚学完 Python 函数、类、循环和列表想找个综合练习的人需要提交 Python 课设或期末项目的人打算往游戏开发方向走想先了解游戏循环、碰撞检测和状态机的人。它能解决的问题很具体帮你理解游戏主循环“事件处理 - 更新逻辑 - 绘制画面 - 控制帧率”的基本流程帮你理解面向对象里类与对象怎么组织帮你理解碰撞检测和简单状态切换比如站立、移动、攻击、防御、受击这些动画状态。但也要说清楚它不适合什么如果你想做一个接近商业街机体验的完整格斗游戏这个项目的基础框架只是起点后面还要加美术、动画、帧表、连招判断、音频、手柄适配工作量和难度都会明显上升。它不适合作为游戏引擎入门pygame 只是帮你处理窗口和绘制引擎级的功能还是要用 Unity、Godot 这类工具。合规使用边界同样要注意。第一不要直接使用拳皇的角色立绘、头像、开场动画和背景音乐做素材。第二如果你的项目要上传到 GitHub、Gitee 或者交到学校评审建议把作品写成“Python 格斗小游戏”并在 README 里注明“仿照经典格斗游戏的玩法素材全部为自制或可商用素材”。第三涉及网络联机对战时要注意玩家昵称、聊天信息和服务端数据的安全不要在项目里收集与游戏无关的个人信息。3. 环境准备与前置条件开发环境不复杂按下面清单检查即可。3.1 操作系统与 PythonWindows、macOS 或 Linux 都可以。游戏是纯桌面的不依赖特定系统。Python 版本建议 3.8 到 3.12 之间太老的环境容易出现依赖兼容问题。打开命令行先确认 Python 是否可用python --version如果提示找不到命令可以试试python3 --version。正常会输出类似Python 3.10.11没有安装 Python 的话去官网下载对应系统安装包安装时注意勾选“Add Python to PATH”这一步很关键不然命令行里找不到 Python。3.2 安装 pygamepygame 是轻量级的 2D 游戏开发库负责窗口创建、键盘事件、图像绘制和声音播放。安装命令pip install pygame国内网络环境如果下载慢可以使用镜像源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后验证python -c import pygame; print(pygame.version.ver)能输出版本号就说明安装成功。如果你的 Python 环境里同时有多个虚拟环境建议先创建独立的虚拟环境再安装避免把不同项目的依赖混在一起。创建虚拟环境的命令python -m venv venvWindows 下激活venv\Scripts\activatemacOS / Linux 下激活source venv/bin/activate3.3 项目目录结构建议从一开始就按这个结构整理文件后面对接打包、上传代码库都会更顺手python-fighting-game/ ├── main.py # 程序入口 ├── fight_character.py # 角色类定义 ├── ai_controller.py # AI 控制逻辑 ├── requirements.txt # 依赖清单 └── assets/ ├── images/ # 贴图素材 └── sounds/ # 音效素材先写主要代码素材目录可以留空等画面和逻辑跑通后再填内容。3.4 运行游戏的基本流程在项目目录下执行python main.py打开后会出现一个窗口。如果窗口一闪而过多半是代码里没有加入“退出事件”的循环或者绘图时死循环中的pygame.quit()提前执行了。后面排查部分会单独说明。4. 游戏设计与核心代码实现先讲设计再给代码。游戏采用边对边格斗的经典方式屏幕左侧是玩家 1右侧是玩家 2双方各有血量条谁先把对方血量打到 0 谁赢。4.1 角色类 Fighter这个类负责管理角色的位置、移动、血量、朝向、攻击状态和受击状态。先写基础属性import pygame class Fighter: def __init__(self, x, y, color, facing1, namePlayer): self.x x self.y y self.width 52 self.height 96 self.color color self.facing facing # 1 表示朝右-1 表示朝左 self.name name self.vx 0 self.vy 0 self.hp 100 self.max_hp 100 self.attack_power 10 self.attack_type 0 self.attacking False self.defending False self.attack_cooldown 0 self.hit_timer 0 self.is_jumping False self.on_ground True这里把所有角色状态都放在一个类里后续要扩展角色选择时可以继承这个基类再分别写不同职业的攻击方式。4.2 移动与边界限制格斗场景需要固定战斗范围防止角色跑出屏幕。坐标用self.x和self.y控制y值越小越靠上地面高度用常量定义方便后续改场景GROUND_Y 500 SCREEN_WIDTH 960 SCREEN_HEIGHT 600 def move(self, dx, dy): new_x self.x dx new_y self.y dy # 左右边界 if new_x 50: new_x 50 elif new_x SCREEN_WIDTH - 50 - self.width: new_x SCREEN_WIDTH - 50 - self.width # 上下边界限制跳跃高度 if new_y GROUND_Y - 160: new_y GROUND_Y - 160 self.x new_x self.y new_y if self.y GROUND_Y - self.height: self.y GROUND_Y - self.height self.on_ground True self.is_jumping False else: self.on_ground False这个写法先限制渲染坐标再把“是否落地”的状态同步给其他逻辑。跳跃时dy为负值向上移动重力则通过主循环里每次加一点vy实现。4.3 攻击判定攻击判定是格斗游戏最核心的部分。简单做法是攻击时生成一个“攻击矩形”矩形位置根据角色朝向偏移如果攻击矩形和对方矩形相交就判定命中。def attack_rect(self): if self.facing 1: return pygame.Rect(self.x self.width, self.y 20, 50, 40) else: return pygame.Rect(self.x - 50, self.y 20, 50, 40) def try_attack(self, target, powerNone): if self.attack_cooldown 0: return False self.attacking True self.attack_cooldown 18 atk power if power is not None else self.attack_power if self.attack_rect().colliderect(target.rect()): # 防御时伤害减半 if target.defending: target.take_damage(atk // 2) return block else: target.take_damage(atk) return hit return misscolliderect是 pygame 内置的矩形碰撞检测方法比手写坐标判断更可靠。命中后主循环里根据返回值更新显示效果比如受击闪烁、音效播放和连击数统计。4.4 受击和防御受击逻辑要控制短暂的无敌帧和受击硬直避免攻击判定每帧都触发导致血量瞬间扣空。用一个受击定时器就能实现def take_damage(self, amount): if self.hit_timer 0: return self.hp - amount if self.hp 0: self.hp 0 self.hit_timer 15 # 受击硬直帧数 def update(self): if self.attack_cooldown 0: self.attack_cooldown - 1 if self.hit_timer 0: self.hit_timer - 1 if not self.attacking: pass4.5 主循环与画面绘制下面给一个可以直接运行的main.py完整骨架。它包含窗口初始化、两个角色创建、键盘事件、AI 调用和画面绘制所有绘制用的都是色块不依赖外部图片import sys import random import pygame from fight_character import Fighter from ai_controller import ai_action SCREEN_WIDTH 960 SCREEN_HEIGHT 600 GROUND_Y 500 FPS 60 BLACK (20, 20, 30) WHITE (240, 240, 240) RED (210, 60, 70) BLUE (70, 140, 230) GREEN (80, 200, 120) YELLOW (240, 200, 60) def draw_hp_bar(screen, x, y, hp, max_hp, color): bar_width 320 bar_height 22 pygame.draw.rect(screen, WHITE, (x, y, bar_width, bar_height), 2) ratio hp / max_hp pygame.draw.rect(screen, color, (x 4, y 4, int((bar_width - 8) * ratio), bar_height - 8)) def draw_fighter(screen, fighter): body_color fighter.color rect pygame.Rect(int(fighter.x), int(fighter.y), fighter.width, fighter.height) pygame.draw.rect(screen, body_color, rect) # 朝向标识朝右时眼睛偏右朝左时偏左 eye_offset 18 if fighter.facing 1 else -18 eye_x fighter.x fighter.width / 2 eye_offset eye_y fighter.y 24 pygame.draw.circle(screen, WHITE, (int(eye_x), int(eye_y)), 6) pygame.draw.circle(screen, BLACK, (int(eye_x) 2 * fighter.facing, int(eye_y)), 3) if fighter.attacking: pygame.draw.circle(screen, YELLOW, (rect.centerx, int(fighter.y 30)), 26, 3) def check_winner(p1, p2): if p1.hp 0: return p2.name if p2.hp 0: return p1.name return None def main(): pygame.init() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(Python 拳皇风格格斗小游戏) clock pygame.time.Clock() p1 Fighter(180, GROUND_Y - 96, BLUE, facing1, nameP1) p2 Fighter(720, GROUND_Y - 96, RED, facing-1, nameP2) game_over False winner None frame_count 0 while True: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() if event.type pygame.KEYDOWN: if event.key pygame.K_r and game_over: p1 Fighter(180, GROUND_Y - 96, BLUE, facing1, nameP1) p2 Fighter(720, GROUND_Y - 96, RED, facing-1, nameP2) game_over False winner None keys pygame.key.get_pressed() if not game_over: # 玩家 P1 控制 if keys[pygame.K_a]: p1.x - 6 if keys[pygame.K_d]: p1.x 6 if keys[pygame.K_w] and p1.on_ground and not p1.is_jumping: p1.vy -16 p1.on_ground False p1.is_jumping True if keys[pygame.K_j]: p1.try_attack(p2, power12) if keys[pygame.K_k]: p1.defending True else: p1.defending False # AI 玩家 P2 控制 ai_action(p2, p1) # 重力与位置更新 for fighter in (p1, p2): fighter.vy 1 fighter.y fighter.vy if fighter.y GROUND_Y - fighter.height: fighter.y GROUND_Y - fighter.height fighter.vy 0 fighter.on_ground True fighter.is_jumping False fighter.x max(50, min(SCREEN_WIDTH - 50 - fighter.width, fighter.x)) fighter.update() frame_count 1 winner check_winner(p1, p2) if winner: game_over True # 绘制画面 screen.fill(BLACK) pygame.draw.rect(screen, (60, 60, 60), (0, GROUND_Y, SCREEN_WIDTH, SCREEN_HEIGHT - GROUND_Y)) draw_hp_bar(screen, 60, 30, p1.hp, p1.max_hp, BLUE) draw_hp_bar(screen, 580, 30, p2.hp, p2.max_hp, RED) draw_fighter(screen, p1) draw_fighter(screen, p2) if game_over: font pygame.font.SysFont(simhei, 56) over_text font.render(WINNER: winner, True, WHITE) tip pygame.font.SysFont(simhei, 28).render(Press R to restart, True, YELLOW) screen.blit(over_text, (SCREEN_WIDTH // 2 - 160, 220)) screen.blit(tip, (SCREEN_WIDTH // 2 - 130, 300)) pygame.display.flip() clock.tick(FPS) if __name__ __main__: main()如果去掉 AI 部分改成两个人用同一套按键逻辑就是经典的双人对战模式。但现在 p2 的控制走的是 AI 函数所以这一个项目同时覆盖“玩家操作”和“电脑对手”两个玩法。4.6 AI 控制逻辑一个简单又能玩的 AI 不需要很复杂它的决策可以只围绕三件事距离太远就靠近距离合适就攻击偶尔防御。写成一个函数方便插到主循环里import random import pygame def ai_action(ai, target): if ai.hp 0: return # 受击或攻击冷却中不执行攻击 if ai.attack_cooldown 0: ai.attacking False return distance abs(target.x - ai.x) # 距离过近降低一半攻击概率 if distance 90: ai.defending True if random.random() 0.45: ai.defending False ai.try_attack(target, power10) elif distance 140: ai.defending False if random.random() 0.55: ai.try_attack(target, power10) else: ai.defending False # 朝目标方向移动 if target.x ai.x: ai.x - 5 else: ai.x 5这个 AI 故意加了一点随机性否则它每一帧都攻击玩家根本没有出招机会。实际跑一下会发现难度比较适中新手能打过但也不会毫无挑战。4.7 主菜单、角色选择和音效扩展基础版本跑通后可以继续加三个模块。主菜单先创建一个menu.py里面循环检测鼠标点击把“开始游戏”、“退出游戏”两个按钮画在屏幕上点击后跳转到战斗场景。角色选择界面可以做成左右切换头像按下回车确认。音效方面pygame.mixer 初始化后用pygame.mixer.Sound(assets/sounds/punch.wav)播放打击音效命中判定返回hit或block时调用播放函数即可。这些扩展不建议一次性全做先把本文的基础代码跑通再逐项加功能。这样万一出现 bug定位范围小很多。5. 功能测试与效果验证写完基础代码按下面的顺序做验证。每个步骤都有明确的通过标准避免“游戏能启动但看不见效果”的问题。5.1 启动测试执行python main.py窗口正常弹出标题栏显示“Python 拳皇风格格斗小游戏”两个彩色角色分别站在屏幕左右两侧下方地面为灰白色上方有蓝色和红色血量条。5.2 移动测试按住A键左侧角色向右移动按住D键向左移动。右侧 AI 角色会自动靠近者移动。角色不会跑出屏幕边界这一点通过碰撞后的坐标最大值和最小值限制。5.3 跳跃测试按W键角色向上起跳松开后自动落回地面。连续按W因为on_ground的判断逻辑存在空中再次起跳会被阻止避免无限跳。5.4 攻击测试靠近对手后按J角色周围会出现黄色圆圈表示攻击发动。如果攻击矩形碰到对手对手的 hp 条会降低。攻击有 18 帧的冷却时间按住J不会变成无限连击。5.5 防御测试按K键进入防御状态。防御时受到攻击伤害减半。从视觉上看防御角色的血量条下降速度明显比正常受击慢。这个功能可以让双人对战时更有策略性。5.6 胜负判定测试把任意一方角色的max_hp临时改成 20再跑一局很快就能看到“WINNER: P1”或“WINNER: P2”的游戏结束画面。按R键重置血量重新开始。测试完成后记得把临时修改的数值改回来。5.7 常见失败原因测试项失败现象排查思路启动测试报 ModuleNotFoundErrorpython -m pip install pygame后重试移动测试按方向键没反应检查事件获取是否写在主循环内pygame.key.get_pressed()没调用到跳跃测试无法起跳检查on_ground状态是否被重力逻辑正确复位攻击测试没有伤害打印attack_rect()和对手rect()的坐标手动确认相交防御测试防御无效检查target.defending是否在攻击开始前就置为 True胜负测试血条归零不结束检查check_winner返回值是否被赋值给winner并被主循环读取6. 接口 API 与批量任务说明这个项目是本地桌面小游戏不提供 HTTP API 接口也不涉及云端批量任务和 Qt 项目那样的事件驱动框架对接。如果你想把游戏对局结果保存下来可以在本地写一个简单的 JSON 文件记录或者用 SQLite 保存积分。这里给一个轻量的成绩记录写入示例适合课设演示时展示“数据持久化”能力import json import os RECORD_FILE records.json def save_result(winner_name, p1_hp, p2_hp): records [] if os.path.exists(RECORD_FILE): with open(RECORD_FILE, r, encodingutf-8) as f: records json.load(f) records.append({ winner: winner_name, p1_hp: p1_hp, p2_hp: p2_hp }) with open(RECORD_FILE, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2)在主循环判定winner时调用这个函数每局结束就保存一条记录。注意如果事后要把项目扩展成“局域网双人对战”则需要引入 socket 编程双方各跑一个实例由服务器转发按键状态和角色位置这是另一个新的练习方向本文不展开。7. 资源占用与性能观察python 小游戏的性能观察不需要看显存也不需要关心 CUDA。重点看三件事FPS 稳定性、CPU 占用和内存占用。运行main.py时主循环里clock.tick(FPS)把速度锁定在每秒 60 帧。如果电脑配置比较旧可以把 FPS 降到 30配合调低窗口分辨率python main.py任务管理器里观察 python 进程正常情况下 CPU 占用不会太高。如果发现 CPU 占用居高不下常见原因是主循环里每一帧都在加载图片或创建字体对象。项目代码中尽量把图片、字体、声音对象放到初始化阶段不要在绘制阶段反复创建。内存占用方面pygame 加载一张大图片和加载十张小图片的差异不明显但如果你后面加了很多角色动画帧建议把每一帧的 Surface 统一缓存到一个字典里避免重复加载class Spritesheet: def __init__(self, path): self.sheet pygame.image.load(path).convert_alpha() def get_image(self, x, y, width, height): image pygame.Surface((width, height), pygame.SRCALPHA) image.blit(self.sheet, (0, 0), (x, y, width, height)) return image使用前确认图片路径存在路径不要写绝对路径用相对路径这样项目拷贝到别的电脑后仍然能运行。8. 常见问题与排查方法新手跑 pygame 项目最容易踩到这几个坑整理成表格遇到问题先对号入座。问题现象可能原因排查方式解决方案pygame模块找不到未安装依赖或激活了错误的虚拟环境终端执行pip show pygame执行pip install pygame或切换虚拟环境安装依赖失败网络超时或镜像源不稳定查看报错信息使用清华源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple窗口一闪而过主循环没有阻塞事件或入口代码直接执行完毕检查while True是否存在确保事件循环在main()中按方向键角色不动键盘焦点不在窗口或pygame.key.get_pressed()顺序写错先点击窗口再试按键检测放在事件循环之后攻击判定打不到人攻击矩形在角色背后打印attack_rect()坐标根据facing修正攻击矩形偏移方向跳跃后飘在半空重力只加了一次没有持续累加观察vy的变化每帧执行fighter.vy 1中文文字显示方框系统字体路径问题检查该字体是否存在使用pygame.font.SysFont(simhei, 56)需要 Windows 自带黑体跨平台建议用字体文件或英文文本打包 exe 后图标缺失pyinstaller 没有打包 assets 目录查看 build 日志用--add-data assets;assets参数高 DPI 屏幕上窗口模糊缩放模式问题检查 pygame 版本在代码开始加入pygame.display.set_mode的 SCALED 参数打包命令示例pip install pyinstaller pyinstaller -F -w --add-data assets;assets main.py-F表示打包成单个文件-w表示运行时不出现控制台窗口--add-data指定素材目录一起复制。9. 最佳实践与使用建议这个项目虽然代码量不大但值得用工程化的方式来做尤其是课程设计评分时代码组织比能跑重要得多。第一把常量集中管理。窗口宽高、角色大小、重力加速度、攻击冷却帧数都应该放在一个config.py里不要散落在各个函数中。修改数值时只需要动一个文件。第二输出日志代替 print 调试。直接 print 在运行结束后就丢了用 Python 的 logging 模块记录攻击命中和状态切换排查起来效率更高import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(fight) # 攻击命中时调用 logger.info(%s hit %s for %d damage, p1.name, p2.name, 12)第三素材管理。不要直接把素材文件扔在项目根目录统一放进assets。通过相对路径读取避免发布后路径失效。如果需要做开源优先选择 CC0、MIT 协议的游戏素材并在 README 中注明来源。第四代码版本控制。从项目第一天就初始化 git每完成一个功能提交一次。这样后期加新动画出问題时可以随时回退到能运行的版本。第五合规检查。如果项目要公开发布检查以下几项角色名是否用了现有商业 IP 名称背景音乐和音效是否有版权授权字体文件是否允许再分发。建议把内置文字改成“PLAYER 1 / PLAYER 2”避免直接使用“八神”“草薙”这类名称。游戏玩法机制不受版权保护但角色形象、名称、美术素材、音乐是受版权保护的。第六性能优化克制一点。初版不要为了“流畅”过度优化先保证逻辑正确再用pygame.sprite.Group和convert_alpha()优化绘制性能。初学阶段过早优化只会让代码更难懂。10. 下一步可以往哪扩展这个 Python 拳皇小游戏最值得先验证的是“攻击判定和血量循环”这两块核心逻辑。你先运行一遍确认碰撞检测稳定、攻击冷却没有让角色僵死再往下面加功能。最容易踩的坑是攻击硬直处理不当攻击判定每帧触发对手血量一秒内被扣空。解决办法就是attack_cooldown和hit_timer这两个定时器它们一个限制出招频率一个限制受击频率运行代码时重点观察这两格数值的变化。推荐扩展顺序第一版双人同屏对战完成移动、跳跃、攻击、防御、血量、胜负。第二版加入 AI 角色让玩家可以单人练习。第三版加入角色选择用不同攻击力和移动速度区分角色。第四版加入连招计数和得分显示。第五版加入音效、背景音乐、粒子特效。第六版用 pyinstaller 打包 exe发给朋友游玩。把这篇文章里的基础代码保存好后续每次扩展前先备份一份能运行的版本。等到你完成了三四个版本迭代回头再打开第一版代码会更清楚自己在这类项目实战里到底积累了多少东西。建议直接建一个python-fighting-game目录把代码和素材分好类这个项目就能变成一份有说服力的作品集素材。