Python 龙与魔法回合制游戏开发实战
📜 项目介绍
这是一款基于 Pygame 开发的回合制 RPG 游戏,玩家将扮演勇敢的冒险者,与强大的 BOSS 展开激烈对战。游戏融合了经典的回合制战斗机制,支持多种技能选择,包括物理攻击、魔法攻击、生命恢复、攻击增益和防御增益。
🎮 游戏特色
| 特性 | 描述 |
|---|---|
| 回合制战斗 | 策略性回合对战,体验经典 RPG 魅力 |
| 丰富技能系统 | 5种技能可选:攻击、魔法、治疗、增伤、增防 |
| 精美动画 | 完整的角色动画系统,包括待机、攻击、施法等状态 |
| 音效反馈 | 战斗音效、背景音乐,带来沉浸式体验 |
| 多元素 BOSS | BOSS 拥有火、冰、土、风四种元素攻击 |
🛠 技术栈
python
import pygame # 游戏引擎
import sys # 系统交互
import os # 文件路径处理
import random # 随机数生成
🎯 核心系统解析
1. 角色属性系统
python
# 玩家角色属性字典
playerData = {
'def': 50, # 防御力
'atk': 100, # 攻击力
'magic': 1000, # 魔法伤害
'addHP': 200, # 生命恢复量
'addAtk': 200, # 攻击增益
'addDef': 100, # 防御增益
'HP': 5000, # 当前生命值
'maxHP': 5000, # 最大生命值
}
# BOSS 属性字典
bossData1 = {
'def': 50, # 防御力
'ice': 100, # 冰系伤害
'earth': 200, # 土系伤害
'fire': 300, # 火系伤害
'wind': 400, # 风系伤害
'HP': 10000, # BOSS 生命值
}
2. 伤害计算机制
python
# 玩家技能计算
def playerSkill():
if playerState == 'attack':
# 物理攻击:攻击力 - 目标防御力
damage = (playerData['atk'] - bossData1['def'])
bossData1['HP'] -= damage
elif playerState == 'magic':
# 魔法攻击:魔法伤害 - 目标防御力
damage = (playerData['magic'] - bossData1['def'])
bossData1['HP'] -= damage
elif playerState == 'addHP':
# 生命恢复(不超过最大值)
playerData['HP'] = playerData['HP'] + playerData['addHP']
if playerData['HP'] > playerData['maxHP']:
playerData['HP'] = playerData['maxHP']
elif playerState == 'addDef':
# 永久增加防御力
playerData['def'] += playerData['addDef']
elif playerState == 'addAtk':
# 永久增加攻击力
playerData['atk'] += playerData['addAtk']
3. 动画系统
游戏使用插入排序算法来确保动画帧按正确顺序播放:
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], mylist[j - 1] = mylist[j - 1], mylist[j]
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
4. 动画播放控制
python
# 动画状态帧
player = 0
boss = 0
def showActor(dict, file, pos=(0, 0)):
global player, boss
if dict == playerDict:
if player >= len(dict[file]):
player = 0
screen.blit(dict[file][player], pos)
player += 1
else:
if boss >= len(dict[file]):
boss = 0
screen.blit(dict[file][boss], pos)
boss += 1
5. 血条绘制系统
python
def showHP(x, y, hp, name):
if name == 'player':
length = int(0.02 * hp) # 玩家血条比例
elif name == 'boss':
length = int(0.01 * hp) # BOSS 血条比例
# 防止负值
if length < 0:
length = 0
# 缩放血条图片
bar = pygame.transform.scale(uiDict['HPbar'][0], (length, 13))
screen.blit(bar, (x, y))
🎮 游戏流程控制
主循环架构
python
while True:
# 事件处理
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.type == pygame.MOUSEBUTTONDOWN:
x, y = e.pos[0], e.pos[1]
if 450 < y < 490: # 技能按钮区域
if 270 < x < 340:
playerState = 'attack'; skill = 1
elif 370 < x < 440:
playerState = 'magic'; skill = 2
# ... 其他技能
游戏状态机
python
state = 'loading' # 初始状态:资源加载
# 游戏状态流转
# loading -> start -> running -> end
elif state == 'running':
# 显示地图、血条、属性
screen.blit(uiDict['map'][skill], (0, 0))
showHP(86, 642, playerData['HP'], 'player')
showHP(818, 642, bossData1['HP'], 'boss')
# 玩家回合 / BOSS 回合并行处理
if turn == 'player':
showActor(playerDict, 'idle') # 待机动画
# ...
🎨 音效系统
python
# 初始化音频系统
pygame.mixer.music.load("music/bg.mp3")
pygame.mixer.music.set_volume(0.3)
pygame.mixer.music.play(-1) # 无限循环
# 攻击音效
attack1_fx = pygame.mixer.Sound("music/attack1.mp3")
attack1_fx.set_volume(0.9)
# 魔法音效
magic1_fx = pygame.mixer.Sound("music/magic1.mp3")
magic1_fx.set_volume(0.9)
# 游戏胜利/失败音效
gameover1_fx = pygame.mixer.Sound("music/gameover.wav")
gamewin1_fx = pygame.mixer.Sound("music/gamewin.wav")
📁 项目结构
Python龙与魔法回合游戏/
├── mainText.py # 🎯 主程序入口
├── UI/ # 🖼️ 界面资源
│ ├── HPbar/ # 血条图片
│ ├── end/ # 结束画面
│ ├── map/ # 地图背景
│ └── start/ # 开始界面
├── animation/ # 🎬 角色动画
│ ├── boss/ # BOSS 动画(idle + 四元素攻击)
│ │ ├── idle/ # 待机
│ │ ├── fire/ # 火系
│ │ ├── ice/ # 冰系
│ │ ├── earth/ # 土系
│ │ └── wind/ # 风系
│ ├── player/ # 玩家动画
│ │ ├── idle/ # 待机
│ │ ├── attack/ # 物理攻击
│ │ ├── magic/ # 魔法攻击
│ │ ├── addHP/ # 治疗
│ │ ├── addAtk/ # 增伤
│ │ └── addDef/ # 增防
│ └── other/ # 其他素材(裁判)
└── music/ # 🔊 音效资源
🚀 运行方式
bash
# 确保已安装 Pygame
pip install pygame
# 运行游戏
python mainText.py
🎮 操作说明
| 操作 | 功能 |
|---|---|
Enter |
开始游戏 |
| 鼠标点击 | 选择技能 |
Space |
确认技能 |
💡 开发心得
1. 模块化设计
将动画系统、技能系统、UI系统分离,使代码结构清晰易维护。
2. 资源预加载
使用加载画面提前加载所有资源,避免游戏过程中卡顿。
3. 状态机模式
使用 state 变量控制游戏流程,逻辑清晰易于扩展。
4. 帧率控制
使用 pygame.time.delay(100) 控制动画播放速度,避免画面过快。
🔮 未来扩展方向
- 添加更多 BOSS 怪物
- 实现玩家升级系统
- 添加道具商店
- 支持存档/读档功能
- 增加更多技能树
🔠 核心代码
python
import pygame
import sys
import os
import random
# 初始化及背景显示
pygame.init()
judge_new_list = []
gameobj = 'animation'
# 设置窗口尺寸大小
screen = pygame.display.set_mode((1000, 700))
# 加载设置游戏的裁判判官的图片
judge = pygame.image.load("./animation/other/0.png")
judge = pygame.transform.scale(judge, (200,150))
# 字体设置
# windows字体 fangsong simhei kaiti
# macOS字体 arialunicode pingfang songti
# 设置游戏中敌我属性文本的字体
font = pygame.font.SysFont('simhei', 20)
# 游戏的背景音乐 音量大小0.3 无限循环播放
pygame.mixer.music.load("music/bg.mp3")
pygame.mixer.music.set_volume(0.3)
pygame.mixer.music.play(-1)
# 设置物理攻击音效
attack1_fx = pygame.mixer.Sound("music/attack1.mp3")
attack1_fx.set_volume(0.9)
# 设置魔法攻击音效
magic1_fx = pygame.mixer.Sound("music/magic1.mp3")
magic1_fx.set_volume(0.9)
# 设置生命值回复音效
addhp1_fx = pygame.mixer.Sound("music/addhp1.mp3")
addhp1_fx.set_volume(0.9)
# 设置增伤音效
addatk1_fx = pygame.mixer.Sound("music/addatk1.mp3")
addatk1_fx.set_volume(0.9)
# 设置增伤音效
adddef1_fx = pygame.mixer.Sound("music/adddef1.mp3")
adddef1_fx.set_volume(0.9)
# 设置游戏失败结束音效
gameover1_fx = pygame.mixer.Sound("music/gameover.wav")
gameover1_fx.set_volume(0.9)
# 设置游戏胜利音效
gamewin1_fx = pygame.mixer.Sound("music/gamewin.wav")
gamewin1_fx.set_volume(0.9)
# 玩家角色属性字典,
playerData = {
'def': 50,
'atk': 100,
'magic': 1000,
'addHP': 200,
'addAtk': 200,
'addDef': 100,
'HP': 5000,
'maxHP': 5000,
}
# boss属性字典
bossData1 = {
'def': 50,
'ice': 100,
'earth': 200,
'fire': 300,
'wind': 400,
'HP': 10000,
}
def showJudge():
# 显示旁观
for i in judge_new_list:
screen.blit(i, (290, 10))
pygame.display.flip()
# 插入排序,使用os库的listdir获取其对应文件下的图片名称是,是无顺序的
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], mylist[j - 1] = mylist[j - 1], mylist[j]
else:
break
# 构建动画字典,返回值是动画字典
def createDict(path):
# 获取文件夹名字
keys = os.listdir(path) # print()列表
# 以文件夹名字列表构建字典
gameDict = dict.fromkeys(keys) # print()空字典
# 字典赋值
for key in keys:
pictures = []
# 获取图片的名字(全名包括扩展名)
names = os.listdir(path + key + '/')
# 把图片变量存在picture列表中
for i in range(len(names)):
names[i] = int(names[i].replace('.png', ''))
insertSort(names)
for name in names:
# 把.png拼上
img = pygame.image.load(path + key + '/' + str(name) + '.png') # convert可以去掉
pictures.append(img)
gameDict[key] = pictures
return gameDict
# 绘制单独角色UI函数,角色字典名,坐标x,y
def showHP(x, y, hp, name): # x,y固定值
if name == 'player':
length = int(0.02 * hp)
elif name == 'boss':
length = int(0.01 * hp)
# 保证血条,不能为负值。
if length < 0:
length = 0
# 缩放图片,参数:图片变量;缩放的像素大小
bar = pygame.transform.scale(uiDict['HPbar'][0], (length, 13))
screen.blit(bar, (x, y))
# 显示人物属性
offset = 0
def showData(d):
global offset
if d == playerData:
x = 260
gap = 21
else:
x = 700
gap = 25
for k, v in d.items():
if k != 'maxHP':
words = k + ':' + str(v)
if d == playerData:
text = font.render(words, True, (255,8,8))
else:
text = font.render(words, True, (5, 255,255))
screen.blit(text, (x, 516 + offset * gap))
offset += 1
offset = 0
# 动画状态帧
player = 0
boss = 0
judge_index=0
def showActor(dict, file, pos=(0, 0)):
global player, boss
if dict == playerDict:
if player >= len(dict[file]):
player = 0
screen.blit(dict[file][player], pos)
player += 1
else:
if boss >= len(dict[file]):
boss = 0
screen.blit(dict[file][boss], pos)
boss += 1
state = 'loading'
turn = 'player'
choose = 'doing'
skill = 0
# 玩家计算
def playerSkill():
if playerState == 'attack':
# 剩余生命计算
damage = (playerData['atk'] - bossData1['def'])
bossData1['HP'] -= damage
elif playerState == 'magic':
damage = (playerData['magic'] - bossData1['def'])
bossData1['HP'] -= damage
# 生命恢复
elif playerState == 'addHP':
playerData['HP'] = playerData['HP'] + playerData['addHP']
if playerData['HP'] > playerData['maxHP']:
playerData['HP'] = playerData['maxHP']
elif playerState == 'addDef':
playerData['def'] += playerData['addDef']
elif playerState == 'addAtk':
playerData['atk'] += playerData['addAtk']
# boss伤害
def bossSkill():
damage = (bossData1[randomSkill] - playerData['def'])
if damage > 0:
playerData['HP'] -= damage
while True:
# 事件获取
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 and skill == 1:
choose = 'done';attack1_fx.play()
elif e.key == pygame.K_SPACE and skill == 2:
choose = 'done';magic1_fx.play()
elif e.key == pygame.K_SPACE and skill == 3:
choose = 'done';addhp1_fx.play()
elif e.key == pygame.K_SPACE and skill == 4:
choose = 'done';addatk1_fx.play()
elif e.key == pygame.K_SPACE and skill == 5:
choose = 'done';adddef1_fx.play()
if choose == 'doing':
if e.type == pygame.MOUSEBUTTONDOWN:
x = e.pos[0]
y = e.pos[1]
if 450 < y < 490: # 根据UI改
# y坐标判断
if 270 < x < 340:
playerState = 'attack'
skill = 1
elif 370 < x < 440:
playerState = 'magic'
skill = 2
elif 470 < x < 540:
playerState = 'addHP'
skill = 3
elif 570 < x < 640:
playerState = 'addAtk'
skill = 4
elif 670 < x < 740:
playerState = 'addDef'
skill = 5
# 资源加载
if state == 'loading':
# 资源加载(更换图片)
uiDict = createDict('UI/')
screen.blit(uiDict['start'][0], (0, 0))
pygame.display.update()
playerDict = createDict(gameobj+'/player/')
screen.blit(uiDict['start'][1], (0, 0))
pygame.display.update()
bossDict = createDict(gameobj+'/boss/')
screen.blit(uiDict['start'][3], (0, 0))
pygame.display.update()
pygame.time.delay(500)
state = 'start'
# 游戏开始画面
elif state == 'start':
# 显示开始界面,按enter
screen.blit(uiDict['start'][4], (0, 0))
# 游戏进行状态
elif state == 'running':
# 绘制背景
screen.blit(uiDict['map'][skill], (0, 0))
# 显示面板UI
showHP(86, 642, playerData['HP'], 'player')
showHP(818, 642, bossData1['HP'], 'boss')
# 显示属性
showData(playerData)
showData(bossData1)
# 显示判官
screen.blit(judge,(350,10))
# 玩家回合
if turn == 'player':
# 技能选择
if choose == 'doing':
# 双方闲置动画
showActor(playerDict, 'idle')
showActor(bossDict, 'idle')
showJudge()
else:
# BOSS为闲置动画
showActor(bossDict, 'idle')
showActor(playerDict, playerState)
num = len(playerDict[playerState])
if player >= num:
playerSkill()
randomSkill = random.choice(['ice', 'earth', 'fire', 'wind'])
turn = 'boss'
# boss回合
elif turn == 'boss':
# player为闲置动画
showActor(playerDict, 'idle')
showActor(bossDict, randomSkill)
num = len(bossDict[randomSkill])
if boss >= num:
bossSkill()
choose = 'doing'
turn = 'player'
# 游戏结束状态,显示胜利或者失败
if playerData['HP'] <= 0 or bossData1['HP'] <= 0:
# 双方任意一方没血,结束游戏
state = 'end'
elif state == 'end':
if playerData['HP'] <= 0:
screen.blit(uiDict['end'][0], (0, 0))
gameover1_fx.play()
elif bossData1['HP'] <= 0:
screen.blit(uiDict['end'][1], (0, 0))
gamewin1_fx.play()
# 全局更新
pygame.display.update()
pygame.time.delay(100)
☑ 最后附上项目截图












💬 欢迎在评论区留言交流!
📧 如需转载,请注明出处