Merge branch 'master' into opengl

This commit is contained in:
星外之神 2022-06-12 16:27:35 +08:00
commit 8b026f5b84
12 changed files with 214 additions and 207 deletions

5
.gitignore vendored
View File

@ -1,8 +1,9 @@
# ignore debug
# 忽略构建内容
out/
build/
# 忽略调试内容
.vscode/
__pycache__/
*/__pycache__/
# ignore test
# 忽略测试文件
test.py

View File

@ -4,7 +4,7 @@
**本项目为个人python语言学习的练习项目仅供个人学习和研究使用不得用于其他用途。如果这个游戏侵犯了版权请联系我删除**
* 已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇
* 已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇,大蒜
* 已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸,潜水僵尸
* 使用 JSON 文件记录关卡信息数据
* 支持选择植物卡片
@ -21,7 +21,7 @@
## 环境要求
* `Python` >= 3.7,最好使用最新版
* `Python` >= 3.10,最好使用最新版
* `Python-Pygame` >= 1.9,最好使用最新版
## 开始游戏
@ -44,7 +44,6 @@ python main.py
- 程序包含名称、版本等信息
- 得到的验证最多(相对)
- 并非每次提交都会更新,更新可能不及时
- `0.7.25.0`之后的部分版本被标注成了`pre-release`,实际上仍然为普通版本,按需下载即可
- 也可以直接下载GitHub Workflow[自动利用Nuitka构建的版本点击跳转](https://github.com/wszqkzqk/pypvz/releases/tag/Latest)(推荐):
- 使用MSVC编译
- 每次提交均会更新,保证更新及时
@ -111,6 +110,7 @@ nuitka --mingw64 --standalone `
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll `
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
--lto=yes `
--windows-disable-console `
--windows-product-name=pypvz `
--windows-company-name=null `
@ -122,6 +122,7 @@ nuitka --mingw64 --standalone `
* 其中`C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\xxx.dll`应当替换为`xxx.dll`实际所在路径
* 由于仅复制了`opus``vorbis`的解码器故要求所有背景音乐都要以opus或vorbis编码
* `--windows-product-version=`表示版本号信息,所跟内容格式必须为`x.x.x.x`
* 建议开启`--lto=yes`选项优化链接,如果编译失败可以关闭此选项
可执行文件生成路径为`./out/main.exe`

View File

@ -2,38 +2,36 @@ import random
import pygame as pg
from .. import tool
from .. import constants as c
from copy import deepcopy
class Map():
def __init__(self, background_type):
self.background_type = background_type
# 注意从0开始编号
# 集合内容需要deepcopy
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
self.width = c.GRID_POOL_X_LEN
self.height = c.GRID_POOL_Y_LEN
self.gridHeightSize = c.GRID_POOL_Y_SIZE
self.map = [[(deepcopy(c.MAP_STATE_EMPTY), deepcopy(c.MAP_STATE_WATER))[y in {2, 3}] for x in range(self.width)] for y in range(self.height)]
self.map = [[(c.INIT_MAP_GRID(c.MAP_GRASS), c.INIT_MAP_GRID(c.MAP_WATER))[y in {2, 3}] 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.gridHeightSize = c.GRID_ROOF_Y_SIZE
self.map = [[deepcopy(c.MAP_STATE_TILE) for x in range(self.width)] for y in range(self.height)]
self.map = [[c.INIT_MAP_GRID(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.gridHeightSize = c.GRID_Y_SIZE
self.map = [[(deepcopy(c.MAP_STATE_UNAVAILABLE), deepcopy(c.MAP_STATE_EMPTY))[y == 2] for x in range(self.width)] for y in range(self.height)]
self.map = [[(c.INIT_MAP_GRID(c.MAP_UNAVAILABLE), c.INIT_MAP_GRID(c.MAP_GRASS))[y == 2] 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.gridHeightSize = c.GRID_Y_SIZE
self.map = [[(deepcopy(c.MAP_STATE_UNAVAILABLE), deepcopy(c.MAP_STATE_EMPTY))[y in {1, 2, 3}] for x in range(self.width)] for y in range(self.height)]
self.map = [[(c.INIT_MAP_GRID(c.MAP_UNAVAILABLE), c.INIT_MAP_GRID(c.MAP_GRASS))[y in {1, 2, 3}] 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.gridHeightSize = c.GRID_Y_SIZE
self.map = [[deepcopy(c.MAP_STATE_EMPTY) for x in range(self.width)] for y in range(self.height)]
self.map = [[c.INIT_MAP_GRID(c.MAP_GRASS) for x in range(self.width)] for y in range(self.height)]
def isValid(self, map_x, map_y):
if (map_x < 0 or map_x >= self.width or
@ -139,7 +137,7 @@ class Map():
map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)
def setMapGridType(self, map_x, map_y, plot_type):
self.map[map_y][map_x] = plot_type
self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type
def addMapPlant(self, map_x, map_y, plantName, sleep=False):
self.map[map_y][map_x][c.MAP_PLANT].add(plantName)

View File

@ -38,7 +38,7 @@ class Car(pg.sprite.Sprite):
# 豌豆及孢子类普通子弹
class Bullet(pg.sprite.Sprite):
def __init__(self, x, start_y, dest_y, name, damage, effect=None, passedTorchWood=None):
def __init__(self, x, start_y, dest_y, name, damage, effect=None, passedTorchWood=None, damageType=c.ZOMBIE_DEAFULT_DAMAGE):
pg.sprite.Sprite.__init__(self)
self.name = name
@ -55,6 +55,7 @@ class Bullet(pg.sprite.Sprite):
self.y_vel = 15 if (dest_y > start_y) else -15
self.x_vel = 10
self.damage = damage
self.damageType = damageType
self.effect = effect
self.state = c.FLY
self.current_time = 0
@ -182,11 +183,11 @@ class Fume(pg.sprite.Sprite):
# 杨桃的子弹
class StarBullet(Bullet):
def __init__(self, x, start_y, damage, direction, level): # direction指星星飞行方向
Bullet.__init__(self, x, start_y, start_y, c.BULLET_STAR, damage)
def __init__(self, x, start_y, damage, direction, level, damageType = c.ZOMBIE_DEAFULT_DAMAGE): # direction指星星飞行方向
Bullet.__init__(self, x, start_y, start_y, c.BULLET_STAR, damage, damageType = damageType)
self.level = level
_, self.map_y = self.level.map.getMapIndex(self.rect.x, self.rect.centery)
self.map_y = self.level.map.getMapIndex(self.rect.x, self.rect.centery)[1]
self.direction = direction
def update(self, game_info):
@ -206,20 +207,19 @@ class StarBullet(Bullet):
else:
self.rect.x -= 10
self.handleMapYPosition()
if ((self.rect.x > c.SCREEN_WIDTH + 60) or (self.rect.x < -40)
if ((self.rect.x > c.SCREEN_WIDTH + 60) or (self.rect.x < -60)
or (self.rect.y > c.SCREEN_HEIGHT) or (self.rect.y < 0)):
self.kill()
elif self.state == c.EXPLODE:
if (self.current_time - self.explode_timer) > 250:
if (self.current_time - self.explode_timer) >= 250:
self.kill()
# 这里用的是坚果保龄球的代码改一下,实现子弹换行
def handleMapYPosition(self):
if self.direction == c.STAR_UPWARD:
_, map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 40)
map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 40)[1]
else:
_, map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 20)
# _, map_y2 = self.level.map.getMapIndex(self.rect.x, self.rect.bottom +20)
map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 20)[1]
if (self.map_y != map_y1) and (0 <= map_y1 <= self.level.map_y_len-1): # 换行
self.level.bullet_groups[self.map_y].remove(self)
self.level.bullet_groups[map_y1].add(self)
@ -1160,7 +1160,7 @@ class WallNutBowling(Plant):
self.handleMapYPosition()
if self.shouldChangeDirection():
self.changeDirection(-1)
if self.init_rect.x > c.SCREEN_WIDTH:
if self.init_rect.x > c.SCREEN_WIDTH + 60:
self.health = 0
self.move_timer += self.move_interval
@ -1170,8 +1170,8 @@ class WallNutBowling(Plant):
return True
def handleMapYPosition(self):
_, map_y1 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.centery)
_, map_y2 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.bottom)
map_y1 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.centery)[1]
map_y2 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.bottom)[1]
if self.map_y != map_y1 and map_y1 == map_y2:
# wallnut bowls to another row, should modify which plant group it belongs to
self.level.plant_groups[self.map_y].remove(self)
@ -1252,7 +1252,7 @@ class RedWallNutBowling(Plant):
elif (self.current_time - self.move_timer) >= self.move_interval:
self.rotate_degree = (self.rotate_degree - 30) % 360
self.init_rect.x += self.vel_x
if self.init_rect.x > c.SCREEN_WIDTH:
if self.init_rect.x > c.SCREEN_WIDTH + 60:
self.health = 0
self.move_timer += self.move_interval
@ -1319,7 +1319,7 @@ class StarFruit(Plant):
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)
zombieMapY = self.level.map.getMapIndex(zombie.rect.centerx, zombie.rect.bottom)[1]
if (self.rect.x >= zombie.rect.x) and (self.map_y == zombieMapY): # 对于同行且在杨桃后的僵尸
return True
# 斜向上理想直线方程为f(zombie.rect.x) = -0.75*(zombie.rect.x - (self.rect.right - 5)) + self.rect.y - 10
@ -1338,7 +1338,9 @@ class StarFruit(Plant):
if self.shoot_timer == 0:
self.shoot_timer = self.current_time - 700
elif (self.current_time - self.shoot_timer) >= 1400:
self.bullet_group.add(StarBullet(self.rect.left - 10, self.rect.y + 15, c.BULLET_DAMAGE_NORMAL, c.STAR_BACKWARD, self.level))
# 向后打的杨桃子弹无视铁门与报纸防具
self.bullet_group.add(StarBullet(self.rect.left - 10, self.rect.y + 15, c.BULLET_DAMAGE_NORMAL, c.STAR_BACKWARD, self.level, damageType = c.ZOMBIE_COMMON_DAMAGE))
# 其他方向的杨桃子弹伤害效果与豌豆等同
self.bullet_group.add(StarBullet(self.rect.centerx - 20, self.rect.bottom - self.rect.h - 15, c.BULLET_DAMAGE_NORMAL, c.STAR_UPWARD, self.level))
self.bullet_group.add(StarBullet(self.rect.centerx - 20, self.rect.bottom - 5, c.BULLET_DAMAGE_NORMAL, c.STAR_DOWNWARD, self.level))
self.bullet_group.add(StarBullet(self.rect.right - 5, self.rect.bottom - 20, c.BULLET_DAMAGE_NORMAL, c.STAR_FORWARD_DOWN, self.level))
@ -1589,6 +1591,7 @@ class DoomShroom(Plant):
else:
self.image.set_alpha(255)
# 用于描述毁灭菇的坑
class Hole(Plant):
def __init__(self, x, y, plotType):
@ -1770,7 +1773,7 @@ class FumeShroom(Plant):
class IceFrozenPlot(Plant):
def __init__(self, x, y):
Plant.__init__(self, x, y, c.ICE_FROZEN_PLOT, c.INF, None)
Plant.__init__(self, x, y, c.ICEFROZENPLOT, c.INF, None)
self.timer = 0
def idling(self):

View File

@ -196,8 +196,8 @@ class Zombie(pg.sprite.Sprite):
self.changeFrames(self.walk_frames)
self.helmetType2 = False
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())
and self.handleGarlicYChange()):
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
@ -206,7 +206,6 @@ class Zombie(pg.sprite.Sprite):
def handleGarlicYChange(self):
if self.targetYChange < 0:
self.setWalk()
if self.rect.bottom > self.originalY + self.targetYChange: # 注意这里加的是负数
self.rect.bottom -= 3
# 过半时换行
@ -214,12 +213,12 @@ class Zombie(pg.sprite.Sprite):
(self.rect.bottom >= self.originalY + 0.5*self.targetYChange)):
self.level.zombie_groups[self.mapY].remove(self)
self.level.zombie_groups[self.targetMapY].add(self)
self.toChangeGroup = False
else:
self.rect.bottom = self.originalY + self.targetYChange
self.originalY = self.rect.bottom
self.targetYChange = 0
return None
elif self.targetYChange > 0:
self.setWalk()
if self.rect.bottom < self.originalY + self.targetYChange: # 注意这里加的是负数
self.rect.bottom += 3
# 过半时换行
@ -227,12 +226,11 @@ class Zombie(pg.sprite.Sprite):
(self.rect.bottom <= self.originalY + 0.5*self.targetYChange)):
self.level.zombie_groups[self.mapY].remove(self)
self.level.zombie_groups[self.targetMapY].add(self)
self.toChangeGroup = False
else:
self.rect.bottom = self.originalY + self.targetYChange
self.originalY = self.rect.bottom
self.targetYChange = 0
return None
else:
return True
def attacking(self):
if self.checkToDie(self.losthead_attack_frames):
@ -435,7 +433,7 @@ class Zombie(pg.sprite.Sprite):
self.health -= damage
else:
print('警告:植物攻击类型错误,现在默认进行类豌豆射手型攻击')
setDamage(damage, effect=effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)
self.setDamage(damage, effect=effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)
# 记录攻击时间
self.hit_timer = self.current_time
@ -715,8 +713,8 @@ class NewspaperZombie(Zombie):
self.helmetType2 = False
# 触发报纸撕裂音效
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "newspaperRip.ogg")).play()
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())
and self.handleGarlicYChange()):
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())):
self.handleGarlicYChange()
self.walk_timer = self.current_time
if self.frames == self.lostnewspaper_frames:
pass
@ -1116,10 +1114,10 @@ class Zomboni(Zombie):
# 造冰
mapX, mapY = self.map.getMapIndex(self.rect.right - 40, self.rect.bottom)
if 0 <= mapX < c.GRID_X_LEN:
if c.ICE_FROZEN_PLOT not in self.map.map[mapY][mapX]:
if c.ICEFROZENPLOT not in self.map.map[mapY][mapX]:
x, y = self.map.getMapGridPos(mapX, mapY)
self.plant_group.add(self.IceFrozenPlot(x, y))
self.map.map[mapY][mapX][c.MAP_PLANT].add(c.ICE_FROZEN_PLOT)
self.map.map[mapY][mapX][c.MAP_PLANT].add(c.ICEFROZENPLOT)
self.speed = max(0.6, 1.5 - (c.GRID_X_LEN + 1 - mapX)*0.225)
@ -1178,6 +1176,9 @@ class SnorkelZombie(Zombie):
self.frames = self.walk_frames
def walking(self):
if self.checkToDie(self.losthead_walk_frames):
return
# 在水池范围内
# 在右侧岸左
if self.rect.centerx <= c.MAP_POOL_FRONT_X - 25:
@ -1199,8 +1200,8 @@ class SnorkelZombie(Zombie):
if self.swimming:
self.changeFrames(self.walk_frames)
self.swimming = False
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())
and self.handleGarlicYChange()):
if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
self.handleGarlicYChange()
self.walk_timer = self.current_time
# 正在上浮或者下潜不用移动
if (self.frames == self.float_frames) or (self.frames == self.sink_frames):

View File

@ -136,7 +136,7 @@ BACKGROUND_DAY_LIKE_BACKGROUNDS = {
# 夜晚地图的墓碑数量等级
GRADE_GRAVES = 'grade_graves'
# 不同墓碑等级对应的信息
# 不同墓碑等级对应的信息,列表位置对应的是墓碑等级
GRAVES_GRADE_INFO = (0, 4, 7, 11)
# 僵尸生成方式
@ -158,10 +158,11 @@ MAP_WATER = 'water'
MAP_TILE = 'tile' # 指屋顶上的瓦片
MAP_UNAVAILABLE = 'unavailable' # 指完全不能种植物的地方,包括无草皮的荒地和坚果保龄球等红线右侧
# 地图单元格状态
MAP_STATE_EMPTY = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_GRASS} # 由于同一格显然不可能种两个相同的植物,所以用集合
MAP_STATE_WATER = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_WATER}
MAP_STATE_TILE = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_TILE}
MAP_STATE_UNAVAILABLE = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_UNAVAILABLE}
# 注意是可变对象,不能直接引用
# 不喜欢用深拷贝,直接改用函数表示,需要时直接调用该函数生成即可
# 由于同一格显然不可能种两个相同的植物,所以用集合
def INIT_MAP_GRID(PLOT_TYPE):
return {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:PLOT_TYPE}
# 地图相关像素数据
BACKGROUND_OFFSET_X = 220
@ -193,7 +194,7 @@ PANEL_Y_INTERNAL = 69
PANEL_X_INTERNAL = 53
BAR_CARD_X_INTERNAL = 51
# 所选植物信息索引
# 植物卡片信息索引
PLANT_NAME_INDEX = 0
CARD_INDEX = 1
SUN_INDEX = 2
@ -237,7 +238,7 @@ SEASHROOM = 'SeaShroom'
TALLNUT = 'TallNut'
TANGLEKLEP = 'TangleKlep'
DOOMSHROOM = 'DoomShroom'
ICE_FROZEN_PLOT = 'IceFrozenPlot'
ICEFROZENPLOT = 'IceFrozenPlot'
HOLE = 'Hole'
GRAVE = 'Grave'
GRAVEBUSTER = 'GraveBuster'
@ -246,9 +247,9 @@ GARLIC = 'Garlic'
# 植物集体属性集合
# 在生效时不用与僵尸进行碰撞检测的对象
# 在生效时不用与僵尸进行碰撞检测的对象(即生效时不可发生被僵尸啃食的事件)
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
# 注意爆炸坚果的触发也是啃食类碰撞,因此这里不能省略
# 注意爆炸坚果的触发也是啃食类碰撞,因此只能算作爆炸后不检测
SQUASH, ICESHROOM,
REDWALLNUTBOWLING, CHERRYBOMB,
JALAPENO, DOOMSHROOM,
@ -257,12 +258,15 @@ SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
# 非植物对象
NON_PLANT_OBJECTS = {
HOLE, ICE_FROZEN_PLOT,
HOLE, ICEFROZENPLOT,
GRAVE,
}
# 所有可能不用与僵尸进行碰撞检测的对象
CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
# 注意这个外围的小括号是用来换行的
# 各个部分末!尾!千!万!不!能!加!逗!号!!!
# 生效时不检测的植物
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING |
# 非植物对象
@ -274,7 +278,7 @@ CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
# 死亡时不触发音效的对象
PLANT_DIE_SOUND_EXCEPTIONS = {
WALLNUTBOWLING, TANGLEKLEP,
ICE_FROZEN_PLOT, HOLE,
ICEFROZENPLOT, HOLE,
GRAVE, JALAPENO,
REDWALLNUTBOWLING, CHERRYBOMB,
}
@ -635,4 +639,4 @@ CHOOSE = 'choose'
PLAY = 'play'
# 无穷大常量
INF = float('inf')
INF = float("inf") # python传递字符串性能较低故在这里对inf声明一次以后仅需调用即可

View File

@ -93,15 +93,11 @@ class Level(tool.State):
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())
# 改用列表生成器直接生成内容不再在这里使用for循环
self.plant_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
self.zombie_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
self.hypno_zombie_groups = [pg.sprite.Group() for i in range(self.map_y_len)] #zombies who are hypno after eating hypnoshroom
self.bullet_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
# 按照规则生成每一波僵尸
@ -189,7 +185,7 @@ class Level(tool.State):
unoccupied = []
occupied = []
# 毁灭菇坑与冰道应当特殊化
exceptionObjects = {c.HOLE, c.ICE_FROZEN_PLOT}
exceptionObjects = {c.HOLE, c.ICEFROZENPLOT}
# 遍历能生成墓碑的区域
for mapY in range(0, 4):
for mapX in range(4, 8):
@ -320,7 +316,7 @@ class Level(tool.State):
def setupCars(self):
self.cars = []
for i in range(self.map_y_len):
_, y = self.map.getMapGridPos(0, i)
y = self.map.getMapGridPos(0, i)[1]
self.cars.append(plant.Car(-40, y+20, i))
# 更新函数每帧被调用,将鼠标事件传入给状态处理函数
@ -344,7 +340,7 @@ class Level(tool.State):
def initBowlingMap(self):
for x in range(3, self.map.width):
for y in range(self.map.height):
self.map.setMapGridType(x, y, c.MAP_STATE_UNAVAILABLE) # 将坚果保龄球红线右侧设置为不可种植任何植物
self.map.setMapGridType(x, y, c.MAP_UNAVAILABLE) # 将坚果保龄球红线右侧设置为不可种植任何植物
def initState(self):
if c.CHOOSEBAR_TYPE in self.map_data:
@ -804,34 +800,34 @@ class Level(tool.State):
x, y = self.map.getMapGridPos(0, map_y)
# 新增的僵尸也需要在这里声明
if name == c.NORMAL_ZOMBIE:
match name:
case c.NORMAL_ZOMBIE:
self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.CONEHEAD_ZOMBIE:
case c.CONEHEAD_ZOMBIE:
self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.BUCKETHEAD_ZOMBIE:
case c.BUCKETHEAD_ZOMBIE:
self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.FLAG_ZOMBIE:
case c.FLAG_ZOMBIE:
self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group))
elif name == c.NEWSPAPER_ZOMBIE:
case c.NEWSPAPER_ZOMBIE:
self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.FOOTBALL_ZOMBIE:
case c.FOOTBALL_ZOMBIE:
self.zombie_groups[map_y].add(zombie.FootballZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.DUCKY_TUBE_ZOMBIE:
case c.DUCKY_TUBE_ZOMBIE:
self.zombie_groups[map_y].add(zombie.DuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.CONEHEAD_DUCKY_TUBE_ZOMBIE:
case c.CONEHEAD_DUCKY_TUBE_ZOMBIE:
self.zombie_groups[map_y].add(zombie.ConeHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.BUCKETHEAD_DUCKY_TUBE_ZOMBIE:
case c.BUCKETHEAD_DUCKY_TUBE_ZOMBIE:
self.zombie_groups[map_y].add(zombie.BucketHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.SCREEN_DOOR_ZOMBIE:
case c.SCREEN_DOOR_ZOMBIE:
self.zombie_groups[map_y].add(zombie.ScreenDoorZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
elif name == c.POLE_VAULTING_ZOMBIE:
case c.POLE_VAULTING_ZOMBIE:
# 本来撑杆跳生成位置不同对齐左端可认为修正了一部分看作移动了70只需要相对修改即可
self.zombie_groups[map_y].add(zombie.PoleVaultingZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
elif name == c.ZOMBONI:
case c.ZOMBONI:
# 冰车僵尸生成位置不同
self.zombie_groups[map_y].add(zombie.Zomboni(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.plant_groups[map_y], self.map, plant.IceFrozenPlot))
elif name == c.SNORKELZOMBIE:
# 潜水僵尸生成位置不同
case c.SNORKELZOMBIE:
self.zombie_groups[map_y].add(zombie.SnorkelZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
# 能否种植物的判断:
@ -856,69 +852,69 @@ class Level(tool.State):
map_x, map_y = self.map.getMapIndex(x, y)
# 新植物也需要在这里声明
if self.plant_name == c.SUNFLOWER:
match self.plant_name:
case c.SUNFLOWER:
new_plant = plant.SunFlower(x, y, self.sun_group)
elif self.plant_name == c.PEASHOOTER:
case c.PEASHOOTER:
new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.SNOWPEASHOOTER:
case c.SNOWPEASHOOTER:
new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.WALLNUT:
case c.WALLNUT:
new_plant = plant.WallNut(x, y)
elif self.plant_name == c.CHERRYBOMB:
case c.CHERRYBOMB:
new_plant = plant.CherryBomb(x, y)
elif self.plant_name == c.THREEPEASHOOTER:
case c.THREEPEASHOOTER:
new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y, self.map.background_type)
elif self.plant_name == c.REPEATERPEA:
case c.REPEATERPEA:
new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.CHOMPER:
case c.CHOMPER:
new_plant = plant.Chomper(x, y)
elif self.plant_name == c.PUFFSHROOM:
case c.PUFFSHROOM:
new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.POTATOMINE:
case c.POTATOMINE:
new_plant = plant.PotatoMine(x, y)
elif self.plant_name == c.SQUASH:
case c.SQUASH:
new_plant = plant.Squash(x, y, self.map.map[map_y][map_x][c.MAP_PLANT])
elif self.plant_name == c.SPIKEWEED:
case c.SPIKEWEED:
new_plant = plant.Spikeweed(x, y)
elif self.plant_name == c.JALAPENO:
case c.JALAPENO:
new_plant = plant.Jalapeno(x, y)
elif self.plant_name == c.SCAREDYSHROOM:
case c.SCAREDYSHROOM:
new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.SUNSHROOM:
case c.SUNSHROOM:
new_plant = plant.SunShroom(x, y, self.sun_group)
elif self.plant_name == c.ICESHROOM:
case c.ICESHROOM:
new_plant = plant.IceShroom(x, y)
elif self.plant_name == c.HYPNOSHROOM:
case c.HYPNOSHROOM:
new_plant = plant.HypnoShroom(x, y)
elif self.plant_name == c.WALLNUTBOWLING:
case c.WALLNUTBOWLING:
new_plant = plant.WallNutBowling(x, y, map_y, self)
elif self.plant_name == c.REDWALLNUTBOWLING:
case c.REDWALLNUTBOWLING:
new_plant = plant.RedWallNutBowling(x, y)
elif self.plant_name == c.LILYPAD:
case c.LILYPAD:
new_plant = plant.LilyPad(x, y)
elif self.plant_name == c.TORCHWOOD:
case c.TORCHWOOD:
new_plant = plant.TorchWood(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.STARFRUIT:
case c.STARFRUIT:
new_plant = plant.StarFruit(x, y, self.bullet_groups[map_y], self)
elif self.plant_name == c.COFFEEBEAN:
case c.COFFEEBEAN:
new_plant = plant.CoffeeBean(x, y, self.plant_groups[map_y], self.map.map[map_y][map_x], self.map, map_x)
elif self.plant_name == c.SEASHROOM:
case c.SEASHROOM:
new_plant = plant.SeaShroom(x, y, self.bullet_groups[map_y])
elif self.plant_name == c.TALLNUT:
case c.TALLNUT:
new_plant = plant.TallNut(x, y)
elif self.plant_name == c.TANGLEKLEP:
case c.TANGLEKLEP:
new_plant = plant.TangleKlep(x, y)
elif self.plant_name == c.DOOMSHROOM:
if ((self.map_data[c.BACKGROUND_TYPE] in c.ON_ROOF_BACKGROUNDS) or
(self.map_data[c.BACKGROUND_TYPE] in c.POOL_EQUIPPED_BACKGROUNDS)):
case c.DOOMSHROOM:
if self.map.gridHeightSize == c.GRID_Y_SIZE:
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=3)
else:
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=2)
elif self.plant_name == c.GRAVEBUSTER:
case c.GRAVEBUSTER:
new_plant = plant.GraveBuster(x, y, self.plant_groups[map_y], self.map, map_x)
elif self.plant_name == c.FUMESHROOM:
case c.FUMESHROOM:
new_plant = plant.FumeShroom(x, y, self.bullet_groups[map_y], self.zombie_groups[map_y])
elif self.plant_name == c.GARLIC:
case c.GARLIC:
new_plant = plant.Garlic(x, y)
@ -1015,7 +1011,7 @@ class Level(tool.State):
continue
if collided_func(zombie, bullet):
if zombie.state != c.DIE:
zombie.setDamage(bullet.damage, effect=bullet.effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)
zombie.setDamage(bullet.damage, effect=bullet.effect, damageType=bullet.damageType)
bullet.setExplode()
# 火球有溅射伤害
if bullet.name == c.BULLET_FIREBALL:
@ -1045,7 +1041,7 @@ class Level(tool.State):
# 被攻击对象是植物时才可能刷新
if zombie.prey_is_plant:
# 新植物种在被攻击植物同一格时才可能刷新
if (mapX, mapY) == self.newPlantAndPositon[1]:
if (zombie.preyMapX, zombie.preyMapY) == self.newPlantAndPositon[1]:
# 如果被攻击植物是睡莲和花盆,同一格种了植物必然刷新
# 如果被攻击植物不是睡莲和花盆,同一格种了南瓜头才刷新
if ((zombie.prey.name not in {c.LILYPAD, "花盆(未实现)"})
@ -1102,11 +1098,11 @@ class Level(tool.State):
targetPlant = None
if targetPlant:
zombie.preyMapX, zombie.preyMapY = self.map.getMapIndex(targetPlant.rect.centerx, targetPlant.rect.centery)
# 撑杆跳的特殊情况
if zombie.name in {c.POLE_VAULTING_ZOMBIE} and (not zombie.jumped):
if not zombie.jumping:
zombie.jumpMap_x, zombie.jumpMap_y = self.map.getMapIndex(targetPlant.rect.centerx, targetPlant.rect.centery)
zombie.jumpMap_x, zombie.jumpMap_y = min(c.GRID_X_LEN - 1, zombie.jumpMap_x), min(self.map_y_len - 1, zombie.jumpMap_y)
zombie.jumpMap_x, zombie.jumpMap_y = min(c.GRID_X_LEN - 1, zombie.preyMapX), min(self.map_y_len - 1, zombie.preyMapY)
jumpX = targetPlant.rect.x - c.GRID_X_SIZE * 0.6
if c.TALLNUT in self.map.map[zombie.jumpMap_y][zombie.jumpMap_x][c.MAP_PLANT]:
zombie.setJump(False, jumpX)
@ -1132,6 +1128,9 @@ class Level(tool.State):
elif targetPlant.name == c.REDWALLNUTBOWLING:
if targetPlant.state == c.IDLE:
targetPlant.setAttack()
elif zombie.targetYChange:
# 大蒜作用正在生效的僵尸不进行传递
continue
elif targetPlant.name == c.GARLIC:
zombie.setAttack(targetPlant)
# 向吃过大蒜的僵尸传入level
@ -1145,7 +1144,7 @@ class Level(tool.State):
else:
_move = random.randint(0, 1)*2 - 1
if self.map.map[i][0][c.MAP_PLOT_TYPE] != self.map.map[i + _move][0][c.MAP_PLOT_TYPE]:
_move = -_move
_move = -(_move)
zombie.targetMapY = i + _move
zombie.targetYChange = _move * self.map.gridHeightSize
else:
@ -1351,7 +1350,7 @@ class Level(tool.State):
targetPlant.explode_x_range, effect=c.BULLET_EFFECT_UNICE)
# 消除冰道
for item in self.plant_groups[i]:
if item.name == c.ICE_FROZEN_PLOT:
if item.name == c.ICEFROZENPLOT:
item.health = 0
elif targetPlant.name == c.ICESHROOM:
self.freezeZombies(targetPlant)
@ -1394,7 +1393,8 @@ class Level(tool.State):
def checkLose(self):
for i in range(self.map_y_len):
for zombie in self.zombie_groups[i]:
if zombie.rect.right < -20 and (not zombie.lostHead) and zombie.state != c.DIE:
if zombie.rect.right < -20 and (not zombie.lostHead) and (zombie.state != c.DIE):
print(zombie.rect.right, zombie.lostHead, zombie.state,zombie.name)
return True
return False

View File

@ -97,8 +97,10 @@ class Menu(tool.State):
# 高亮冒险模式按钮
if self.inAreaAdventure(x, y):
self.adventure_highlight_time = self.current_time
# 高亮退出按钮
elif self.inAreaExit(x, y):
self.exit_highlight_time = self.current_time
# 高亮小游戏按钮
elif self.inAreaLittleGame(x, y):
self.littleGame_highlight_time = self.current_time
@ -126,8 +128,6 @@ class Menu(tool.State):
x, y = mouse_pos
if self.inAreaLittleGame(x, y):
self.done = True
# 确实小游戏还是用的level
# 因为目前暂时没有生存模式和解谜模式,所以暂时设置为这样
self.persist[c.GAME_MODE] = c.MODE_LITTLEGAME
# 播放点击音效
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "buttonclick.ogg")).play()

View File

@ -90,7 +90,8 @@ class Control():
self.mouse_pos = pg.mouse.get_pos()
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed()
# self.mouse_click[0]表示左键self.mouse_click[1]表示右键
print('点击位置:', self.mouse_pos, '左右键点击情况:', self.mouse_click)
print( f"点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3})",
f"左右键点击情况: {self.mouse_click}")
def run(self):
@ -188,16 +189,14 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.
# 用于消除文件边框影响
def loadZombieImageRect():
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'zombie.json')
f = open(file_path)
with open(file_path) as f:
data = json.load(f)
f.close()
return data[c.ZOMBIE_IMAGE_RECT]
def loadPlantImageRect():
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'plant.json')
f = open(file_path)
with open(file_path) as f:
data = json.load(f)
f.close()
return data[c.PLANT_IMAGE_RECT]
pg.init()