利用pygame模块设计一个植物大战僵尸游戏初版设定

摘要
在当今电子信息高速发展时代下,网络游戏和单机游戏已经遍布了我们的生活,随着游戏的发展,有许多游戏早已被淘汰掉,只剩下少部分的经典还依旧有人玩,植物大战僵尸就是其中之一。
植物大战僵尸是一款伴随着无数人成长的策略类游戏,可怕的僵尸因不明原因而向屋子内进军,作为屋子主人的我们,需要通过对植物的合理安排来守卫我们宝贵的家园。
此次设计是中Window10企业版下使用Python在编译软件Vscode进行开发的。
1.引言
随着社会压力的变大,人们通过玩游戏来缓解生活的压力,如今的游戏有太多种选择,与过去的建模,游戏性都有了肉眼可见的进步,但也在此情况下,由于审美的疲劳以及游戏性的难度增加,这导致了部分人的压力增大而使得他们选择过去操作简单舒适的经典来休闲娱乐自己。
《植物大战僵尸》集成了即时战略、塔防御战和卡片收集等要素,玩家控制植物抵御僵尸的进攻,保护这片植物园。游戏中可以选用的植物有40多种,而每个场景最多只能选用10种植物,这就需要玩家根据自己的游戏策略来作出取舍。因为它成功地借鉴了一些战略游戏的要素——采集资源并利用资源建造其它单位,有些玩家甚至拿星际的战略往这款游戏中套用,以阐述这款游戏需要在何时发展“经济”,何时发展“兵力”。[1]
这款游戏要求玩家既有是大脑的智慧,又有小脑的反应。在有了正确的战略思想之后,还要靠战术将战略实现出来。战术范围包括很广,植物的搭配、战斗时的阵型、植物与僵尸相遇时是战是防这都属于战术的范畴。正确的战术是玩家在战斗中胜利关键。选择正确的战术,需要先分析情况,再做出决定。那么提高战术水平也是要提高分析情况的能力。[2]

2.系统结构
系统的一开始会先建立一个块列表(用来存放记录可种植的合法的块范围),怪物列表(用来存放怪物对象),阳光列表(用来存放阳光对象),还有保卫者列表(用来存放豌豆射手以及向日葵),攻击列表(用来存放保卫者发出的攻击)
关于如何加载动图:获取素材的帧图,通过pygame提供的双缓冲技术每次刷新界面显示不同的图片。

关于如何完成购买表1的植物:首先检测下鼠标单击的位置是否在卡槽中植物卡的范围,其次确定金钱是否足够,如果以上操作均合法,那么将进入种植状态,种植的植物将会跟谁你的鼠标的移动而移动,之后开始选择位置,这将会根据鼠标右击一下确定安放位置,检查种植植物的地点是否规范,如所选位置是否已有植物已经种植或者所选的范围不为规定的范围,如果操作均合法就种植该在地区,并执行自身职责;如果属于违法操作,那么将取消种植形态。

表1 植物类型
名称 图标 类型 属性 产物描述 产物图片
豌豆射手 进攻型 血量:130
价格:100阳光值
攻击频率:3600ms 自身可发射出一种可攻击僵尸的产物用来进攻敌人
向日葵 生产型 血量:150
价格:50阳光值
生产频率:4200ms 自身可生成出阳光值,可用于购买植物

关于如何获取阳光值:如果当前状态不为种植形态的话,通过鼠标单击的位置来确定单击的鼠标位置范围内是否存在有阳光,有的话增加阳光值。
表2保卫者产物
名称 图标 属性 作用 来源
阳光 价格:25阳光值
生产来源:向日葵生产获得以及每隔3900ms随机位置生成 点击后可增加自身阳光值大小 向日葵
豌豆攻击 速度:每帧位移1单位
攻击力:30 通过攻击僵尸来保卫屋子 豌豆射手
关于豌豆射手如何发射攻击和向日葵产生阳光:用时间事件来记录时间,并通过调用类方法同时调用增加时间的方法,当达到设定的值的大小时,就会分别给攻击列表,和阳光列表增加对象。
关于攻击列表的消除:为了防止内存溢出,当攻击单位超出屏幕指定的范围后,自动消除攻击对象,除此之外,当攻击单位命中怪物,也会消除这个攻击对象
关于如何进入让保卫者拦截僵尸:当保卫者的左边与僵尸的坐标靠的十分近的时候,那么怪物就会转入攻击状态,并将速度调为0,禁止不动。
关于如何攻击僵尸:检测攻击列表中对象的位置是否与僵尸的坐标有一定的重合,如果重合的话,扣除怪物的血量,如果怪物死亡的话,那么将怪物质为死亡状态
如何让保卫者受伤:遍历保卫者列表中对象的位置和怪物列表中的对象,如果两者重合的话,那么植物就会收到怪物攻击力大小的伤害,当植物的生命小于等于0的时候,植物就会从保卫者列表中清除.
表3 怪物属性图
怪物名称 怪物图片 怪物属性 怪物状态(以下只展示部分状态图)
僵尸 HP:100
速度:走路状态:每帧位移1单位;死亡和攻击状态不位移
攻击力:30
走路状态
攻击状态
死亡状态

3.实现代码。
定义一个怪物父类,将它的图片 HP 攻击力 当前速度 走路速度 状态初始化,并给怪物添加三种设置状态的方法,在方法中改变它的状态值,加载图片,攻击速度,除此之外还有获得攻击力,血量,扣除血量的方法,定义初始位置,以及加载动图的方法

class monster:
    def __init__(self,image):
        #怪物图片 怪物血量 怪物速度 怪物攻击力大小
        self.image = []
        self.HP = 0
        self.attack=0
        self.speed = 0
        #当前帧图
        self.runspeed=0
        self.index = 0
        self.run = []
        #0代表着run 1代表attack -1代表die
        self.staus=0
    def abateHP(self, attack):
        self.HP -= attack
    #获得怪物攻击力
    def getAttack(self):
        return self.attack
    #获得怪物血量
    def getHP(self):
        return self.HP
    #获得当前位置
    def getXY(self):
        return [self.x,self.y]

    #定义初始位置
    def setSeat(self, x, y):
        self.x = x+50
        self.y = y + 10
    #移动
    def move(self, screen):
        if (self.index > len(self.image)-1):
            self.index=0
        self.x = self.x - self.speed
        
        screen.blit(pg.transform.scale(
            self.image[self.index], (106, 109)), (self.x, self.y))
        
        self.index += 1
        if (self.index == len(self.image)) and (self.staus == -1):
            return - 1
        else:
            return 1
    #转为攻击形态
    def attackStatus(self,image):
        #将速度降为0
        self.speed = 0
        #重新载入图片
        self.image = image
        self.staus=1
    #转为跑步形态

    def runStatus(self, image):
        #改变速度
        self.staus=0
        self.speed = self.runspeed
        self.image = image

    #死亡形态

    def dieStaus(self, image):
        #改变图片
        self.image = image
        self.speed = 0
        self.staus = -1
    def getStaus(self):
        return self.staus

在这之后,我们定义它的子类通过覆盖父类的属性就可以实现第一个自定义的僵尸了

class littlemonster(monster):

    def __init__(self,image):
        #0代表着run 1代表attack -1代表die
        self.staus = 0
        self.image = image
        
        self.HP = 100
        self.attack = 30
        self.runspeed = 1
        self.speed = self.runspeed

        self.index = 0

按照同样的方法编写一个保卫者的类,编写它的血量,图片,位置,时间(用来触发生产阳光或者豌豆攻击的方法),构造方法获得保卫者当前的横纵坐标,血量,以及对保卫者产生的伤害

class denfendor:
    def __init__(self):
        self.HP=0
        self.image=[]
        self.rect=self.image[0].get_rect()
        self.time=0
        self.index = 0

    #获得保卫者的位置
    def getXY(self):
        return [self.rect.left, self.rect.top]
    #获得血量
    def getHP(self):
        return self.HP
    #设置受到的伤害
    def abateHP(self,damage):
        self.HP -= damage
    #设置时间
    def addTime(self):
        self.time += 300
    #是否产生产物
    def isCreate(self,time):
        self.addTime()
        if (self.time >= time):
            self.time = 0
            return True
        return False
#加载模型
    def setModel(self, screen, x, y,num):
        size = (80, 80)
        if (num == 1):
            size=(55,55)
            
        if (self.index == len(self.image)):
            self.index = 0
        
        self.rect.left = x
        self.rect.top = y
        #重设大小
        screen.blit(pg.transform.scale(
            self.image[self.index], size), self.rect)
        self.index += 1

之后开始编写保卫者的两个子类,豌豆射手以及向日葵,通过覆盖父类的变量来达到使用效果,之后也可以按照这个方法来写更多的子类保卫者
#豌豆射手

class peas(denfendor):
    def __init__(self):
        #受到的伤害
        self.dagame=0
        #设置血量
        self.HP = 130
        #加载图片
        self.time = 0
        self.index=0
        self.image = [pg.image.load(r'images/peasModel/22072568-{:d}.png'.format(i)).convert_alpha() for i in range(1,9)]
        self.rect = self.image[0].get_rect()
        self.rect.left, self.rect.top = 300, 300

#设置向日葵
class sunflower(denfendor):
    def __init__(self):
        super()
        #受到的伤害
        self.dagame = 0
        self.HP=150
        self.time = 0
        self.index = 0
        self.image = [pg.image.load(r'images/sunflowerModel/summer-{:d}.png'.format(i)).convert_alpha() for i in range(1, 55)]
        self.rect = self.image[0].get_rect()
        self.rect.left, self.rect.top = 300, 300


#开始编写阳光类 实现对图片的加载,以及设置阳光的位置,还有判断是否被点击到


```cpp
class sunshine():
    def __init__(self):
        self.image = pg.image.load(r'images/sunshine.png')
        self.image = pg.transform.scale(self.image, (70, 70))
        self.clickAfterWait = 5
        self.flag = False
        
        self.seat=(0,0)
    def setModel(self, screen, x, y):
        self.seat = (x, y)
        screen.blit(self.image, self.seat)
    
    def isClick(self, x, y):
        flag=False
        if (self.seat[0] <= x <= self.seat[0] + 70):
            if (self.seat[1] <= y <= self.seat[1] + 70):
                flag = True
        self.flag=flag
        return flag

编写一个Block类,这个类是用来确定植物是否有合法的放置位置,通过初始化块的信息来定义每个块的具体范围,定义一个方法判断是否该块有被种植以及鼠标点击的坐标是否中块内

class Block:
    def __init__(self, num):
        #块的位置
        col = int(num % 9)
        row = int(num / 9)
        self.leftTopX = 359 + col * 55
        self.leftTopY = 137 + row * 66
        self.rightDownX = self.leftTopX + 55
        self.rightDownY = self.leftTopY + 66
        
        
        #是否被种植了
        self.isHave = False
    
    #设置获得isHave
    def setIsHave(self):
        self.isHave = not self.isHave
    
    def getIsHave(self):
        return self.isHave
    def getXY(self):
        return [self.leftTopX, self.leftTopY, self.rightDownX, self.rightDownY]
    #检查是否在这个方块内
    def checkIsInBlock(self, x, y):
        if (self.leftTopX <= x <= self.rightDownX) and (self.leftTopY <= y <= self.rightDownY):
            return True
        return False

然后开始编写攻击方式块的父类AttackWay,初始化载入图片,以及攻击大小,定义设置攻击的初始位置,以及实现攻击的移动,还有获得当前攻击对象的位置,除此之外还有攻击力大小

class AttackWay:
    def __init__(self):
        #载入图像
        self.image = pg.image.load('').convert_alpha()
        self.attack=0
    #定义初始位置
    def setSeat(self, x, y):
        self.x = x+50
        self.y = y+10
        
    #攻击移动
    def move(self, screen):

        self.x+=1
        self.seat = (self.x, self.y)
        screen.blit(self.image, self.seat)
        

    #获得攻击的位置
    def getXY(self):
        return [self.x,self.y]
    
    #获得攻击力大小
    def getAttack(self):
        return self.attack
定义豌豆攻击的子类继承攻击父类
#豌豆攻击方式
class wdAttack(AttackWay):
    def __init__(self):
        self.attack=30
        self.image = pg.image.load(r'images\attack\wandou.png').convert_alpha()
        self.image = pg.transform.scale(self.image, (30,30))

接着卡槽内卡牌的放置模型
初始化图片,图片大小,卡牌价格,设置卡牌位置,获得图片的宽高以及位置,还有设置卡牌的金额,将它价格转为图片的形式加载在游戏界面中

class card:
    def __init__(self):
        self.image=pg.image.load('')
        self.rect = pg.image.load('')
        self.rect.left = 0
        self.rect.top = 0
        self.price = 100
        
    #获得价格
    def getPrice(self):
        return self.price
    #设置卡牌位置
    def setModel(self, screen, num=0):
        size = (80, 80)
        self.rect.left = 380 + num * 95
        self.rect.top = 10
        #设置向日葵大小
        if num == 1:
            size = (70, 70)
            self.rect.left=390+num*95
        
        self.image = pg.transform.scale(self.image, size)
        screen.blit(self.image, self.rect)
        self.setPrice(screen, num)

    

    #获得图片宽高,起始位置
    def getImageWidth(self):
        return self.rect.width

    def getImageHeigth(self):
        return self.rect.height

    def getImageX(self):
        return self.rect.left

    def getImageY(self):
        return self.rect.top

    #设置金钱
    def setPrice(self, screen,num):
        rect = self.rect
        rect.top = 70
        rect.left=410+92*num
        screen.blit(pg.font.SysFont('arial', 20).render(
            str(self.price), True, (255, 255, 255)), rect)
令豌豆射手和向日葵继承卡牌父类
#豌豆射手模型
class peasCard(card):
    def __init__(self):
        super()
        self.image = pg.image.load('images/peasModel/22072568-3.png')
        self.rect = self.image.get_rect()
        self.price = 100
        self.rect.left = 390
        self.rect.top = 0
        

#向日葵模型
class sunflowerCard(card):
    def __init__(self):
        super()
        self.image = pg.image.load('images/sunflowerModel/summer-1.png')
        self.rect = self.image.get_rect()
        self.price = 50
        self.rect.left = 390
        self.rect.top = 0
接着上面的类实现后就可以开始编写主方法了,首先初始化块的信息
    # 初始化块信息
    blocks = createBlock()
定义一个方法来调用block类里面的构造方法创建45个块的对象
def createBlock():
    blocks = []
    for i in range(45):
        temp = data.block.Block(i)
        blocks.append(temp)
    return blocks
接着初始化信息 是否暂停、怪物出现的频率 计时器,攻击列表,阳光列表,怪物列表等信息,下有注释

isPause = False
    #怪物出现频率
    monster_delay = 3000
    #计时器
    delay = 0
    # 攻击方式
    allAttack = []
    # 初始化阳光
    sunshineList = []
# 怪物列表
monsterList = []
    # 初始化金钱
    sunshineNotes = 1000
    #保卫者列表
    defendors = []
    # 购买该植物需要扣除的金钱
    record = 0

在全局变量中先布置下环境,设置界面大小,设置时间事件,初始化卡牌信息,怪物走路,攻击,死亡的帧图(这里本来想放在Monster类中定义的,但由于每新建一个僵尸对象时由于加载的图片太多导致会卡一次,所以放在全局中直接先声明一次)

import pygame as pg
import sys
import model.wdModel
import model.defendorCard
import model.monster
import model.attackWay
import data.block
import random
import time

import pygame as pg
pg.init()

# 设置窗口大小
window_size = (1320, 513)
screen = pg.display.set_mode(window_size)
# 设置卡牌
peasCard = model.defendorCard.peasCard()
sunflowerCard = model.defendorCard.sunflowerCard()

# 阳光
SUNDELAY = 300
# 设置窗口标题
pg.display.set_caption('no konw')

# 设置背景图片
bgPath = r'images/bg.jpg'
bg = pg.image.load(bgPath).convert()

# 设置卡槽图片
cardPath = r'images/card.png'
card = pg.image.load(cardPath).convert()
pauseimage = pg.image.load('images/pause.png')
#由于要加载的怪物形态太多了,所以在这里先初始化图片
run = [pg.image.load(
    r'images/littlemonst/run/427090eef01f3a29384879ac8e25bc315d607cf0-{:d}.png'.format(i)).convert_alpha() for i in range(1, 125)]
attack = [pg.image.load(
    r'images/littlemonst/attack/427090eef01f3a29384879ac8e25bc315d607cf0-{:d}.png'.format(i)).convert_alpha() for i in range(1, 30)]
die = [pg.image.load(
    r'images/littlemonst/goToDie/cafe8d66d016092468b62432da0735fae6cd3408-{:d}.png'.format(i)).convert_alpha() for i in range(1, 38)]

HP = 5

300ms触发一次事件

SUNSHINEEVENT = pg.USEREVENT
pg.time.set_timer(SUNSHINEEVENT, SUNDELAY)


# 刷新界面
clock = pg.time.Clock()
接着进入While True循环中,在开头判断当前是否是已经停止的状态,如果不为停止那么当前血量是否小于等于0,如果小于等于0的话,将当前状态置为停止,并播放录音game over

    if not isPause:
        if (HP <= 0):
            pg.mixer.init()
            pg.mixer.music.load(r"images/gameover.mp3")
            pg.mixer.music.play()
            isPause=not isPause

如果为暂停状态下的话,加载暂停图片

    else:
        pg.display.update()
        screen.blit(pg.transform.scale(pauseimage,(300,300)), (500, 100))

接着开始对背景和卡牌,以及剩余的血量大小加载进入界面中
然后通过pygame提供的mouse中的类属性获得当前按下的鼠标情况以及记录鼠标的x,y信息

       HPIMAGE=pg.font.SysFont('arial', 35).render(
            "The rest of life:"+ str(HP), True, (0, 0, 0))
        # 设置金钱图片
        fontImg = pg.font.SysFont('arial', 20).render(
            str(sunshineNotes), True, (0, 0, 0))
        press = pg.mouse.get_pressed()
        
        pg.display.update()
        screen.blit(bg, (0, 0))
        screen.blit(card, (300, 0))
        screen.blit(fontImg, (330, 70))
        screen.blit(HPIMAGE,(0,0))
        x, y = pg.mouse.get_pos()

然后判断监听下事件判断是否要退出,以及是否有按下空格键,如果按下空格键,将当前isPause的信息设置为相反的值

    for event in pg.event.get(): 
        # EXIT
        if event.type == pg.QUIT:
            sys.exit()
        if event.type == pg.KEYDOWN:
            if (event.key == 32):
                isPause = not isPause

如果不为暂停的话,那么为鼠标定义一个监听事件
如果鼠标点击的卡牌上并且当前还未处于购买状态的话,首先判断是否单机到阳光,这里通过调用方法判断当前鼠标的x,y是否点击到其中一个阳光,如果点击到的话,增加阳光值,并将当前对象改为None
之后再判断点击位置是否为哪个的购卡位置,根据不同位置判断购买的种类并把它添加到保卫者列表中去,记录购卡所需的金额,并且标志当前处于购卡状态,购买的植物将会随着鼠标的移动而移动,如果金额不够的话,无法进行购买

                # 购买卡牌
                if event.type == pg.MOUSEBUTTONDOWN:
                    # 如果还未选中

                    if not isSelect:
                        # 获取阳光值
                        sunshineNotes = clickSunshine(
                            sunshineList, sunshineNotes, x, y)
                        # 购卡
                        temp = buyCard(defendors, x, y, sunshineNotes)
                        defendors, isSelect, record = temp[0], temp[1], temp[2]

def clickSunshine(sunshineList, sunshineNotes, x, y):
    for i in range(len(sunshineList)):
        if sunshineList[i][0].isClick(x, y) and sunshineList[i]:
            sunshineList[i] = None
            sunshineNotes += 25
    return sunshineNotes

购卡

def buyCard(defendors, x, y, sunshineNote):
    record = 0
    isSelect = False
    # 豌豆射手
    if 387 <= x <= 465 and 0 <= y <= 92 and sunshineNote-peasCard.getPrice() >= 0:
        defendor = model.wdModel.peas()
        defendors.append([defendor, x, y, 0, None])

        record = peasCard.getPrice()
        isSelect = True
    # 向日葵
    if 477 <= x <= 555 and 0 <= y <= 97 and sunshineNote-sunflowerCard.getPrice() >= 0:
        defendor = model.wdModel.sunflower()
        defendors.append([defendor, x, y, 1, None])
        record = sunflowerCard.getPrice()

        isSelect = True
    return [defendors, isSelect, record]

如果当前处于购卡状态下的话,判断鼠标是否单击右键
首先判断当前鼠标放置的位置是否在块的范围内,并且该块是否已经被种植了
如果可以种植的话,更改当前的购买状态,并将当前的植物的位置固定在改块之中,且调用该块的方法重新设定该块已经有保卫者了,这里加了一个方法,如果当前保卫者为豌豆射手的话,种植下去的一瞬间就会发射一枚攻击对象。
如果不可以种植的话,那么就在当前的保卫者列表中清除这个保卫者。

   # 设置放置位置
    if press[2] and isSelect:
        # 检查该围着附近是否有花
        temp = check(blocks, x, y)

        # 如果该区域没有花的话就放置保卫者
        if not temp[0] and temp[1] != -1:
                
            isSelect = False
            sunshineNotes -= record
            blocks[temp[1]].setIsHave()
            defendors[-1].append(temp[1])
            defendors[-1][1] = blocks[temp[1]].leftTopX-10
            defendors[-1][2] = blocks[temp[1]].leftTopY - 5
            if(str(type(defendors[-1][0])) == "<class 'model.wdModel.peas'>"):
                attack = model.attackWay.wdAttack()
                attack.setSeat(defendors[-1][1], defendors[-1][2])
                allAttack.append(attack)
                # attack = model.attackWay.wdAttack(defendors[-1][0])

        else:
            # 如果选中区域内有保卫者或者在选区外面的话 注销保卫者
            isSelect = False
            defendors.pop()

这是用来检验当前鼠标指定的位置是否中块里面

def check(blocks, x, y):
    index = -1
    flag = True
    for i in range(len(blocks)):
        # 它在哪个方块里面
        if blocks[i].checkIsInBlock(x, y):
            index = i
            break
    # 该方块是否存在保卫者
    if index != -1 and not blocks[index].getIsHave():
        flag = not flag

    return [flag, index]

接下来是给监听事件添加一个时间监听事件,每次触发监听事件一次,则会记录事件+300ms
每1,2秒时计算一次保卫者收到的伤害,到60秒之后怪物的生成事件加快,每monster_delay秒后就会自动新建一个怪物对象并添加到怪物列表中去,每隔3.9秒就会在地图上随机生成一个阳光。
除此之外豌豆射手和向日葵会分别在上一次产生产物之后的3.6秒以及3.9秒后再次产生相对应的产物

                if event.type == SUNSHINEEVENT:
                    delay += 300
                    if (sys.maxsize-8000<delay < sys.maxsize-2000):
                        delay = 0
                    #abateHP(defendors, monsterList, defendorsLen, blocks)
                    if(delay%1200):
                        defendorsLen = selectSeat(defendors, isSelect, screen, x, y)
                        lista = abateHP(defendors, monsterList, defendorsLen, blocks)
                        defendors = lista[0]
                        monsterList = lista[1]
                    if (delay == 60000):
                        monster_delay = 2100

                        
                    #每monster_delay ms生成一个怪物
                    if (delay % monster_delay == 0):
                        littleMonster = model.monster.littlemonster(run)
                        littleMonster.setSeat(900, random.randint(1, 5)*70)
                        monsterList.append(littleMonster)
                    # 每隔3900ms生成一个阳光
                    if(delay%3900==0):
                        sunshineList = createSunshine(sunshineList)
                    
                    for i in range(len(defendors)):
                        #根据植物种类添加不同事情的触发时间
                        if (str(type(defendors[i][0])) == "<class 'model.wdModel.peas'>"):
                            #每隔3600ms发送一次攻击
                            if(defendors[i][0].isCreate(3600)):
                                attack = model.attackWay.wdAttack()
                                attack.setSeat(defendors[i][1], defendors[i][2])
                                allAttack.append(attack)
                        elif (str(type(defendors[i][0])) == "<class 'model.wdModel.sunflower'>"):
                            #每隔4200ms产生阳光值
                            if(defendors[i][0].isCreate(4200)):
                                sunshineList=createSunshine(sunshineList,defendors[i][1],defendors[i][2])
通过判断两者是否在图上位置属于重合位置,让植物收到怪物攻击力大小的伤害,如果植物的血量小于等于0,那么将该植物从保卫者列表中删除,并且将当前块设置为暂无种植状态
def abateHP(defendors,monsterList,defendorsLen,blocks):
    for i in range(defendorsLen):
        defendorXY = defendors[i][0].getXY()
        if (defendors[i] != None):
            
            for j in range(len(monsterList)):
                
                if(monsterList[j]!=None):
                    monsterXY = monsterList[j].getXY()
                    if ((monsterXY[0] - 30 <= defendorXY[0] <= monsterXY[0] + 30) and ((35+monsterXY[1]) < defendorXY[1] <= monsterXY[1]+60)) and (defendors[i][0]!=None):
                        
                        defendors[i][0].abateHP(monsterList[j].getAttack())

                        if (defendors[i][0].getHP() <= 0):
                            #守卫者死后 将那块地还原成可种植的状态
                            if (defendors[i][-1] != None):
                                blocks[defendors[i][-1]].setIsHave()
                            monsterList[j].runStatus(run)
                            defendors[i] = None
                        
    monsters=[monster for monster in monsterList if monster!=None]
    temp = [defendor for defendor in defendors if defendor != None]
    return [temp,monsters]
                        
这里为产生阳光的方法,新建一个对象添加到阳光列表中去
def createSunshine(sunshineList,x=-1,y=-1):
    sunshine = model.wdModel.sunshine()
    sunshineSeatX = random.randint(50, 900)
    sunshineSeatY = random.randint(40, 70)
    #如果位置有被设定到的话
    if (x != -1):
        sunshineSeatX = x
        sunshineSeatY = y
        
    sunshineList.append([sunshine, sunshineSeatX, sunshineSeatY])
    return sunshineList

接着开始清理下阳光列表中被点击的阳光或者过界的阳光
编写方法判断怪物是否越界 如果怪物越界的话,将这个怪物对象变为None 并将血量自减
#怪物是否越界

def checkOutOfBorder(monsterList):
    global HP
    for i in range(len(monsterList)):
        XY = monsterList[i].getXY()
        if (XY[0] < 290):
            monsterList[i] = None
            HP-=1
    return [monster for monster in monsterList if monster!=None]
将攻击列表全都加载出来显示
def showAttack(allAttack, screen):
    
    for i in range(len(allAttack)):
        if (allAttack[i] != None):
            allAttack[i].move(screen)

清除下已经越过界面范围内攻击对象

#清空已过边界的攻击者

def clearAttack(allAttack):
    for i in range(len(allAttack)):

        if(allAttack[i]!=None):
            XY = allAttack[i].getXY()
            if (XY[0] > 1000 or XY[1] > 500):
                allAttack[i] = None
    aA = []
    for i in range(len(allAttack)):
        if (allAttack[i] != None):
            aA.append(allAttack[i])
    return aA

根据判断当前是否处于购买状态下,判断最后一个保卫者是否跟随鼠标一起移动

def selectSeat(defendors, isSelect, screen, x, y):
    # 如果在购卡
    if isSelect:
        defendors[-1][0].setModel(screen, x - 35, y - 35, defendors[-1][3])
        return len(defendors) - 1
    return len(defendors)

加载显示已经购买安置好的守卫者

检测下僵尸是否被保卫者拦截到了,如果被拦截到的话,将僵尸的状态改为attck的状态
#是否被拦截到了

def isStop(defendors, monsterList,defendorsLen):
    for i in range(defendorsLen):
        defendorXY = defendors[i][0].getXY()
        if(defendors[i]!=None):
            for j in range(len(monsterList)):
                monsterXY = monsterList[j].getXY()   
                if ((monsterXY[0] - 30 <= defendorXY[0]<=monsterXY[0] + 30 ) and (35+monsterXY[1])<defendorXY[1]<=monsterXY[1]+60):
                    monsterList[j].attackStatus(attack)


显示怪物当前所进行的动作
#显示怪物
def showMonster(monsterList,screen):

    for i in range(len(monsterList)):
        if (monsterList[i].move(screen) == -1) :
            monsterList[i] = None
    return [monster for monster in monsterList if monster != None]

显示阳光的掉落,如果阳光过了边界就清除当前阳光对象

def showSunshine(screen, sunshineList):
    for i in range(len(sunshineList)):
        if sunshineList[i] != None:
            if (sunshineList[i][2] > 540):
                sunshineList[i] = None
            else:
                sunshineList[i][0].setModel(
                    screen, sunshineList[i][1], sunshineList[i][2])
                sunshineList[i][2] += 1
    return [sunshine for sunshine in sunshineList if sunshine != None]

检查是否攻击对象攻击到僵尸,如果攻击到的话,攻击对象删除,并对怪物的血量进行扣除,扣除大小由攻击对象的攻击所决定,如果在扣除血量之后怪物对象的血量低于等于0,则将该怪物置为死亡状态

#是否攻击
def isAttack(allAttack, monsterList):
    #遍历怪物列表
    for i in range(len(monsterList)):
        #获得当前怪物的XY值
        monsterXY = monsterList[i].getXY()
        for j in range(len(allAttack)):
            if (allAttack[j] != None):
                #获得当前攻击的XY值
                attackXY = allAttack[j].getXY()
                if (monsterXY[1] + 45 <= attackXY[1] <= monsterXY[1] + 70):
                    num = 20
                    #这里出现了一个问题 如果出现了怪物停下的话 左边的举例应该改变
                    num=20
                    if (monsterList[i].getStaus() == 1):
                        num=70
                    if (monsterXY[0] - num <= attackXY[0] <= monsterXY[0] + num):
                        monsterList[i].abateHP(allAttack[j].getAttack())
                        allAttack[j] = None
                        #检查血量,当血量低于等于0时,改变怪物状态
                        if (monsterList[i].getHP() <= 0):
                            monsterList[i].dieStaus(die)
                            break
    return [monster for monster in monsterList if monster != None]
然后在循环中对上面的方法进行调用
           # 清理下阳光列表
            sunshineList = [
                sunshine for sunshine in sunshineList if sunshine != None]
            # 检查是否越界
            monsterList=checkOutOfBorder(monsterList)
            # 发射攻击
            showAttack(allAttack, screen)
            #清除已经过界的攻击
            allAttack=clearAttack(allAttack)
            # 显示守卫者
            defendorsLen = selectSeat(defendors, isSelect, screen, x, y)
            showDefendor(defendors, defendorsLen)
            #是否守卫者拦截到了怪物
            isStop(defendors, monsterList, defendorsLen)
            #显示怪物
            monsterList=showMonster(monsterList, screen)
            # 设置卡牌位置
            setCard([peasCard, sunflowerCard], screen)

            # 显示阳光的掉落
            sunshin = showSunshine(screen, sunshineList)

            monsterList=isAttack(allAttack, monsterList)
            # FPS
            clock.tick(60)

4.实验
图1暂停状态
图2怪物进入攻击植物状态
向日葵挨打以及产生阳光状态
怪物走路状态
豌豆射手发动攻击状态
怪物死亡状态

5.总结和展望。
这个程序由于找到的素材很少,所以只用了两个保卫者,和一种形态的僵尸,如果只想要加载静态的话,其实素材还是很容易找的,麻烦的找动图,其实主要是受限于我的美工有限所导致的,希望中以后的实践中能多加强下自己的美工能力。
参考文献:
[1]百度百科-植物大战僵尸
[2]百度百科-植物大战僵尸

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值