当前位置: 首页 > news >正文

仅用pygame+python实现植物大战僵尸-----完成比完美更重要

前言

  • 其实这个项目再我上半年就想着做一下的,但是一直拖到现在,我现在深刻的理解到,不要想那么多,先做,因为永远不可能准备好,都是边做边学便准备的,完成比完美更重要;
  • 使用python,是因为简单,我感觉大多数00后程序员应该都一个实现植物大战僵尸的梦吧;
  • 这一次我深刻体会到了业务的重要性,很多时候对业务不理解,是很难做出点什么东西的,更难成为架构师;
  • 还有,我深刻体会到了一句话:“仅修改少量代码,就实现功能”。

环境

  • python:3.11.7,pygame:2.6.1
  • 编译器:vscode
  • 游戏运行结果截图

在这里插入图片描述

  • 游戏任务图

在这里插入图片描述

  • 游戏架构图

在这里插入图片描述

  • main.py
import pygame
import sys
from pygame.locals import *
from const import *
from game import *pygame.init()DS = pygame.display.set_mode((1280, 600))
game = Game(DS)while True:for event in pygame.event.get():if event.type == QUIT:pygame.quit()sys.exit()elif event.type == pygame.MOUSEBUTTONDOWN:game.mouseClickHandle(event.button)   # 鼠标事件触发DS.fill((255, 255, 255))game.draw()game.update()pygame.display.update()
  • cosnt.py
# 定义一些常量
GAME_SIZE = (1280, 600)  # 游戏地图大小
# 网格游戏,一些参数
LEFT_TOP = (200, 65)  # 游戏网格,左上角坐标
GRID_SIZE = (76, 96)  # 每一个格子大小
GRID_COUNT = (9, 5)  # 网格数量,9列5行
# 图片路径
PATH_BACK = "pic/other/back.png"
PATH_LOSS = "pic/other/lose.png"SUNFLOWER_ID = 3
PEASHOOTER_ID = 4
  • data_object.py
# 储存固定参数
data = {0 : {   # 豌豆'PATH' : 'pic/other/peabullet.png','IMAGE_INDEX_MAX' : 0,   # 图片索引范围'IMAGE_INDEX_CD' : 0.0,  # 图片更新频率'POSITION_CD' : 0.008,   # 位置更新速度'SUMMON_CD' : -1,        # 产生物的CD'SIZE' : (44, 44),       # 图片缩放大小'SPEED' : (4, 0),        # 速度'CAN_LOOT' : False,      # 是否可以捡 'PRICE' : 0,             # 价格为0'HP' : 1,                # 血量'ATT' : 1,               # 攻击力},1 : {   # 僵尸'PATH' : 'pic/zombie/0/%d.png','IMAGE_INDEX_MAX' : 15,'IMAGE_INDEX_CD' : 0.2,'POSITION_CD' : 0.2,'SUMMON_CD' : -1, 'SIZE' : (100, 128),'SPEED' : (-2.5, 0),'CAN_LOOT' : False,'PRICE' : 0, 'HP' : 5,                'ATT' : 1,               },2 : {  # 阳光'PATH' : 'pic/other/sunlight/%d.png','IMAGE_INDEX_MAX' : 30,'IMAGE_INDEX_CD' : 0.06,'POSITION_CD' : 0.05,'SUMMON_CD' : -1, 'SIZE' : (80, 80),'SPEED' : (0, 2),'CAN_LOOT' : True,'PRICE' : 25, 'HP' : 1000000000,                'ATT' : 0,   },3 : {   # 向日葵'PATH' : 'pic/plant/sunflower/%d.png','IMAGE_INDEX_MAX' : 19,'IMAGE_INDEX_CD' : 0.07,'POSITION_CD' : 10000,'SUMMON_CD' : 8, 'SIZE' : (128, 128),'SPEED' : (0, 0),'CAN_LOOT' : False,'PRICE' : 50,  'HP' : 5,                'ATT' : 0,   },4 : {   # 射手'PATH' : 'pic/plant/peashooter/%d.png','IMAGE_INDEX_MAX' : 15,'IMAGE_INDEX_CD' : 0.15,'POSITION_CD' : 10000,'SUMMON_CD' : 3, 'SIZE' : (128, 128),'SPEED' : (0, 0),'CAN_LOOT' : False,'PRICE' : 100,  'HP' : 5,                'ATT' : 0,   },
}
  • game.py
import pygame
import image
import zombiebase
import peabullet
import data_object
import sunlight
import sunflower
import peashooter
import data_object
import time
import random
from const import *class Game(object):def __init__(self, ds):self.ds = ds  self.back = image.Image(PATH_BACK, 0, (0, 0), GAME_SIZE, 0)  # 储存背景self.loss = image.Image(PATH_LOSS, 0, (0, 0), GAME_SIZE, 0)  # 游戏结束self.isGameOver = Falseself.plants = []self.summons = []self.zombies = []self.allPrivce = 100   # 初始价格self.priveFont = pygame.font.Font(None, 60)  # 字体self.zombieGenerateTime = 0   # 上一次生成僵尸的时间# 打僵尸几分self.zombie = 0self.zombieFont = pygame.font.Font(None, 60)self.hasPlant = []for i in range(GRID_SIZE[0]):   # 赋值的是一个格子的col = []for j in range(GRID_SIZE[1]):col.append(0)self.hasPlant.append(col)# 得到要种植的坐标def getIndexByPos(self, pos):    # 得到x,y坐标(注意是那种压缩的,就是x * 每个格子宽度 == 真实位置)x = (pos[0] - LEFT_TOP[0]) // GRID_SIZE[0]y = (pos[1] - LEFT_TOP[1]) // GRID_SIZE[1]return x, ydef renderFont(self):textImage = self.priveFont.render("Glod: " + str(self.allPrivce), True, (0, 0, 0))self.ds.blit(textImage, (13, 23))textImage = self.priveFont.render("Glod: " + str(self.allPrivce), True, (255, 255, 255))self.ds.blit(textImage, (10, 20))textImage = self.zombieFont.render("Score: " + str(self.zombie), True, (0, 0, 0))self.ds.blit(textImage, (13, 83))textImage = self.zombieFont.render("Score: " + str(self.zombie), True, (255, 255, 255))self.ds.blit(textImage, (10, 80))def draw(self):self.back.draw(self.ds)for plant in self.plants:    # 植物绘制plant.draw(self.ds)for summon in self.summons:   # 生成物绘制summon.draw(self.ds)for zombie in self.zombies:zombie.draw(self.ds)# 绘制金额self.renderFont()# 是否结束if self.isGameOver:self.loss.draw(self.ds)def update(self):self.back.update()for plant in self.plants:plant.update()if plant.hasSummon():   # 有生成物summ = plant.doSummon()   # 就生成self.summons.append(summ)   # 给game管理生命周期for summon in self.summons:summon.update()for zombie in self.zombies:zombie.update()# 更新一次,看是否能产生僵尸if time.time() - self.zombieGenerateTime > 10:self.zombieGenerateTime = time.time()self.addZombie(14, random.randint(0, 4))self.checkSummonVsZombie()self.checkZombieVsPlant()# 游戏是否结束for z in self.zombies:if z.getRect().x < 0:self.isGameOver = True# 子弹超出屏幕,需要销毁for summon in self.summons:if summon.getRect().x > GAME_SIZE[0] or summon.getRect().y > GAME_SIZE[1]:self.summons.remove(summon)break  # 退出是因为[]索引会改变# 僵尸和植物对抗def checkSummonVsZombie(self):for summon in self.summons:for zombie in self.zombies:if summon.isCollide(zombie):   # 僵尸和植物对抗self.fight(summon, zombie)  # 对抗if zombie.hp <= 0:self.zombies.remove(zombie)   # 移除僵尸self.zombie += 1   # 加分if summon.hp <= 0:self.summons.remove(summon)   # 移除植物return # 僵尸吃植物def checkZombieVsPlant(self):for zombie in self.zombies:for plant in self.plants:if zombie.isCollide(plant):self.fight(zombie, plant)if plant.hp <= 0:self.plants.remove(plant)break# 产生阳光def addSunFlower(self, i, j):pos = LEFT_TOP[0] + i * GRID_SIZE[0], LEFT_TOP[1] + j * GRID_SIZE[1]sf = sunflower.SunFlower(3, pos)self.plants.append(sf)# 产生豌豆def addPeaShooter(self, x, y):pos = LEFT_TOP[0] + x * GRID_SIZE[0], LEFT_TOP[1] + y * GRID_SIZE[1]sf = peashooter.PeaShooter(PEASHOOTER_ID, pos)self.plants.append(sf)# 产生僵尸def addZombie(self, x, y):pos = LEFT_TOP[0] + x * GRID_SIZE[0], LEFT_TOP[1] + y * GRID_SIZE[1]zom = zombiebase.ZombieBase(1, pos)self.zombies.append(zom)# 对抗def fight(self, a, b):while True:a.hp -= b.attackb.hp -= a.attackif b.hp <= 0:    # a 打败 breturn True if a.hp <= 0:    # b 打败 areturn Falsereturn Falsedef checkLoot(self, mousePos):for summon in self.summons:if not summon.getIsLoot():continue rect = summon.getRect()  # 获取图片矩形if rect.collidepoint(mousePos):   # 点击坐标是否在举行区域内self.summons.remove(summon)   # 移除内存self.allPrivce += summon.getPrice()   # 金额增加return True return False# 种def checkAddPlant(self, mousePos, objjId):x, y = self.getIndexByPos(mousePos)# 判断是否能种植if x < 0 or x >= GRID_COUNT[0]:return if y < 0 or y >= GRID_COUNT[1]:return# 不能重复种种植判断if self.hasPlant[x][y] == 1:returnself.hasPlant[x][y] = 1# 金币扣除if self.allPrivce < data_object.data[objjId]['PRICE']:returnself.allPrivce -= data_object.data[objjId]['PRICE']if objjId == SUNFLOWER_ID:    # 种花self.addSunFlower(x, y)elif objjId == PEASHOOTER_ID:  # 种射手self.addPeaShooter(x, y)# 鼠标事件def mouseClickHandle(self, btn):# 游戏结束,鼠标不能种植if self.isGameOver:return mousePos = pygame.mouse.get_pos()  # 获取鼠标位置if self.checkLoot(mousePos):           # 触发,不能再种其他东西了return if btn == 1:   # 鼠标左键self.checkAddPlant(mousePos, SUNFLOWER_ID)elif btn == 3:self.checkAddPlant(mousePos, PEASHOOTER_ID)
  • image.py
import pygameclass Image(pygame.sprite.Sprite):def __init__(self, pathFmt, pathIndex, pos, size=None, pathIndexCount=0):self.pathFmt = pathFmtself.pathIndex = pathIndex  # 存储索引self.pos = list(pos)  # ()元组不支持修改,但list可以,这里可以修改坐标self.size = size  # 窗口大小self.pathIndexCount = pathIndexCount  # 储存图片索引最大下标self.updateImage()  # 显示图片# 更新图片def updateImage(self):path = self.pathFmtif self.pathIndexCount != 0:  # 更新图片目录path = path % self.pathIndexself.image = pygame.image.load(path)  # 更新图片,贴图if self.size:  # 有大小,则缩放self.image = pygame.transform.scale(self.image, self.size)# 更新图片大小def updateSize(self):self.size = sizeself.updateImage()# 更新图片索引def updateIndex(self, pathIndex):self.pathIndex = pathIndexself.updateImage()  # 索引更新,则更新图片# 获取图片大小和坐标def getRect(self):rect = self.image.get_rect()rect.x, rect.y = self.pos  # 移动本质是图片坐标的更改(左上角)return rect# 僵尸移动def doLeft(self):self.pos[0] -= 0.15  # 移动速度,本质是坐标修改def draw(self, ds):ds.blit(self.image, self.getRect())
  • objectbase.py
import image
import time
import data_objectclass ObjectBase(image.Image):def __init__(self, id, pos):# 定义时间,实现自驱动self.preTimeIndex = 0self.prePositionTime = 0self.preSummonTime = 0# 储存不同物体的idself.id = id# 血量和攻击力self.hp = self.getData()['HP']self.attack = self.getData()['ATT']# 继承super(ObjectBase, self).__init__(self.getData()['PATH'],0,pos,self.getData()['SIZE'],self.getData()['IMAGE_INDEX_MAX'])# 返回不同的数据def getData(self):return data_object.data[self.id]# 返回速度def getSpeed(self):return self.getData()['SPEED']# 返回更新动画的时间def getPositionCD(self):return self.getData()['POSITION_CD']# 返回图片更新时间def getImageIndexCD(self):return self.getData()['IMAGE_INDEX_CD']# 产生物的时间def getSummonCD(self):return self.getData()['SUMMON_CD']# 返回是否可以捡def getIsLoot(self):return self.getData()['CAN_LOOT']# 返回植物相应的价格def getPrice(self):return self.getData()['PRICE']# 相撞def isCollide(self, other):return self.getRect().colliderect(other.getRect())  # 相撞# 更新动画def update(self):self.checkImageIndex()  # 更新帧动画self.checkPosition()  # 更新图片坐标self.checkSummon()# 是否产生了生成物def checkSummon(self):if time.time() - self.preSummonTime <= self.getSummonCD():return self.preSummonTime = time.time()# 调用产生阳光self.preSummon()def checkImageIndex(self):#储存更新时间if time.time() - self.preTimeIndex <= self.getImageIndexCD():  # 间隔时间和位置更新一致return self.preTimeIndex = time.time()idx = self.pathIndex + 1if idx >= self.pathIndexCount:idx = 0self.updateIndex(idx)def checkPosition(self):if  time.time() - self.prePositionTime <= self.getPositionCD():  # 间隔时间和位置更新一致return Falseself.prePositionTime = time.time()speed = self.getSpeed()self.pos = (self.pos[0] + speed[0], self.pos[1] + speed[1])return Truedef preSummon(self):pass# 是否有产生物def hasSummon(self):pass# 生产产生物def doSummon(self):pass
  • peabullet.py
import objectbase class PeaBullet(objectbase.ObjectBase):pass
  • peashooter.py
import objectbase 
import peabullet
import timeclass PeaShooter(objectbase.ObjectBase):def __init__(self, id, pos):super(PeaShooter, self).__init__(id, pos)self.hasBullet = False  # 可以发射子弹self.hasShoot = False   # 立即发射def hasSummon(self):return self.hasBullet# 产生阳光def preSummon(self):self.hasShoot = True        # 发射self.pathIndex = 0   # 索引为0# 种阳光def doSummon(self):if self.hasSummon():self.hasBullet = False return peabullet.PeaBullet(0, (self.pos[0] + 20, self.pos[1] + 30))def checkImageIndex(self):#储存更新时间if time.time() - self.preTimeIndex <= self.getImageIndexCD():  # 间隔时间和位置更新一致return self.preTimeIndex = time.time()idx = self.pathIndex + 1if idx == 8 and self.hasShoot:self.hasBullet = True  # 发送if idx >= self.pathIndexCount:  # 不发射情况idx = 9self.updateIndex(idx)
  • sunflower.py
import objectbase 
import sunlightclass SunFlower(objectbase.ObjectBase):def __init__(self, id, pos):super(SunFlower, self).__init__(id, pos)self.hasSunlight = Falsedef hasSummon(self):return self.hasSunlight# 产生阳光def preSummon(self):self.hasSunlight = True# 种阳光def doSummon(self):if self.hasSummon():self.hasSunlight = False return sunlight.SunLight(2, (self.pos[0] + 20, self.pos[1] + 10))
  • sunlight.py
import objectbase class SunLight(objectbase.ObjectBase): pass
  • zombiebase.py
import objectbase class ZombieBase(objectbase.ObjectBase): pass

http://www.mrgr.cn/news/42170.html

相关文章:

  • 畅聊博客项目
  • 12.梯度下降法的具体解析——举足轻重的模型优化算法
  • FBX福币历史重演,ETH可能会在第四季度出现熊市
  • 行为设计模式 -观察者模式- JAVA
  • PDF转PPT:四款热门工具的亲身体验分享!
  • 搭建k8s集群服务(kubeadm方式)
  • 2019~2023博文汇总目录
  • MISC -第十天(音符加解密、敲击码、NtfsStreamsEditor工具)
  • SpringCloud Config配置中心 SpringCloud Bus消息总线
  • 【web安全】——文件包含漏洞
  • some 蓝桥杯题
  • (六)Shell 脚本应用(1):基础与环境变量详解
  • Linux驱动开发(速记版)--设备树插件
  • Linux命令:用于显示 Linux 发行版信息的命令行工具lsb_release详解
  • JAVA思维提升案例2
  • 总结一下 KNN、K-means 和 SVM【附代码实现】
  • 杀疯啦!yolov11+strongsort的目标跟踪实现
  • 华为OD机试 - 密室逃生游戏(Python/JS/C/C++ 2024 E卷 100分)
  • 更美观的HTTP性能监测工具:httpstat
  • 【自然语言处理】(1) --语言转换方法