【Python小游戏】植物大战僵尸的实现与源码分享

文章目录

    • Python版植物大战僵尸
    • 环境要求
    • 方法
    • 源码分享
      • 初始化页面(部分)
      • 地图搭建(部分)
      • 定义植物类 (部分)
      • 定义僵尸类(部分)
      • 游戏运行入口
    • 游戏源码获取

Python版植物大战僵尸

  • 已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇,大蒜,南瓜头
  • 已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸,潜水僵尸
  • 支持选择植物卡片
  • 支持白昼模式,夜晚模式,泳池模式,浓雾模式(暂时没有加入雾),传送带模式和坚果保龄球模式
  • 支持背景音乐播放
    • 支持调节音量
  • 支持音效
    • 支持与背景音乐一起调节音量
  • 支持全屏模式
    • F键进入全屏模式,按U键恢复至窗口模式
  • 支持用小铲子移除植物
  • 支持分波生成僵尸
  • 支持“关卡进程”进度条显示
  • 夜晚模式支持墓碑以及从墓碑生成僵尸
  • 含有泳池的模式支持在最后一波时从泳池中自动冒出僵尸

环境要求

  • Python3 (建议 >= 3.10,最好使用最新版)
  • Python-Pygame (建议 >= 2.0,最好使用最新版)

方法

  • 使用鼠标收集阳光,种植植物

源码分享

在这里插入图片描述

初始化页面(部分)

import os
import pygame as pg# 用户数据及日志存储路径
if os.name == "nt": # Windows系统存储路径USERDATA_PATH = os.path.expandvars(os.path.join("%APPDATA%", "pypvz", "userdata.json"))USERLOG_PATH = os.path.expandvars(os.path.join("%APPDATA%", "pypvz", "run.log"))
else:   # 非Windows系统存储路径USERDATA_PATH = os.path.expanduser(os.path.join("~", ".config", "pypvz", "userdata.json"))USERLOG_PATH = os.path.expanduser(os.path.join("~", ".config", "pypvz", "run.log"))# 游戏图片资源路径
PATH_IMG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "graphics")
# 游戏音乐文件夹路径
PATH_MUSIC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources","music")
# 窗口图标
ORIGINAL_LOGO = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pypvz-exec-logo.png")
# 字体路径
FONT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "DroidSansFallback.ttf")# 窗口标题
ORIGINAL_CAPTION = "pypvz"# 游戏模式
GAME_MODE = "mode"
MODE_ADVENTURE = "adventure"
MODE_LITTLEGAME = "littleGame"# 窗口大小
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)# 选卡数量
# 最大数量
CARD_MAX_NUM = 10   # 这里以后可以增加解锁功能,从最初的6格逐渐解锁到10格
# 最小数量
CARD_LIST_NUM = CARD_MAX_NUM# 方格数据
# 一般
GRID_X_LEN = 9
GRID_Y_LEN = 5
GRID_X_SIZE = 80
GRID_Y_SIZE = 100
# 带有泳池
GRID_POOL_X_LEN = GRID_X_LEN
GRID_POOL_Y_LEN = 6
GRID_POOL_X_SIZE = GRID_X_SIZE
GRID_POOL_Y_SIZE = 85
# 屋顶
GRID_ROOF_X_LEN = GRID_X_LEN
GRID_ROOF_Y_LEN = GRID_Y_LEN
GRID_ROOF_X_SIZE = GRID_X_SIZE
GRID_ROOF_Y_SIZE = 85# 颜色
WHITE        = (255, 255, 255)
NAVYBLUE     = ( 60,  60, 100)
SKY_BLUE     = ( 39, 145, 251)
BLACK        = (  0,   0,   0)
LIGHTYELLOW  = (234, 233, 171)
RED          = (255,   0,   0)
PURPLE       = (255,   0, 255)
GOLD         = (255, 215,   0)
GREEN        = (  0, 255,   0)
YELLOWGREEN  = ( 55, 200,   0)
LIGHTGRAY    = (107, 108, 145)
PARCHMENT_YELLOW = (207, 146, 83)# 退出游戏按钮
EXIT = "exit"
HELP = "help"
# 游戏界面可选的菜单
LITTLE_MENU = "littleMenu"
BIG_MENU = "bigMenu"
RESTART_BUTTON = "restartButton"
MAINMENU_BUTTON = "mainMenuButton"
LITTLEGAME_BUTTON = "littleGameButton"
OPTION_BUTTON = "optionButton"
SOUND_VOLUME_BUTTON = "volumeButton"
UNIVERSAL_BUTTON = "universalButton"
# 金银向日葵奖杯
TROPHY_SUNFLOWER = "sunflowerTrophy"
# 小铲子
SHOVEL = "shovel"
SHOVEL_BOX = "shovelBox"
# 一大波僵尸来袭图片
HUGE_WAVE_APPROCHING = "Approching"
# 关卡进程图片
LEVEL_PROGRESS_BAR = "LevelProgressBar"
LEVEL_PROGRESS_ZOMBIE_HEAD = "LevelProgressZombieHead"
LEVEL_PROGRESS_FLAG = "LevelProgressFlag"

地图搭建(部分)

class Map():def __init__(self, background_type:int):self.background_type = background_type# 注意:从0开始编号if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:self.width = c.GRID_POOL_X_LENself.height = c.GRID_POOL_Y_LENself.grid_height_size = c.GRID_POOL_Y_SIZEself.map =  [   [self.initMapGrid(c.MAP_WATER) if 2 <= y <= 3else self.initMapGrid(c.MAP_GRASS)for x in range(self.width)]for y in range(self.height)]elif self.background_type in c.ON_ROOF_BACKGROUNDS:self.width = c.GRID_ROOF_X_LENself.height = c.GRID_ROOF_Y_LENself.grid_height_size = c.GRID_ROOF_Y_SIZEself.map =  [   [self.initMapGrid(c.MAP_TILE)for x in range(self.width)]for y in range(self.height)]elif self.background_type == c.BACKGROUND_SINGLE:self.width = c.GRID_X_LENself.height = c.GRID_Y_LENself.grid_height_size = c.GRID_Y_SIZEself.map =  [   [self.initMapGrid(c.MAP_GRASS) if y ==2else self.initMapGrid(c.MAP_UNAVAILABLE)for x in range(self.width)]for y in range(self.height)]elif self.background_type == c.BACKGROUND_TRIPLE:self.width = c.GRID_X_LENself.height = c.GRID_Y_LENself.grid_height_size = c.GRID_Y_SIZEself.map =  [   [self.initMapGrid(c.MAP_GRASS) if 1 <= y <= 3else self.initMapGrid(c.MAP_UNAVAILABLE)for x in range(self.width)]for y in range(self.height)]else:self.width = c.GRID_X_LENself.height = c.GRID_Y_LENself.grid_height_size = c.GRID_Y_SIZEself.map =  [   [self.initMapGrid(c.MAP_GRASS)for x in range(self.width)]for y in range(self.height)]def isValid(self, map_x:int, map_y:int) -> bool:if ((0 <= map_x < self.width)and (0 <= map_y < self.height)):return Truereturn False# 地图单元格状态# 注意是可变对象,不能直接引用# 由于同一格显然不可能种两个相同的植物,所以用集合def initMapGrid(self, plot_type:str) -> set:return {c.MAP_PLANT:set(), c.MAP_SLEEP:False, c.MAP_PLOT_TYPE:plot_type}# 判断位置是否可用# 暂时没有写紫卡植物的判断方法# 由于紫卡植物需要移除以前的植物,所以可用另外定义一个函数def isAvailable(self, map_x:int, map_y:int, plant_name:str) -> bool:# 咖啡豆和墓碑吞噬者的判别最为特殊if plant_name == c.COFFEEBEAN:if (self.map[map_y][map_x][c.MAP_SLEEP]and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])):return Trueelse:return Falseif plant_name == c.GRAVEBUSTER:if (c.GRAVE in self.map[map_y][map_x][c.MAP_PLANT]and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])):return Trueelse:return False# 被非植物障碍占据的格子对于一般植物不可种植if any((i in c.NON_PLANT_OBJECTS) for i in self.map[map_y][map_x][c.MAP_PLANT]):return Falseif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_GRASS:  # 草地# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上if plant_name not in c.WATER_PLANTS:if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植return Trueelif (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植return Trueelif ((plant_name == c.PUMPKINHEAD)and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):   # 没有南瓜头就能种南瓜头return Trueelse:return Falseelse:return Falseelif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_TILE: # 屋顶# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上if plant_name not in c.WATER_PLANTS:if "花盆(未实现)" in self.map[map_y][map_x][c.MAP_PLANT]:if (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物return Falseelse:return Trueelif ((plant_name == c.PUMPKINHEAD)and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):    # 有花盆且没有南瓜头就能种南瓜头return Trueelse:return Falseelif plant_name == "花盆(未实现)": # 这一格本来没有花盆而且新来的植物是花盆,可以种return Trueelse:return Falseelse:return Falseelif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_WATER:   # 水里if plant_name in c.WATER_PLANTS:   # 是水生植物if not self.map[map_y][map_x][c.MAP_PLANT]: # 只有无植物时才能在水里种植水生植物return Trueelse:return Falseelse:   # 非水生植物,依赖睡莲if c.LILYPAD in self.map[map_y][map_x][c.MAP_PLANT]:if (all((i in {c.LILYPAD, c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])):if plant_name in {c.SPIKEWEED, c.POTATOMINE, "花盆(未实现)"}: # 不能在睡莲上种植的植物return Falseelse:return Trueelif ((plant_name == c.PUMPKINHEAD)and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):   # 在睡莲上且没有南瓜头就能种南瓜头return Trueelse:return Falseelse:return Falseelse:   # 不可种植区域return Falsedef getMapIndex(self, x:int, y:int) -> tuple[int, int]:if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:x -= c.MAP_POOL_OFFSET_Xy -= c.MAP_POOL_OFFSET_Yreturn (x // c.GRID_POOL_X_SIZE, y // c.GRID_POOL_Y_SIZE)elif self.background_type in c.ON_ROOF_BACKGROUNDS:x -= c.MAP_ROOF_OFFSET_Xy -= c.MAP_ROOF_OFFSET_Xgrid_x = x // c.GRID_ROOF_X_SIZEif grid_x >= 5:grid_y = y // c.GRID_ROOF_Y_SIZEelse:grid_y = (y - 20*(6 - grid_x)) // 85return (grid_x, grid_y)else:x -= c.MAP_OFFSET_Xy -= c.MAP_OFFSET_Yreturn (x // c.GRID_X_SIZE, y // c.GRID_Y_SIZE)def getMapGridPos(self, map_x:int, map_y:int) -> tuple[int, int]:if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:return (map_x * c.GRID_POOL_X_SIZE + c.GRID_POOL_X_SIZE//2 + c.MAP_POOL_OFFSET_X,map_y * c.GRID_POOL_Y_SIZE + c.GRID_POOL_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y)elif self.background_type in c.ON_ROOF_BACKGROUNDS:return (map_x * c.GRID_ROOF_X_SIZE + c.GRID_ROOF_X_SIZE//2 + c.MAP_ROOF_OFFSET_X,map_y * c.GRID_ROOF_Y_SIZE + 20 * max(0, (6 - map_y)) + c.GRID_ROOF_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y)else:return (map_x * c.GRID_X_SIZE + c.GRID_X_SIZE//2 + c.MAP_OFFSET_X,map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)def setMapGridType(self, map_x:int, map_y:int, plot_type:str):self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_typedef addMapPlant(self, map_x:int, map_y:int, plant_name:int, sleep:bool=False):self.map[map_y][map_x][c.MAP_PLANT].add(plant_name)self.map[map_y][map_x][c.MAP_SLEEP] = sleepdef removeMapPlant(self, map_x:int, map_y:int, plant_name:str):self.map[map_y][map_x][c.MAP_PLANT].discard(plant_name)def getRandomMapIndex(self) -> tuple[int, int]:map_x = random.randint(0, self.width-1)map_y = random.randint(0, self.height-1)return (map_x, map_y)def checkPlantToSeed(self, x:int, y:int, plant_name:str) -> tuple[int, int]:pos = Nonemap_x, map_y = self.getMapIndex(x, y)if self.isValid(map_x, map_y) and self.isAvailable(map_x, map_y, plant_name):pos = self.getMapGridPos(map_x, map_y)return pos

定义植物类 (部分)

在这里插入图片描述

# 豌豆及孢子类普通子弹
class Bullet(pg.sprite.Sprite):def __init__(   self, x:int, start_y:int, dest_y:int, name:str, damage:int,effect:str=None, passed_torchwood_x:int=None,damage_type:str=c.ZOMBIE_DEAFULT_DAMAGE):pg.sprite.Sprite.__init__(self)self.name = nameself.frames = []self.frame_index = 0self.load_images()self.frame_num = len(self.frames)self.image = self.frames[self.frame_index]self.mask = pg.mask.from_surface(self.image)self.rect = self.image.get_rect()self.rect.x = xself.rect.y = start_yself.dest_y = dest_yself.y_vel = 15 if (dest_y > start_y) else -15self.x_vel = 10self.damage = damageself.damage_type = damage_typeself.effect = effectself.state = c.FLYself.current_time = 0self.animate_timer = 0self.animate_interval = 70self.passed_torchwood_x = passed_torchwood_x  # 记录最近通过的火炬树横坐标,如果没有缺省为Nonedef loadFrames(self, frames, name):frame_list = tool.GFX[name]if name in c.PLANT_RECT:data = c.PLANT_RECT[name]x, y, width, height = data["x"], data["y"], data["width"], data["height"]else:x, y = 0, 0rect = frame_list[0].get_rect()width, height = rect.w, rect.hfor frame in frame_list:frames.append(tool.get_image(frame, x, y, width, height))def load_images(self):self.fly_frames = []self.explode_frames = []fly_name = self.nameif self.name in c.BULLET_INDEPENDENT_BOOM_IMG:explode_name = f"{self.name}Explode"else:explode_name = "PeaNormalExplode"self.loadFrames(self.fly_frames, fly_name)self.loadFrames(self.explode_frames, explode_name)self.frames = self.fly_framesdef update(self, game_info):self.current_time = game_info[c.CURRENT_TIME]if self.state == c.FLY:if self.rect.y != self.dest_y:self.rect.y += self.y_velif self.y_vel * (self.dest_y - self.rect.y) < 0:self.rect.y = self.dest_yself.rect.x += self.x_velif self.rect.x >= c.SCREEN_WIDTH + 20:self.kill()elif self.state == c.EXPLODE:if (self.current_time - self.explode_timer) > 250:self.kill()if self.current_time - self.animate_timer >= self.animate_interval:self.frame_index += 1self.animate_timer = self.current_timeif self.frame_index >= self.frame_num:self.frame_index = 0self.image = self.frames[self.frame_index]def setExplode(self):self.state = c.EXPLODEself.explode_timer = self.current_timeself.frames = self.explode_framesself.frame_num = len(self.frames)self.image = self.frames[0]self.mask = pg.mask.from_surface(self.image)# 播放子弹爆炸音效if self.name == c.BULLET_FIREBALL:c.SOUND_FIREPEA_EXPLODE.play()else:c.SOUND_BULLET_EXPLODE.play()def draw(self, surface):surface.blit(self.image, self.rect)# 大喷菇的烟雾
# 仅有动画效果,不参与攻击运算
class Fume(pg.sprite.Sprite):def __init__(self, x, y):pg.sprite.Sprite.__init__(self)self.name = c.FUMEself.timer = 0self.frame_index = 0self.load_images()self.frame_num = len(self.frames)self.image = self.frames[self.frame_index]self.mask = pg.mask.from_surface(self.image)self.rect = self.image.get_rect()self.rect.x = xself.rect.y = ydef load_images(self):self.fly_frames = []fly_name = self.nameself.loadFrames(self.fly_frames, fly_name)self.frames = self.fly_framesdef draw(self, surface):surface.blit(self.image, self.rect)def update(self, game_info):self.current_time = game_info[c.CURRENT_TIME]if self.current_time - self.timer >= 100:self.frame_index += 1if self.frame_index >= self.frame_num:self.frame_index = self.frame_num - 1self.kill()self.timer = self.current_timeself.image = self.frames[self.frame_index]def loadFrames(self, frames, name):frame_list = tool.GFX[name]x, y = 0, 0rect = frame_list[0].get_rect()width, height = rect.w, rect.hfor frame in frame_list:frames.append(tool.get_image(frame, x, y, width, height))

定义僵尸类(部分)

在这里插入图片描述

class Zombie(pg.sprite.Sprite):def __init__(   self, x, y, name, head_group=None,helmet_health=0,                helmet_type2_health=0,body_health=c.NORMAL_HEALTH,    losthead_health=c.LOSTHEAD_HEALTH,damage=c.ZOMBIE_ATTACK_DAMAGE,  can_swim=False):pg.sprite.Sprite.__init__(self)self.name = nameself.frames = []self.frame_index = 0self.loadImages()self.frame_num = len(self.frames)self.image = self.frames[self.frame_index]self.rect = self.image.get_rect()self.mask = pg.mask.from_surface(self.image)self.rect.x = xself.rect.bottom = y# 大蒜换行移动像素值,< 0时向上,= 0时不变,> 0时向上self.target_y_change = 0self.original_y = yself.to_change_group = Falseself.helmet_health = helmet_healthself.helmet_type2_health = helmet_type2_healthself.health = body_health + losthead_healthself.losthead_health = losthead_healthself.damage = damageself.dead = Falseself.losthead = Falseself.can_swim = can_swimself.swimming = Falseself.helmet = (self.helmet_health > 0)self.helmet_type2 = (self.helmet_type2_health > 0)self.head_group = head_groupself.walk_timer = 0self.animate_timer = 0self.attack_timer = 0self.state = c.WALKself.animate_interval = 150self.walk_animate_interval = 180self.attack_animate_interval = 100self.losthead_animate_interval = 180self.die_animate_interval = 50self.boomDie_animate_interval = 100self.ice_slow_ratio = 1self.ice_slow_timer = 0self.hit_timer = 0self.speed = 1self.freeze_timer = 0self.losthead_timer = 0self.is_hypno = False  # the zombie is hypo and attack other zombies when it ate a HypnoShroomdef loadFrames(self, frames, name, colorkey=c.BLACK):frame_list = tool.GFX[name]rect = frame_list[0].get_rect()width, height = rect.w, rect.hif name in c.ZOMBIE_RECT:data = c.ZOMBIE_RECT[name]x, width = data["x"], data["width"]else:x = 0for frame in frame_list:frames.append(tool.get_image(frame, x, 0, width, height, colorkey))def update(self, game_info):self.current_time = game_info[c.CURRENT_TIME]self.handleState()self.updateIceSlow()self.animation()def handleState(self):if self.state == c.WALK:self.walking()elif self.state == c.ATTACK:self.attacking()elif self.state == c.DIE:self.dying()elif self.state == c.FREEZE:self.freezing()# 濒死状态用函数def checkToDie(self, framesKind):if self.health <= 0:self.setDie()return Trueelif self.health <= self.losthead_health:if not self.losthead:self.changeFrames(framesKind)self.setLostHead()return Trueelse:self.health -= (self.current_time - self.losthead_timer) / 40self.losthead_timer = self.current_timereturn Falseelse:return Falsedef walking(self):if self.checkToDie(self.losthead_walk_frames):return# 能游泳的僵尸if self.can_swim:# 在水池范围内# 在右侧岸左if self.rect.right <= c.MAP_POOL_FRONT_X:# 在左侧岸右,左侧岸位置为预估if self.rect.right - 25 >= c.MAP_POOL_OFFSET_X:# 还未进入游泳状态if not self.swimming:self.swimming = Trueself.changeFrames(self.swim_frames)# 播放入水音效c.SOUND_ZOMBIE_ENTERING_WATER.play()# 同样没有兼容双防具if self.helmet:if self.helmet_health <= 0:self.helmet = Falseelse:self.changeFrames(self.helmet_swim_frames)if self.helmet_type2:if self.helmet_type2_health <= 0:self.helmet_type2 = Falseelse:self.changeFrames(self.helmet_swim_frames)# 已经进入游泳状态else:if self.helmet:if self.helmet_health <= 0:self.changeFrames(self.swim_frames)self.helmet = Falseif self.helmet_type2:if self.helmet_type2_health <= 0:self.changeFrames(self.swim_frames)self.helmet_type2 = False# 水生僵尸已经接近家门口并且上岸else:if self.swimming:self.changeFrames(self.walk_frames)self.swimming = False# 同样没有兼容双防具if self.helmet:if self.helmet_health <= 0:self.helmet = Falseelse:self.changeFrames(self.helmet_walk_frames)if self.helmet_type2:if self.helmet_type2_health <= 0:self.helmet_type2 = Falseelse:self.changeFrames(self.helmet_walk_frames)if self.helmet:if self.helmet_health <= 0:self.helmet = Falseself.changeFrames(self.walk_frames)if self.helmet_type2:if self.helmet_type2_health <= 0:self.helmet_type2 = Falseself.changeFrames(self.walk_frames)elif self.is_hypno and self.rect.right > c.MAP_POOL_FRONT_X + 55:   # 常数拟合暂时缺乏检验if self.swimming:self.changeFrames(self.walk_frames)if self.helmet:if self.helmet_health <= 0:self.changeFrames(self.walk_frames)self.helmet = Falseelif self.swimming: # 游泳状态需要改为步行self.changeFrames(self.helmet_walk_frames)if self.helmet_type2:if self.helmet_type2_health <= 0:self.changeFrames(self.walk_frames)self.helmet_type2 = Falseelif self.swimming: # 游泳状态需要改为步行self.changeFrames(self.helmet_walk_frames)self.swimming = False# 尚未进入水池else:if self.helmet_health <= 0 and self.helmet:self.changeFrames(self.walk_frames)self.helmet = Falseif self.helmet_type2_health <= 0 and self.helmet_type2:self.changeFrames(self.walk_frames)self.helmet_type2 = False# 不能游泳的一般僵尸else:if self.helmet_health <= 0 and self.helmet:self.changeFrames(self.walk_frames)self.helmet = Falseif self.helmet_type2_health <= 0 and self.helmet_type2:self.changeFrames(self.walk_frames)self.helmet_type2 = Falseif (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):self.handleGarlicYChange()self.walk_timer = self.current_timeif self.is_hypno:self.rect.x += 1else:self.rect.x -= 1

游戏运行入口

在这里插入图片描述


#!/usr/bin/env python
import logging
import traceback
import os
import pygame as pg
from logging.handlers import RotatingFileHandler
# 由于在后续本地模块中存在对pygame的调用,在此处必须完成pygame的初始化
os.environ["SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"]="0" # 设置临时环境变量以避免Linux下禁用x11合成器
pg.init()from source import tool
from source import constants as c
from source.state import mainmenu, screen, levelif __name__ == "__main__":# 日志设置if not os.path.exists(os.path.dirname(c.USERLOG_PATH)):os.makedirs(os.path.dirname(c.USERLOG_PATH))logger = logging.getLogger("main")formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")fileHandler = RotatingFileHandler(c.USERLOG_PATH, "a", 1_000_000, 0, "utf-8")# 设置日志文件权限,Unix为644,Windows为可读写;Python的os.chmod与Unix chmod相同,但要显式说明8进制os.chmod(c.USERLOG_PATH, 0o644)fileHandler.setFormatter(formatter)streamHandler = logging.StreamHandler()streamHandler.setFormatter(formatter)logger.addHandler(fileHandler)logger.addHandler(streamHandler)try:# 控制状态机运行game = tool.Control()state_dict = {  c.MAIN_MENU:    mainmenu.Menu(),c.GAME_VICTORY: screen.GameVictoryScreen(),c.GAME_LOSE:    screen.GameLoseScreen(),c.LEVEL:        level.Level(),c.AWARD_SCREEN: screen.AwardScreen(),c.HELP_SCREEN:  screen.HelpScreen(),}game.setup_states(state_dict, c.MAIN_MENU)game.run()except:print() # 将日志输出与上文内容分隔开,增加可读性logger.error(f"\n{traceback.format_exc()}") 

游戏源码获取

关注vx公众号【苏凉闲谈社】回复“777””即可免费领取游戏源码,同时还为大家准备了相关图书资料、视频资料以及其他python小游戏源码等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/825120.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Proteus】51单片机对直流电机的控制

直流电机&#xff1a;输出或输入为直流电能的旋转电机。能实现直流电能和机械能互相转换的电机。把它作电动机运行时是直流电动机&#xff0c;电能转换为机械能&#xff1b;作发电机运行时是直流发电机&#xff0c;机 械能转换为电能。 直流电机的控制&#xff1a; 1、方向控制…

动态多目标测试函数DF1-DF14,FDA1-FDA5,SDP1-SDP12的TurePOF(MATLAB代码)

动态多目标测试函数FDA1、FDA2、FDA3、FDA4、FDA5的turePOF&#xff08;MATLAB代码&#xff09; 动态多目标测试函数DF1-DF14的turePOF变化&#xff08;提供MATLAB代码&#xff09; 动态多目标测试函数SDP1-SDP12的TurePOF变化视频&#xff08;含MATLAB代码及参考文献&#xff…

Java Swing制作大鱼吃小鱼魔改版本

《大鱼吃小鱼》这款游戏的历史渊源可以追溯到休闲游戏的兴起和发展。在游戏的早期发展阶段&#xff0c;开发者们开始探索各种简单而有趣的游戏玩法&#xff0c;以吸引玩家的注意力。在这样的背景下&#xff0c;《大鱼吃小鱼》应运而生&#xff0c;它结合了自然界的食物链原理与…

AI大模型之idea通义灵码智能AI插件安装方式

问题描述 主要讲述如何进行开发工具 idea中如何进行通义灵码的插件的安装解决方案 直接在idea的plugin市场中安装 下载插件之后进行安装 见资源

lua 光速入门

文章目录 安装注释字符串变量逻辑运算条件判断循环函数Table (表)常用全局函数模块化 首先明确 lua 和 js Python一样是动态解释性语言&#xff0c;需要解释器执行。并且不同于 Python 的强类型与 js 的弱类型&#xff0c;它有点居中&#xff0c;倾向于强类型。 安装 下载解释…

【OpenHarmony】TDD-FUZZ环境配置

零、参考 1、AttributeError: ‘ElementTree‘ object has no attribute ‘getiterator‘&#xff1a;https://blog.csdn.net/suhao0911/article/details/110950742 一、创建工作目录 1、新建工作目录如&#xff1a;D:\0000_TDD_FUZZ\0000_ohos_tdd_fuzz。 2、gitee上下载 t…

陇剑杯 ios 流量分析 CTF writeup

陇剑杯 ios 流量分析 链接&#xff1a;https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码&#xff1a;haek目录结构 LearnCTF ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志分析 │ │…

html+vue编写分页功能

效果&#xff1a; html关键代码&#xff1a; <div class"ui-jqgrid-resize-mark" id"rs_mlist_table_C87E35BE"> </div><div class"list_component_pager ui-jqgrid-pager undefined" dir"ltr"><div id"pg…

Linux编辑器-vim的使用

vim的基本概念 vim的三种模式(其实有好多模式&#xff0c;目前掌握这3种即可),分别是命令模式&#xff08;command mode&#xff09;、插 入模式&#xff08;Insert mode&#xff09;和底行模式&#xff08;last line mode&#xff09;&#xff0c;各模式的功能区分如下&#…

中医优势病种诊疗方案数据库

中医诊疗方案结合了几千年的实践经验与理论体系&#xff0c;形成了一套独特的诊疗方法。随着国家对中医药事业的重视&#xff0c;多个中医诊疗方案被国家卫生健康委员会和国家中医药管理局等权威机构正式发布&#xff0c;这对规范中医临床诊疗行为&#xff0c;提升医疗服务质量…

执行npm命令一直出现sill idealTree buildDeps怎么办?

一、问题 今天在运行npm时候一直出项sill idealTree buildDeps问题 二、 解决 1、先删除用户界面下的npmrc文件&#xff08;注意一定是用户C:\Users\{账户}\下的.npmrc文件下不是nodejs里面&#xff09;&#xff0c;进入到对应目录下&#xff0c;Mac启动显示隐藏文件操作&…

生产服务器变卡怎么排查

服务器变卡怎么排查&#xff0c;可以从以下四个方面去考虑 生产服务器变卡怎么排查 1、网络2、cpu的利用率3、io效率4、内存瓶颈 1、网络 可以使用netstat、iftop等工具查看网络流量和网络连接情况&#xff0c;检查是否网络堵塞、丢包等问题 2、cpu的利用率 1、用top命令定…

驱动执行篇之电机编码器:编码器基础与双编码器方案

目录 |1.编码器概述 |2.编码器分类 |2.1.增量式编码器和绝对值编码器 |2.2.光电编码器 |3.双编码器方案 |3.1几种扭矩感知方案 |3.3双编码器安装方式 |1.编码器概述 编码器 编码器&#xff0c;是将信号&#xff08;如比特流&#xff09;或数据进行编制、转换为可用以通讯…

ECA-Net:深度卷积神经网络中的高效通道注意力机制【原理讲解及代码!!!】

ECA-Net&#xff1a;深度卷积神经网络中的高效通道注意力机制 在深度学习领域&#xff0c;特别是在深度卷积神经网络&#xff08;DCNN&#xff09;中&#xff0c;注意力机制已经成为提升模型性能的关键技术之一。其中&#xff0c;ECA模块&#xff08;Efficient Channel Attent…

前端项目的导入和启动

安装依赖 前端安装依赖只需要在控制台执行“npm i”即可。Tips&#xff1a;当我们执行的时候&#xff0c;有时候会很慢。可以考虑使用yarn或者pnpm。然而使用yarn或者pnpm有时候有一些莫名其妙的问题。所以还是得使用npm&#xff0c; 这个时候可以通过更换镜像源为淘宝镜像源。…

flex上下固定中间占固定高度(中间左右菜单)且内容自动滚动

效果图 布局&#xff1a; <view class"pop_tSet"><view class"pop_Con"><view class"box_bb"><view class"bb_title">{{titleObj[popType]}}</view></view><view class"box_bb_bot"…

The Sandbox 推出全新后室模板!

我们非常高兴地向你介绍游戏制作器的另一个新成员&#xff1a; 后室模板&#xff01; 步入神秘而不自然的空旷空间&#xff0c;感觉有些......不对劲。准备好探索、创造和拥抱引人入胜的后室世界吧。 什么是后室&#xff08;Backroom&#xff09;游戏&#xff1f; 早在 2019 年…

获取公募基金持仓【数据分析系列博文】

摘要 从指定网址获取公募基金持仓数据&#xff0c;快速解析并存储数据。 &#xff08;该博文针对自由学习者获取数据&#xff1b;而在投顾、基金、证券等公司&#xff0c;通常有Wind、聚源、通联等厂商采购的数据&#xff09; 1. 导入必要的库&#xff1a; pandas 用于数据处理…

Java中类装载的执行过程

类装载的执行过程 类从加载到虚拟机中开始&#xff0c;直到卸载为止&#xff0c;它的整个生命周期包括了&#xff1a;加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中&#xff0c;验证、准备和解析这三个部分统称为连接&#xff08;linking&#xff09;。 1.加载 …

第一天学C++(C++入门)

一、HelloWorld &#xff08;第一个C入门程序&#xff09; 1.1代码 #include<iostream> using namespace std; // 1.单行注释// 2. 多行注释 /* main 是一个程序的入口 每个程序都必须有这么一个函数 有且仅有一个 */ int main() {// 第九行代码的含义就是在屏幕中输出…