以前很火的植物大战僵尸游戏, 本想在网上找个python版本游戏学习下,无奈没有发现比较完整的,那就自己来写一个把。图片资源是从github上下载的,因为图片资源有限,只能实现几种植物和僵尸。
功能实现如下:
支持的植物类型:太阳花,豌豆射手,寒冰射手,坚果,樱桃炸弹。新增加植物:双重豌豆射手,三重豌豆射手。
支持的僵尸类型:普通僵尸,棋子僵尸,路障僵尸,铁桶僵尸。
使用json文件保存关卡信息,设置僵尸出现的时间和位置。
新增加除草机。
下面是游戏的截图:
图1
游戏截图一
图2
游戏截图二
游戏截图三
图片显示切换从图1和图2可以看到,僵尸的行走和攻击时的图片显示会有不同,这篇文章讲下如何进行图片显示的切换。
以上面的路障僵尸为例,一共有下面几种图片类型。
- 带着路障行走
- 带着路障攻击
- 不带路障行走(即变成普通僵尸的行走)
- 不带路障攻击(即变成普通僵尸的攻击)
- 没有头的行走
- 没有头的攻击
- 死亡
图3是路障僵尸的这7种图片类型的示例
路障僵尸的7种图片类型的示例
图片加载植物大战僵尸的图片资源比较特别,一种图片类型的每一个动作是一个单独的图片,如图4是路障僵尸带着路障攻击的动作图片,一共有11个图片,所以加载图片的代码要做对应的修改。
游戏实现代码的github链接: https://github.com/marblexu/PythonPlantsVsZombies
代码实现这里就只讲下图片加载 和 图片显示切换的代码,整个游戏的文件目录结构和 之前的 超级玛丽 是一样的。
图片加载在 source\tool.py 中 load_all_gfx 函数遍历resources\graphics 目录和子目录。
代码中做了一个简单的区分:
- 如果在resources\graphics\subfolder\ 目录中是图片,那就是单独的一个图片,比如resources\graphics\Screen 目录中的界面图片
- 如果在resources\graphics\subfolder\ 目录中是子目录,那这个子目录或子子目录中的所有图片都属于一个图片类型,比如resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack 目录下就是路障僵尸带着路障攻击的动作图片, 如图4所示。
def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.gif')):
graphics = {}
for name1 in os.listdir(directory):
# subfolders under the folder resources\graphics
dir1 = os.path.join(directory, name1)
if os.path.isdir(dir1):
for name2 in os.listdir(dir1):
dir2 = os.path.join(dir1, name2)
if os.path.isdir(dir2):
# e.g. subfolders under the folder resources\graphics\Zombies
for name3 in os.listdir(dir2):
dir3 = os.path.join(dir2, name3)
# e.g. subfolders or pics under the folder resources\graphics\Zombies\ConeheadZombie
if os.path.isdir(dir3):
# e.g. it's the folder resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack
image_name, _ = os.path.splitext(name3)
graphics[image_name] = load_image_frames(dir3, image_name, colorkey, accept)
else:
# e.g. pics under the folder resources\graphics\Plants\Peashooter
image_name, _ = os.path.splitext(name2)
graphics[image_name] = load_image_frames(dir2, image_name, colorkey, accept)
break
else:
# e.g. pics under the folder resources\graphics\Screen
name, ext = os.path.splitext(name2)
if ext.lower() in accept:
img = pg.image.load(dir2)
if img.get_alpha():
img = img.convert_alpha()
else:
img = img.convert()
img.set_colorkey(colorkey)
graphics[name] = img
return graphics
GFX = load_all_gfx(os.path.join("resources","graphics"))
load_image_frames 函数 将目录中的所有图片按照 图片名称中的index值为key,保存在tmp 字典中。比如图片名称为"ConeheadZombieAttack_2", 它的index值就为2。
然后将图片按index值依次加入到 frame_list 中。
def load_image_frames(directory, image_name, colorkey, accept):
frame_list = []
tmp = {}
# image_name is "Peashooter", pic name is 'Peashooter_1', get the index 1
index_start = len(image_name) 1
frame_num = 0;
for pic in os.listdir(directory):
name, ext = os.path.splitext(pic)
if ext.lower() in accept:
index = int(name[index_start:])
img = pg.image.load(os.path.join(directory, pic))
if img.get_alpha():
img = img.convert_alpha()
else:
img = img.convert()
img.set_colorkey(colorkey)
tmp[index]= img
frame_num = 1
for i in range(frame_num):
frame_list.append(tmp[i])
return frame_list
图片显示切换
在 source\component\zombie.py 中, Zombie 类是所有僵尸类的父类,初始化 函数调用loadImages函数加载所有支持的图片类型,设置Sprite 精灵类显示需要的成员变量 image和rect。
loadFrames函数给具体的子类来调用,获取图片。
class Zombie(pg.sprite.Sprite):
def __init__(self, x, y, name, health, head_group=None, damage=1):
pg.sprite.Sprite.__init__(self)
self.name = name
self.frames = []
self.frame_index = 0
self.loadImages()
self.frame_num = len(self.frames)
self.image = self.frames[self.frame_index]
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.bottom = y
...
def loadFrames(self, frames, name, image_x):
frame_list = tool.GFX[name]
rect = frame_list[0].get_rect()
width, height = rect.w, rect.h
width -= image_x
for frame in frame_list:
frames.append(tool.get_image(frame, image_x, 0, width, height))
基本的功能都在Zombie 父类中实现,如果子类有特殊需求,可以重定义同名函数。
- update 函数:每个tick 都会调用的入口函数,用来更新僵尸的位置,切换状态和更新图片显示。
- handleState 函数:根据僵尸当前的状态来执行不同的函数。
- animation 函数:每隔指定的 animate_interval 时间会显示图片类型的下一个动作。
def update(self, game_info):
self.current_time = game_info[c.CURRENT_TIME]
self.handleState()
self.animation()
def handleState(self):
if self.state == c.WALK:
self.walking()
elif self.state == c.ATTACK:
self.attacking()
elif self.state == c.DIE:
self.dying()
def animation(self):
if (self.current_time - self.animate_timer) > self.animate_interval:
self.frame_index = 1
if self.frame_index >= self.frame_num:
if self.state == c.DIE:
self.kill()
return
self.frame_index = 0
self.animate_timer = self.current_time
self.image = self.frames[self.frame_index]
下面四个函数是修改僵尸的当前状态和图片显示。
- setWalk 函数:修改为行走状态,图片显示会根据不同值设置不同的图片类型。
- setAttack 函数:修改为攻击状态,图片显示会根据不同值设置不同的图片类型。
- setDie 函数:修改为死亡状态。
- changeFrames 函数:修改图片类型后,需要重新设置成员变量frame_num,frame_index, image和rect的值。
def setWalk(self):
self.state = c.WALK
self.animate_interval = 150
if self.helmet:
self.changeFrames(self.helmet_walk_frames)
elif self.losHead:
self.changeFrames(self.losthead_walk_frames)
else:
self.changeFrames(self.walk_frames)
def setAttack(self, plant):
self.plant = plant
self.state = c.ATTACK
self.animate_interval = 100
if self.helmet:
self.changeFrames(self.helmet_attack_frames)
elif self.losHead:
self.changeFrames(self.losthead_attack_frames)
else:
self.changeFrames(self.attack_frames)
def setDie(self):
self.state = c.DIE
self.animate_interval = 200
self.changeFrames(self.die_frames)
def changeFrames(self, frames):
'''change image frames and modify rect position'''
self.frames = frames
self.frame_num = len(self.frames)
self.frame_index = 0
bottom = self.rect.bottom
centerx = self.rect.centerx
self.image = self.frames[self.frame_index]
self.rect = self.image.get_rect()
self.rect.bottom = bottom
self.rect.centerx = centerx
路障僵尸类就比较简单,只需要实现 loadImages 函数,调用loadFrames函数加载该种僵尸支持的图片类型,这边主要的差异在于不同种类僵尸的图片类型的名称会有区别。
class ConeHeadZombie(Zombie):
def __init__(self, x, y, head_group):
Zombie.__init__(self, x, y, c.CONEHEAD_ZOMBIE, c.CONEHEAD_HEALTH, head_group)
self.helmet = True
def loadImages(self):
self.helmet_walk_frames = []
self.helmet_attack_frames = []
self.walk_frames = []
self.attack_frames = []
self.losthead_walk_frames = []
self.losthead_attack_frames = []
self.die_frames = []
helmet_walk_name = self.name
helmet_attack_name = self.name 'Attack'
walk_name = c.NORMAL_ZOMBIE
attack_name = c.NORMAL_ZOMBIE 'Attack'
losthead_walk_name = c.NORMAL_ZOMBIE 'LostHead'
losthead_attack_name = c.NORMAL_ZOMBIE 'LostHeadAttack'
die_name = c.NORMAL_ZOMBIE 'Die'
frame_list = [self.helmet_walk_frames, self.helmet_attack_frames,
self.walk_frames, self.attack_frames, self.losthead_walk_frames,
self.losthead_attack_frames, self.die_frames]
name_list = [helmet_walk_name, helmet_attack_name,
walk_name, attack_name, losthead_walk_name,
losthead_attack_name, die_name]
for i, name in enumerate(name_list):
self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x'])
self.frames = self.helmet_walk_frames
编译环境
python3.7 pygame1.9
原文链接:https://blog.csdn.net/marble_xu/article/details/100129768?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param