🏰 从零打造一款回合制 RPG 游戏:基于 Pygame 的《塔影守卫》全解析
📅 发布时间:2026-06-06
🏷️ 标签:
PythonPygame游戏开发回合制RPG动画系统
📖 前言
你是否也曾梦想过自己亲手做一款游戏?不需要商业引擎,不需要几十万行代码------用 Python + Pygame,200 多行代码就能做出一款拥有完整战斗动画、技能系统、回合制逻辑的 RPG 游戏!
《塔影守卫 》是一款 1v1 回合制对战游戏 ,玩家操控塔影守卫(正义方)对抗强大的魔兽 Boss。游戏采用 帧动画系统 驱动角色动作,支持 攻击 / 防御 / 烈焰狂怒 三大技能,带有怒气值攒满后的 全屏必杀技特效,体验感十足。
本文将带你从零复盘这款游戏的 完整开发流程,逐行解读核心代码,并附上截图展示区域。无论你是 Pygame 初学者还是想做独立小游戏,都能从中获得灵感和实践指引。
🛠️ 技术栈分析
| 技术 / 工具 | 用途说明 |
|---|---|
| Python 3 | 核心编程语言,语法简洁,适合快速原型开发 |
| Pygame 2.x | 跨平台的 2D 游戏框架,负责渲染、事件、音频等 |
| Sprite 动画 | 逐帧图片序列,实现角色攻击/受击/技能动画 |
| 状态机模式 | 管理游戏全局状态(加载/开始/战斗/结束) |
| 回合制系统 | 玩家回合 → 选择技能 → 执行 → Boss 回合 |
Pygame 简介
Pygame 是 Python 生态中最成熟的 2D 游戏开发库,封装了 SDL(Simple DirectMedia Layer),提供以下核心能力:
- 🎨 图形渲染 :
pygame.display、screen.blit()绘制精灵和背景 - 🖱️ 事件处理 :
pygame.event处理键盘、鼠标输入 - ⏱️ 帧率控制 :
pygame.time.delay()/Clock.tick()控制游戏速度 - 🎵 音效支持 :
pygame.mixer播放 BGM 和音效(本项目暂未启用)
🎮 项目完整实现流程
🧩 一、整体架构概览
游戏采用 有限状态机 (FSM) 管理生命周期:
text
┌──────────┐
│ loading │ 加载图片资源,构建动画字典
└────┬─────┘
▼
┌──────────┐
───── │ start │ 按 Enter 进入战斗
└────┬─────┘
▼
┌─────────────────────────┐
│ running │
│ ┌──────┐ ┌──────┐ │
│ │player│─▶│ boss │ │ 回合交替执行
│ │ turn │◀─│ turn │ │
│ └──────┘ └──────┘ │
└────────┬───────────────┘
▼
┌──────────┐
│ end │ 胜负判定,显示结局画面
└──────────┘
核心数据流:属性字典 (playerData / bossData) 存储实时 HP/ATK/DEF,每一帧动画播放完毕后触发伤害计算,形成「播放动画 → 结算伤害 → 切换回合」的闭环。
📦 二、资源加载与动画字典构建
这是整个游戏的基础设施 ------ 将所有 PNG 序列帧读取到内存,组织成「动画字典」方便后续调用。
目录结构
animation/
player/
idle/ → 站立动画帧 (0.png ~ 7.png)
attack/ → 攻击动画帧 (0.png ~ 39.png)
defense/ → 防御动画帧 (0.png ~ 39.png)
anger/ → 烈焰狂怒帧 (0.png ~ 67.png)
boss/
idle/ → 待机帧
magic/ → 魔法攻击帧
grab/ → 抓取攻击帧
cards/ → 卡牌攻击帧
bow/ → 弓箭攻击帧
UI/
map/ → 战斗背景(随技能切换)
HPbar/ → 血条素材
anger1/ → 怒气爆发第一阶段
anger2/ → 怒气爆发第二阶段
start/ → 加载/开始界面
end/ → 胜利/失败结局
核心代码:createDict()
python
def createDict(path):
keys = os.listdir(path) # 获取所有子目录名
gameDict = dict.fromkeys(keys) # 预建字典 {key: None}
for key in keys:
pictures = []
names = os.listdir(path + key + '/') # 获取帧文件列表
for i in range(len(names)):
names[i] = int(names[i].replace('.png', '')) # 提取数字序号
insertSort(names) # 按数字排序
for name in names:
img = pygame.image.load(path + key + '/' + str(name) + '.png')
pictures.append(img)
gameDict[key] = pictures # 字典值 = 帧列表
return gameDict
💡 设计亮点 :利用
os.listdir+ 条带.png后缀 + 插入排序,自动将散乱的帧文件按序号组装成动画序列,无需手动硬编码帧数。
构建调用
python
uiDict = createDict('UI/') # 界面 UI 字典
playerDict = createDict('animation/player/') # 塔影守卫动画字典
bossDict = createDict('animation/boss/') # 魔兽动画字典
最终数据结构示意:
python
playerDict = {
'idle': [<Surface>, <Surface>, ...], # 8 帧
'attack': [<Surface>, <Surface>, ...], # 40 帧
'defense': [...], # 40 帧
'anger': [...], # 68 帧
}
⚔️ 三、回合制战斗系统
属性设计
python
playerData = {
'def': 50, # 防御力
'atk': 100, # 攻击力
'HP': 1000, # 当前生命值
'maxHP': 1000, # 最大生命值
}
bossData = {
'def': 50,
'magic': 150, # 魔法攻击力
'cards': 200, # 卡牌攻击力
'grab': 250, # 抓取攻击力
'bow': 300, # 弓箭攻击力
'HP': 10000,
}
三大技能
| 技能 | 触发方式 | 效果 |
|---|---|---|
| ⚔️ 攻击 | 鼠标点击左侧按钮 | atk - def 伤害,怒气归零 |
| 🛡️ 防御 | 鼠标点击右侧按钮 | 防御力 +150,被击中额外积攒 10 怒气 |
| 🔥 烈焰 | 怒气 ≥ 100 后点击中间 | 无视防御造成 10000 点巨额伤害 |
技能选择:skillChoose()
python
def skillChoose(e):
global playerState, skill
x, y = e.pos
if 290 < x < 440 and 605 < y < 675: # 左侧按钮 → 攻击
playerState = 'attack'
skill = 1
elif 560 < x < 710 and 605 < y < 675: # 右侧按钮 → 防御
playerState = 'defense'
skill = 2
elif 420 < x < 570 and 540 < y < 700 and angerSkill: # 中间 → 怒气技
playerState = 'anger'
skill = 3
通过 鼠标点击坐标范围检测 实现 UI 按钮交互,angerSkill 标志位确保怒气技只有在攒满后才能释放。
玩家技能结算
python
def playerSkill(playerState):
global anger
if playerState == 'attack':
damage = playerData['atk'] - bossData['def']
bossData['HP'] -= damage
anger = 0 # 攻击后怒气清零
elif playerState == 'defense':
playerData['def'] += 150 # 防御强化(回合结束后恢复)
elif playerState == 'anger':
bossData['HP'] -= 10000 # 必杀技
Boss 回合
python
def bossSkill():
global anger, angerSkill
damage = bossData[randomSkill] - playerData['def']
if damage > 0:
playerData['HP'] -= damage
if playerState == 'defense':
anger += 10 # 防御被击中 → 积攒怒气
if anger >= 100:
angerSkill = True # 解锁烈焰狂怒
Boss 每次随机选择 magic / grab / cards / bow 之一进行攻击,不同技能伤害不同(150~300),增加了不确定性。
🎞️ 四、帧动画播放系统
showActor() ------ 逐帧渲染角色
python
def showActor(dict, file, pos=(0, 0)):
global player, boss
frame_list = dict[file] # 取出该动作的帧列表
num = len(frame_list)
if dict == playerDict:
if player >= num:
player = 0
screen.blit(frame_list[player], pos)
player += 1 # 下一帧索引递增
else:
if boss >= num:
boss = 0
screen.blit(frame_list[boss], pos)
boss += 1
每调用一次 showActor,索引 +1 播放下一帧;当播完所有帧时自动归零。配合 pygame.time.delay(100) 控制游戏主循环速度,实现平滑的逐帧动画。
🔁 循环播放机制 :
player/boss这两个全局计数器在每一次pygame.display.update()时递增,自然形成动画循环。
动画播完即结算
python
if player >= num: # 玩家攻击动画播完最后一帧
playerSkill(playerState) # 立即结算伤害
randomSkill = random.choice([...]) # Boss 随机技能
turn = 'boss' # 切换到 Boss 回合
这种「动画帧播完 = 动作执行完成」的设计,让动画时长天然成了「技能前摇」的计时器。
🎨 五、UI 渲染层
血条系统
python
def showHP(x, y, hp, name):
if name == 'player':
ratio = int(0.25 * hp) # 1:4 比例映射
elif name == 'boss':
ratio = int(0.025 * hp) # 1:40 比例映射(Boss 血量高 10 倍)
bar = pygame.transform.scale(uiDict['HPbar'][0], (ratio, 13))
screen.blit(bar, (x, y))
通过 pygame.transform.scale 动态缩放血条图片宽度,直观反映剩余血量。
属性面板
python
def showData(dict):
# 遍历 playerData / bossData 字典
# 按行渲染 ATK / DEF / HP 等属性到指定坐标
# 支持两列布局,自动换行
背景动态切换
python
def bgControl():
if angerSkill and not angerAnimation:
screen.blit(uiDict['map'][3], (0, 0))
screen.blit(uiDict['anger1'][index], (0, 0)) # 怒气爆发第一阶段
elif angerAnimation:
screen.blit(uiDict['map'][3], (0, 0))
screen.blit(uiDict['anger2'][index], (0, 0)) # 怒气爆发第二阶段
else:
screen.blit(uiDict['map'][skill], (0, 0)) # 普通背景随技能切换
怒气爆发时依次播放两段全屏特效动画,给玩家强烈的视觉冲击。
🖥️ 六、主循环与事件处理
python
while True:
# ──── 状态分发 ────
if state == 'loading':
# 顺序加载 UI → Player → Boss 资源,每步刷新进度画面
...
if state == 'start':
screen.blit(uiDict['start'][3], (0, 0))
if state == 'running':
bgControl()
showHP(...)
showData(...)
# 回合判断 + 动画播放 + 技能结算
...
if state == 'end':
# 显示胜负画面
...
pygame.display.update()
pygame.time.delay(100)
# ──── 事件监听 ────
for e in pygame.event.get():
if e.type == pygame.QUIT: sys.exit()
if e.type == pygame.KEYDOWN:
if e.key == K_RETURN: state = 'running' # 进入战斗
if e.key == K_SPACE: choose = 'done' # 确认技能
if e.type == pygame.MOUSEBUTTONDOWN:
skillChoose(e) # 鼠标选技能
关键控制流:
- Enter 键 → 从开始界面进入战斗
- 鼠标点击 → 在玩家回合选择技能
- Space 键 → 确认技能,触发动画播放
- 动画播放完毕 → 自动结算伤害,回合切换
📐 七、插入排序辅助工具
python
def insertSort(mylist):
for i in range(1, len(mylist)):
for j in range(i, 0, -1):
if mylist[j] < mylist[j - 1]:
mylist[j - 1], mylist[j] = mylist[j], mylist[j - 1]
else:
break
用于将动画帧的文件名数字序号从小到大排序,确保帧序列正确拼接。虽然使用 Python 内置 sort() 更简洁,但这里手写排序算法也展示了基础的算法功底。
📸 截图展示
🖼️ 请将你的游戏运行截图放在下方各模块中,替换示例说明文字。
1️⃣ 开始界面
text
┌─────────────────────────────────────────┐
│ │
│ 🏰 塔 影 守 卫 🏰 │
│ │
│ 「按下 Enter 开始战斗」 │
│ │
│ [此处放置开始界面截图] │
│ │
└─────────────────────────────────────────┘

2️⃣ 技能选择界面
text
┌─────────────────────────────────────────┐
│ ┌─────────┐ ┌─────────┐ │
│ │ ⚔️ 攻击 │ 🔥 烈焰 │ 🛡️ 防御 │ │
│ └─────────┘ └─────────┘ │
│ │
│ [此处放置技能选择界面截图] │
│ │
│ 塔影守卫 HP: ████████░░ 800/1000 │
│ 魔兽 HP: ██████████████ 9200/10000 │
│ │
└─────────────────────────────────────────┘

3️⃣ 攻击动画
text
┌─────────────────────────────────────────┐
│ │
│ 塔影守卫 ⚔️ 对 魔兽 造成 50 点伤害 │
│ │
│ [此处放置攻击动画截图] │
│ │
└─────────────────────────────────────────┘

4️⃣ 防御姿态 & Boss 攻击
text
┌─────────────────────────────────────────┐
│ │
│ 魔兽 🏹 弓箭攻击!塔影守卫举起盾牌... │
│ │
│ [此处放置防御+受击截图] │
│ │
└─────────────────────────────────────────┘


5️⃣ 🔥 烈焰狂怒 ------ 必杀技!
text
┌─────────────────────────────────────────┐
│ │
│ 🔥🔥🔥 烈 焰 狂 怒 🔥🔥🔥 │
│ │
│ 💥 造成 10000 点伤害!💥 │
│ │
│ [此处放置全屏怒气技截图] │
│ │
│ 第一 & 第二阶段全屏特效呈现 │
│ │
└─────────────────────────────────────────┘
📷 截图位置 :怒气攒满 100 后释放烈焰狂怒,截取全屏特效
anger1/anger2动画播放时的画面。
6️⃣ 胜利 / 失败结局
text
┌─────────────────────────────────────────┐
│ │
│ 🎉 胜 利 🎉 │
│ (或 💀 失 败) │
│ │
│ [此处放置结局画面截图] │
│ │
└─────────────────────────────────────────┘
📷 截图位置 :Boss HP 归零(胜利)或玩家 HP 归零(失败)时的结局界面。
🎯 总结与展望
✨ 项目亮点
- 纯 Python + Pygame,零外部依赖,环境搭建极简
- 完整的游戏循环:加载 → 开始 → 战斗 → 结算,麻雀虽小五脏俱全
- 灵活的动画系统:基于文件系统的动态帧加载,新增动画只需放入文件夹
- 策略性技能设计:攻击 / 防御 / 怒气技三者形成战术循环------防御攒怒,怒气爆发,攻守兼备
- 状态机架构:清晰可扩展,新增游戏状态非常方便
🚀 可以进一步优化的方向
| 方向 | 具体方案 |
|---|---|
| 🎵 音效 & BGM | 集成 pygame.mixer,为攻击/技能/背景添加音效 |
| 📊 更多 UI 提示 | 伤害飘字、技能冷却指示器、战斗日志 |
| 🧠 平衡性调整 | 将属性值配置化,便于调整游戏难度 |
| 🔄 多关卡 | 引入多个 Boss 和关卡选择 |
| 🛍️ 道具系统 | 回复药水、增益 Buff 等 |
| 📦 打包分发 | 使用 pyinstaller 打包为 exe,方便分享游玩 |
📂 项目源码
python
import pygame
import sys
import os
import random
pygame.init()
pygame.display.set_caption('塔影守卫')
screen = pygame.display.set_mode((1000, 700))
font = pygame.font.SysFont('simhei', 18)
# 塔影守卫属性字典,
playerData = {
'def': 50,
'atk': 100,
'HP': 1000,
'maxHP': 1000,
}
# 魔兽属性字典
bossData = {
'def': 50,
'magic':150,
'cards': 200,
'grab': 250,
'bow':300,
'HP': 10000,
}
# 插入排序
def insertSort(mylist):
for i in range(1, len(mylist)):
for j in range(i, 0, -1):
if mylist[j] < mylist[j - 1]:
mylist[j - 1], mylist[j] = mylist[j], mylist[j - 1]
else:
break
# 加载图片,构建动画字典
def createDict(path):
keys = os.listdir(path)
gameDict = dict.fromkeys(keys)
for key in keys:
pictures = []
names = os.listdir(path + key + '/')
for i in range(len(names)):
names[i] = int(names[i].replace('.png', ''))
insertSort(names)
for name in names:
img = pygame.image.load(path + key + '/' + str(name) + '.png')
pictures.append(img)
gameDict[key] = pictures
return gameDict
# 背景控制
def bgControl():
global index,angerAnimation
if angerSkill and not angerAnimation:
screen.blit(uiDict['map'][3],(0,0))
screen.blit(uiDict['anger1'][index],(0,0))
index += 1
if index == 10:
index = 0
angerAnimation = True
elif angerAnimation:
screen.blit(uiDict['map'][3],(0,0))
screen.blit(uiDict['anger2'][index],(0,0))
index += 1
if index == 12:
index = 0
else:
screen.blit(uiDict['map'][skill],(0,0))
# 绘制血条
def showHP(x,y,hp,name):
if name == 'player':
ratio = int(0.25 * hp)
elif name == 'boss':
ratio = int(0.025 * hp)
if ratio < 0:
ratio = 0
bar = pygame.transform.scale(uiDict['HPbar'][0],(ratio,13))
screen.blit(bar,(x,y))
# 绘制角色
def showActor(dict,file,pos=(0,0)):
global player, boss
list = dict[file]
num = len(list)
if dict == playerDict:
if player >= num:
player = 0
screen.blit(list[player],pos)
player += 1
else:
if boss >= num:
boss = 0
screen.blit(list[boss],pos)
boss += 1
# 显示人物属性
def showData(dict):
global offset
row = 1
if dict == playerData:
x = 190
gap = 30
else:
x = 620
gap = 29
distance = 138
for k, v in dict.items():
if k != 'maxHP':
if row > 3:
row = 1
offset = 0
x += distance
words = k + ':' + str(v)
text = font.render(words, True, 'white')
screen.blit(text, (x, 42 + offset * gap))
row += 1
offset += 1
offset = 0
# 塔影守卫技能
def playerSkill(playerState):
global anger
if playerState == 'attack':
damage = (playerData['atk'] - bossData['def'])
bossData['HP'] -= damage
anger = 0
elif playerState == 'defense':
playerData['def'] += 150
elif playerState == 'anger':
bossData['HP'] -= 10000
# 魔兽技能
def bossSkill():
global anger,angerSkill
damage = (bossData[randomSkill] - playerData['def'])
if damage > 0:
playerData['HP'] -= damage
if playerState == 'defense':
anger += 10
if anger >= 100:
angerSkill = True
# 技能选择
def skillChoose(e):
global playerState,skill
x = e.pos[0]
y = e.pos[1]
if 290 < x < 440 and 605 < y < 675:
playerState = 'attack'
skill = 1
elif 560 < x < 710 and 605 < y < 675:
playerState = 'defense'
skill = 2
elif 420 < x < 570 and 540 < y < 700 and angerSkill:
playerState = 'anger'
skill = 3
# 动画状态帧
player = 0
boss = 0
index = 0
# 游戏状态
state = 'loading'
# 塔影守卫状态
playerState = 'idle'
# 游戏回合
turn = 'player'
# 技能选择
choose = 'doing'
# 塔影守卫技能
skill = 0
# 魔兽随机技能
randomSkill = 'magic'
# 位置计数
offset = 0
# 怒气值
anger = 0
# 烈焰狂怒技能
angerSkill = False
# 烈焰狂怒动画
angerAnimation = False
while True:
# 加载状态
if state == 'loading':
# 图片加载
uiDict = createDict('UI/')
screen.blit(uiDict['start'][0],(0,0))
pygame.display.update()
playerDict = createDict('animation/player/')
screen.blit(uiDict['start'][1],(0,0))
pygame.display.update()
bossDict = createDict('animation/boss/')
screen.blit(uiDict['start'][2],(0,0))
pygame.display.update()
pygame.time.delay(500)
state = 'start'
# 开始状态
if state == 'start':
screen.blit(uiDict['start'][3],(0,0))
# 进行状态
if state == 'running':
bgControl()
# 显示血条和属性值
showHP(161,12,playerData['HP'],'player')
showHP(589,12,bossData['HP'],'boss')
showData(playerData)
showData(bossData)
# 玩家回合
if turn == 'player':
# 选择技能
if choose == 'doing':
showActor(playerDict,'idle')
showActor(bossDict,'idle')
# 发动攻击
else:
showActor(bossDict,'idle')
showActor(playerDict,playerState)
num = len(playerDict[playerState])
if player >= num:
playerSkill(playerState)
randomSkill = random.choice(['magic','grab','cards','bow'])
turn = 'boss'
# boss回合
elif turn == 'boss':
if playerState == 'defense':
showActor(playerDict,'idle2')
else:
showActor(playerDict,'idle')
showActor(bossDict,randomSkill)
num = len(bossDict[randomSkill])
if boss >= num:
bossSkill()
choose = 'doing'
turn = 'player'
playerData['def'] = 50
if playerData['HP'] <= 0 or bossData['HP'] <= 0:
state = 'end'
# 结束状态
if state == 'end':
if playerData['HP'] <= 0:
screen.blit(uiDict['end'][0],(0,0))
elif bossData['HP'] <= 0:
screen.blit(uiDict['end'][1],(0,0))
# 刷新屏幕
pygame.display.update()
pygame.time.delay(100)
# pygame事件
for e in pygame.event.get():
if e.type == pygame.QUIT:
sys.exit()
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_KP_ENTER or e.key == pygame.K_RETURN:
state = 'running'
if e.key == pygame.K_SPACE:
choose = 'done'
player = 0
if skill == 3:
angerAnimation = False
angerSkill = False
if e.type == pygame.MOUSEBUTTONDOWN:
if choose == 'doing':
skillChoose(e)
bash
git clone <你的仓库地址>
cd Pygame守望之塔回合制游戏
pip install pygame
python main.py
🙏 感谢阅读!如果你跟着本文动手实践,相信你也能做出属于自己的第一款 Pygame RPG 游戏。
有任何问题欢迎在评论区交流讨论 💬

