介绍
在 上一讲 中介绍了如何绘制地图、加载英雄头像图片,并用鼠标控制英雄移动,在这一讲中将介绍用pandas 加载自制的数据库(excel文件),在地图上行走遭遇怪时显示从数据表格中读取的怪的属性。
pandas 是基于NumPy 的一种工具,该工具是为解决数据分析任务而创建的。pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。pandas 提供了大量能使我们快速便捷地处理数据的函数和方法。
安装 pandas 需要基础环境是 Python,如果已经安装了 Python 和 Pip,就可以使用 pip 安装 pandas:
pip install pandas
接着用import 导入pandas ,查看版本
>>> import pandas
>>> pandas.__version__
'1.4.2'
如果出现这样的信息,就表示pandas 安装成功了。
三、加载excel数据文件
在Excel中制作了若干表格,包括怪、技能、天赋、装备、任务物品、魔杖、任务、地图和NPC,如下图所示。这个Excel文件已经上传到GitCode,链接见文末。
要读入这个Excel中的数据,可以使用pd.read_excel() 方法。为此,在Game类的__init__() 方法中添加如下代码:
import pandas as pd
class Game():
def __init__(self, fps, window_width, window_height, caption):
...
self.data = GameConst.data
self.Monster_data = pd.read_excel(self.data, sheet_name="怪")
由于Excel表格都是二维的矩阵,所以可以用字典进行存储。为此,在Game类中新建一个字典成员变量。
print('loading monsters, please wait...')
self.Monster = {}
self.load_monsters()
pandas把Excel表格的第一行作为标题,存储在data.columns 中。要读入这一行,可以在Monster 字典中新建一个name_list 列表变量,然后把这一行的内容存储在name_list 中:
self.Monster['name_list'] = list(Monster_data.columns)
self.Monster['name_list']
['name', '绿史莱姆', '红史莱姆', '蝙蝠', '术士', '骷髅', '狼']
self.Monster['name_list'].pop(0)
然后读入怪表格中的整个矩阵,方法是用双重for 循环:
for i in range(self.Monster_data.shape[0]):
attr = str(self.Monster_data.iloc[i, 0])
for j in range(1, self.Monster_data.shape[1]):
name = self.Monster['name_list'][j-1]
if i <= 1:
b = str(self.Monster_data.iloc[i, j])
else:
b = int(self.Monster_data.iloc[i, j])
self.Monster[name][attr] = b
Monster_data.shape 是一个元组,存储了这个矩阵的行数和列数:
Monster_data.shape
(24, 7)
可以看到,怪表格中有24行(除去第一行标题),7列。
Monster_data.iloc[i, j] 是表格中单元格中存放的数据,i是行,j是列,比如要提取第3行第2列的单元格:
Monster_data.iloc[2, 1]
1
注意行和列是从0开始计数的。
还有一点,就是在实践中发现,pandas在提取单元格的值时,有时会生成numpy格式的值,而在后续处理中希望这些值是字符串或整数。为此,可以用str() 或int() 方法转换一下:
if i <= 1:
b = str(self.Monster_data.iloc[i, j])
else:
b = int(self.Monster_data.iloc[i, j])
这样,整个怪表格的数据就存放在Monster字典中了。可以用print() 函数将整个Monster字典打印出来:
python run.py
pygame 2.1.2 (SDL 2.0.20, Python 3.10.4)
Hello from the pygame community. https://www.pygame.org/contribute.html
loading game data, please wait...
loading monsters, please wait...
{'name_list': ['绿史莱姆', '红史莱姆', '蝙蝠', '术士', '骷髅', '狼'], 'name': {'name': 'name'}, '绿史莱姆': {'name': '绿史莱姆', 'drop_rate': '1. +小药草 110; 2. +中药草 105; 4. +攻击力提升药水 110; 5. +铁剑 101; 6. +皮盾 101', 'status': '无', 'lv': 1, 'HP': 40, 'HP_max': 40, 'attack': 10, '最小属性攻击力': 5, '最大属性攻击力': 15, 'defence': 1, '火焰伤害': 0, '寒冷伤害': 0, '闪电伤害': 0, '毒素伤害': 0, '火焰抵抗力': 0, '寒冷抵抗力': 0, '闪电抵抗力': 0, '毒素抵抗力': 0, 'Exp': 15, 'Exp_max': 15, 'evade_rate': 0, 'double_hit_rate': 0, 'critical_hit_rate': 1, 'coin': 13, 'status_times': 0}, '红史莱姆': {'name': '红史莱姆', 'drop_rate': '1. +小药草 110; 2. +中药草 105; 4. +攻击力提升药水 110; 5. +铁剑 130; 6. +皮盾 125', 'status': '无', 'lv': 1, 'HP': 150, 'HP_max': 150, 'attack': 15, '最小属性攻击力': 10, '最大属性攻击力': 20, 'defence': 2, '火焰伤害': 0, '寒冷伤害': 5, '闪电伤害': 5, '毒素伤害': 0, '火焰抵抗力': 0, '寒冷抵抗力': 5, '闪电抵抗力': 5, '毒素抵抗力': 0, 'Exp': 56, 'Exp_max': 56, 'evade_rate': 0, 'double_hit_rate': 1, 'critical_hit_rate': 0, 'coin': 52, 'status_times': 0}, '蝙蝠': {'name': '蝙蝠', 'drop_rate': '1. +小药草 110; 2. +中药草 105; 4. +攻击力提升药水 110; 5. +蝙蝠毛披风 110; 6. +布靴 105', 'status': '无', 'lv': 2, 'HP': 180, 'HP_max': 180, 'attack': 30, '最小属性攻击力': 25, '最大属性攻击力': 35, 'defence': 5, '火焰伤害': 0, '寒冷伤害': 0, '闪电伤害': 0, '毒素伤害': 9, '火焰抵抗力': 0, '寒冷抵抗力': 0, '闪电抵抗力': 0, '毒素抵抗力': 9, 'Exp': 70, 'Exp_max': 70, 'evade_rate': 1, 'double_hit_rate': 0, 'critical_hit_rate': 0, 'coin': 65, 'status_times': 0}, '术士': {'name': '术士', 'drop_rate': '1. +小药草 110; 2. +中药草 105; 3. +攻击力提升药水 110; 5. +蝙蝠毛披风 110; 6. +布靴 105', 'status': '无', 'lv': 2, 'HP': 350, 'HP_max': 350, 'attack': 35, '最小属性攻击力': 30, '最大属性攻击力': 40, 'defence': 10, '火焰伤害': 5, '寒冷伤害': 0, '闪电伤害': 0, '毒素伤害': 0, '火焰抵抗力': 5, '寒冷抵抗力': 0, '闪电抵抗力': 0, '毒素抵抗力': 0, 'Exp': 109, 'Exp_max': 109, 'evade_rate': 2, 'double_hit_rate': 3, 'critical_hit_rate': 2, 'coin': 88, 'status_times': 0}, '骷髅': {'name': '骷髅', 'drop_rate': '1. +小药草 110; 2. +中药草 105; 6. +攻击力提升药水 105; 5. +铁甲 110; 6. +铁盔 105', 'status': '无', 'lv': 3, 'HP': 600, 'HP_max': 600, 'attack': 56, '最小属性攻击力': 51, '最大属性攻击力': 61, 'defence': 20, '火焰伤害': 3, '寒冷伤害': 5, '闪电伤害': 5, '毒素伤害': 3, '火焰抵抗力': 3, '寒冷抵抗力': 5, '闪电抵抗力': 5, '毒素抵抗力': 3, 'Exp': 190, 'Exp_max': 190, 'evade_rate': 0, 'double_hit_rate': 0, 'critical_hit_rate': 1, 'coin': 153, 'status_times': 0}, '狼': {'name': '狼', 'drop_rate': '1. +小药草 110; 2. +中药草 105; 3. +攻击力提升药水 110; 5. +铁甲 110; 6. +铁盔 105', 'status': '无', 'lv': 4, 'HP': 2700, 'HP_max': 2700, 'attack': 66, '最小属性攻击力': 61, '最大属性攻击力': 71, 'defence': 33, '火焰伤害': 5, '寒冷伤害': 0, '闪电伤害': 5, '毒素伤害': 0, '火焰抵抗力': 5, '寒冷抵抗力': 0, '闪电抵抗力': 5, '毒素抵抗力': 0, 'Exp': 210, 'Exp_max': 210, 'evade_rate': 0, 'double_hit_rate': 15, 'critical_hit_rate': 2, 'coin': 161, 'status_times': 0}, 'attr_list': ['drop_rate', 'status', 'lv', 'HP', 'HP_max', 'attack', '最小属性攻击力', '最大属性攻击力', 'defence', '火焰伤害', '寒冷伤害', '闪电伤害', '毒素伤害', '火焰抵抗力', '寒冷抵抗力', '闪电抵抗力', '毒素抵抗力', 'Exp', 'Exp_max', 'evade_rate', 'double_hit_rate', 'critical_hit_rate', 'coin', 'status_times']}
遭遇怪时显示怪的属性
只有在地图上移动时才有可能会遭遇怪,所以在Player.move() 方法中添加以下代码:
if self.is_moved == 1:
self.print_location(hero, game)
m1 = ['绿史莱姆', '术士', '蝙蝠']
m2 = ['红史莱姆', '蝙蝠', '骷髅']
self.generate_monsters_in_a_map(m1, m2, hero, game)
is_moved 是一个标记,0 表示英雄没有移动,1 表示移动了,2 表示无法移动(比如到了地图边缘)。
print_location() 是打印英雄当前XY坐标、所处地点。
def print_location(self, hero, game):
game.message_1.append('你的位置: {0}'.format(hero.location))
game.message_1.append('你的坐标(x, y): ({0}, {1})'.format(hero.x_coordinate, hero.y_coordinate))
generate_monsters_in_a_map() 是英雄在地图上行走时按一定概率发生的事件,如捡到药材,遇到怪。为了区分遇到的怪,设计了两个列表,m1 是小怪,m2 是首领怪。
def generate_monsters_in_a_map(self, m1, m2, hero, game):
if hero.x_coordinate == 9 and hero.y_coordinate == 0:
self.combat_with_monster('狼', hero, game)
return 0
if hero.x_coordinate == 0 and hero.y_coordinate >= 0 and hero.y_coordinate <= 10:
r1 = random.randrange(0, 100)
if r1 < GameConst.prob_m1:
self.combat_with_monster(m1[0], hero, game)
elif r1 > 100 - GameConst.prob_m2:
self.combat_with_monster(m2[0], hero, game)
elif hero.y_coordinate == 9 and hero.x_coordinate > 0 and hero.x_coordinate < 10:
r1 = random.randrange(0, 100)
if r1 < GameConst.prob_m1:
self.combat_with_monster(m1[1], hero, game)
elif r1 > 100 - GameConst.prob_m2:
self.combat_with_monster(m2[1], hero, game)
elif hero.x_coordinate == 9 and hero.y_coordinate >= 0 and hero.y_coordinate <= 10:
r1 = random.randrange(0, 100)
if r1 < GameConst.prob_m1:
self.combat_with_monster(m1[2], hero, game)
elif r1 > 100 - GameConst.prob_m2:
self.combat_with_monster(m2[2], hero, game)
else:
r1 = random.randrange(0, 100)
if r1 < 20:
self.obtained_item('小灵芝', 1, hero, game)
elif r1 < 35:
self.obtained_item('中灵芝', 1, hero, game)
elif r1 < 65:
self.obtained_item('小药草', 1, hero, game)
elif r1 < 80:
self.obtained_item('中药草', 1, hero, game)
elif r1 < 85:
self.obtained_item('大药草', 1, hero, game)
combat_with_monster() 方法表示英雄进入战斗状态,为此,使用2个变量game_process 和status 进行存储(一个标记,一个作为对照)。
def combat_with_monster(self, m, hero, game):
self.status = GameConst.HERO_ATTACKING
self.game_process = GameConst.HERO_ATTACKING
hero.current_monster = m
game.message_2 = []
game.message_2.append("遇到敌人: {0}。".format(m))
game.message_2.append('开始战斗: ')
然后,用一个计时器产生怪的攻击事件。
MYEVENT01 = pygame.USEREVENT + 1
pygame.time.set_timer(MYEVENT01, 1000)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION:
player.mouseX, player.mouseY = event.pos
elif event.type == MYEVENT01:
if player.game_process == GameConst.HERO_ATTACKING:
player.Monster_do_melee_attack(hero, game)
elif event.type == pygame.KEYDOWN:
keys = pygame.key.get_pressed()
game.respond_to_keys(keys, player, hero, game)
而英雄的攻击则由玩家控制,设计的是按a 就进行攻击。为此,在pygame.KEYDOWN 事件中记录玩家按下的按键,然后用game.respond_to_keys() 方法进行处理。
def respond_to_keys(self, keys, player, hero, game):
''' 对按键输入的响应。'''
if keys[K_a]:
if player.game_process == GameConst.HERO_ATTACKING:
result = player.Hero_do_melee_attack(hero, game)
if result == 1:
player.Hero_wins_combat(hero, game)
显示英雄的属性面板
为了查看英雄的详细属性,查看英雄的背包,并且使用背包里的物品,需要设计一个属性面板,如图所示。 为此,在Game类中增加一个方法,打印这个面板。
def print_panels(self, player, hero, game):
if player.game_process == GameConst.HERO_PROPERTIES_PANEL:
self.screen.fill(RGB.Black)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.CharBox, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.CharBox, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.EquipBox, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.EquipBox, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.BackpackBox, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.BackpackBox, 1)
然后要实现鼠标移动时消息框高亮,原理与之前讲的一样,请参考:用pygame编写单人RPG小游戏(一)_下唐人的博客-CSDN博客
完整的程序运行结果如图所示。
完整的源代码已经上传到GitCode:class3 · master · 下唐人 / magic_tower_chapter_0 · GitCode
|