从零打造一款回合制 RPG 游戏:基于 Pygame 的《塔影守卫》全解析

🏰 从零打造一款回合制 RPG 游戏:基于 Pygame 的《塔影守卫》全解析

📅 发布时间:2026-06-06

🏷️ 标签:Python Pygame 游戏开发 回合制 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.displayscreen.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 游戏。

有任何问题欢迎在评论区交流讨论 💬

相关推荐
myenjoy_11 小时前
串口采集与 Modbus RTU——字节流里的时间敏感博弈
网络·python·网络协议·tcp/ip
小雨下雨的雨1 小时前
iOS风格计算器 - 鸿蒙PC Electron框架上的技术实现详解
游戏·ios·华为·electron·harmonyos·鸿蒙
易舟云财务软件1 小时前
财务 AI Python 实战:从自动化报表到智能风控的应用场景
人工智能·python·自动化
武雄(小星Ai)1 小时前
一个模型干五件事:拆解 NVIDIA Cosmos 3 的物理 AI 全模态架构
人工智能·python·agent
Mr.Daozhi2 小时前
跨境电商选品完整流水线:Google Trends筛词+Meta广告分析,CLI工具设计实战
开发语言·爬虫·python·跨境电商·工具链·选品
小雨下雨的雨2 小时前
五子棋AI在鸿蒙PC Electron上的实现的原理与实践
人工智能·游戏·华为·electron·harmonyos·鸿蒙
装不满的克莱因瓶2 小时前
掌握典型卷积神经网络的搭建
人工智能·python·深度学习·神经网络·机器学习·ai·cnn