这次我们来拆一个很适合开学练手的 Python 项目:用 Python 写一版拳皇格斗小游戏。这类项目不涉及复杂的 AI 模型,也不吃显卡,核心是 pygame 游戏开发里最常见的一套东西——角色类设计、状态机、碰撞检测、AI 行为控制和动画切换。如果你正在找 Python 项目实战案例,或者想从控制台程序跳到图形界面游戏开发,这个练手项目比写管理系统更有即时反馈。
先给结论:这个项目的重点不在于招式多华丽,而是把格斗游戏的最小闭环打通。玩家主控、AI 对手、血条扣减、攻击判定、胜负判断,这五件事全部处理完,你就拥有一个能实际对战、能反复扩展的拳皇风格小游戏。下面会从环境准备、项目结构、核心代码、运行验证到常见问题排查,完整走一遍。
1. 拳皇小游戏核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | Python 游戏开发实战,格斗对战类小游戏 |
| 核心语言 | Python 3.x |
| 主要依赖 | pygame 图形库,纯 CPU 运行,无显卡要求 |
| 核心玩法 | 玩家控制角色移动、跳跃、攻击,与 AI 对手对战 |
| 关键机制 | 角色状态机、碰撞检测、攻击判定、血条管理、胜负判定 |
| 适合人群 | 有 Python 基础、想练面向对象设计和游戏逻辑的初学者 |
| 运行平台 | Windows / macOS / Linux 均可 |
| 启动方式 | 命令行执行主程序文件 |
| 是否需要 GPU | 不需要 |
| 是否支持 API | 不支持,游戏本体项目 |
| 扩展方向 | 双人对战、多角色、必杀技、音效、动画帧替换、招式表 |
从表格可以看出来,这个项目定位很明确:不吃硬件、依赖少、逻辑密度高。你不需要为它准备 8G 显存或者 CUDA 环境,一台普通笔记本就够跑。
2. 玩法拆解与使用边界
2.1 拳皇小游戏的核心玩法
拳皇类格斗游戏和普通平台跳跃游戏的区别在于“对抗性”。整个游戏围绕两个核心对象展开:
- 玩家角色:由键盘控制,可以左右移动、跳跃、出拳、出腿,核心操作是靠近对手并发动攻击。
- AI 对手:不需要键盘控制,根据与玩家的距离自动做出行为选择,可能是靠近、防御或主动攻击。
胜利条件很简单:谁先把对方的血量打到 0,谁就赢。因此游戏的主循环必须持续判断两件事——攻击是否命中,以及血量是否归零。
2.2 版权与合规边界
这里特别提醒一点:拳皇(The King of Fighters)是 SNK 的经典游戏 IP。作为 Python 练手项目,你可以用自己的方式实现“格斗小游戏”的玩法逻辑,也可以参考拳皇的对战模式设计;但如果要公开发布或商用,不要直接使用拳皇的官方角色立绘、名称、音乐等素材。更稳妥的做法是用自制方块人、占位角色和独立世界观,保证玩法学习与素材版权两者互不干扰。
2.3 技术边界
从技术范围看,这个项目适合解决以下问题:
- 理解面向对象编程中“类”与“对象”的实战用法。
- 理解游戏循环中的输入、更新、渲染三层结构。
- 理解碰撞检测中的矩形相交判断。
- 理解简单 AI 状态机的编写。
不适合做的是:网络联机对战、复杂物理效果、3D 渲染。这些需要额外引入网络框架或游戏引擎,已经超出“Python 小游戏练手”的范畴。
3. 环境准备与依赖安装
在写代码之前,先把运行环境准备好。这个项目对环境的要求非常低,核心就两样:Python 3.x 和 pygame 库。
3.1 检查 Python 版本
打开终端或命令行窗口,输入以下命令查看当前 Python 版本:
python --version如果提示找不到命令,可以尝试python3 --version。建议使用 Python 3.8 以上版本,pygame 对新老版本都兼容得比较好。
3.2 安装 pygame
pygame 是 Python 社区最常用的 2D 游戏开发库,负责窗口创建、图片绘制、键盘事件监听、矩形碰撞检测等底层工作。安装命令:
pip install pygame如果你的机器同时存在 Python 2 和 Python 3,需要指定 pip 版本:
python3 -m pip install pygame3.3 验证安装结果
安装完成后,输入以下命令确认 pygame 可以正常导入:
import pygame print(pygame.__version__)如果能输出版本号,说明环境已经可用。至此,环境准备工作完成。
4. 项目结构设计与文件组织
不要把所有代码写进一个文件里硬扛,后面维护会非常痛苦。建议从第一步就按模块拆分。一个结构清晰的拳皇小游戏项目可以长这样:
kof_game/ ├── main.py # 游戏入口,负责主循环 ├── settings.py # 全局配置:窗口尺寸、帧率、颜色、速度常量 ├── fighter.py # 角色类:移动、跳跃、攻击、受击逻辑 ├── ai.py # AI 对手行为控制 ├── weapon.py # 攻击判定框与伤害结算(可选,也可并入 fighter.py) └── assets/ ├── bg.png # 背景图 ├── player.png # 玩家角色图 ├── enemy.png # 对手角色图 └── sound/ # 音效目录如果暂时没有美术素材,可以直接用 pygame 的矩形色块表示两个角色。先跑通逻辑,再替换素材,是最稳妥的开发顺序。
4.1 settings.py 示例
把游戏中的魔法数字集中到一个配置文件中,可以避免在主程序里到处修改数字。
# settings.py SCREEN_WIDTH = 800 SCREEN_HEIGHT = 450 FPS = 60 GROUND_Y = 350 # 地面高度 PLAYER_SPEED = 200 # 像素/秒 JUMP_SPEED = -300 # 起跳初速度,负数表示向上 GRAVITY = 600 # 重力加速度,像素/秒² PLAYER_COLOR = (50, 150, 255) ENEMY_COLOR = (255, 80, 80) BG_COLOR = (30, 30, 30)这样在后面实现角色移动和跳跃时,能直接引用配置,而不是到处写数字。
5. 角色类与状态机设计
格斗游戏里,角色不是一个孤立的图形对象,而是一个“有状态的对象”。站立、移动、攻击、被击中、倒地,这些状态决定角色的行为逻辑。实现时可以用状态机方式管理。
5.1 角色类核心属性
# fighter.py import pygame from settings import GROUND_Y class Fighter: def __init__(self, x, color, controls=None): self.x = x self.y = GROUND_Y self.vx = 0 self.vy = 0 self.width = 60 self.height = 120 self.color = color self.health = 100 self.facing = 1 # 1 朝右,-1 朝左 self.state = "stand" # stand / walk / jump / attack / hit self.grounded = True self.attack_timer = 0 self.hit_timer = 0 self.controls = controls or {}从类的设计可以看到,角色至少需要记录位置、速度、朝向、血量、当前状态和攻击计时。这些属性是所有后续逻辑的基础。
5.2 状态切换规则
状态不能任意跳转,比如攻击过程中不应立刻切入跳跃,否则游戏手感会很飘。因此要定义基本的切换规则:
stand可以切换到walk、jump、attack、hit。walk可以切换到stand、jump、attack、hit。jump在落地前不应再次起跳,落地后回到stand。attack在攻击动画结束前不重复攻击,结束后回到stand。hit表示受击硬直,硬直结束后回到stand。
一个简单的判断方法:只有在非攻击、非受击状态下才能发起新的移动和攻击。这样既保留操作自由度,又不会让角色失控。
5.3 角色更新逻辑骨架
def update(self, dt): # 水平移动 self.x += self.vx * dt # 垂直移动与重力 if not self.grounded: self.vy += GRAVITY * dt self.y += self.vy * dt if self.y >= GROUND_Y: self.y = GROUND_Y self.vy = 0 self.grounded = True if self.state == "jump": self.state = "stand" # 攻击计时 if self.state == "attack": self.attack_timer -= dt if self.attack_timer <= 0: self.state = "stand" # 受击计时 if self.state == "hit": self.hit_timer -= dt if self.hit_timer <= 0: self.state = "stand"这一段就是角色行为变化的核心。注意dt是上一帧到当前帧的时间差,以秒为单位,用它乘以速度才能保证不同帧率下的移动速度一致。
6. 玩家按键控制与招式触发
玩家控制通过 pygame 的键盘事件实现。这里推荐使用pygame.key.get_pressed()来读取持续按下的按键状态,优点是代码直观、不容易丢帧。
6.1 输入处理示例
# main.py 中的玩家输入处理 keys = pygame.key.get_pressed() # 只有站立和行走状态下才能重新设置水平速度 if player.state in ("stand", "walk"): player.vx = 0 if keys[pygame.K_a]: player.vx = -PLAYER_SPEED player.facing = -1 player.state = "walk" elif keys[pygame.K_d]: player.vx = PLAYER_SPEED player.facing = 1 player.state = "walk" # 跳跃 if keys[pygame.K_w] and player.grounded: player.vy = JUMP_SPEED player.grounded = False player.state = "jump" # 攻击 if keys[pygame.K_j]: start_attack(player) elif keys[pygame.K_k]: start_attack(player, damage=12, range=70)6.2 攻击动作初始化
def start_attack(fighter, damage=8, range=60): if fighter.state not in ("attack", "hit"): fighter.state = "attack" fighter.attack_timer = 0.3 fighter.attack_damage = damage fighter.attack_range = range这里把攻击作为一个“动作状态”处理:攻击开始后进入attack状态,持续 0.3 秒左右,期间攻击判定框会出现一次,之后状态自动回到stand。通过不同的按键绑定不同伤害和范围,就能实现拳和腿的区分。
7. 攻击判定与伤害结算
格斗游戏是否“有手感”,很大程度取决于攻击判定做得好不好。这里采用最简单也最实用的方法:把角色身体和攻击范围都抽象成矩形,然后用colliderect做相交判断。
7.1 获取角色碰撞框
def get_rect(self): return pygame.Rect(self.x - self.width // 2, self.y - self.height, self.width, self.height)角色中心点存储在x, y中,绘制时以中心点为基准向左右上下展开,这样在屏幕上也容易对齐。
7.2 获取攻击判定框
def get_attack_rect(self): # 攻击框出现在角色朝向的一侧 if self.facing == 1: return pygame.Rect(self.x, self.y - self.height, self.attack_range, self.height) else: return pygame.Rect(self.x - self.attack_range, self.y - self.height, self.attack_range, self.height)7.3 伤害结算时机
攻击框并不是整个攻击状态都有效,否则会出现“攻击已收招,但对方还被打中”的体验问题。更合理的做法是只在攻击状态中的某一个时间点执行一次判定:
def check_hit(attacker, defender): # 只在攻击状态且计时器位于判定区间时触发 if attacker.state == "attack": if 0.1 < attacker.attack_timer < 0.2: attack_rect = attacker.get_attack_rect() if attack_rect.colliderect(defender.get_rect()): defender.health -= attacker.attack_damage defender.state = "hit" defender.hit_timer = 0.2这样每次攻击只会结算一次,不会出现同一拳连续多帧扣血的 bug。受击后对手进入hit状态,短暂硬直,硬直结束才能继续行动。
7.4 血条与胜负判断
def draw_health_bar(screen, x, y, health, color): bar_width = 200 bar_height = 20 fill_width = int(bar_width * max(health, 0) / 100) pygame.draw.rect(screen, (60, 60, 60), pygame.Rect(x, y, bar_width, bar_height)) pygame.draw.rect(screen, color, pygame.Rect(x, y, fill_width, bar_height))在每个角色更新完血量后,主循环判断血量是否小于等于 0,是则结束游戏并展示胜负结果。
8. AI 对手的行为逻辑
AI 的作用是在没有第二个玩家的前提下,让游戏依然能对战。这个项目的 AI 不需要很强,只需要表现出“会移动、会攻击、会偶尔防御”的简单策略即可。
8.1 状态机 AI 设计
AI 可以按以下逻辑驱动:
- 计算自身与玩家的距离。
- 距离远时向玩家靠近。
- 距离近时按概率决定攻击还是后退。
- 受击硬直期间不做任何选择。
# ai.py import random from settings import PLAYER_SPEED def ai_update(enemy, player, dt): if enemy.state in ("attack", "hit"): return distance = player.x - enemy.x # 太远就靠近 if abs(distance) > 100: enemy.vx = PLAYER_SPEED * (1 if distance > 0 else -1) enemy.facing = 1 if distance > 0 else -1 enemy.state = "walk" return # 近距离随机决策 action = random.choices( ["attack", "walk", "back", "stand"], weights=[30, 20, 20, 30], k=1 )[0] if action == "attack": enemy.vx = 0 start_attack(enemy) elif action == "back": enemy.vx = -PLAYER_SPEED * enemy.facing enemy.state = "walk" else: enemy.vx = 0 enemy.state = "stand"这里使用了random.choices加权重的方式,让 AI 的表现不是完全固定,而是有一定的随机性和可读性。你可以继续调权重,让对手更激进或更保守。
8.2 AI 避不开的碰撞问题
注意:AI 只是根据位置做决策,游戏本身没有物理阻挡。也就是说,AI 可能直接走过玩家角色。对于练手项目,这是可以接受的;如果你希望更真实,可以在检测到两个角色矩形重叠时,强制把他们的 x 坐标推开,做一个简单的“碰撞推开”逻辑。
9. 游戏主循环、绘制与刷新
到这里,主角、AI、攻击判定都有了,最后只需要把这一切放进游戏主循环里持续运行。
9.1 主循环基本框架
# main.py import pygame from settings import * from fighter import Fighter from fighter import start_attack from ai import ai_update def main(): pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Python 拳皇小游戏 - 练手版") clock = pygame.time.Clock() player = Fighter(200, PLAYER_COLOR) enemy = Fighter(600, ENEMY_COLOR) enemy.facing = -1 running = True while running: dt = clock.tick(FPS) / 1000 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 玩家输入 keys = pygame.key.get_pressed() handle_player_input(keys, player) # AI ai_update(enemy, player, dt) # 更新 player.update(dt) enemy.update(dt) # 攻击判定 check_hit(player, enemy) check_hit(enemy, player) # 绘制 screen.fill(BG_COLOR) pygame.draw.rect(screen, (0, 0, 0), pygame.Rect(0, GROUND_Y, SCREEN_WIDTH, 50)) draw_fighter(screen, player) draw_fighter(screen, enemy) draw_health_bar(screen, 50, 30, player.health, PLAYER_COLOR) draw_health_bar(screen, SCREEN_WIDTH - 250, 30, enemy.health, ENEMY_COLOR) # 胜负判断 if player.health <= 0: show_result(screen, "AI 获胜") running = False elif enemy.health <= 0: show_result(screen, "玩家获胜") running = False pygame.display.flip() pygame.quit() if __name__ == "__main__": main()9.2 角色绘制函数
def draw_fighter(screen, fighter): rect = fighter.get_rect() pygame.draw.rect(screen, fighter.color, rect)当然后续你可以用角色图片替换这个色块。替换时只需要在绘制阶段加载pygame.image.load(),然后screen.blit()即可,角色逻辑代码完全不需要改动。
10. 运行测试与效果验证
10.1 启动游戏
在项目根目录执行:
python main.py如果环境正常,会弹出一个 800×450 的黑色游戏窗口,两个不同颜色的角色出现在屏幕两侧,顶部有两个血条。
10.2 功能验证清单
启动后按以下步骤逐项验证:
| 验证项 | 操作 | 预期结果 |
|---|---|---|
| 左右移动 | 按 A / D 键 | 玩家角色沿水平方向移动 |
| 跳跃 | 按 W 键 | 角色向上跳起,随后落回地面 |
| 攻击 | 按 J / K 键 | 角色进入攻击状态,若靠近对手则对手扣血并硬直 |
| AI 行为 | 不操作角色 | AI 会主动靠近并尝试攻击玩家 |
| 血条更新 | 让角色被 AI 打中 | 血条按伤害值减少 |
| 胜负判定 | 把 AI 血量打到 0 | 显示“玩家获胜”并结束游戏 |
10.3 调试信息输出
如果觉得角色状态切换不直观,可以在fighter.update()中临时加一行控制台输出,方便观察状态变化:
if self.state != self.last_state: print(self.state) self.last_state = self.state这一步不是必需,但对理解状态机非常有帮助。调完逻辑后再删掉即可。
11. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 运行提示 ModuleNotFoundError: pygame | pygame 未安装 | pip show pygame | 执行pip install pygame |
| 窗口一闪而过 | 主循环异常退出 | 查看终端错误堆栈 | 检查代码中是否有语法错误或空引用 |
| 角色移动速度不一样 | 没有使用 dt 计算位移 | 检查更新函数是否乘以时间差 | 把x += vx * dt应用到所有移动逻辑 |
| 攻击打不到人 | 攻击范围过小或朝向不对 | 打印攻击判定框坐标 | 调大attack_range,检查facing方向 |
| 一次攻击多次扣血 | 攻击判定持续触发 | 检查伤害结算条件 | 只在攻击计时的判定区间执行一次结算 |
| 跳跃像瞬移 | 帧率不稳定且未用 dt | 检查主循环clock.tick(60) | 统一使用 dt 计算位移 |
| AI 一直贴脸不走开 | AI 决策逻辑没有随机后退 | 检查 AI 行动分支 | 在近距离分支增加后退或站立选项 |
| 角色重叠严重 | 没有做物理推开 | 检查位置更新顺序 | 在更新阶段检测矩形重叠并修正 x 坐标 |
这组排查表基本覆盖了初学者最容易遇到的问题。实际开发中,绝大多数 bug 都出在“状态没有切换干净”和“位移没有乘时间差”这两件事上。
12. 扩展方向与工程化建议
12.1 从练手到完整的五步扩展
- 素材替换:用角色行走图、攻击图、背景图替换色块,游戏观感会立刻提升。
- 招式机制:加入能量条和必杀技,按能量状态触发不同攻击效果。
- 双人对战:增加第二套按键控制,让两个玩家在同一键盘上对战。
- 音效与反馈:攻击命中时播放音效,受击时角色变色或闪烁。
- 帧动画系统:把角色的每个状态对应到多帧图片列表,按帧率逐帧切换。
12.2 工程化建议
不要一上来就往工程里加复杂设计。先保证最小可用版本能跑通,再做增量修改。几个具体建议:
- 保持
settings.py独立:所有速度、颜色、尺寸都放进去,方便调参。 - 每次只改一个功能:例如先只做移动,验证移动;再做跳跃,验证跳跃;再做攻击。
- 定期备份可运行版本:把“能正常对战”的版本标记为稳定版,再去尝试新功能。
- 目录管理:素材、代码、输出分开存放,后续加资源不会混乱。
- 逻辑与渲染分离:角色属性、血量、状态逻辑写在
fighter.py,绘制逻辑写在主程序的绘制函数中。后续替换成图片素材时,不需要改动逻辑代码。
12.3 关于安全使用的补充
最后再补充一点:如果是学习练手,项目可以放 GitHub 或 CSDN 分享,但要注意不要使用官方拳皇的 IP 素材,也不要将他人的美术资源、音频资源打包发布到公开仓库。用自制素材或纯色块完成逻辑演示,既合规又完全够用。
13. 下一步怎么玩
最值得先验证的永远是“攻击判定是否稳定”。整个项目的成败就在这一环:状态切换得干净,攻击结算只执行一次,受击硬直能正常结束,游戏的手感就基本合格了。最容易踩的坑则是状态机混乱和位移不乘帧时间差,遇到问题优先去检查这两个地方。
跑通这一版之后,你手里就有一套完整的格斗游戏最小框架。后续无论是给角色换美术素材、增加必杀技系统,还是改成双人对战模式,都只是在现有结构里加模块,不需要推翻重来。
把这一版完整写出来、运行一遍、把 bug 调完,Python 面向对象、事件处理和基础游戏循环的能力也就真正落地了。建议收藏这篇,按章节一步步实现,有问题随时对照排查表找原因。