From b281094b44d7899821cb9eef6b4f228e040f714a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E5=A4=96=E4=B9=8B=E7=A5=9E?= Date: Thu, 26 May 2022 23:51:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=BD=9C=E6=B0=B4=E5=83=B5?= =?UTF-8?q?=E5=B0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- resources/data/map/level_0.json | 3 +- source/component/plant.py | 18 +++++- source/component/zombie.py | 104 +++++++++++++++++++++++++++++++- source/state/level.py | 6 +- 5 files changed, 124 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3aa836d..f36d28e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **本项目为个人python语言学习的练习项目,仅供个人学习和研究使用,不得用于其他用途。如果这个游戏侵犯了版权,请联系我删除** * 已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇 -* 已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸 +* 已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸,潜水僵尸 * 使用 JSON 文件记录关卡信息数据 * 支持选择植物卡片 * 支持白昼模式,夜晚模式,泳池模式,传送带模式和坚果保龄球模式 diff --git a/resources/data/map/level_0.json b/resources/data/map/level_0.json index d762f2d..5dfd1e8 100644 --- a/resources/data/map/level_0.json +++ b/resources/data/map/level_0.json @@ -16,7 +16,8 @@ {"time":0, "map_y":1, "name":"NewspaperZombie"}, {"time":0, "map_y":0, "name":"PoleVaultingZombie"}, {"time":6000, "map_y":0, "name":"FootballZombie"}, - {"time":0, "map_y":2, "name":"ConeheadDuckyTubeZombie"}, + {"time":0, "map_y":3, "name":"ConeheadDuckyTubeZombie"}, + {"time":0, "map_y":2, "name":"SnorkelZombie"}, {"time":90000, "map_y":2, "name":"ConeheadDuckyTubeZombie"} ] } diff --git a/source/component/plant.py b/source/component/plant.py index 427a457..8e9a31f 100755 --- a/source/component/plant.py +++ b/source/component/plant.py @@ -320,9 +320,11 @@ class Plant(pg.sprite.Sprite): self.image.set_alpha(255) def canAttack(self, zombie): + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + return False if (self.state != c.SLEEP and zombie.state != c.DIE and - self.rect.x <= zombie.rect.right and zombie.rect.left <= c.SCREEN_WIDTH): - return True + self.rect.x <= zombie.rect.right and zombie.rect.left <= c.SCREEN_WIDTH): + return True return False def setAttack(self): @@ -620,6 +622,8 @@ class Chomper(Plant): def canAttack(self, zombie): if (zombie.name in {c.POLE_VAULTING_ZOMBIE}) and (not zombie.jumped): return False + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + return False elif (self.state == c.IDLE and zombie.state != c.DIGEST and self.rect.x <= zombie.rect.centerx and (not zombie.lostHead) and (self.rect.x + c.GRID_X_SIZE*2.7 >= zombie.rect.centerx)): @@ -694,6 +698,8 @@ class PuffShroom(Plant): pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "puff.ogg")).play() def canAttack(self, zombie): + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + return False if (self.rect.x <= zombie.rect.right and (self.rect.x + c.GRID_X_SIZE * 4 >= zombie.rect.x) and (zombie.rect.left <= c.SCREEN_WIDTH + 10)): return True @@ -741,7 +747,7 @@ class PotatoMine(Plant): self.changeFrames(self.idle_frames) self.is_init = False - def canAttack(self, zombie): + def canAttack(self, zombie): # 土豆雷不可能遇上潜水僵尸 if (zombie.name == c.POLE_VAULTING_ZOMBIE and (not zombie.jumped)): return False # 这里碰撞应当比碰撞一般更容易,就设置成圆形或矩形模式,不宜采用mask @@ -1310,6 +1316,8 @@ class StarFruit(Plant): self.map_x, self.map_y = self.level.map.getMapIndex(x, y) def canAttack(self, zombie): + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + return False if zombie.state != c.DIE: _, zombieMapY = self.level.map.getMapIndex(zombie.rect.centerx, zombie.rect.bottom) if (self.rect.x >= zombie.rect.x) and (self.map_y == zombieMapY): # 对于同行且在杨桃后的僵尸 @@ -1418,6 +1426,8 @@ class SeaShroom(Plant): pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "puff.ogg")).play() def canAttack(self, zombie): + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + return False if (self.rect.x <= zombie.rect.right and (self.rect.x + c.GRID_X_SIZE * 4 >= zombie.rect.x) and (zombie.rect.left <= c.SCREEN_WIDTH + 10)): return True @@ -1707,6 +1717,8 @@ class FumeShroom(Plant): self.frames = self.idle_frames def canAttack(self, zombie): + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + return False if (self.rect.x <= zombie.rect.right and (self.rect.x + c.GRID_X_SIZE * 5 >= zombie.rect.x) and (zombie.rect.left <= c.SCREEN_WIDTH + 10)): return True diff --git a/source/component/zombie.py b/source/component/zombie.py index 7356221..3e44fe4 100755 --- a/source/component/zombie.py +++ b/source/component/zombie.py @@ -210,7 +210,8 @@ class Zombie(pg.sprite.Sprite): self.helmetType2 = False if self.name == c.NEWSPAPER_ZOMBIE: self.speed = 2.5 - if ((self.current_time - self.attack_timer) > (c.ATTACK_INTERVAL * self.getAttackTimeRatio())) and (not self.lostHead): + if (((self.current_time - self.attack_timer) > (c.ATTACK_INTERVAL * self.getAttackTimeRatio())) + and (not self.lostHead)): if self.prey.health > 0: if self.prey_is_plant: self.prey.setDamage(self.damage, self) @@ -1093,6 +1094,8 @@ class SnorkelZombie(Zombie): def __init__(self, x, y, head_group): Zombie.__init__(self, x, y, c.SNORKELZOMBIE, canSwim=True) self.speed = 1.175 + self.walk_animate_interval = 60 + self.canSetAttack = True def loadImages(self): self.walk_frames = [] @@ -1100,7 +1103,7 @@ class SnorkelZombie(Zombie): self.attack_frames = [] self.jump_frames = [] self.float_frames = [] - self.sink_frame = [] + self.sink_frames = [] self.losthead_walk_frames = [] self.losthead_attack_frames = [] self.die_frames = [] @@ -1115,4 +1118,99 @@ class SnorkelZombie(Zombie): losthead_walk_name = self.name + 'LostHead' losthead_attack_name = self.name + 'LostHeadAttack' die_name = self.name + 'Die' - self.boomdie_name = c.BOOMDIE + boomdie_name = c.BOOMDIE + + frame_list = [ self.walk_frames, self.swim_frames, + self.attack_frames, self.jump_frames, + self.float_frames, self.sink_frames, + self.losthead_walk_frames, self.losthead_attack_frames, + self.die_frames, self.boomdie_frames] + name_list = [ walk_name, swim_name, + attack_name, jump_name, + float_name, sink_name, + losthead_walk_name, losthead_attack_name, + die_name, boomdie_name] + + for i, name in enumerate(name_list): + self.loadFrames(frame_list[i], name) + + self.frames = self.walk_frames + + def walking(self): + # 在水池范围内 + # 在右侧岸左 + if self.rect.centerx <= c.MAP_POOL_FRONT_X - 25: + # 在左侧岸右,左侧岸位置为预估 + if self.rect.right - 25 >= c.MAP_POOL_OFFSET_X: + # 还未进入游泳状态 + if not self.swimming: + self.swimming = True + self.changeFrames(self.jump_frames) + # 播放入水音效 + pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "zombieEnteringWater.ogg")).play() + # 已经接近家门口并且上岸 + else: + if self.swimming: + self.changeFrames(self.walk_frames) + self.swimming = False + # 被魅惑时走到岸上需要起立 + elif self.is_hypno and (self.rect.right > c.MAP_POOL_FRONT_X + 55): # 常数拟合暂时缺乏检验 + if self.swimming: + self.changeFrames(self.walk_frames) + self.swimming = False + if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()): + self.walk_timer = self.current_time + # 正在上浮或者下潜不用移动 + if (self.frames == self.float_frames) or (self.frames == self.sink_frames): + pass + elif self.is_hypno: + self.rect.x += 1 + else: + self.rect.x -= 1 + + def animation(self): + if self.state == c.FREEZE: + self.image.set_alpha(192) + return + + if (self.current_time - self.animate_timer) > (self.animate_interval * self.getTimeRatio()): + self.frame_index += 1 + if self.frame_index >= self.frame_num: + if self.state == c.DIE: + self.kill() + return + elif (self.frames == self.jump_frames): + self.changeFrames(self.swim_frames) + elif (self.frames == self.sink_frames): + self.changeFrames(self.swim_frames) + # 还需要改回原来的可进入攻击状态的设定 + self.canSetAttack = True + elif self.frames == self.float_frames: + self.state = c.ATTACK + self.attack_timer = self.current_time + self.changeFrames(self.attack_frames) + self.frame_index = 0 + self.animate_timer = self.current_time + + self.image = self.frames[self.frame_index] + if self.is_hypno: + self.image = pg.transform.flip(self.image, True, False) + self.mask = pg.mask.from_surface(self.image) + + if (self.current_time - self.hit_timer) >= 200: + self.image.set_alpha(255) + else: + self.image.set_alpha(192) + + # 注意潜水僵尸较为特殊:这里的setAttack并没有直接触发攻击状态,而是触发从水面浮起 + def setAttack(self, prey, is_plant=True): + self.prey = prey # prey can be plant or other zombies + self.prey_is_plant = is_plant + self.animate_interval = self.attack_animate_interval + + if self.lostHead: + self.changeFrames(self.losthead_attack_frames) + elif self.canSetAttack: + self.changeFrames(self.float_frames) + self.canSetAttack = False + diff --git a/source/state/level.py b/source/state/level.py index 6ed1349..6a4a20d 100644 --- a/source/state/level.py +++ b/source/state/level.py @@ -41,7 +41,7 @@ class Level(tool.State): f = open(file_path) self.map_data = json.load(f) f.close() - finally: + except Exception: print("成功通关!") if self.game_info[c.GAME_MODE] == c.MODE_LITTLEGAME: self.game_info[c.LEVEL_NUM] = c.START_LEVEL_NUM @@ -826,6 +826,8 @@ class Level(tool.State): elif name == c.ZOMBONI: # 冰车僵尸生成位置不同 self.zombie_groups[map_y].add(zombie.Zomboni(c.ZOMBIE_START_X + randint(0, 10) + hugeWaveMove, y, self.plant_groups[map_y], self.map, plant.IceFrozenPlot)) + elif name == c.SNORKELZOMBIE: + self.zombie_groups[map_y].add(zombie.SnorkelZombie(c.ZOMBIE_START_X + randint(0, 10) + hugeWaveMove, y, self.head_group)) # 能否种植物的判断: # 先判断位置是否合法 isValid(map_x, map_y) @@ -999,6 +1001,8 @@ class Level(tool.State): if bullet.state == c.FLY: # 利用循环而非内建精灵组碰撞判断函数,处理更加灵活,可排除已死亡僵尸 for zombie in self.zombie_groups[i]: + if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): + continue if collided_func(zombie, bullet): if zombie.state != c.DIE: zombie.setDamage(bullet.damage, effect=bullet.effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)