import os import json import sys import pygame as pg from random import randint from random import choices from random import uniform from .. import tool from .. import constants as c from ..component import map, plant, zombie, menubar class Level(tool.State): def __init__(self): tool.State.__init__(self) def startup(self, current_time, persist): self.game_info = persist self.persist = self.game_info self.game_info[c.CURRENT_TIME] = current_time # 暂停状态 self.pause = False self.pauseTime = 0 # 默认显然不用显示菜单 self.showLittleMenu = False # 导入地图参数 self.loadMap() self.map = map.Map(self.map_data[c.BACKGROUND_TYPE]) self.map_y_len = self.map.height self.setupBackground() self.initState() def loadMap(self): if c.LITTLEGAME_BUTTON in self.game_info and self.game_info[c.LITTLEGAME_BUTTON]: map_file = 'littleGame_' + str(self.game_info[c.LITTLEGAME_NUM]) + '.json' self.mode = c.MODE_LITTLEGAME else: map_file = 'level_' + str(self.game_info[c.LEVEL_NUM]) + '.json' self.mode = c.MODE_ADVENTURE file_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),'resources' , 'data', 'map', map_file) # 最后一关之后应该结束了 try: f = open(file_path) self.map_data = json.load(f) f.close() except Exception as e: print("游戏结束") if self.mode == c.MODE_ADVENTURE: self.game_info[c.LEVEL_NUM] = c.START_LEVEL_NUM elif self.mode == c.MODE_LITTLEGAME: self.game_info[c.LITTLEGAME_NUM] = c.START_LITTLE_GAME_NUM self.done = True self.next = c.MAIN_MENU pg.mixer.music.stop() pg.mixer.music.load(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "music", "intro.opus")) pg.mixer.music.play(-1, 0) return if self.map_data[c.SHOVEL] == 0: self.hasShovel = False else: self.hasShovel = True # 同时播放音乐 global bgm if self.mode == c.MODE_ADVENTURE: # 冒险模式 if self.game_info[c.LEVEL_NUM] in {0, 1, 2}: # 白天关卡 bgm = 'dayLevel.opus' elif self.game_info[c.LEVEL_NUM] in {3}: # 夜晚关卡 bgm = 'nightLevel.opus' elif self.game_info[c.LEVEL_NUM] in {4}: bgm = 'poolLevel.opus' elif self.mode == c.MODE_LITTLEGAME: # 小游戏模式 if self.game_info[c.LITTLEGAME_NUM] in {1}: # 传送带大战 bgm = 'battle.opus' elif self.game_info[c.LITTLEGAME_NUM] in {2}: # 坚果保龄球 bgm = 'bowling.opus' pg.mixer.music.stop() pg.mixer.music.load(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "music", bgm)) pg.mixer.music.play(-1, 0) def setupBackground(self): img_index = self.map_data[c.BACKGROUND_TYPE] self.background_type = img_index self.background = tool.GFX[c.BACKGROUND_NAME][img_index] self.bg_rect = self.background.get_rect() self.level = pg.Surface((self.bg_rect.w, self.bg_rect.h)).convert() self.viewport = tool.SCREEN.get_rect(bottom=self.bg_rect.bottom) self.viewport.x += c.BACKGROUND_OFFSET_X def setupGroups(self): self.sun_group = pg.sprite.Group() self.head_group = pg.sprite.Group() self.plant_groups = [] self.zombie_groups = [] self.hypno_zombie_groups = [] #zombies who are hypno after eating hypnoshroom self.bullet_groups = [] for i in range(self.map_y_len): self.plant_groups.append(pg.sprite.Group()) self.zombie_groups.append(pg.sprite.Group()) self.hypno_zombie_groups.append(pg.sprite.Group()) self.bullet_groups.append(pg.sprite.Group()) # 按照规则生成每一波僵尸 # 可以考虑将波刷新和一波中的僵尸生成分开 # useableZombie是指可用的僵尸种类的元组 # inevitableZombie指在本轮必然出现的僵尸,输入形式为字典: {波数1:(僵尸1, 僵尸2……), 波数2:(僵尸1, 僵尸2……)……} def createWaves(self, useableZombies, numFlags, survivalRounds=0, inevitableZombieDict=None): waves = [] # 权重值 weights = [] for zombie in useableZombies: weights.append(self.createZombieInfo[zombie][1]) # 按照原版pvz设计的僵尸容量函数,是从无尽解析的,但是普通关卡也可以遵循 for wave in range(1, 10 * numFlags + 1): volume = int(int((wave + survivalRounds*20)*0.8)/2) + 1 zombieList = [] # 大波僵尸情况 if wave % 10 == 0: # 容量增大至2.5倍 volume = int(volume*2.5) # 先生成旗帜僵尸 zombieList.append(c.FLAG_ZOMBIE) volume -= self.createZombieInfo[c.FLAG_ZOMBIE][0] if inevitableZombieDict and (str(wave) in inevitableZombieDict.keys()): for newZombie in inevitableZombieDict[str(wave)]: zombieList.append(newZombie) volume -= self.createZombieInfo[newZombie][0] if volume < 0: print('警告:第{}波中手动设置的僵尸级别总数超过上限!'.format(wave)) while (volume > 0) and (len(zombieList) < 50): newZombie = choices(useableZombies, weights)[0] if self.createZombieInfo[newZombie][0] <= volume: zombieList.append(newZombie) volume -= self.createZombieInfo[newZombie][0] waves.append(zombieList) self.waves = waves # 僵尸的刷新机制 def refreshWaves(self, current_time, survivalRounds=0): if self.waveNum >= self.map_data[c.NUM_FLAGS] * 10: return if (self.waveNum == 0): # 还未开始出现僵尸 if (self.waveTime == 0): # 表明刚刚开始游戏 self.waveTime = current_time else: if (survivalRounds == 0) and (self.bar_type == c.CHOOSEBAR_STATIC): # 首次选卡等待时间较长 if current_time - self.waveTime >= 18000: self.waveNum += 1 self.waveTime = current_time self.waveZombies = self.waves[self.waveNum - 1] self.numZombie = len(self.waveZombies) else: if (current_time - self.waveTime >= 6000): self.waveNum += 1 self.waveTime = current_time self.waveZombies = self.waves[self.waveNum - 1] self.numZombie = len(self.waveZombies) return if (self.waveNum % 10 != 9): if ((current_time - self.waveTime >= 25000 + randint(0, 6000)) or (self.bar_type != c.CHOOSEBAR_STATIC and current_time - self.waveTime >= 12500 + randint(0, 3000))): self.waveNum += 1 self.waveTime = current_time self.waveZombies = self.waves[self.waveNum - 1] self.numZombie = len(self.waveZombies) return else: if ((current_time - self.waveTime >= 45000) or (self.bar_type != c.CHOOSEBAR_STATIC and current_time - self.waveTime >= 25000)): self.waveNum += 1 self.waveTime = current_time self.waveZombies = self.waves[self.waveNum - 1] self.numZombie = len(self.waveZombies) return numZombies = 0 for i in range(self.map_y_len): numZombies += len(self.zombie_groups[i]) if numZombies / self.numZombie < uniform(0.15, 0.25): self.waveNum += 1 self.waveTime = current_time self.waveZombies = self.waves[self.waveNum - 1] return def setupZombies(self): def takeTime(element): return element[0] self.zombie_list = [] # 目前设置为从JSON文件中读取僵尸出现的时间、种类、位置信息,以后可以将时间设置为模仿原版的机制 for data in self.map_data[c.ZOMBIE_LIST]: if 'map_y' in data.keys(): self.zombie_list.append((data['time'], data['name'], data['map_y'])) else: self.zombie_list.append((data['time'], data['name'])) self.zombie_start_time = 0 self.zombie_list.sort(key=takeTime) def setupCars(self): self.cars = [] for i in range(self.map_y_len): _, y = self.map.getMapGridPos(0, i) self.cars.append(plant.Car(-25, y+20, i)) # 更新函数每帧被调用,将鼠标事件传入给状态处理函数 def update(self, surface, current_time, mouse_pos, mouse_click): self.current_time = self.game_info[c.CURRENT_TIME] = self.pvzTime(current_time) if self.state == c.CHOOSE: self.choose(mouse_pos, mouse_click) elif self.state == c.PLAY: self.play(mouse_pos, mouse_click) self.draw(surface) def pvzTime(self, current_time): # 扣除暂停时间 if not self.pause: self.beforePauseTime = current_time - self.pauseTime else: self.pauseTime = current_time - self.beforePauseTime return self.beforePauseTime def initBowlingMap(self): print('initBowlingMap') for x in range(3, self.map.width): for y in range(self.map.height): self.map.setMapGridType(x, y, c.MAP_STATE_UNAVAILABLE) # 将坚果保龄球红线右侧设置为不可种植任何植物 def initState(self): if c.CHOOSEBAR_TYPE in self.map_data: self.bar_type = self.map_data[c.CHOOSEBAR_TYPE] else: self.bar_type = c.CHOOSEBAR_STATIC if self.bar_type == c.CHOOSEBAR_STATIC: self.initChoose() else: card_pool = menubar.getCardPool(self.map_data[c.CARD_POOL]) self.initPlay(card_pool) if self.bar_type == c.CHOSSEBAR_BOWLING: self.initBowlingMap() def initChoose(self): self.state = c.CHOOSE self.panel = menubar.Panel(menubar.cards_to_choose, self.map_data[c.INIT_SUN_NAME]) def choose(self, mouse_pos, mouse_click): if mouse_pos and mouse_click[0]: self.panel.checkCardClick(mouse_pos) if self.panel.checkStartButtonClick(mouse_pos): self.initPlay(self.panel.getSelectedCards()) def initPlay(self, card_list): self.state = c.PLAY if self.bar_type == c.CHOOSEBAR_STATIC: self.menubar = menubar.MenuBar(card_list, self.map_data[c.INIT_SUN_NAME]) else: self.menubar = menubar.MoveBar(card_list) # 是否拖住植物或者铲子 self.drag_plant = False self.drag_shovel = False self.hint_image = None self.hint_plant = False # 种植植物后应当刷新僵尸的攻击对象,当然,默认初始时不用刷新 self.refreshZombieAttack = False # 0:白天 1:夜晚 2:泳池 3:浓雾 4:屋顶 5:月夜 6:坚果保龄球 # 还准备加入 7:单行草皮 8:三行草皮 但是目前没有找到图( if self.background_type in {0, 2, 4, 7, 8} and self.bar_type == c.CHOOSEBAR_STATIC: self.produce_sun = True else: self.produce_sun = False self.sun_timer = self.current_time self.removeMouseImage() self.setupGroups() if (c.ZOMBIE_LIST in self.map_data.keys()) and self.map_data[c.SPAWN_ZOMBIES] == c.SPAWN_ZOMBIES_LIST: self.setupZombies() else: # 僵尸波数数据及僵尸生成数据 self.waveNum = 0 # 还未出现僵尸时定义为0 self.waveTime = 0 self.waveZombies = [] self.numZombie = 0 # 新的僵尸生成机制:级别——权重生成 self.createZombieInfo = {# 生成僵尸:(级别, 权重) c.NORMAL_ZOMBIE:(1, 4000), c.FLAG_ZOMBIE:(1, 0), c.CONEHEAD_ZOMBIE:(2, 4000), c.BUCKETHEAD_ZOMBIE:(4, 3000), c.NEWSPAPER_ZOMBIE:(2, 1000), c.FOOTBALL_ZOMBIE:(2, 2000) } # 暂时没有生存模式,所以 survivalRounds = 0 if c.INEVITABLE_ZOMBIE_DICT in self.map_data.keys(): self.createWaves( useableZombies=self.map_data[c.INCLUDED_ZOMBIES], numFlags=self.map_data[c.NUM_FLAGS], survivalRounds=0, inevitableZombieDict=self.map_data[c.INEVITABLE_ZOMBIE_DICT]) else: self.createWaves( useableZombies=self.map_data[c.INCLUDED_ZOMBIES], numFlags=self.map_data[c.NUM_FLAGS], survivalRounds=0) self.setupCars() # 地图有铲子才添加铲子 if self.hasShovel: # 导入小铲子 frame_rect = [0, 0, 71, 67] self.shovel = tool.get_image_menu(tool.GFX[c.SHOVEL], *frame_rect, c.BLACK, 1.1) self.shovel_rect = self.shovel.get_rect() frame_rect = [0, 0, 77, 75] self.shovel_positon = (608, 1) self.shovel_box = tool.get_image_menu(tool.GFX[c.SHOVEL_BOX], *frame_rect, c.BLACK, 1.1) self.shovel_box_rect = self.shovel_box.get_rect() self.shovel_rect.x = self.shovel_box_rect.x = self.shovel_positon[0] self.shovel_rect.y = self.shovel_box_rect.y = self.shovel_positon[1] self.setupLittleMenu() # 小菜单 def setupLittleMenu(self): # 具体运行游戏必定有个小菜单, 导入菜单和选项 frame_rect = [0, 0, 108, 31] self.little_menu = tool.get_image_menu(tool.GFX[c.LITTLE_MENU], *frame_rect, c.BLACK, 1.1) self.little_menu_rect = self.little_menu.get_rect() self.little_menu_rect.x = 690 self.little_menu_rect.y = 0 frame_rect = [0, 0, 500, 500] self.big_menu = tool.get_image_menu(tool.GFX[c.BIG_MENU], *frame_rect, c.BLACK, 1.1) self.big_menu_rect = self.big_menu.get_rect() self.big_menu_rect.x = 150 self.big_menu_rect.y = 0 frame_rect = [0, 0, 342, 87] self.return_button = tool.get_image_menu(tool.GFX[c.RETURN_BUTTON], *frame_rect, c.BLACK, 1.1) self.return_button_rect = self.return_button.get_rect() self.return_button_rect.x = 220 self.return_button_rect.y = 440 frame_rect = [0, 0, 207, 45] self.restart_button = tool.get_image_menu(tool.GFX[c.RESTART_BUTTON], *frame_rect, c.BLACK, 1.1) self.restart_button_rect = self.restart_button.get_rect() self.restart_button_rect.x = 295 self.restart_button_rect.y = 325 frame_rect = [0, 0, 206, 43] self.mainMenu_button = tool.get_image_menu(tool.GFX[c.MAINMENU_BUTTON], *frame_rect, c.BLACK, 1.1) self.mainMenu_button_rect = self.mainMenu_button.get_rect() self.mainMenu_button_rect.x = 299 self.mainMenu_button_rect.y = 372 # 检查小菜单有没有被点击 def checkLittleMenuClick(self, mouse_pos): x, y = mouse_pos if(x >= self.little_menu_rect.x and x <= self.little_menu_rect.right and y >= self.little_menu_rect.y and y <= self.little_menu_rect.bottom): return True return False # 检查小菜单的返回有没有被点击 def checkReturnClick(self, mouse_pos): x, y = mouse_pos if(x >= self.return_button_rect.x and x <= self.return_button_rect.right and y >= self.return_button_rect.y and y <= self.return_button_rect.bottom): return True return False # 检查小菜单的重新开始有没有被点击 def checkRestartClick(self, mouse_pos): x, y = mouse_pos if(x >= self.restart_button_rect.x and x <= self.restart_button_rect.right and y >= self.restart_button_rect.y and y <= self.restart_button_rect.bottom): return True return False # 检查小菜单的主菜单有没有被点击 def checkMainMenuClick(self, mouse_pos): x, y = mouse_pos if(x >= self.mainMenu_button_rect.x and x <= self.mainMenu_button_rect.right and y >= self.mainMenu_button_rect.y and y <= self.mainMenu_button_rect.bottom): return True return False # 用小铲子移除植物 def shovelRemovePlant(self, mouse_pos): x, y = mouse_pos map_x, map_y = self.map.getMapIndex(x, y) for i in self.plant_groups[map_y]: if (x >= i.rect.x and x <= i.rect.right and y >= i.rect.y and y <= i.rect.bottom): # 优先移除花盆、睡莲上的植物而非花盆、睡莲本身 if len(self.map.map[map_y][map_x][c.MAP_PLANT]) >= 2: if c.LILYPAD in self.map.map[map_y][map_x][c.MAP_PLANT]: if i.name == c.LILYPAD: continue elif '花盆(未实现)' in self.map.map[map_y][map_x][c.MAP_PLANT]: if i.name == '花盆(未实现)': continue self.killPlant(i, shovel=True) # 使用后默认铲子复原 self.drag_shovel = not self.drag_shovel self.removeMouseImagePlus() return # 检查小铲子的位置有没有被点击 # 方便放回去 def checkShovelClick(self, mouse_pos): x, y = mouse_pos if( self.hasShovel and x >= self.shovel_box_rect.x and x <= self.shovel_box_rect.right and y >= self.shovel_box_rect.y and y <= self.shovel_box_rect.bottom): return True return False def play(self, mouse_pos, mouse_click): # 原版阳光掉落机制需要 # 已掉落的阳光 self.fallenSun = 0 # 如果暂停 if self.showLittleMenu: # 设置暂停状态 self.pause = True # 暂停播放音乐 pg.mixer.music.pause() if mouse_click[0]: if self.checkReturnClick(mouse_pos): # 终止暂停,停止显示菜单 self.pause = False self.showLittleMenu = False # 继续播放音乐 pg.mixer.music.unpause() elif self.checkRestartClick(mouse_pos): self.done = True self.next = c.LEVEL pg.mixer.music.stop() pg.mixer.music.load(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "music", bgm)) pg.mixer.music.play(-1, 0) elif self.checkMainMenuClick(mouse_pos): self.done = True self.next = c.MAIN_MENU #self.persist = {c.CURRENT_TIME:0, c.LEVEL_NUM:c.START_LEVEL_NUM} # 应该不能用c.LEVEL_NUM:c.START_LEVEL_NUM self.persist = {c.CURRENT_TIME:0, c.LEVEL_NUM:self.persist[c.LEVEL_NUM], c.LITTLEGAME_NUM:self.persist[c.LITTLEGAME_NUM]} pg.mixer.music.stop() pg.mixer.music.load(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "music", "intro.opus")) pg.mixer.music.play(-1, 0) return if (c.ZOMBIE_LIST in self.map_data.keys()) and self.map_data[c.SPAWN_ZOMBIES] == c.SPAWN_ZOMBIES_LIST: # 旧僵尸生成方式 if self.zombie_start_time == 0: self.zombie_start_time = self.current_time elif len(self.zombie_list) > 0: data = self.zombie_list[0] # 因此要求僵尸列表按照时间顺序排列 # data内容排列:[0]:时间 [1]:名称 [2]:坐标 if data[0] <= (self.current_time - self.zombie_start_time): if len(data) == 3: self.createZombie(data[1], data[2]) self.zombie_list.remove(data) else: # len(data) == 2 没有指定map_y self.createZombie(data[1]) self.zombie_list.remove(data) else: # 新僵尸生成方式 self.refreshWaves(self.current_time) for i in self.waveZombies: self.createZombie(i) else: self.waveZombies = [] for i in range(self.map_y_len): self.bullet_groups[i].update(self.game_info) self.plant_groups[i].update(self.game_info) self.zombie_groups[i].update(self.game_info) self.hypno_zombie_groups[i].update(self.game_info) # 清除走出去的魅惑僵尸 for zombie in self.hypno_zombie_groups[i]: if zombie.rect.x > c.SCREEN_WIDTH: zombie.kill() self.head_group.update(self.game_info) self.sun_group.update(self.game_info) if self.produce_sun: # 原版阳光掉落机制:(已掉落阳光数*100 ms + 4250 ms) 与 9500 ms的最小值,再加 0 ~ 2750 ms 之间的一个数 if (self.current_time - self.sun_timer) > min(c.PRODUCE_SUN_INTERVAL + 100*self.fallenSun, 9500) + randint(0, 2750): self.sun_timer = self.current_time map_x, map_y = self.map.getRandomMapIndex() x, y = self.map.getMapGridPos(map_x, map_y) self.sun_group.add(plant.Sun(x, 0, x, y)) self.fallenSun += 1 # wcb 添加 # 检查有没有捡到阳光 clickedSun = False clickedCardsOrMap = False if not self.drag_plant and not self.drag_shovel and mouse_pos and mouse_click[0]: for sun in self.sun_group: if sun.checkCollision(mouse_pos[0], mouse_pos[1]): self.menubar.increaseSunValue(sun.sun_value) clickedSun = True # 拖动植物或者铲子 if not self.drag_plant and mouse_pos and mouse_click[0] and not clickedSun: result = self.menubar.checkCardClick(mouse_pos) if result: self.setupMouseImage(result[0], result[1]) clickedCardsOrMap = True elif self.drag_plant: if mouse_click[1]: self.removeMouseImage() clickedCardsOrMap = True elif mouse_click[0]: if self.menubar.checkMenuBarClick(mouse_pos): self.removeMouseImage() else: self.addPlant() elif mouse_pos is None: self.setupHintImage() elif self.drag_shovel: if mouse_click[1]: self.removeMouseImagePlus() # 检查是否点击菜单 if mouse_click[0] and (not clickedSun) and (not clickedCardsOrMap): if self.checkLittleMenuClick(mouse_pos): # 暂停 显示菜单 self.showLittleMenu = True elif self.checkShovelClick(mouse_pos): self.drag_shovel = not self.drag_shovel if not self.drag_shovel: self.removeMouseImagePlus() elif self.drag_shovel: # 移出这地方的植物 self.shovelRemovePlant(mouse_pos) for car in self.cars: car.update(self.game_info) self.menubar.update(self.current_time) # 检查碰撞啥的 self.checkBulletCollisions() self.checkZombieCollisions() self.checkPlants() self.checkCarCollisions() self.checkGameState() def createZombie(self, name, map_y=None): # 有指定时按照指定生成,无指定时随机位置生成 # 0:白天 1:夜晚 2:泳池 3:浓雾 4:屋顶 5:月夜 6:坚果保龄球 if map_y == None: # 情况复杂:分水路和陆路,不能简单实现,需要另外加判断 # 0, 1, 4, 5路为陆路,2, 3路为水路 if self.map_data[c.BACKGROUND_TYPE] in {c.BACKGROUND_POOL, c.BACKGROUND_FOG}: if name in {}: # 这里还没填,以后加了泳池模式填:水生僵尸集合 map_y = randint(2, 3) elif name == '这里应该换成气球僵尸的名字(最好写调用的变量名,最好不要直接写,保持风格统一)': map_y = randint(0, 5) else: # 陆生僵尸 map_y = randint(0, 3) if map_y >= 2: # 后两路的map_y应当+2 map_y += 2 elif self.map_data[c.BACKGROUND_TYPE] == c.BACKGROUND_SINGLE: map_y = 2 elif self.map_data[c.BACKGROUND_TYPE] == c.BACKGROUND_TRIPLE: map_y = randint(1, 3) else: map_y = randint(0, 4) # 新增的僵尸也需要在这里声明 x, y = self.map.getMapGridPos(0, map_y) if name == c.NORMAL_ZOMBIE: self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X, y, self.head_group)) elif name == c.CONEHEAD_ZOMBIE: self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X, y, self.head_group)) elif name == c.BUCKETHEAD_ZOMBIE: self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X, y, self.head_group)) elif name == c.FLAG_ZOMBIE: self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group)) elif name == c.NEWSPAPER_ZOMBIE: self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X, y, self.head_group)) elif name == c.FOOTBALL_ZOMBIE: self.zombie_groups[map_y].add(zombie.FootballZombie(c.ZOMBIE_START_X, y, self.head_group)) # 能否种植物的判断: # 先判断位置是否合法 isValid(map_x, map_y) # 再判断位置是否可用 isMovable(map_x, map_y) def canSeedPlant(self, plantName): x, y = pg.mouse.get_pos() return self.map.checkPlantToSeed(x, y, plantName) # 种植物 def addPlant(self): pos = self.canSeedPlant(self.plant_name) if pos is None: return if self.hint_image is None: self.setupHintImage() x, y = self.hint_rect.centerx, self.hint_rect.bottom map_x, map_y = self.map.getMapIndex(x, y) # 新植物也需要在这里声明 if self.plant_name == c.SUNFLOWER: new_plant = plant.SunFlower(x, y, self.sun_group) elif self.plant_name == c.PEASHOOTER: new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y]) elif self.plant_name == c.SNOWPEASHOOTER: new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y]) elif self.plant_name == c.WALLNUT: new_plant = plant.WallNut(x, y) elif self.plant_name == c.CHERRYBOMB: new_plant = plant.CherryBomb(x, y) elif self.plant_name == c.THREEPEASHOOTER: new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y, self.map.background_type) elif self.plant_name == c.REPEATERPEA: new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y]) elif self.plant_name == c.CHOMPER: new_plant = plant.Chomper(x, y) elif self.plant_name == c.PUFFSHROOM: new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y]) elif self.plant_name == c.POTATOMINE: new_plant = plant.PotatoMine(x, y) elif self.plant_name == c.SQUASH: new_plant = plant.Squash(x, y, self.map.map[map_y][map_x][c.MAP_PLANT]) elif self.plant_name == c.SPIKEWEED: new_plant = plant.Spikeweed(x, y) elif self.plant_name == c.JALAPENO: new_plant = plant.Jalapeno(x, y) elif self.plant_name == c.SCAREDYSHROOM: new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y]) elif self.plant_name == c.SUNSHROOM: new_plant = plant.SunShroom(x, y, self.sun_group) elif self.plant_name == c.ICESHROOM: new_plant = plant.IceShroom(x, y) elif self.plant_name == c.HYPNOSHROOM: new_plant = plant.HypnoShroom(x, y) elif self.plant_name == c.WALLNUTBOWLING: new_plant = plant.WallNutBowling(x, y, map_y, self) elif self.plant_name == c.REDWALLNUTBOWLING: new_plant = plant.RedWallNutBowling(x, y) elif self.plant_name == c.LILYPAD: new_plant = plant.LilyPad(x, y) elif self.plant_name == c.TORCHWOOD: new_plant = plant.TorchWood(x, y, self.bullet_groups[map_y]) elif self.plant_name == c.STARFRUIT: new_plant = plant.StarFruit(x, y, self.bullet_groups[map_y], self) if new_plant.can_sleep and self.background_type in {c.BACKGROUND_DAY, c.BACKGROUND_POOL, c.BACKGROUND_ROOF, c.BACKGROUND_WALLNUTBOWLING, c.BACKGROUND_SINGLE, c.BACKGROUND_TRIPLE}: new_plant.setSleep() mushroomSleep = True else: mushroomSleep = False self.plant_groups[map_y].add(new_plant) # 种植植物后应当刷新僵尸的攻击对象 self.refreshZombieAttack = True if self.bar_type == c.CHOOSEBAR_STATIC: self.menubar.decreaseSunValue(self.select_plant.sun_cost) self.menubar.setCardFrozenTime(self.plant_name) else: self.menubar.deleateCard(self.select_plant) if self.bar_type != c.CHOSSEBAR_BOWLING: self.map.addMapPlant(map_x, map_y, self.plant_name, sleep=mushroomSleep) self.removeMouseImage() #print('addPlant map[%d,%d], grid pos[%d, %d] pos[%d, %d]' % (map_x, map_y, x, y, pos[0], pos[1])) def setupHintImage(self): pos = self.canSeedPlant(self.plant_name) if pos and self.mouse_image: if (self.hint_image and pos[0] == self.hint_rect.x and pos[1] == self.hint_rect.y): return width, height = self.mouse_rect.w, self.mouse_rect.h image = pg.Surface([width, height]) image.blit(self.mouse_image, (0, 0), (0, 0, width, height)) image.set_colorkey(c.BLACK) image.set_alpha(128) self.hint_image = image self.hint_rect = image.get_rect() # 花盆、睡莲图片应当下移一些 if self.plant_name in {c.LILYPAD, '花盆(未实现)'}: self.hint_rect.centerx = pos[0] self.hint_rect.bottom = pos[1] + 25 else: self.hint_rect.centerx = pos[0] self.hint_rect.bottom = pos[1] self.hint_plant = True else: self.hint_plant = False def setupMouseImage(self, plant_name, select_plant): frame_list = tool.GFX[plant_name] if plant_name in tool.PLANT_RECT: data = tool.PLANT_RECT[plant_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 if (plant_name == c.POTATOMINE or plant_name == c.SQUASH or plant_name == c.SPIKEWEED or plant_name == c.JALAPENO or plant_name == c.SCAREDYSHROOM or plant_name == c.SUNSHROOM or plant_name == c.ICESHROOM or plant_name == c.HYPNOSHROOM or plant_name == c.WALLNUTBOWLING or plant_name == c.REDWALLNUTBOWLING): color = c.WHITE else: color = c.BLACK self.mouse_image = tool.get_image(frame_list[0], x, y, width, height, color, 1) self.mouse_rect = self.mouse_image.get_rect() self.drag_plant = True self.plant_name = plant_name self.select_plant = select_plant def removeMouseImage(self): self.drag_plant = False self.mouse_image = None self.hint_image = None self.hint_plant = False # 移除小铲子 def removeMouseImagePlus(self): self.drag_shovel = False self.shovel_rect.x = self.shovel_positon[0] self.shovel_rect.y = self.shovel_positon[1] def checkBulletCollisions(self): for i in range(self.map_y_len): for bullet in self.bullet_groups[i]: if bullet.name == c.BULLET_STAR: collided_func = pg.sprite.collide_circle_ratio(1) else: collided_func = pg.sprite.collide_circle_ratio(0.7) if bullet.state == c.FLY: zombie = pg.sprite.spritecollideany(bullet, self.zombie_groups[i], collided_func) if zombie and zombie.state != c.DIE: # 这里生效代表已经发生了碰撞 zombie.setDamage(bullet.damage, effect=bullet.effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE) bullet.setExplode() # 火球有溅射伤害 if bullet.name == c.BULLET_FIREBALL: for rangeZombie in self.zombie_groups[i]: if abs(rangeZombie.rect.x - bullet.rect.x) <= (c.GRID_X_SIZE // 2): rangeZombie.setDamage(c.BULLET_DAMAGE_FIREBALL_RANGE, effect=False, damageType=c.ZOMBIE_DEAFULT_DAMAGE) def checkZombieCollisions(self): if self.bar_type == c.CHOSSEBAR_BOWLING: ratio = 0.6 else: ratio = 0.5 collided_func = pg.sprite.collide_circle_ratio(ratio) for i in range(self.map_y_len): hypo_zombies = [] for zombie in self.zombie_groups[i]: if zombie.state != c.WALK: if not self.refreshZombieAttack: continue plant = pg.sprite.spritecollideany(zombie, self.plant_groups[i], collided_func) if plant: if plant.name == c.WALLNUTBOWLING: if plant.canHit(i): zombie.setDamage(c.WALLNUT_BOWLING_DAMAGE, damageType=c.ZOMBIE_WALLNUT_BOWLING_DANMAGE) # 注意:以上语句为通用处理,以后加入了铁门僵尸需要单独设置直接冲撞就直接杀死 # 可以给坚果保龄球设置attacked属性,如果attacked就秒杀(setDamage的攻击类型此时设置为COMMMON)铁门 plant.changeDirection(i) elif plant.name == c.REDWALLNUTBOWLING: if plant.state == c.IDLE: plant.setAttack() elif plant.name == c.SPIKEWEED: continue # 在睡莲、花盆上有植物时应当优先攻击其上的植物 elif plant.name in {c.LILYPAD, '花盆(未实现)'}: map_x, map_y = self.map.getMapIndex(plant.rect.centerx, plant.rect.bottom) if len(self.map.map[map_y][map_x][c.MAP_PLANT]) >= 2: # 这里暂时没有南瓜头优先攻击逻辑,这整个模块都没有对南瓜头的设计 for actualTargetPlant in self.plant_groups[i]: # 检测同一格的其他植物 if self.map.getMapIndex(actualTargetPlant.rect.centerx, actualTargetPlant.rect.bottom) == (map_x, map_y): if actualTargetPlant.name != plant.name: zombie.setAttack(actualTargetPlant) break else: zombie.setAttack(plant) else: zombie.setAttack(plant) for hypno_zombie in self.hypno_zombie_groups[i]: if hypno_zombie.health <= 0: continue zombie_list = pg.sprite.spritecollide(hypno_zombie, self.zombie_groups[i], False,collided_func) for zombie in zombie_list: if zombie.state == c.DIE: continue if zombie.state == c.WALK: zombie.setAttack(hypno_zombie, False) if hypno_zombie.state == c.WALK: hypno_zombie.setAttack(zombie, False) else: self.refreshZombieAttack = False # 生效后需要解除刷新设置 def checkCarCollisions(self): for car in self.cars: for zombie in self.zombie_groups[car.map_y]: if zombie and zombie.state != c.DIE and (not zombie.lostHead) and zombie.rect.x <= 0: car.setWalk() if zombie.rect.x <= car.rect.x: zombie.health = 0 zombie.kill() if car.dead: self.cars.remove(car) def boomZombies(self, x, map_y, y_range, x_range, effect=False): for i in range(self.map_y_len): if abs(i - map_y) > y_range: continue for zombie in self.zombie_groups[i]: if ((abs(zombie.rect.centerx - x) <= x_range) or ((zombie.rect.right - (x-x_range) > 20) or (zombie.rect.right - (x-x_range))/zombie.rect.width > 0.2, ((x+x_range) - zombie.rect.left > 20) or ((x+x_range) - zombie.rect.left)/zombie.rect.width > 0.2)[zombie.rect.x > x]): # 这代码不太好懂,后面是一个判断僵尸在左还是在右,前面是一个元组,[0]是在左边的情况,[1]是在右边的情况 if effect == c.BULLET_EFFECT_UNICE: zombie.ice_slow_ratio = 1 zombie.setDamage(1800, damageType=c.ZOMBIE_ASH_DAMAGE) if zombie.health <= 0: zombie.setBoomDie() def freezeZombies(self, plant): for i in range(self.map_y_len): for zombie in self.zombie_groups[i]: if zombie.rect.left <= c.SCREEN_WIDTH: zombie.setFreeze(plant.trap_frames[0]) zombie.setDamage(20, damageType=c.ZOMBIE_RANGE_DAMAGE) # 寒冰菇还有全场20的伤害 def killPlant(self, plant, shovel=False): x, y = plant.getPosition() map_x, map_y = self.map.getMapIndex(x, y) if self.bar_type != c.CHOSSEBAR_BOWLING: try: # 避免炸弹等本身就不在集合里面的问题 self.map.removeMapPlant(map_x, map_y, plant.name) except KeyError: pass # 将睡眠植物移除后更新睡眠状态 if plant.state == c.SLEEP: self.map.map[map_y][map_x][c.MAP_SLEEP] = False # 用铲子铲不用触发植物功能 if not shovel: if (plant.name == c.CHERRYBOMB or plant.name == c.REDWALLNUTBOWLING): self.boomZombies(plant.rect.centerx, map_y, plant.explode_y_range, plant.explode_x_range) elif plant.name == c.JALAPENO: self.boomZombies(plant.rect.centerx, map_y, plant.explode_y_range, plant.explode_x_range, effect=c.BULLET_EFFECT_UNICE) elif plant.name == c.ICESHROOM and plant.state != c.SLEEP: self.freezeZombies(plant) elif plant.name == c.HYPNOSHROOM and plant.state != c.SLEEP: zombie = plant.kill_zombie zombie.setHypno() _, map_y = self.map.getMapIndex(zombie.rect.centerx, zombie.rect.bottom) self.zombie_groups[map_y].remove(zombie) self.hypno_zombie_groups[map_y].add(zombie) elif (plant.name == c.POTATOMINE and not plant.is_init): # 土豆雷不是灰烬植物,不能用Boom for i in range(self.map_y_len): if abs(i - map_y) > plant.explode_y_range: continue for zombie in self.zombie_groups[i]: if ((abs(zombie.rect.centerx - x) <= plant.explode_y_range) or ((zombie.rect.right - (x-plant.explode_x_range) > 20) or (zombie.rect.right - (x-plant.explode_x_range))/zombie.rect.width > 0.2, ((x+plant.explode_x_range) - zombie.rect.left > 20) or ((x+plant.explode_x_range) - zombie.rect.left)/zombie.rect.width > 0.2)[zombie.rect.x > x]): # 这代码不太好懂,后面是一个判断僵尸在左还是在右,前面是一个元组,[0]是在左边的情况,[1]是在右边的情况 zombie.setDamage(1800, damageType=c.ZOMBIE_RANGE_DAMAGE) # 避免僵尸在用铲子移除植物后还在原位啃食 plant.health = 0 plant.kill() def checkPlant(self, plant, i): zombie_len = len(self.zombie_groups[i]) if plant.name == c.THREEPEASHOOTER: if plant.state == c.IDLE: if zombie_len > 0: plant.setAttack() elif (i-1) >= 0 and len(self.zombie_groups[i-1]) > 0: plant.setAttack() elif (i+1) < self.map_y_len and len(self.zombie_groups[i+1]) > 0: plant.setAttack() elif plant.state == c.ATTACK: if zombie_len > 0: pass elif (i-1) >= 0 and len(self.zombie_groups[i-1]) > 0: pass elif (i+1) < self.map_y_len and len(self.zombie_groups[i+1]) > 0: pass else: plant.setIdle() elif plant.name == c.CHOMPER: for zombie in self.zombie_groups[i]: if plant.canAttack(zombie): plant.setAttack(zombie, self.zombie_groups[i]) break elif plant.name == c.POTATOMINE: for zombie in self.zombie_groups[i]: if plant.canAttack(zombie): plant.setAttack() break elif plant.name == c.SQUASH: for zombie in self.zombie_groups[i]: if plant.canAttack(zombie): plant.setAttack(zombie, self.zombie_groups[i]) break elif plant.name == c.SPIKEWEED: can_attack = False for zombie in self.zombie_groups[i]: if plant.canAttack(zombie): can_attack = True break if plant.state == c.IDLE and can_attack: plant.setAttack(self.zombie_groups[i]) elif plant.state == c.ATTACK and not can_attack: plant.setIdle() elif plant.name == c.SCAREDYSHROOM: need_cry = False can_attack = False for zombie in self.zombie_groups[i]: if plant.needCry(zombie): need_cry = True break elif plant.canAttack(zombie): can_attack = True if need_cry: if plant.state != c.CRY: plant.setCry() elif can_attack: if plant.state != c.ATTACK: plant.setAttack() elif plant.state != c.IDLE: plant.setIdle() elif plant.name == c.STARFRUIT: can_attack = False if (plant.state == c.IDLE): for zombie_group in self.zombie_groups: # 遍历循环所有僵尸 for zombie in zombie_group: if plant.canAttack(zombie): can_attack = True break if plant.state == c.IDLE and can_attack: plant.setAttack() elif (plant.state == c.ATTACK and not can_attack): plant.setIdle() elif(plant.name == c.WALLNUTBOWLING or plant.name == c.REDWALLNUTBOWLING): pass else: can_attack = False if (plant.state == c.IDLE and zombie_len > 0): for zombie in self.zombie_groups[i]: if plant.canAttack(zombie): can_attack = True break if plant.state == c.IDLE and can_attack: plant.setAttack() elif (plant.state == c.ATTACK and not can_attack): plant.setIdle() def checkPlants(self): for i in range(self.map_y_len): for plant in self.plant_groups[i]: if plant.state != c.SLEEP: self.checkPlant(plant, i) if plant.health <= 0: self.killPlant(plant) def checkVictory(self): if (c.ZOMBIE_LIST in self.map_data.keys()) and self.map_data[c.SPAWN_ZOMBIES] == c.SPAWN_ZOMBIES_LIST: if len(self.zombie_list) > 0: return False for i in range(self.map_y_len): if len(self.zombie_groups[i]) > 0: return False return True else: if self.waveNum < self.map_data[c.NUM_FLAGS] * 10: return False for i in range(self.map_y_len): if len(self.zombie_groups[i]) > 0: return False return True def checkLose(self): for i in range(self.map_y_len): for zombie in self.zombie_groups[i]: if zombie.rect.right < -10 and (not zombie.lostHead): return True return False def checkGameState(self): if self.checkVictory(): if c.LITTLEGAME_BUTTON in self.game_info: self.game_info[c.LITTLEGAME_NUM] += 1 else: self.game_info[c.LEVEL_NUM] += 1 self.next = c.GAME_VICTORY self.done = True elif self.checkLose(): self.next = c.GAME_LOSE self.done = True def drawMouseShow(self, surface): if self.hint_plant: surface.blit(self.hint_image, self.hint_rect) x, y = pg.mouse.get_pos() self.mouse_rect.centerx = x self.mouse_rect.centery = y surface.blit(self.mouse_image, self.mouse_rect) def drawMouseShowPlus(self, surface): # 拖动铲子时的显示 x, y = pg.mouse.get_pos() self.shovel_rect.centerx = x self.shovel_rect.centery = y # 铲子接近植物时会高亮提示 map_x, map_y = self.map.getMapIndex(x, y) surface.blit(self.shovel, self.shovel_rect) for i in self.plant_groups[map_y]: if (x >= i.rect.x and x <= i.rect.right and y >= i.rect.y and y <= i.rect.bottom): # 优先选中睡莲、花盆上的植物 if len(self.map.map[map_y][map_x][c.MAP_PLANT]) >= 2: if c.LILYPAD in self.map.map[map_y][map_x][c.MAP_PLANT]: if i.name == c.LILYPAD: continue elif '花盆(未实现)' in self.map.map[map_y][map_x][c.MAP_PLANT]: if i.name == '花盆(未实现)': continue i.highlightTime = self.current_time return def drawZombieFreezeTrap(self, i, surface): for zombie in self.zombie_groups[i]: zombie.drawFreezeTrap(surface) def draw(self, surface): self.level.blit(self.background, self.viewport, self.viewport) surface.blit(self.level, (0,0), self.viewport) if self.state == c.CHOOSE: self.panel.draw(surface) # 以后可能需要插入一个预备的状态(预览显示僵尸、返回战场) elif self.state == c.PLAY: if self.hasShovel: # 画铲子 surface.blit(self.shovel_box, self.shovel_box_rect) surface.blit(self.shovel, self.shovel_rect) # 画小菜单 surface.blit(self.little_menu, self.little_menu_rect) self.menubar.draw(surface) for car in self.cars: car.draw(surface) for i in range(self.map_y_len): self.plant_groups[i].draw(surface) self.zombie_groups[i].draw(surface) self.hypno_zombie_groups[i].draw(surface) self.bullet_groups[i].draw(surface) self.drawZombieFreezeTrap(i, surface) self.head_group.draw(surface) self.sun_group.draw(surface) if self.drag_plant: self.drawMouseShow(surface) if self.hasShovel and self.drag_shovel: self.drawMouseShowPlus(surface) if self.showLittleMenu: surface.blit(self.big_menu, self.big_menu_rect) surface.blit(self.return_button, self.return_button_rect) surface.blit(self.restart_button, self.restart_button_rect) surface.blit(self.mainMenu_button, self.mainMenu_button_rect)