一、作品展示
二、作品分析与制作
2.1 创建窗口与加载背景
任务1:创建一个合适大小(长1100,宽600)的游戏窗口,设置游戏窗口的标题,加载植物大战僵尸的背景图片,显示在游戏窗口上,点击关闭窗口按钮,窗口关闭
import pygame# 初始化pygame环境pygame.init()# 设置一个长为1100,宽为600的窗口screen = pygame.display.set_mode((1100, 600))# 设置窗口标题pygame.display.set_caption("植物大战僵尸")# 背景图片加载bg_img = pygame.image.load("./img/bg_play.jpg")running = Truewhile running: # 将背景显示在窗口上 screen.blit(bg_img, (0, 0)) # 监测游戏窗口事件 for event in pygame.event.get(): # 如果是退出事件,游戏退出 if event.type == pygame.QUIT: running = False pygame.quit() # 刷新游戏画面 pygame.display.flip()
2.2 坚果
2.2.1 坚果静态显示
任务2:定义一个坚果类,包含init初始化函数和paint显示函数。
初始化函数中初始化坚果的大小(宽高)、位置和造型。
显示函数实现将坚果显示在窗口上。实现坚果静态显示在窗口上的效果。

1)加载坚果的相关造型图片
# 坚果图片加载nut1 = pygame.image.load('./img/plants/TallNut.gif')nut2 = pygame.image.load('./img/plants/TallnutCracked1.gif')nut3 = pygame.image.load('./img/plants/TallnutCracked2.gif')
2)定义坚果类
# 坚果类class Tallnut(): # 初始化 def __init__(self): # 初始化位置 self.x = 500 self.y = 250 # 初始化大小 self.width = 83 self.height = 119 # 初始化造型 self.image = nut1 # 显示 def paint(self): screen.blit(self.image, (self.x, self.y))
3)创建坚果对象,显示在游戏窗口上

2.2.2 坚果动态显示
任务3:将坚果的静态显示优化成动态显示
1)安装PIL模块
2)导入PIL模块中的Image和ImageSequence
from PIL import Image, ImageSequence
3)加载坚果gif图的所有帧
# 坚果加载动图,获取所有帧nuts1 = []with open('./img/plants/TallNut.gif', 'rb') as f: for frame in ImageSequence.Iterator(Image.open(f)): image = frame.convert() image = pygame.image.frombuffer(image.tobytes(), image.size, image.mode) nuts1.append(image)nuts2 = []with open('./img/plants/TallnutCracked1.gif', 'rb') as f: for frame in ImageSequence.Iterator(Image.open(f)): image = frame.convert() image = pygame.image.frombuffer(image.tobytes(), image.size, image.mode) nuts2.append(image)nuts3 = []with open('./img/plants/TallnutCracked2.gif', 'rb') as f: for frame in ImageSequence.Iterator(Image.open(f)): image = frame.convert() image = pygame.image.frombuffer(image.tobytes(), image.size, image.mode) nuts3.append(image)
4)优化上诉代码,创建加载gif图片返回图片列表的函数load_gif()
def load_gif(path): nuts = [] with open(path, 'rb') as f: for frame in ImageSequence.Iterator(Image.open(f)): image = frame.convert() image = pygame.image.frombuffer(image.tobytes(), image.size, image.mode) nuts.append(image) return nuts# 加载坚果gif图片,获取所有帧nuts1 = load_gif('./img/plants/TallNut.gif')nuts2 = load_gif('./img/plants/TallnutCracked1.gif')nuts3 = load_gif('./img/plants/TallnutCracked2.gif')
5)修改坚果类的初始化函数和显示函数

6)此时坚果仍然是静态的,如何让坚果动起来呢?定义播放动画animation函数修改显示的造型编号

7)调用这个函数

8)解决造型变化太快的问题

2.3 僵尸
任务4:定义一个僵尸类,僵尸有三种状态(移动、站立和攻击),包含初始化函数、显示函数和播放动画函数,将僵尸动态显示在游戏界面上

1)创建一个方法load_images加载僵尸三种状态的所有图片
# 加载多个图片,返回一个造型列表def load_images(path, suffix, start, end): images = [] for i in range(start, end + 1): image_name = "%02d%s" % (i, suffix) img = pygame.image.load(path + image_name) images.append(img) return images# 僵尸的三种状态设置,移动,站立,攻击MOVE, STAND, ATTACK = 0, 1, 2# 将僵尸三种状态的图片加载zombieM = load_images('./img/move/', '.png', 1, 13)zombieS = load_images('./img/stand/', '.png', 21, 26)zombieA = load_images('./img/attack/', '.png', 31, 41)
2)定义一个僵尸类,同样的有三个函数,初始化函数,显示函数,移动函数和播放动画函数
初始化函数:初始化僵尸的位置大小状态和造型
显示函数:将僵尸显示在游戏窗口的指定位置
移动函数:僵尸从右往左移动,即x坐标减小
播放动画函数:根据3种不同的状态播放指定的动画
①移动状态,僵尸播放移动的造型,并从右到左移动
②站立状态,僵尸播放站立的造型,位置不变
③攻击状态,僵尸播放攻击的造型,位置不变
class Zombie(): def __init__(self): # 初始化位置 self.x = 1000 self.y = 210 # 初始化大小 self.width = 180 self.height = 180 # 初始化状态 self.state = MOVE # 初始化造型 self.images = zombieM self.images_index = 0 # 起始造型的下标 def paint(self): screen.blit(self.images[self.images_index], (self.x, self.y)) def move(self): self.x -= 10 def animation(self): if self.state == MOVE: # 移动 self.images = zombieM self.move() # 移动 elif self.state == STAND: # 站立 self.images = zombieS else: # 攻击 self.images = zombieA self.images_index = (self.images_index + 1) % len(self.images)
3)创建一个僵尸对象,显示在游戏界面并播放相关动画

2.4 碰撞检测
任务5:
如果僵尸碰到了坚果,僵尸变成攻击状态
坚果被攻击后,随着生命值的减少,会展示不同的造型,直到生命值为0,坚果消失
僵尸继续移动的窗口的左方后,站立在原地不动,显示提示信息,直到玩家关闭游戏窗口

1)在坚果的初始化方法中,将坚果的生命值初始化为66

2)在全局中创建一个检测碰到的函数check()
思考1:如何进行碰撞检测?碰撞检测实际上检测的是什么?
回答1:碰撞检测实际上检测的是僵尸和坚果的图片坐标是否有重合的地方,如果有重合的地方则,说明碰撞上了,如下方左图


思考2:为什么真实写代码是小于等于nut.x+nut.width / 2而不是nut.x+nut.width?
回答2:width和height是整个图片的宽高,而不是图片中角色的宽高,如上诉右图。在本作品中如果使用图片的宽高,显示的效果是,两者还有很宽的距离,僵尸就已经在攻击了,不符合常理,所以需要除以2,更符合实际情况
def check(zom, nut): # 僵尸碰到了坚果 if zom.x <= nut.x + nut.width / 2: # 僵尸的状态设置为攻击状态 zom.state = ATTACK # 坚果被攻击后,随着生命值的减少,造型也跟着变化 if nut.life > 44: nut.images = nuts1 elif nut.life > 22: nut.images = nuts2 elif nut.life > 0: nut.images = nuts3 elif zom.x > 200: # 僵尸还没走到门口,继续移动 zom.state = MOVE return nut.life -= 1 # 生命值减少
3)调用check()函数,坚果生命值小于0时,不显示,僵尸到达门口,僵尸变成站立状态,显示结束提示语

