文章目录

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

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: