Pygame实战!——自制PVZ(植物大战僵尸)

QQ技术分享交流互助群

 我把整个文件(这个实践的)放“喵做梦技术讨论群”里了,需者加群自取。

QQ群号:566341887   (大家一起互帮互助,共同进步,交流分享,奔赴未来!)

书接上回, 废话不多说,直接开干!

第一步、创建文件夹

第一步就迅速点,直接过。

同一级中放代码,存放图片的文件夹P和存放数据的文件夹data。

好,非常快,继续! 

第二步、搭建基本框架

也就是最最最基础,简单的,什么导入模块啊,弹出窗口啊之类了。

这里也是非常的简单,速过!

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    pygame.display.flip()

顺手把帧率和字体渲染一类简单的给准备好。

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

#基础
screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")
zf=pygame.time.Clock()
ts=pygame.font.SysFont("simhei",20)
tc=ts.render("",True,(0,0,0))

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    zf.tick(30)
    pygame.display.flip()

非常的简单喵,下一步。

第三步、状态机雏形

目前这几步都挺简单的,状态机的话,浅浅分一下:

这么来分,普通模式、娱乐模式、无尽模式、商店、各种各样……太多就先不一一举例了。

无论是什么模式,其实只要会一种了,其他的也就简单了,所以我们先从普通模式开始。

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

#全局变量
menu=0
putongmark=1

#基础
screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")
zf=pygame.time.Clock()
ts=pygame.font.SysFont("simhei",20)
tc=ts.render("",True,(0,0,0))

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    if menu==0:#进入界面
        pass
    elif menu==1:#菜单
        pass
    zf.tick(30)
    pygame.display.flip()

第四步、基础化

1.目标

这一步的目标非常明确,是为了让游戏能运行(但是不能打僵尸目前)。

2.准备

我们要根据窗口的大小,绘制相应的图片。

并且要通过读取鼠标的坐标,以及判断是否按下来确定是否改变menu的值。

这种图片如果要还原的话,大家最好还是去网上搜原图,截下来,然后扣掉边边角角拿来用。

本喵的选择是:自己手搓,这样好玩。 

那么开始!

 我自己画了一张,非常抽象,无所谓,自己开心就行。

(通过不同状态下的login按键,提示玩家是否触屏到了按键。) 

 接下来,让我们输入一些简单的代码。(不懂的看以前写的。)

3.缝合

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

#全局变量
menu=0
putongmark=1

#基础
screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")
zf=pygame.time.Clock()
ts=pygame.font.SysFont("simhei",20)
tc=ts.render("",True,(0,0,0))

#login
loginback=pygame.image.load("P/login/loginback.png")
login=pygame.image.load("P/login/loginno.png")
loginmark=0
#menu
menuback=pygame.image.load("P/menu/back.png")

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    mox, moy = pygame.mouse.get_pos() #实时读取鼠标x,y
    if menu==0:#进入界面
        screen.blit(loginback,(0,0))
        screen.blit(login,(400,400))
        if loginmark==0:
            login=pygame.image.load("P/login/loginno.png")
        else : login=pygame.image.load("P/login/loginyes.png")
        loginmark=0
        if mox>=400 and mox<=600:
            if moy>=400 and moy<=450:
                loginmark=1
        if loginmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=1
    elif menu==1:#菜单
        screen.blit(menuback,(0,0))
    zf.tick(30)
    pygame.display.flip()

非常的简单,里面这个对loginmark的判断,就是提示玩家鼠标是否碰到该按键。

我们就先从最简单的开始。

(还要做一个进入到冒险模式的按键)

第五步、冒险模式

把这个也缝合进去之后,我们就可以真正开始了。 

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

#全局变量
menu=0
putongmark=1

#基础
screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")
zf=pygame.time.Clock()
ts=pygame.font.SysFont("simhei",20)
tc=ts.render("",True,(0,0,0))

#login
loginback=pygame.image.load("P/login/loginback.png")
login=pygame.image.load("P/login/loginno.png")
loginmark=0
#menu
menuback=pygame.image.load("P/menu/back.png")
mx=pygame.image.load("P/menu/maoxianno.png")
mxmark=0

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    mox, moy = pygame.mouse.get_pos() #实时读取鼠标x,y
    if menu==0:#进入界面
        screen.blit(loginback,(0,0))
        screen.blit(login,(400,400))
        if loginmark==0:
            login=pygame.image.load("P/login/loginno.png")
        else : login=pygame.image.load("P/login/loginyes.png")
        loginmark=0
        if mox>=400 and mox<=600:
            if moy>=400 and moy<=450:
                loginmark=1
        if loginmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=1
    elif menu==1:#菜单
        screen.blit(menuback,(0,0))
        screen.blit(mx,(700,200))
        if mxmark==0:
            mx=pygame.image.load("P/menu/maoxianno.png")
        else : mx=pygame.image.load("P/menu/maoxianyes.png")
        mxmark=0
        if mox>=700 and mox<=880:
            if moy>=200 and moy<=250:
                mxmark=1
        if mxmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=2
    elif menu==2:#冒险模式
        pass
    zf.tick(30)
    pygame.display.flip()

1.要素准备

在植物大战僵尸中,关卡里面有几个重要的元素:草坪背景、阳光数量显示、卡牌、僵尸

那么就先随便画点图片(大不了后期做优化)。

但是,不急着画,先理论,后运用。毕竟这个图片大小,是有点影响后面的。

2.理论基础

(1)草坪

关卡中,我们要先知道,这个窗口的大小为1000×600,我们接下来要对其进行合理地分划。

我数出来,关卡中草坪的区块是9×5个,那么我们可以把每个区块的大小画成100×100。

 从上到下,从左到右依次为:

阳光卡片槽铲子菜单
小推车草坪僵尸产地
关卡名称僵尸进度

简单完善一下这个背景。

有点简陋,不过也还好。

(2)植物放置

当我们选取卡片槽中的一张卡片时,要把它放进各个区块中,就要对鼠标在哪个区块进行判断。

分别用qkx和qky 来表示鼠标所处草坪区块的位置,根据我的这个图片的话,这个草坪的最左上角差不多是(30,90),接下来通过一些列的对鼠标mox和moy的判断,来确定qkx和qky。

点击过卡片后,一株虚拟的植物应该随着鼠标而移动,简而言之就是screen.blit(choice,(mox,moy)),再通过if判断鼠标是否按下,来确定是否种下植物。

而且要注意的是,如果鼠标的mox和moy不在这个草坪的范围内的话,点击鼠标时,就要重新将植物返回到卡片槽中。

(3)僵尸

这个的话,randint是必不可少的,但是不止这点。

randint可以用来确定僵尸出现在那一排,以及出现什么类型的僵尸。后面这个出现什么类型的僵尸,可以通过当前游戏关卡的进度来确定。

出现之后,通过建立精灵组和精灵,控制每一个僵尸的血量,以及速度。

(4)阳光

对于阳光的数量,我们可以通过pygame.font.SysFont("simhei",20)来实现显示数据。

其次,对于白天和屋顶的关卡,阳光会随机自动从天上掉下来,因此可以使用randint来实现。(还要考虑到阳光下落的位置,下落时长,都要考虑)

(5)进度条

这个更加简单了,通过图片缩放即可。但是有个注意点,这个进度条是从右到左,于是我们就可以在图片缩放的条件下,把这个进度条往左边挪一点,这么来看的话,就像是进度条往左移动了。

(6)铲子和菜单

铲子:和植物放置一个原理,只要把放置改为清除即可。

菜单:那就再跳出一块,并且把关卡中僵尸移动速度,植物的子弹发射之类全部改为0,这样就像是被静止了。再在菜单中按选项。

3.代码

不急哈,慢慢来。

先来张卡片,豌豆射手!(图片均为自己画的,不喜勿喷,谢谢!)

如此,也就将就一下吧。 

(1)精灵类

这个我们先设豌豆射手的生命值为100,经过本喵的检测,豌豆射手大概每1.5s发射一颗豌豆。

为了更加方便,我们将频率改成25帧/s。

    zf.tick(25)

每秒运行25次,即运行一遍程序耗时0.04秒,每运行一次,将豌豆射手的cd减1,可设豌豆射手的初始cd约为38。

#shiwu
#wandousheshou
class wandousheshou(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/kp/wandousheshou/shiwu.png")
        hp=100
        cd=38

暂且得到这么一个类。为了判断鼠标选择植物后,种下植物的位置,我们需要先制作卡片在卡槽中。

(2)卡片制作

这个卡片,不单单是读取图片,直接绘制在屏幕上。首先要知道整个流程,先是在正式开始前,选择好卡片,后面卡片再根据玩家选择顺序出现在卡槽中。

所以说,我们需要一个列表,用来存储玩家选择卡片的顺序,并且赋予不同卡片不同的编号。

(当前提供9个卡槽)

choice=[0]*10 #1-豌豆射手

这样一来,许多乱七八糟的问题就解决了,直接用编号来判断所选择的植物,先用编号代替,后期再一次性渲染即可。

那么对于我这个草坪而言,卡槽的最左端的x为104,差不多100,且卡片大小为50×70,则第一张卡片应该位于(100,0),用center的话,应该(125,35)。

先做一个简单的class,如下。

choice=[0]*10 #1-豌豆射手
choicemark=1
choicelocation=1
#kp
class kp(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        if choice[choicemark]==1:
            self.image=pygame.image.laod("P/kp/wandousheshou/kpshiwu.png")
        self.rect=self.image.get_rect(center=(75+choicelocation*50,35))

kp_group=pygame.sprite.Group()

(3)判断种植地区

chyn=0 #0未选取卡片;1已选取卡片

先来一个全局变量,确认是否选取了植物。

    elif menu==2:#冒险模式
        screen.blit(gqcp,(0,0))
        for i in range(0,10):
            kp_group.add(kp())
            choicemark=i+1
        kp_group.draw(screen)
        if event.type == pygame.MOUSEBUTTONDOWN:
            if chyn==0:
                chyn=1
                pdx=(mox-100)//50+1
                if pdx==1: choicekp=pygame.image.load("P/kp/wandousheshou/shiwu.png")
                else: chyn=0
        if chyn==1:
            screen.blit(choicekp,(mox,moy))

在冒险模式中,这个for是用来渲染的,使卡槽中出现卡片。

其次,用if判断鼠标是否按下,再根据是否已经选择卡片的不同情况运行不同程序。

这个位置是怎么判断的呢?

喵:减去初始位置的x值100,采用取整的方法,整除50是因为每张卡片的宽为50。

+1是因为,整除不是四舍五入,如果鼠标位置为130时,没有+1的话,运行出来结果是0。

下面的那个if是在鼠标选择卡片后,让植物被鼠标拖动。 

接下来是把植物种在草坪上,不但要判断mox,还有moy。上文已知每个草坪的大小为100×100,且草坪左上角为(30,90),那么同理可以判断种植位置。

wandousheshou_group=pygame.sprite.Group()

存放豌豆射手之类植物的组。

elif chyn==1 and mox>=30 and moy>=90:
                pdx=(mox-30)//100
                pdy=(moy-90)//100
                wandousheshou_group.add(wandousheshou())
                chyn=0

(pdx和pdy都要设置一个全局变量)

相应的,我们要对wandousheshou这个类class进行一些调整和补充。

class wandousheshou(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/kp/wandousheshou/shiwu.png")
        self.rect=self.image.get_rect(center=(30+30+pdx*100,90+35+pdy*100))
        hp=100
        cd=38

这样一来,我们就能很好地实现了这个选择卡片,以及放置相应植物。

4.测试

写到这里,差不多有模有样的已经有了,那让我们来小小测试一下。

上面的代码比较零散,本喵就在下面展示一下本喵当前写的。

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

#全局变量
menu=0
putongmark=1
pdx,pdy=0,0

#基础
screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")
zf=pygame.time.Clock()
ts=pygame.font.SysFont("simhei",20)
tc=ts.render("",True,(0,0,0))

#login
loginback=pygame.image.load("P/login/loginback.png")
login=pygame.image.load("P/login/loginno.png")
loginmark=0
#menu
menuback=pygame.image.load("P/menu/back.png")
mx=pygame.image.load("P/menu/maoxianno.png")
mxmark=0
#base
gqcp=pygame.image.load("P/base/cp.png")
choice=[1]*11 #1-豌豆射手
choicemark=1
choicelocation=1
chyn=0 #0未选取卡片;1已选取卡片
choicekp=pygame.image.load("P/kp/wandousheshou/kpshiwu.png")
#kp
class kp(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        if choice[choicemark]==1:
            self.image=pygame.image.load("P/kp/wandousheshou/kpshiwu.png")
        self.rect=self.image.get_rect(center=(75+choicelocation*50,35))

#shiwu
#wandousheshou
class wandousheshou(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/kp/wandousheshou/shiwu.png")
        self.rect=self.image.get_rect(center=(30+30+pdx*100,90+35+pdy*100))
        hp=100
        cd=38

kp_group=pygame.sprite.Group()
wandousheshou_group=pygame.sprite.Group()

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    mox, moy = pygame.mouse.get_pos() #实时读取鼠标x,y
    if menu==0:#进入界面
        screen.blit(loginback,(0,0))
        screen.blit(login,(400,400))
        if loginmark==0:
            login=pygame.image.load("P/login/loginno.png")
        else : login=pygame.image.load("P/login/loginyes.png")
        loginmark=0
        if mox>=400 and mox<=600:
            if moy>=400 and moy<=450:
                loginmark=1
        if loginmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=1
    elif menu==1:#菜单
        screen.blit(menuback,(0,0))
        screen.blit(mx,(700,200))
        if mxmark==0:
            mx=pygame.image.load("P/menu/maoxianno.png")
        else : mx=pygame.image.load("P/menu/maoxianyes.png")
        mxmark=0
        if mox>=700 and mox<=880:
            if moy>=200 and moy<=250:
                mxmark=1
        if mxmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=2
    elif menu==2:#冒险模式
        screen.blit(gqcp,(0,0))
        for i in range(0,10):
            kp_group.add(kp())
            choicemark=i+1
        kp_group.draw(screen)
        wandousheshou_group.draw(screen)
        if event.type == pygame.MOUSEBUTTONDOWN:
            if chyn==0:
                chyn=1
                pdx=(mox-100)//50+1
                if pdx==1: choicekp=pygame.image.load("P/kp/wandousheshou/shiwu.png")
                else: chyn=0
            elif chyn==1 and mox>=30 and moy>=90:
                pdx=(mox-30)//100
                pdy=(moy-90)//100
                wandousheshou_group.add(wandousheshou())
                chyn=0
        if chyn==1:
            screen.blit(choicekp,(mox,moy))
    zf.tick(25)
    pygame.display.flip()

再展示一下视频,这个效果。

自制PVZ的小测试

5.僵尸

僵尸也是必不可少的一部分,先来一只(风韵犹存的)普通僵尸。

 非常的好看,接下来就交给代码了。

先创建一个精灵类。

#jiangshi
#putong
class putong(pygame.sprite.Sprite):
    def __init__()
        super().__init__()
        self.image=pygame.image.load("P/jiangshi/putong.png")
        hp=100

不急好吧,假设血量为100。我试了一下。

数出来普通豌豆射手打普通僵尸,打了5颗后,僵尸掉手臂,10颗后僵尸死亡。7秒左右,普通僵尸就会走完一块草坪。

据此进行编写代码。

(1)思路

先随机生成数字1~5,来确定僵尸生成在哪一路。再多加几张图,让僵尸正常走路,不是死板平移。控制好僵尸的移动速度,以及血量。

(2)初步

我们需要定时来生成新的僵尸,并且随机生成在任意一条路。

time=0
shch=0

#jiangshi
#putong
class putong(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/jiangshi/putong.png")
        self.rect=self.image.get_rect(center=(950,shch*100+30))
        hp=100

putongjiangshi_group=pygame.sprite.Group()

然后再在冒险模式中,对精灵与精灵组进行操作。

elif menu==2:#冒险模式
    time+=1
    if time>=100:
        time=0
        shch=randint(1,5)
        putongjiangshi_group.add(putong())
    putongjiangshi_group.draw(screen)

非常的简单,无需多言解释吧。

(3)优化僵尸移动

给僵尸多画几张图~

在精灵类里面,加一个update,让它实时更新走路姿势和位置。

#jiangshi
#putong
class putong(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        self.rect=self.image.get_rect(center=(950,shch*100+30))
        self.zishi=1
        self.hp=100
    def update(self):
        if self.zishi==1:
            self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        elif self.zishi==3:
            self.image=pygame.image.load("P/jiangshi/putong/putongd1.png")
        elif self.zishi==5:
            self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        elif self.zishi==7:
            self.image=pygame.image.load("P/jiangshi/putong/putongd2.png")
        elif self.zishi>=9 : self.zishi=0
        self.zishi+=1
        self.rect.x-=0.6

最底下这个self.rect.x-=0.6是算出来的,为了保证约7秒走一格草坪。 

 6.豌豆发射

我们要知道,不是所有的路线都有僵尸,不是所有的植物发射的都是豌豆。

来一个二维数组,存储草坪上的植物。为了方便,既然草坪是9×5,那么来个10×6的二维数组好了。

zhiwu=[[0]*10]*6

再来一个精灵类,用来控制豌豆的发射。

我刚刚又去测了一下,豌豆飞过5格草坪的时间约是1秒,再结合运行一次程序是0.04秒,每格草坪为100×100,可以得出每运行一次程序,豌豆在屏幕中x上移动4个像素。

#wandou
class wandou(pygame.sprite.Sprtie):
    def __init__(self):
        super().__init__()
        self.image=pyagme.image.load("P/kp/wandousheshou/wandou.png")
    def update(self):
        self.rect.x+=4

非常简单,除此之外我们还需要一个列表来读取每条路线僵尸的个数。

jiance=[0]*6

在每次生成僵尸的时候顺便把这个jiance[i]+=1,相对应的还要对僵尸之类做一些调整,先不管,后期再优化。

豌豆射手发射豌豆的条件是只要有僵尸即可,与数量无关。

1、那么对jiance这个列表进行遍历,且要遍历检测这一列草坪上的植物。如果没有僵尸就不遍历草坪上植物,这样还能避免没必要的遍历。

for i in range(1,6):
    if jiance[i]>0 :
        for j in range(1,10):
            if zhiwu[i][j]==1 and cd[i][j]<=0:
                wandoux=j
                wandouy=i
                wandou_group.add(wandou())
                cd[i][j]=38

2、种植植物的时候也要顺带加个改变二维数组里面的编号。

3、检测到有豌豆射手的时候,那就要读取它的位置,这个用全局变量也可以,或是调用的时候直接读取也可以。

elif chyn==1 and mox>=30 and moy>=90:
    pdx=(mox-30)//100
    pdy=(moy-90)//100
    zhiwu[pdy+1][pdx+1]=1
    wandousheshou_group.add(wandousheshou())
    chyn=0

4、再来一个cd的二维数组,来控制发射间隔。

cd=[([0]*10) for i in range(6)]

elif menu==2:#冒险模式
    for i in range(1,6):
        for j in range(1,10):
            cd[i][j]-=1

5、删去原豌豆射手精灵类里面的cd。

7.完善优化

okok啊!下面就是简简单单的了,加个碰撞,从而修改生命值,先来僵尸的和豌豆的。

(1)豌豆与僵尸碰撞

在冒险模式中加入以下碰撞检测。

col = pygame.sprite.groupcollide(wandou_group, putongjiangshi_group, True, False)
for sprite, list in col.items():
    for s in list:
        s.hp -= 10

确认碰撞之后,就是修改僵尸的ph值了。

#jiangshi
#putong
class putong(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        self.rect=self.image.get_rect(center=(950,shch*100+30))
        self.zishi=1
        self.location=shch
        self.hp=100
    def update(self):
        if self.zishi==1:
            self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        elif self.zishi==3:
            self.image=pygame.image.load("P/jiangshi/putong/putongd1.png")
        elif self.zishi==5:
            self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        elif self.zishi==7:
            self.image=pygame.image.load("P/jiangshi/putong/putongd2.png")
        elif self.zishi>=9 : self.zishi=0
        self.zishi+=1
        self.rect.x-=0.6
        global wf
        global jiance
        if self.rect.x<0:
            wf=0
        if self.hp<=0:
            jiance[self.location]-=1
            self.kill()

哦对了,那个wf是用于失败检测的,也就是游戏失败。

然后最后那个if是没血的时候,把该精灵从精灵组移除,还要改变当前路线上对僵尸数量的检测。

(2)优化豌豆射手攻击

目前写代码写下来呢就是有个问题,僵尸哪怕在植物身后,在僵尸身后的豌豆射手也会发生豌豆,这个就不得不把这个检测升级成二维了。

jiance=[([0]*10) for i in range(6)]

生成僵尸的时候也要改动。

if time>=100:
    time=0
    shch=randint(1,5)
    jiance[shch][9]+=1
    putongjiangshi_group.add(putong())

僵尸这个精灵里面每次移动也要对应的检测一下是否发生了变化。

class putong(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        self.rect=self.image.get_rect(center=(950,shch*100+30))
        self.zishi=1
        self.location=shch
        self.hp=100
        self.pastlo=9
    def update(self):
        if self.zishi==1:
            self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        elif self.zishi==3:
            self.image=pygame.image.load("P/jiangshi/putong/putongd1.png")
        elif self.zishi==5:
            self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        elif self.zishi==7:
            self.image=pygame.image.load("P/jiangshi/putong/putongd2.png")
        elif self.zishi>=9 : self.zishi=0
        self.zishi+=1
        self.rect.x-=0.6
        global wf
        global jiance
        lo=(self.rect.x-30)//100+1
        if self.pastlo-lo>0:
            jiance[self.location][self.pastlo]-=1
            jiance[self.location][lo]+=1
            self.pastlo=lo
        if self.rect.x<0:
            wf=0
        if self.hp<=0:
            jiance[self.location][lo]-=1
            self.kill()

那么改了哪些地方呢:

1、__init__里面多加了一个pastlo,用来保存上一次位置。

2、update中对当前位置和上次位置的比较,从而即使改变jiance里面数量,保存准确性。

(3)加入阳光系统

当前为止,为了方便测试,阳光一直没加,那么接下来就让我们加入阳光系统。

又要画图哩,来张阳光的图片。

既然一般来说这个阳光的初始值是50,那全局变量一开始赋值50先。

sunsum=50

还要把这个数字明确实时地显示在游戏界面中。

tc=ts.render(str(sunsum),True,(0,0,0))
screen.blit(tc,(35,60))

 (这个tc的绘制要绘制在背景图之后,这样不会被背景图覆盖。)

接下来要考虑两个问题,一个是阳光在白天会从天上落下,还有就是植物会产生阳光,综合一下,可以弄一个精灵类。

全局变量先来一对。sunv表示掉阳光速度。

sunx,suny,sunv=0,0,70

先在while中,根据速率生成阳光,并随机位置。

sun_group=pygame.sprite.Group()
if sunv<=0:
    sunx=randint(50,850)
    suny=randint(5,50)
    sun_group.add(sun())
    sunv=randint(100,135)
sunv-=1

注意缩进喵!

一开始呢,我们就来一个简单的精灵。

#sun
class sun(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/base/sun.png")
        self.rect=self.image.get_rect(center=(sunx+50,suny+50))
    def update(self):
        self.rect.y+=3
        global chyn
        if mox>=self.rect.x and mox<=self.rect.x+100 and moy>=self.rect.y and moy<=self.rect.y+100 and event.type == pygame.MOUSEBUTTONDOWN:
            global sunsum
            sunsum+=25
            self.kill()
        if moy>=650:
            self.kill()

在这个游戏中呢,点阳光拾取另外算。

if event.type == pygame.MOUSEBUTTONDOWN:
    sun_group.update()

 还有种植植物的时候减去相应的阳光数量。植物前再加一个判断,要满足阳光数量。

if event.type == pygame.MOUSEBUTTONDOWN:
    if chyn==0 and sunsum>=100:
        chyn=1
        pdx=(mox-100)//50+1
        if pdx==1: choicekp=pygame.image.load("P/kp/wandousheshou/shiwu.png")
        else: chyn=0
    elif chyn==1 and mox>=30 and moy>=90:
        pdx=(mox-30)//100
        pdy=(moy-90)//100
        zhiwu[pdy+1][pdx+1]=1
        wandousheshou_group.add(wandousheshou())
        sunsum-=100
        chyn=0

如果要弄那个,阳光数量不足,字体变红闪动的话,只要改变tc颜色即可,就不展示了。

(4)植物受伤

这个僵尸再来两张图片表示体现僵尸在吃,跟豌豆和僵尸碰撞差不多,简简单单。

好吧,其实一点也不简单,琢磨了个把小时……

方便点的,我们只需要在植物的update中加点东西即可。

    def update(self):
        self.hp-=jiance[self.cuny+1][self.cunx+1]*1
        if self.hp<=0:
            zhiwu[self.cuny+1][self.cunx+1]=0
            self.kill()

每次update的时候都这么运行一下,有僵尸就扣,根据数量来看,因为这个是运行一遍就扣一次,所以说扣少一点。

同样的,当僵尸靠近的时候,僵尸要原地不动且做出吃的动作。

    def update(self):
        if zhiwu[self.location][self.pastlo] > 0:
            self.zishi+=1
            if self.zishi<5:
                self.image=pygame.image.load("P/jiangshi/putong/putongchi1.png")
            if self.zishi>=5:
                self.image=pygame.image.load("P/jiangshi/putong/putongchi2.png")
            if self.zishi>=10 :self.zishi=0
        else :
            if self.zishi==1:
                self.image=pygame.image.load("P/jiangshi/putong/putong.png")
            elif self.zishi==3:
                self.image=pygame.image.load("P/jiangshi/putong/putongd1.png")
            elif self.zishi==5:
                self.image=pygame.image.load("P/jiangshi/putong/putong.png")
            elif self.zishi==7:
                self.image=pygame.image.load("P/jiangshi/putong/putongd2.png")
            elif self.zishi>=9 : self.zishi=0
            self.zishi+=1
            self.rect.x-=0.6

第六步、微调

累死了属于是,这个琢磨了好些天,大家在测试的时候根据实际情况微调即可,其他什么娱乐模式啊什么的就不写了。

喵:其他种类的植物和僵尸以此类推即可!

第七步、结

 我把整个文件放“喵做梦技术讨论群”里了,需者加群自取。

这里不太方便展示,就只放一下代码部分吧。(毕竟这个图片,影响对代码某些数字的理解。)

import pygame
from random import randint

pygame.init()
scsize=(1000,600)

def pzh(pr1,pr2,position1,position2):
    return pr1.overlap(pr2,(position2[0]-position1[0],position2[1]-position1[1]))

#全局变量
menu=0
putongmark=1
pdx,pdy=0,0

#基础
screen=pygame.display.set_mode(scsize)
pygame.display.set_caption("乱来的PVZ")
zf=pygame.time.Clock()
ts=pygame.font.SysFont("simhei",20)
tc=ts.render("",True,(0,0,0))
time=0
shch=0
zhiwu=[([0]*10) for i in range(6)]
cd=[([0]*10) for i in range(6)]
jiance=[([0]*10) for i in range(6)]
wandoux,wandouy=0,0
sunx,suny,sunv=0,0,70

#login
loginback=pygame.image.load("P/login/loginback.png")
login=pygame.image.load("P/login/loginno.png")
loginmark=0
#menu
menuback=pygame.image.load("P/menu/back.png")
mx=pygame.image.load("P/menu/maoxianno.png")
mxmark=0
#base
gqcp=pygame.image.load("P/base/cp.png")
choice=[1]*11 #1-豌豆射手
choicemark=1
choicelocation=1
chyn=0 #0未选取卡片;1已选取卡片
choicekp=pygame.image.load("P/kp/wandousheshou/kpshiwu.png")
fail=pygame.image.load("P/base/fail.png")
wf=1
sunsum=50
#kp
class kp(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        if choice[choicemark]==1:
            self.image=pygame.image.load("P/kp/wandousheshou/kpshiwu.png")
        self.rect=self.image.get_rect(center=(75+choicelocation*50,35))

#shiwu
#wandousheshou
class wandousheshou(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/kp/wandousheshou/shiwu.png")
        self.rect=self.image.get_rect(center=(30+30+pdx*100,90+35+pdy*100))
        self.cunx=pdx
        self.cuny=pdy
        self.hp=100
    def update(self):
        self.hp-=jiance[self.cuny+1][self.cunx+1]*1
        if self.hp<=0:
            zhiwu[self.cuny+1][self.cunx+1]=0
            self.kill()

#wandou
class wandou(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/kp/wandousheshou/wandou.png")
        self.rect=self.image.get_rect(center=(wandoux*100-20,wandouy*100+10))
    def update(self):
        self.rect.x+=4

#jiangshi
#putong
class putong(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/jiangshi/putong/putong.png")
        self.rect=self.image.get_rect(center=(950,shch*100+30))
        self.zishi=1
        self.location=shch
        self.hp=100
        self.pastlo=9
    def update(self):
        if zhiwu[self.location][self.pastlo] > 0:
            self.zishi+=1
            if self.zishi<5:
                self.image=pygame.image.load("P/jiangshi/putong/putongchi1.png")
            if self.zishi>=5:
                self.image=pygame.image.load("P/jiangshi/putong/putongchi2.png")
            if self.zishi>=10 :self.zishi=0
        else :
            if self.zishi==1:
                self.image=pygame.image.load("P/jiangshi/putong/putong.png")
            elif self.zishi==3:
                self.image=pygame.image.load("P/jiangshi/putong/putongd1.png")
            elif self.zishi==5:
                self.image=pygame.image.load("P/jiangshi/putong/putong.png")
            elif self.zishi==7:
                self.image=pygame.image.load("P/jiangshi/putong/putongd2.png")
            elif self.zishi>=9 : self.zishi=0
            self.zishi+=1
            self.rect.x-=0.6
        global wf
        global jiance
        lo=(self.rect.x-30)//100+1
        if self.pastlo-lo>0:
            jiance[self.location][self.pastlo]-=1
            jiance[self.location][lo]+=1
            self.pastlo=lo
        if self.rect.x<0:
            wf=0
        if self.hp<=0:
            jiance[self.location][lo]-=1
            self.kill()

#sun
class sun(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("P/base/sun.png")
        self.rect=self.image.get_rect(center=(sunx+50,suny+50))
    def update(self):
        self.rect.y+=3
        global chyn
        if mox>=self.rect.x and mox<=self.rect.x+100 and moy>=self.rect.y and moy<=self.rect.y+100 and event.type == pygame.MOUSEBUTTONDOWN:
            global sunsum
            sunsum+=25
            self.kill()
        if moy>=650:
            self.kill()

kp_group=pygame.sprite.Group()
wandousheshou_group=pygame.sprite.Group()
wandou_group=pygame.sprite.Group()
putongjiangshi_group=pygame.sprite.Group()
sun_group=pygame.sprite.Group()

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
    mox, moy = pygame.mouse.get_pos() #实时读取鼠标x,y
    if menu==0:#进入界面
        screen.blit(loginback,(0,0))
        screen.blit(login,(400,400))
        if loginmark==0:
            login=pygame.image.load("P/login/loginno.png")
        else : login=pygame.image.load("P/login/loginyes.png")
        loginmark=0
        if mox>=400 and mox<=600:
            if moy>=400 and moy<=450:
                loginmark=1
        if loginmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=1
    elif menu==1:#菜单
        screen.blit(menuback,(0,0))
        screen.blit(mx,(700,200))
        if mxmark==0:
            mx=pygame.image.load("P/menu/maoxianno.png")
        else : mx=pygame.image.load("P/menu/maoxianyes.png")
        mxmark=0
        if mox>=700 and mox<=880:
            if moy>=200 and moy<=250:
                mxmark=1
        if mxmark==1 and event.type == pygame.MOUSEBUTTONDOWN:
            menu=2
    elif menu==2:#冒险模式
        tc=ts.render(str(sunsum),True,(0,0,0))
        time+=1
        if time>=100:
            time=0
            shch=randint(1,5)
            jiance[shch][9]+=1
            putongjiangshi_group.add(putong())
        screen.blit(gqcp,(0,0))
        for i in range(0,10):
            kp_group.add(kp())
            choicemark=i+1
        kp_group.draw(screen)
        wandousheshou_group.draw(screen)
        putongjiangshi_group.draw(screen)
        wandou_group.draw(screen)
        sun_group.draw(screen)
        screen.blit(tc,(35,60))
        putongjiangshi_group.update()
        wandou_group.update()
        wandousheshou_group.update()
        sun_group.update()
        for i in range(1,6):
            for j in range(1,10):
                if zhiwu[i][j]==1 and cd[i][j]<=0:
                    youmeiyou=0
                    for o in range(j,10):
                        if jiance[i][o]>0:
                            youmeiyou=1
                            break
                    if youmeiyou==1:
                        wandoux=j
                        wandouy=i
                        wandou_group.add(wandou())
                        cd[i][j]=38
        col = pygame.sprite.groupcollide(wandou_group, putongjiangshi_group, True, False)
        for sprite, list in col.items():
            for s in list:
                s.hp -= 10
        if event.type == pygame.MOUSEBUTTONDOWN:
            if chyn==0 and sunsum>=100:
                chyn=1
                pdx=(mox-100)//50+1
                if pdx==1: choicekp=pygame.image.load("P/kp/wandousheshou/shiwu.png")
                else: chyn=0
            elif chyn==1 and mox>=30 and moy>=90:
                pdx=(mox-30)//100
                pdy=(moy-90)//100
                zhiwu[pdy+1][pdx+1]=1
                wandousheshou_group.add(wandousheshou())
                sunsum-=100
                chyn=0
        if event.type == pygame.MOUSEBUTTONDOWN:
            sun_group.update()
        if chyn==1:
            screen.blit(choicekp,(mox,moy))
        if wf==0:
            screen.blit(fail,(200,100))
        if sunv<=0:
            sunx=randint(50,850)
            suny=randint(5,50)
            sun_group.add(sun())
            sunv=randint(100,135)
        sunv-=1
        for i in range(1,6):
            for j in range(1,10):
                cd[i][j]-=1
    zf.tick(25)
    pygame.display.flip()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值