想撸一个以前很火的植物大战僵尸游戏, 在网上找了许多python版本的游戏,发现没有比较完整的,那就自己来写一个把。图片资源是从github上下载的,因为图片资源有限,只能实现几种植物和僵尸。

功能实现如下:

支持的植物类型:太阳花,豌豆射手,寒冰射手,坚果,樱桃炸弹。

新增加植物:双重豌豆射手,三重豌豆射手。

支持的僵尸类型:普通僵尸,棋子僵尸,路障僵尸,铁桶僵尸。

使用json文件保存关卡信息,设置僵尸出现的时间和位置。

新增加除草机。

下面是游戏的截图:

图一

48f3588123bc91dad0a457fa676af90a.png

图二

b0911b58134dac60c203ed40608b9248.png

图三

cb96d22cd5a9f7f7fc316b52eef40c6b.png

图片显示切换

从图1和图2可以看到,僵尸的行走和攻击时的图片显示会有不同,这篇文章讲下如何进行图片显示的切换。

以上面的路障僵尸为例,一共有下面几种图片类型。

  • 带着路障行走
  • 带着路障攻击
  • 不带路障行走(即变成普通僵尸的行走)
  • 不带路障攻击(即变成普通僵尸的攻击)
  • 没有头的行走
  • 没有头的攻击
  • 死亡

图3是路障僵尸的这7种图片类型的示例

a06b7ccff4274b4b8452181d154e6c70.png

图片加载

植物大战僵尸的图片资源比较特别,一种图片类型的每一个动作是一个单独的图片,如下是路障僵尸带着路障攻击的动作图片,一共有11个图片,所以加载图片的代码要做对应的修改。

608f17076c41484bfea5d61d32322ed0.png

在 sourcetool.py 中 load_all_gfx 函数遍历resourcesgraphics 目录和子目录。

代码中做了一个简单的区分:

  • 如果在resourcesgraphicssubfolder 目录中是图片,那就是单独的一个图片,比如resourcesgraphicsScreen 目录中的界面图片
  • 如果在resourcesgraphicssubfolder 目录中是子目录,那这个子目录或子子目录中的所有图片都属于一个图片类型,比如resourcesgraphicsZombiesConeheadZombieConeheadZombieAttack 目录下就是路障僵尸带着路障攻击的动作图片, 如图4所示。
 1 def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.gif')):
 2     graphics = {}
 3     for name1 in os.listdir(directory):
 4         # subfolders under the folder resourcesgraphics
 5         dir1 = os.path.join(directory, name1)
 6         if os.path.isdir(dir1):
 7             for name2 in os.listdir(dir1):
 8                 dir2 = os.path.join(dir1, name2)
 9                 if os.path.isdir(dir2):
10                 # e.g. subfolders under the folder resourcesgraphicsZombies
11                     for name3 in os.listdir(dir2):
12                         dir3 = os.path.join(dir2, name3)
13                         # e.g. subfolders or pics under the folder resourcesgraphicsZombiesConeheadZombie
14                         if os.path.isdir(dir3):
15                             # e.g. it's the folder resourcesgraphicsZombiesConeheadZombieConeheadZombieAttack
16                             image_name, _ = os.path.splitext(name3)
17                             graphics[image_name] = load_image_frames(dir3, image_name, colorkey, accept)
18                         else:
19                             # e.g. pics under the folder resourcesgraphicsPlantsPeashooter
20                             image_name, _ = os.path.splitext(name2)
21                             graphics[image_name] = load_image_frames(dir2, image_name, colorkey, accept)
22                             break
23                 else:
24                 # e.g. pics under the folder resourcesgraphicsScreen
25                     name, ext = os.path.splitext(name2)
26                     if ext.lower() in accept:
27                         img = pg.image.load(dir2)
28                         if img.get_alpha():
29                             img = img.convert_alpha()
30                         else:
31                             img = img.convert()
32                             img.set_colorkey(colorkey)
33                         graphics[name] = img
34     return graphics
35 
36 GFX = load_all_gfx(os.path.join("resources","graphics"))复制代码

load_image_frames 函数 将目录中的所有图片按照图片名称中的index值为key,保存在tmp 字典中。比如图片名称为"ConeheadZombieAttack_2",它的index值就为2。
然后将图片按index值依次加入到 frame_list 中。

 1 def load_image_frames(directory, image_name, colorkey, accept):
 2     frame_list = []
 3     tmp = {}
 4     # image_name is "Peashooter", pic name is 'Peashooter_1', get the index 1
 5     index_start = len(image_name) + 1
 6     frame_num = 0;
 7     for pic in os.listdir(directory):
 8         name, ext = os.path.splitext(pic)
 9         if ext.lower() in accept:
10             index = int(name[index_start:])
11             img = pg.image.load(os.path.join(directory, pic))
12             if img.get_alpha():
13                 img = img.convert_alpha()
14             else:
15                 img = img.convert()
16                 img.set_colorkey(colorkey)
17             tmp[index]= img
18             frame_num += 1
19
20     for i in range(frame_num):
21         frame_list.append(tmp[i])
22     return frame_list复制代码

图片显示切换

在 sourcecomponentzombie.py 中, Zombie 类是所有僵尸类的父类,初始化 函数调用loadImages函数加载所有支持的图片类型,设置Sprite 精灵类显示需要的成员变量 image和rect。

loadFrames函数给具体的子类来调用,获取图片。

 1 class Zombie(pg.sprite.Sprite):
 2     def __init__(self, x, y, name, health, head_group=None, damage=1):
 3         pg.sprite.Sprite.__init__(self)
 4         
 5         self.name = name
 6         self.frames = []
 7         self.frame_index = 0
 8         self.loadImages()
 9         self.frame_num = len(self.frames)
10 
11         self.image = self.frames[self.frame_index]
12         self.rect = self.image.get_rect()
13         self.rect.centerx = x
14         self.rect.bottom = y
15         ...
16 
17     def loadFrames(self, frames, name, image_x):
18         frame_list = tool.GFX[name]
19         rect = frame_list[0].get_rect()
20         width, height = rect.w, rect.h
21         width -= image_x
22 
23         for frame in frame_list:
24             frames.append(tool.get_image(frame, image_x, 0, width, height))复制代码

基本的功能都在Zombie 父类中实现,如果子类有特殊需求,可以重定义同名函数。

update 函数:每个tick 都会调用的入口函数,用来更新僵尸的位置,切换状态和更新图片显示。

handleState 函数:根据僵尸当前的状态来执行不同的函数。

animation 函数:每隔指定的 animate_interval 时间会显示图片类型的下一个动作。

 1     def update(self, game_info):
 2         self.current_time = game_info[c.CURRENT_TIME]
 3         self.handleState()
 4         self.animation()
 5     
 6     def handleState(self):
 7         if self.state == c.WALK:
 8             self.walking()
 9         elif self.state == c.ATTACK:
10             self.attacking()
11         elif self.state == c.DIE:
12             self.dying()
13     
14     def animation(self):
15         if (self.current_time - self.animate_timer) > self.animate_interval:
16             self.frame_index += 1
17             if self.frame_index >= self.frame_num:
18                 if self.state == c.DIE:
19                     self.kill()
20                     return
21                 self.frame_index = 0
22             self.animate_timer = self.current_time
23         
24         self.image = self.frames[self.frame_index]复制代码

下面四个函数是修改僵尸的当前状态和图片显示。

  • setWalk 函数:修改为行走状态,图片显示会根据不同值设置不同的图片类型。
  • setAttack 函数:修改为攻击状态,图片显示会根据不同值设置不同的图片类型。
  • setDie 函数:修改为死亡状态。
  • changeFrames 函数:修改图片类型后,需要重新设置成员变量frame_num,frame_index, image和rect的值。
 1  def setWalk(self):
 2         self.state = c.WALK
 3         self.animate_interval = 150
 4         
 5         if self.helmet:
 6             self.changeFrames(self.helmet_walk_frames)
 7         elif self.losHead:
 8             self.changeFrames(self.losthead_walk_frames)
 9         else:
10             self.changeFrames(self.walk_frames)
11 
12     def setAttack(self, plant):
13         self.plant = plant
14         self.state = c.ATTACK
15         self.animate_interval = 100
16         
17         if self.helmet:
18             self.changeFrames(self.helmet_attack_frames)
19         elif self.losHead:
20             self.changeFrames(self.losthead_attack_frames)
21         else:
22             self.changeFrames(self.attack_frames)
23     
24     def setDie(self):
25         self.state = c.DIE
26         self.animate_interval = 200
27         self.changeFrames(self.die_frames)
28     
29     def changeFrames(self, frames):
30         '''change image frames and modify rect position'''
31         self.frames = frames
32         self.frame_num = len(self.frames)
33         self.frame_index = 0
34         
35         bottom = self.rect.bottom
36         centerx = self.rect.centerx
37         self.image = self.frames[self.frame_index]
38         self.rect = self.image.get_rect()
39         self.rect.bottom = bottom
40         self.rect.centerx = centerx复制代码

路障僵尸类就比较简单,只需要实现 loadImages 函数,调用loadFrames函数加载该种僵尸支持的图片类型,这边主要的差异在于不同种类僵尸的图片类型的名称会有区别。

 1 class ConeHeadZombie(Zombie):
 2     def __init__(self, x, y, head_group):
 3         Zombie.__init__(self, x, y, c.CONEHEAD_ZOMBIE, c.CONEHEAD_HEALTH, head_group)
 4         self.helmet = True
 5 
 6     def loadImages(self):
 7         self.helmet_walk_frames = []
 8         self.helmet_attack_frames = []
 9         self.walk_frames = []
10         self.attack_frames = []
11         self.losthead_walk_frames = []
12         self.losthead_attack_frames = []
13         self.die_frames = []
14         
15         helmet_walk_name = self.name
16         helmet_attack_name = self.name + 'Attack'
17         walk_name = c.NORMAL_ZOMBIE
18         attack_name = c.NORMAL_ZOMBIE + 'Attack'
19         losthead_walk_name = c.NORMAL_ZOMBIE + 'LostHead'
20         losthead_attack_name = c.NORMAL_ZOMBIE + 'LostHeadAttack'
21         die_name = c.NORMAL_ZOMBIE + 'Die'
22 
23         frame_list = [self.helmet_walk_frames, self.helmet_attack_frames,
24                       self.walk_frames, self.attack_frames, self.losthead_walk_frames,
25                       self.losthead_attack_frames, self.die_frames]
26         name_list = [helmet_walk_name, helmet_attack_name,
27                      walk_name, attack_name, losthead_walk_name,
28                      losthead_attack_name, die_name]
29         
30         for i, name in enumerate(name_list):
31             self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x'])
32 
33         self.frames = self.helmet_walk_frames

​源码下载:

​https://download.csdn.net/download/marble_xu/11639414

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐