【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_LEN
            self.height = c.GRID_POOL_Y_LEN
            self.grid_height_size = c.GRID_POOL_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_WATER) if 2 <= y <= 3
                                else 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_LEN
            self.height = c.GRID_ROOF_Y_LEN
            self.grid_height_size = c.GRID_ROOF_Y_SIZE
            self.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_LEN
            self.height = c.GRID_Y_LEN
            self.grid_height_size = c.GRID_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_GRASS) if y ==2
                                else 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_LEN
            self.height = c.GRID_Y_LEN
            self.grid_height_size = c.GRID_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_GRASS) if 1 <= y <= 3
                                else self.initMapGrid(c.MAP_UNAVAILABLE)
                            for x in range(self.width)
                            ]
                        for y in range(self.height)
                        ]
        else:
            self.width = c.GRID_X_LEN
            self.height = c.GRID_Y_LEN
            self.grid_height_size = c.GRID_Y_SIZE
            self.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 True
        return 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 True
            else:
                return False
        if 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 True
            else:
                return False
        # 被非植物障碍占据的格子对于一般植物不可种植
        if any((i in c.NON_PLANT_OBJECTS) for i in self.map[map_y][map_x][c.MAP_PLANT]):
            return False
        if 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 True
                elif (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 True
                elif ((plant_name == c.PUMPKINHEAD)
                and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):   # 没有南瓜头就能种南瓜头
                    return True
                else:
                    return False
            else:
                return False
        elif 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 False
                        else:
                            return True
                    elif ((plant_name == c.PUMPKINHEAD)
                    and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):    # 有花盆且没有南瓜头就能种南瓜头
                        return True
                    else:
                        return False
                elif plant_name == "花盆(未实现)": # 这一格本来没有花盆而且新来的植物是花盆,可以种
                    return True
                else:
                    return False
            else:
                return False
        elif 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 True
                else:
                    return False
            else:   # 非水生植物,依赖睡莲
                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 False
                        else:
                            return True
                    elif ((plant_name == c.PUMPKINHEAD)
                    and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):   # 在睡莲上且没有南瓜头就能种南瓜头
                        return True
                    else:
                        return False
                else:
                    return False
        else:   # 不可种植区域
            return False
    
    def getMapIndex(self, x:int, y:int) -> tuple[int, int]:
        if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
            x -= c.MAP_POOL_OFFSET_X
            y -= c.MAP_POOL_OFFSET_Y
            return (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_X
            y -= c.MAP_ROOF_OFFSET_X
            grid_x = x // c.GRID_ROOF_X_SIZE
            if grid_x >= 5:
                grid_y = y // c.GRID_ROOF_Y_SIZE
            else:
                grid_y = (y - 20*(6 - grid_x)) // 85
            return (grid_x, grid_y)
        else:
            x -= c.MAP_OFFSET_X
            y -= c.MAP_OFFSET_Y
            return (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_type

    def 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] = sleep
    
    def 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 = None
        map_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 = name
        self.frames = []
        self.frame_index = 0
        self.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 = x
        self.rect.y = start_y
        self.dest_y = dest_y
        self.y_vel = 15 if (dest_y > start_y) else -15
        self.x_vel = 10
        self.damage = damage
        self.damage_type = damage_type
        self.effect = effect
        self.state = c.FLY
        self.current_time = 0
        self.animate_timer = 0
        self.animate_interval = 70
        self.passed_torchwood_x = passed_torchwood_x  # 记录最近通过的火炬树横坐标,如果没有缺省为None

    def 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, 0
            rect = frame_list[0].get_rect()
            width, height = rect.w, rect.h

        for 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.name
        if 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_frames

    def 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_vel
                if self.y_vel * (self.dest_y - self.rect.y) < 0:
                    self.rect.y = self.dest_y
            self.rect.x += self.x_vel
            if 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 += 1
            self.animate_timer = self.current_time
            if self.frame_index >= self.frame_num:
                self.frame_index = 0
            self.image = self.frames[self.frame_index]

    def setExplode(self):
        self.state = c.EXPLODE
        self.explode_timer = self.current_time
        self.frames = self.explode_frames
        self.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.FUME
        self.timer = 0
        self.frame_index = 0
        self.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 = x
        self.rect.y = y

    def load_images(self):
        self.fly_frames = []

        fly_name = self.name

        self.loadFrames(self.fly_frames, fly_name)

        self.frames = self.fly_frames

    def 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 += 1
            if self.frame_index >= self.frame_num:
                self.frame_index = self.frame_num - 1
                self.kill()
            self.timer = self.current_time
        self.image = self.frames[self.frame_index]

    def loadFrames(self, frames, name):
        frame_list = tool.GFX[name]
        x, y = 0, 0
        rect = frame_list[0].get_rect()
        width, height = rect.w, rect.h

        for 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 = name
        self.frames = []
        self.frame_index = 0
        self.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 = x
        self.rect.bottom = y
        # 大蒜换行移动像素值,< 0时向上,= 0时不变,> 0时向上
        self.target_y_change = 0
        self.original_y = y
        self.to_change_group = False

        self.helmet_health = helmet_health
        self.helmet_type2_health = helmet_type2_health
        self.health = body_health + losthead_health
        self.losthead_health = losthead_health
        self.damage = damage
        self.dead = False
        self.losthead = False
        self.can_swim = can_swim
        self.swimming = False
        self.helmet = (self.helmet_health > 0)
        self.helmet_type2 = (self.helmet_type2_health > 0)
        self.head_group = head_group

        self.walk_timer = 0
        self.animate_timer = 0
        self.attack_timer = 0
        self.state = c.WALK
        self.animate_interval = 150
        self.walk_animate_interval = 180
        self.attack_animate_interval = 100
        self.losthead_animate_interval = 180
        self.die_animate_interval = 50
        self.boomDie_animate_interval = 100
        self.ice_slow_ratio = 1
        self.ice_slow_timer = 0
        self.hit_timer = 0
        self.speed = 1
        self.freeze_timer = 0
        self.losthead_timer = 0
        self.is_hypno = False  # the zombie is hypo and attack other zombies when it ate a HypnoShroom

    def loadFrames(self, frames, name, colorkey=c.BLACK):
        frame_list = tool.GFX[name]
        rect = frame_list[0].get_rect()
        width, height = rect.w, rect.h
        if name in c.ZOMBIE_RECT:
            data = c.ZOMBIE_RECT[name]
            x, width = data["x"], data["width"]
        else:
            x = 0
        for 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 True
        elif self.health <= self.losthead_health:
            if not self.losthead:
                self.changeFrames(framesKind)
                self.setLostHead()
                return True
            else:
                self.health -= (self.current_time - self.losthead_timer) / 40
                self.losthead_timer = self.current_time
                return False
        else:
            return False

    def 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 = True
                        self.changeFrames(self.swim_frames)
                        # 播放入水音效
                        c.SOUND_ZOMBIE_ENTERING_WATER.play()
                        # 同样没有兼容双防具
                        if self.helmet:
                            if self.helmet_health <= 0:
                                self.helmet = False
                            else:
                                self.changeFrames(self.helmet_swim_frames)
                        if self.helmet_type2:
                            if self.helmet_type2_health <= 0:
                                self.helmet_type2 = False
                            else:
                                self.changeFrames(self.helmet_swim_frames)
                    # 已经进入游泳状态
                    else:
                        if self.helmet:
                            if self.helmet_health <= 0:
                                self.changeFrames(self.swim_frames)
                                self.helmet = False
                        if 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 = False
                            else:
                                self.changeFrames(self.helmet_walk_frames)
                        if self.helmet_type2:
                            if self.helmet_type2_health <= 0:
                                self.helmet_type2 = False
                            else:
                                self.changeFrames(self.helmet_walk_frames)
                    if self.helmet:
                        if self.helmet_health <= 0:
                            self.helmet = False
                            self.changeFrames(self.walk_frames)
                    if self.helmet_type2:
                        if self.helmet_type2_health <= 0:
                            self.helmet_type2 = False
                            self.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 = False
                    elif 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 = False
                    elif 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 = False
                if 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 = False
            if self.helmet_type2_health <= 0 and self.helmet_type2:
                self.changeFrames(self.walk_frames)
                self.helmet_type2 = False

        if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
            self.handleGarlicYChange()
            self.walk_timer = self.current_time
            if self.is_hypno:
                self.rect.x += 1
            else:
                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, level

if __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小游戏源码等等。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vscode调试文件(C++,ROS和cmake文件)

VsCode调试文件 参考文档&#xff1a; code.visualstudio.com/docs/editor/variables-reference code.visualstudio.com/docs/editor/tasks 主要修改task.json下的"args"、launch.json中的"program",“args” 注意task.json中的label以及launch.json中…

OpenCV 学习笔记2 C++

1.图像直方图 直方图&#xff08;Histogram&#xff09;是图像处理中常用的工具&#xff0c;它表示图像中每个像素强度值的分布情况。在OpenCV中&#xff0c;可以使用 cv::calcHist 函数来计算图像的直方图。 图像直方图是一种展示图像像素强度分布的统计图表。它显示了图像中…

市场份额第一!博睿数据持续领跑中国APM市场

近日&#xff0c;全球领先的IT市场研究和咨询公司IDC发布《中国IT统一运维软件产品市场跟踪报告&#xff0c;2023H2》。报告显示&#xff0c;2023下半年博睿数据以 17.6%的市场份额蝉联 APM(应用性能监控)市场第一。2023年全年博睿数据以18.8%的市场份额持续领跑中国APM市场。 …

实现联系人前后端界面,实现分页查询04.15

实现联系人前后端界面&#xff0c;实现分页查询项目包-CSDN博客 项目结构 数据库中建立两个表&#xff1a; 完整的后端目录 建立联系人People表&#xff0c;分组Type表&#xff0c;实现对应实体类 根据需求在mapper中写对应的sql语句 查询所有&#xff0c;删除&#xff0c;添…

网工交换基础——Access、Trunk、Hybrid处理VLAN的过程

一、VLAN帧格式&#xff1a; 通过Tag区分不同VLAN。打tag/剥离tag都有交换机接口进行。 1.接收方向&#xff1a;不会进行tag的剥离/弹出&#xff08;永远不会剥离VLAN Tag&#xff09;。 2.交换机内部&#xff1a;处理的都是带Tag的数据帧。 交换机开机&#xff0c;默认生成树…

振弦式土压力计性能评估指南

振弦式土压力计是一种常用的岩土工程监测仪器&#xff0c;主要用于测量土体中各点的土压力变化。在长期的监测过程中&#xff0c;保持土压力计的性能稳定与准确至关重要。因此&#xff0c;定期检查和确定振弦式土压力计的性能是否正常成为了一项必要的工作。本文将详细介绍如何…

PHP-extract变量覆盖

[题目信息]&#xff1a; 题目名称题目难度PHP-extract变量覆盖1 [题目考点]&#xff1a; 变量覆盖指的是用我们自定义的参数值替换程序原有的变量值&#xff0c;一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。 经常导致变量覆盖漏洞场景有&#xff1a;$$&#x…

【智能算法】花朵授粉算法(FPA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2012年&#xff0c;Yang等人受到自然界花朵授粉过程启发&#xff0c;提出了鸭群算法&#xff08;Flower Pollination Algorithm, FPA&#xff09;。 2.算法原理 2.1算法思想 FPA基于自然界花朵授…

Git 指令

一.常见命令 1.1 初始化仓库 #初始化仓库 git init #查看隐藏文件 ll -a #查看隐藏文件.git结构 tree .git/1.2 创建/删除用户信息 #创建用户信息 git config user.email "151859163.com" git config user.name "zsl" #查看 git config -l #删除用户信息…

水电预付费远程管理

1.概述&#xff1a;水电预付费远程管理的定义与重要性 水电预付费远程管理系统是一种先进的能源管理模式&#xff0c;它允许用户在使用水电资源前预先支付费用&#xff0c;并通过远程技术进行实时监控和管理。这种系统不仅提高了效率&#xff0c;减少了欠费风险&#xff0c;也…

【缓存常见问题】

在使用缓存时特别是在高并发场景下会遇到很多问题&#xff0c;常用的问题有缓存穿透、缓存击穿、缓存雪崩以及缓存一致性问题。 1、缓存穿透 首先&#xff0c;什么是缓存穿透呢&#xff1f; 缓存穿透是指请求一个不存在的数据&#xff0c;缓存层和数据库层都没有这个数据&…

VTK 动画:利用 Qt 的时间控制

VTK 动画&#xff1a;利用 Qt 的时间控制 VTK 动画&#xff1a;利用 Qt 的时间控制VS2022 QT6.2.3 VTK9.3.0 环境配置Qt 定时器类 QTimer实现运行结果参考 VTK 动画&#xff1a;利用 Qt 的时间控制 第一篇文章 《VTK 动画&#xff1a;框架、流程与实现》 讲到了 VTK 的动画框…

小行星碰撞

题目链接 小行星碰撞 题目描述 注意点 两个小行星相互碰撞&#xff0c;较小的小行星会爆炸如果两颗小行星大小相同&#xff0c;则两颗小行星都会爆炸每一颗小行星以相同的速度移动正负表示小行星的移动方向&#xff08;正表示向右移动&#xff0c;负表示向左移动&#xff09…

超净实验室用耐强酸碱耐高温PFA酸缸进口透明聚四氟塑料方槽

PFA清洗槽是四氟清洗桶后的升级款&#xff0c;主要用于半导体光伏光电等行业&#xff0c;一体成型&#xff0c;无需担心漏液&#xff0c;表面光滑无毛刺。 别名PFA浸泡桶、PFA酸缸、PFA方槽等&#xff0c;可定制尺寸&#xff0c;可配套盖子&#xff0c;盖子有PFA/PTFE两种材质…

如何寻找可靠的第三方软件检测机构

随着科技的飞速发展和数字化进程的加速&#xff0c;软件质量成为了企业竞争的关键。为了确保软件的质量和性能&#xff0c;许多企业选择寻找第三方的软件检测机构来进行软件的质量控制和评估。那么&#xff0c;如何找到一家可靠的第三方软件检测机构呢&#xff1f; 1.明确检测…

前段css中border-radius的简单使用

前段css中border-radius的使用 一、前言二、border-radius语法三、border-radius的模型例子1.源码12.源码1效果截图 四、border-radius的动画效果&#xff08;动态交互&#xff09;1.源码22.源码2显示效果 五、结语六、定位日期 一、前言 在CSS中&#xff0c;我们常用border-r…

AUS Global 与皇家贝蒂斯俱乐部在对战皇家马德里的比赛日举办现场体验活动

AUS Global 最近前往西班牙庆祝与皇家贝蒂斯的赞助合作&#xff0c;并获得了难忘的比赛日体验&#xff0c;包括在贵宾室中观看皇家贝蒂斯对阵皇家马德里的精彩比赛。 活动开始时&#xff0c;AUS Global 受邀来到皇家贝蒂斯主场贝尼托-比利亚马林体育场的独家 Showbox 贵宾室。…

微信小程序的支付功能,纯前端步骤,超级详细

1、首先在微信开放平台&#xff0c;申请移动应用并开通支付功能&#xff0c;申请应用后可以获取 AppID 和 AppSecret 值 2、在前端项目中的配置参数中打开支付功能 步骤&#xff1a;项目的根目录下有个manifest.json文件&#xff0c;打开并在左侧目录找到APP SDK配置&#xf…

AI实景无人直播自动卖卷系统,开创了实体商家直播自运营先河。

AI实景无人直播自动卖卷系统&#xff0c;开创了实体商家直播自运营先河。 从当下这一刻起&#xff0c;拒绝内耗&#xff0c;做行动的巨人。因为&#xff0c;命运不会偏袒任何人&#xff0c;却会眷顾一直朝着光亮前进的人。 《人民日报》 随着新媒体的快速发展&#xff0c;很…

【力扣】55. 跳跃游戏 - 力扣(LeetCode)

Problem: 55. 跳跃游戏 记录自己解答的思路和代码 文章目录 问题思路复杂度Code 问题 思路 这个题的主要思路就是先找到0对应的位置&#xff0c;然后标记起来对应left&#xff0c;如果只有一个零&#xff0c;只需要left后面的数中有>1的数就能跳过去&#xff0c;如果是00&a…