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/ out/
build/ build/
# 忽略调试内容
.vscode/ .vscode/
__pycache__/ __pycache__/
*/__pycache__/ */__pycache__/
# ignore test # 忽略测试文件
test.py test.py

View File

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

View File

@ -7,10 +7,10 @@ from source.state import mainmenu, screen, level
if __name__=='__main__': if __name__=='__main__':
# 控制状态机运行 # 控制状态机运行
game = tool.Control() game = tool.Control()
state_dict = { c.MAIN_MENU: mainmenu.Menu(), state_dict = { c.MAIN_MENU: mainmenu.Menu(),
c.GAME_VICTORY: screen.GameVictoryScreen(), c.GAME_VICTORY: screen.GameVictoryScreen(),
c.GAME_LOSE: screen.GameLoseScreen(), c.GAME_LOSE: screen.GameLoseScreen(),
c.LEVEL: level.Level() c.LEVEL: level.Level()
} }
game.setup_states(state_dict, c.MAIN_MENU) game.setup_states(state_dict, c.MAIN_MENU)
game.run() game.run()

View File

@ -2,38 +2,36 @@ import random
import pygame as pg import pygame as pg
from .. import tool from .. import tool
from .. import constants as c from .. import constants as c
from copy import deepcopy
class Map(): class Map():
def __init__(self, background_type): def __init__(self, background_type):
self.background_type = background_type self.background_type = background_type
# 注意从0开始编号 # 注意从0开始编号
# 集合内容需要deepcopy
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS: if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
self.width = c.GRID_POOL_X_LEN self.width = c.GRID_POOL_X_LEN
self.height = c.GRID_POOL_Y_LEN self.height = c.GRID_POOL_Y_LEN
self.gridHeightSize = c.GRID_POOL_Y_SIZE 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: elif self.background_type in c.ON_ROOF_BACKGROUNDS:
self.width = c.GRID_ROOF_X_LEN self.width = c.GRID_ROOF_X_LEN
self.height = c.GRID_ROOF_Y_LEN self.height = c.GRID_ROOF_Y_LEN
self.gridHeightSize = c.GRID_ROOF_Y_SIZE 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: elif self.background_type == c.BACKGROUND_SINGLE:
self.width = c.GRID_X_LEN self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN self.height = c.GRID_Y_LEN
self.gridHeightSize = c.GRID_Y_SIZE 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: elif self.background_type == c.BACKGROUND_TRIPLE:
self.width = c.GRID_X_LEN self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN self.height = c.GRID_Y_LEN
self.gridHeightSize = c.GRID_Y_SIZE 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: else:
self.width = c.GRID_X_LEN self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN self.height = c.GRID_Y_LEN
self.gridHeightSize = c.GRID_Y_SIZE 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): def isValid(self, map_x, map_y):
if (map_x < 0 or map_x >= self.width or 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) 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): 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): def addMapPlant(self, map_x, map_y, plantName, sleep=False):
self.map[map_y][map_x][c.MAP_PLANT].add(plantName) self.map[map_y][map_x][c.MAP_PLANT].add(plantName)

View File

@ -168,8 +168,8 @@ class MenuBar():
def checkMenuBarClick(self, mouse_pos): def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.rect.x and x <= self.rect.right and if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and y <= self.rect.bottom): y >= self.rect.y and y <= self.rect.bottom):
return True return True
return False return False
@ -340,8 +340,8 @@ class MoveCard():
def checkMouseClick(self, mouse_pos): def checkMouseClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.rect.x and x <= self.rect.right and if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and y <= self.rect.bottom): y >= self.rect.y and y <= self.rect.bottom):
return True return True
return False return False
@ -425,8 +425,8 @@ class MoveBar():
def checkMenuBarClick(self, mouse_pos): def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.rect.x and x <= self.rect.right and if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and y <= self.rect.bottom): y >= self.rect.y and y <= self.rect.bottom):
return True return True
return False return False

View File

@ -38,7 +38,7 @@ class Car(pg.sprite.Sprite):
# 豌豆及孢子类普通子弹 # 豌豆及孢子类普通子弹
class Bullet(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) pg.sprite.Sprite.__init__(self)
self.name = name self.name = name
@ -55,6 +55,7 @@ class Bullet(pg.sprite.Sprite):
self.y_vel = 15 if (dest_y > start_y) else -15 self.y_vel = 15 if (dest_y > start_y) else -15
self.x_vel = 10 self.x_vel = 10
self.damage = damage self.damage = damage
self.damageType = damageType
self.effect = effect self.effect = effect
self.state = c.FLY self.state = c.FLY
self.current_time = 0 self.current_time = 0
@ -182,11 +183,11 @@ class Fume(pg.sprite.Sprite):
# 杨桃的子弹 # 杨桃的子弹
class StarBullet(Bullet): class StarBullet(Bullet):
def __init__(self, x, start_y, damage, direction, level): # direction指星星飞行方向 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) Bullet.__init__(self, x, start_y, start_y, c.BULLET_STAR, damage, damageType = damageType)
self.level = level 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 self.direction = direction
def update(self, game_info): def update(self, game_info):
@ -206,20 +207,19 @@ class StarBullet(Bullet):
else: else:
self.rect.x -= 10 self.rect.x -= 10
self.handleMapYPosition() 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)): or (self.rect.y > c.SCREEN_HEIGHT) or (self.rect.y < 0)):
self.kill() self.kill()
elif self.state == c.EXPLODE: elif self.state == c.EXPLODE:
if (self.current_time - self.explode_timer) > 250: if (self.current_time - self.explode_timer) >= 250:
self.kill() self.kill()
# 这里用的是坚果保龄球的代码改一下,实现子弹换行 # 这里用的是坚果保龄球的代码改一下,实现子弹换行
def handleMapYPosition(self): def handleMapYPosition(self):
if self.direction == c.STAR_UPWARD: 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: else:
_, map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 20) map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 20)[1]
# _, map_y2 = self.level.map.getMapIndex(self.rect.x, self.rect.bottom +20)
if (self.map_y != map_y1) and (0 <= map_y1 <= self.level.map_y_len-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[self.map_y].remove(self)
self.level.bullet_groups[map_y1].add(self) self.level.bullet_groups[map_y1].add(self)
@ -343,8 +343,8 @@ class Plant(pg.sprite.Sprite):
self.health -= damage self.health -= damage
self.hit_timer = self.current_time self.hit_timer = self.current_time
if ((self.name == c.HYPNOSHROOM) and if ((self.name == c.HYPNOSHROOM) and
(self.state != c.SLEEP) and (self.state != c.SLEEP) and
(zombie.name not in {c.ZOMBONI, "投石车僵尸(未实现)", "加刚特尔(未实现)"})): (zombie.name not in {c.ZOMBONI, "投石车僵尸(未实现)", "加刚特尔(未实现)"})):
self.zombie_to_hypno = zombie self.zombie_to_hypno = zombie
def getPosition(self): def getPosition(self):
@ -382,7 +382,7 @@ class Sun(Plant):
if self.state == c.DIE: if self.state == c.DIE:
return False return False
if (x >= self.rect.x and x <= self.rect.right and if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and y <= self.rect.bottom): y >= self.rect.y and y <= self.rect.bottom):
self.state = c.DIE self.state = c.DIE
self.kill() self.kill()
return True return True
@ -625,8 +625,8 @@ class Chomper(Plant):
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
return False return False
elif (self.state == c.IDLE and zombie.state != c.DIGEST and 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 <= zombie.rect.centerx and (not zombie.lostHead) and
(self.rect.x + c.GRID_X_SIZE*2.7 >= zombie.rect.centerx)): (self.rect.x + c.GRID_X_SIZE*2.7 >= zombie.rect.centerx)):
return True return True
return False return False
@ -701,7 +701,7 @@ class PuffShroom(Plant):
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
return False return False
if (self.rect.x <= zombie.rect.right and 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)): (self.rect.x + c.GRID_X_SIZE * 4 >= zombie.rect.x) and (zombie.rect.left <= c.SCREEN_WIDTH + 10)):
return True return True
return False return False
@ -795,7 +795,7 @@ class Squash(Plant):
def canAttack(self, zombie): def canAttack(self, zombie):
# 普通状态 # 普通状态
if (self.state == c.IDLE and self.rect.x <= zombie.rect.right and if (self.state == c.IDLE and self.rect.x <= zombie.rect.right and
(self.rect.right + c.GRID_X_SIZE >= zombie.rect.x)): (self.rect.right + c.GRID_X_SIZE >= zombie.rect.x)):
return True return True
# 攻击状态 # 攻击状态
elif (self.state == c.ATTACK): elif (self.state == c.ATTACK):
@ -1160,7 +1160,7 @@ class WallNutBowling(Plant):
self.handleMapYPosition() self.handleMapYPosition()
if self.shouldChangeDirection(): if self.shouldChangeDirection():
self.changeDirection(-1) self.changeDirection(-1)
if self.init_rect.x > c.SCREEN_WIDTH: if self.init_rect.x > c.SCREEN_WIDTH + 60:
self.health = 0 self.health = 0
self.move_timer += self.move_interval self.move_timer += self.move_interval
@ -1170,8 +1170,8 @@ class WallNutBowling(Plant):
return True return True
def handleMapYPosition(self): def handleMapYPosition(self):
_, map_y1 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.centery) 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) 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: if self.map_y != map_y1 and map_y1 == map_y2:
# wallnut bowls to another row, should modify which plant group it belongs to # wallnut bowls to another row, should modify which plant group it belongs to
self.level.plant_groups[self.map_y].remove(self) 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: elif (self.current_time - self.move_timer) >= self.move_interval:
self.rotate_degree = (self.rotate_degree - 30) % 360 self.rotate_degree = (self.rotate_degree - 30) % 360
self.init_rect.x += self.vel_x 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.health = 0
self.move_timer += self.move_interval self.move_timer += self.move_interval
@ -1319,7 +1319,7 @@ class StarFruit(Plant):
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
return False return False
if zombie.state != c.DIE: 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): # 对于同行且在杨桃后的僵尸 if (self.rect.x >= zombie.rect.x) and (self.map_y == zombieMapY): # 对于同行且在杨桃后的僵尸
return True return True
# 斜向上理想直线方程为f(zombie.rect.x) = -0.75*(zombie.rect.x - (self.rect.right - 5)) + self.rect.y - 10 # 斜向上理想直线方程为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: if self.shoot_timer == 0:
self.shoot_timer = self.current_time - 700 self.shoot_timer = self.current_time - 700
elif (self.current_time - self.shoot_timer) >= 1400: 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 - 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.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)) self.bullet_group.add(StarBullet(self.rect.right - 5, self.rect.bottom - 20, c.BULLET_DAMAGE_NORMAL, c.STAR_FORWARD_DOWN, self.level))
@ -1428,7 +1430,7 @@ class SeaShroom(Plant):
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
return False return False
if (self.rect.x <= zombie.rect.right and 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)): (self.rect.x + c.GRID_X_SIZE * 4 >= zombie.rect.x) and (zombie.rect.left <= c.SCREEN_WIDTH + 10)):
return True return True
return False return False
@ -1589,6 +1591,7 @@ class DoomShroom(Plant):
else: else:
self.image.set_alpha(255) self.image.set_alpha(255)
# 用于描述毁灭菇的坑 # 用于描述毁灭菇的坑
class Hole(Plant): class Hole(Plant):
def __init__(self, x, y, plotType): def __init__(self, x, y, plotType):
@ -1719,7 +1722,7 @@ class FumeShroom(Plant):
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames): if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
return False return False
if (self.rect.x <= zombie.rect.right and 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)): (self.rect.x + c.GRID_X_SIZE * 5 >= zombie.rect.x) and (zombie.rect.left <= c.SCREEN_WIDTH + 10)):
return True return True
return False return False
@ -1770,7 +1773,7 @@ class FumeShroom(Plant):
class IceFrozenPlot(Plant): class IceFrozenPlot(Plant):
def __init__(self, x, y): 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 self.timer = 0
def idling(self): def idling(self):

View File

@ -196,8 +196,8 @@ class Zombie(pg.sprite.Sprite):
self.changeFrames(self.walk_frames) self.changeFrames(self.walk_frames)
self.helmetType2 = False self.helmetType2 = False
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()) if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
and self.handleGarlicYChange()): self.handleGarlicYChange()
self.walk_timer = self.current_time self.walk_timer = self.current_time
if self.is_hypno: if self.is_hypno:
self.rect.x += 1 self.rect.x += 1
@ -206,7 +206,6 @@ class Zombie(pg.sprite.Sprite):
def handleGarlicYChange(self): def handleGarlicYChange(self):
if self.targetYChange < 0: if self.targetYChange < 0:
self.setWalk()
if self.rect.bottom > self.originalY + self.targetYChange: # 注意这里加的是负数 if self.rect.bottom > self.originalY + self.targetYChange: # 注意这里加的是负数
self.rect.bottom -= 3 self.rect.bottom -= 3
# 过半时换行 # 过半时换行
@ -214,12 +213,12 @@ class Zombie(pg.sprite.Sprite):
(self.rect.bottom >= self.originalY + 0.5*self.targetYChange)): (self.rect.bottom >= self.originalY + 0.5*self.targetYChange)):
self.level.zombie_groups[self.mapY].remove(self) self.level.zombie_groups[self.mapY].remove(self)
self.level.zombie_groups[self.targetMapY].add(self) self.level.zombie_groups[self.targetMapY].add(self)
self.toChangeGroup = False
else: else:
self.rect.bottom = self.originalY + self.targetYChange self.rect.bottom = self.originalY + self.targetYChange
self.originalY = self.rect.bottom
self.targetYChange = 0 self.targetYChange = 0
return None
elif self.targetYChange > 0: elif self.targetYChange > 0:
self.setWalk()
if self.rect.bottom < self.originalY + self.targetYChange: # 注意这里加的是负数 if self.rect.bottom < self.originalY + self.targetYChange: # 注意这里加的是负数
self.rect.bottom += 3 self.rect.bottom += 3
# 过半时换行 # 过半时换行
@ -227,12 +226,11 @@ class Zombie(pg.sprite.Sprite):
(self.rect.bottom <= self.originalY + 0.5*self.targetYChange)): (self.rect.bottom <= self.originalY + 0.5*self.targetYChange)):
self.level.zombie_groups[self.mapY].remove(self) self.level.zombie_groups[self.mapY].remove(self)
self.level.zombie_groups[self.targetMapY].add(self) self.level.zombie_groups[self.targetMapY].add(self)
self.toChangeGroup = False
else: else:
self.rect.bottom = self.originalY + self.targetYChange self.rect.bottom = self.originalY + self.targetYChange
self.originalY = self.rect.bottom
self.targetYChange = 0 self.targetYChange = 0
return None
else:
return True
def attacking(self): def attacking(self):
if self.checkToDie(self.losthead_attack_frames): if self.checkToDie(self.losthead_attack_frames):
@ -435,7 +433,7 @@ class Zombie(pg.sprite.Sprite):
self.health -= damage self.health -= damage
else: else:
print('警告:植物攻击类型错误,现在默认进行类豌豆射手型攻击') 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 self.hit_timer = self.current_time
@ -715,8 +713,8 @@ class NewspaperZombie(Zombie):
self.helmetType2 = False self.helmetType2 = False
# 触发报纸撕裂音效 # 触发报纸撕裂音效
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "newspaperRip.ogg")).play() 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()) if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())):
and self.handleGarlicYChange()): self.handleGarlicYChange()
self.walk_timer = self.current_time self.walk_timer = self.current_time
if self.frames == self.lostnewspaper_frames: if self.frames == self.lostnewspaper_frames:
pass pass
@ -1116,10 +1114,10 @@ class Zomboni(Zombie):
# 造冰 # 造冰
mapX, mapY = self.map.getMapIndex(self.rect.right - 40, self.rect.bottom) mapX, mapY = self.map.getMapIndex(self.rect.right - 40, self.rect.bottom)
if 0 <= mapX < c.GRID_X_LEN: 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) x, y = self.map.getMapGridPos(mapX, mapY)
self.plant_group.add(self.IceFrozenPlot(x, y)) 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) 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 self.frames = self.walk_frames
def walking(self): def walking(self):
if self.checkToDie(self.losthead_walk_frames):
return
# 在水池范围内 # 在水池范围内
# 在右侧岸左 # 在右侧岸左
if self.rect.centerx <= c.MAP_POOL_FRONT_X - 25: if self.rect.centerx <= c.MAP_POOL_FRONT_X - 25:
@ -1199,8 +1200,8 @@ class SnorkelZombie(Zombie):
if self.swimming: if self.swimming:
self.changeFrames(self.walk_frames) self.changeFrames(self.walk_frames)
self.swimming = False self.swimming = False
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()) if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
and self.handleGarlicYChange()): self.handleGarlicYChange()
self.walk_timer = self.current_time self.walk_timer = self.current_time
# 正在上浮或者下潜不用移动 # 正在上浮或者下潜不用移动
if (self.frames == self.float_frames) or (self.frames == self.sink_frames): 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' GRADE_GRAVES = 'grade_graves'
# 不同墓碑等级对应的信息 # 不同墓碑等级对应的信息,列表位置对应的是墓碑等级
GRAVES_GRADE_INFO = (0, 4, 7, 11) GRAVES_GRADE_INFO = (0, 4, 7, 11)
# 僵尸生成方式 # 僵尸生成方式
@ -158,10 +158,11 @@ MAP_WATER = 'water'
MAP_TILE = 'tile' # 指屋顶上的瓦片 MAP_TILE = 'tile' # 指屋顶上的瓦片
MAP_UNAVAILABLE = 'unavailable' # 指完全不能种植物的地方,包括无草皮的荒地和坚果保龄球等红线右侧 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 BACKGROUND_OFFSET_X = 220
@ -193,7 +194,7 @@ PANEL_Y_INTERNAL = 69
PANEL_X_INTERNAL = 53 PANEL_X_INTERNAL = 53
BAR_CARD_X_INTERNAL = 51 BAR_CARD_X_INTERNAL = 51
# 所选植物信息索引 # 植物卡片信息索引
PLANT_NAME_INDEX = 0 PLANT_NAME_INDEX = 0
CARD_INDEX = 1 CARD_INDEX = 1
SUN_INDEX = 2 SUN_INDEX = 2
@ -237,7 +238,7 @@ SEASHROOM = 'SeaShroom'
TALLNUT = 'TallNut' TALLNUT = 'TallNut'
TANGLEKLEP = 'TangleKlep' TANGLEKLEP = 'TangleKlep'
DOOMSHROOM = 'DoomShroom' DOOMSHROOM = 'DoomShroom'
ICE_FROZEN_PLOT = 'IceFrozenPlot' ICEFROZENPLOT = 'IceFrozenPlot'
HOLE = 'Hole' HOLE = 'Hole'
GRAVE = 'Grave' GRAVE = 'Grave'
GRAVEBUSTER = 'GraveBuster' GRAVEBUSTER = 'GraveBuster'
@ -246,9 +247,9 @@ GARLIC = 'Garlic'
# 植物集体属性集合 # 植物集体属性集合
# 在生效时不用与僵尸进行碰撞检测的对象 # 在生效时不用与僵尸进行碰撞检测的对象(即生效时不可发生被僵尸啃食的事件)
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = { SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
# 注意爆炸坚果的触发也是啃食类碰撞,因此这里不能省略 # 注意爆炸坚果的触发也是啃食类碰撞,因此只能算作爆炸后不检测
SQUASH, ICESHROOM, SQUASH, ICESHROOM,
REDWALLNUTBOWLING, CHERRYBOMB, REDWALLNUTBOWLING, CHERRYBOMB,
JALAPENO, DOOMSHROOM, JALAPENO, DOOMSHROOM,
@ -257,12 +258,15 @@ SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
# 非植物对象 # 非植物对象
NON_PLANT_OBJECTS = { NON_PLANT_OBJECTS = {
HOLE, ICE_FROZEN_PLOT, HOLE, ICEFROZENPLOT,
GRAVE, GRAVE,
} }
# 所有可能不用与僵尸进行碰撞检测的对象 # 所有可能不用与僵尸进行碰撞检测的对象
CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算 CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
# 注意这个外围的小括号是用来换行的
# 各个部分末!尾!千!万!不!能!加!逗!号!!!
# 生效时不检测的植物 # 生效时不检测的植物
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING | SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING |
# 非植物对象 # 非植物对象
@ -274,7 +278,7 @@ CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
# 死亡时不触发音效的对象 # 死亡时不触发音效的对象
PLANT_DIE_SOUND_EXCEPTIONS = { PLANT_DIE_SOUND_EXCEPTIONS = {
WALLNUTBOWLING, TANGLEKLEP, WALLNUTBOWLING, TANGLEKLEP,
ICE_FROZEN_PLOT, HOLE, ICEFROZENPLOT, HOLE,
GRAVE, JALAPENO, GRAVE, JALAPENO,
REDWALLNUTBOWLING, CHERRYBOMB, REDWALLNUTBOWLING, CHERRYBOMB,
} }
@ -635,4 +639,4 @@ CHOOSE = 'choose'
PLAY = 'play' 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.sun_group = pg.sprite.Group()
self.head_group = pg.sprite.Group() self.head_group = pg.sprite.Group()
self.plant_groups = [] # 改用列表生成器直接生成内容不再在这里使用for循环
self.zombie_groups = [] self.plant_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
self.hypno_zombie_groups = [] #zombies who are hypno after eating hypnoshroom self.zombie_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
self.bullet_groups = [] self.hypno_zombie_groups = [pg.sprite.Group() for i in range(self.map_y_len)] #zombies who are hypno after eating hypnoshroom
for i in range(self.map_y_len): self.bullet_groups = [pg.sprite.Group() 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())
# 按照规则生成每一波僵尸 # 按照规则生成每一波僵尸
@ -189,7 +185,7 @@ class Level(tool.State):
unoccupied = [] unoccupied = []
occupied = [] occupied = []
# 毁灭菇坑与冰道应当特殊化 # 毁灭菇坑与冰道应当特殊化
exceptionObjects = {c.HOLE, c.ICE_FROZEN_PLOT} exceptionObjects = {c.HOLE, c.ICEFROZENPLOT}
# 遍历能生成墓碑的区域 # 遍历能生成墓碑的区域
for mapY in range(0, 4): for mapY in range(0, 4):
for mapX in range(4, 8): for mapX in range(4, 8):
@ -320,7 +316,7 @@ class Level(tool.State):
def setupCars(self): def setupCars(self):
self.cars = [] self.cars = []
for i in range(self.map_y_len): 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)) self.cars.append(plant.Car(-40, y+20, i))
# 更新函数每帧被调用,将鼠标事件传入给状态处理函数 # 更新函数每帧被调用,将鼠标事件传入给状态处理函数
@ -344,7 +340,7 @@ class Level(tool.State):
def initBowlingMap(self): def initBowlingMap(self):
for x in range(3, self.map.width): for x in range(3, self.map.width):
for y in range(self.map.height): 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): def initState(self):
if c.CHOOSEBAR_TYPE in self.map_data: if c.CHOOSEBAR_TYPE in self.map_data:
@ -581,32 +577,32 @@ class Level(tool.State):
# 检查小菜单有没有被点击 # 检查小菜单有没有被点击
def checkLittleMenuClick(self, mouse_pos): def checkLittleMenuClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.little_menu_rect.x and x <= self.little_menu_rect.right and 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): y >= self.little_menu_rect.y and y <= self.little_menu_rect.bottom):
return True return True
return False return False
# 检查小菜单的返回有没有被点击 # 检查小菜单的返回有没有被点击
def checkReturnClick(self, mouse_pos): def checkReturnClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.return_button_rect.x and x <= self.return_button_rect.right and 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): y >= self.return_button_rect.y and y <= self.return_button_rect.bottom):
return True return True
return False return False
# 检查小菜单的重新开始有没有被点击 # 检查小菜单的重新开始有没有被点击
def checkRestartClick(self, mouse_pos): def checkRestartClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.restart_button_rect.x and x <= self.restart_button_rect.right and 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): y >= self.restart_button_rect.y and y <= self.restart_button_rect.bottom):
return True return True
return False return False
# 检查小菜单的主菜单有没有被点击 # 检查小菜单的主菜单有没有被点击
def checkMainMenuClick(self, mouse_pos): def checkMainMenuClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if(x >= self.mainMenu_button_rect.x and x <= self.mainMenu_button_rect.right and 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): y >= self.mainMenu_button_rect.y and y <= self.mainMenu_button_rect.bottom):
return True return True
return False return False
@ -804,35 +800,35 @@ class Level(tool.State):
x, y = self.map.getMapGridPos(0, map_y) x, y = self.map.getMapGridPos(0, map_y)
# 新增的僵尸也需要在这里声明 # 新增的僵尸也需要在这里声明
if name == c.NORMAL_ZOMBIE: match name:
self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.NORMAL_ZOMBIE:
elif name == c.CONEHEAD_ZOMBIE: self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.CONEHEAD_ZOMBIE:
elif name == c.BUCKETHEAD_ZOMBIE: self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.BUCKETHEAD_ZOMBIE:
elif name == c.FLAG_ZOMBIE: self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group)) case c.FLAG_ZOMBIE:
elif name == c.NEWSPAPER_ZOMBIE: self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group))
self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.NEWSPAPER_ZOMBIE:
elif name == c.FOOTBALL_ZOMBIE: self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.FootballZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.FOOTBALL_ZOMBIE:
elif name == c.DUCKY_TUBE_ZOMBIE: self.zombie_groups[map_y].add(zombie.FootballZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.DuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.DUCKY_TUBE_ZOMBIE:
elif name == c.CONEHEAD_DUCKY_TUBE_ZOMBIE: self.zombie_groups[map_y].add(zombie.DuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.ConeHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.CONEHEAD_DUCKY_TUBE_ZOMBIE:
elif name == c.BUCKETHEAD_DUCKY_TUBE_ZOMBIE: self.zombie_groups[map_y].add(zombie.ConeHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.BucketHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.BUCKETHEAD_DUCKY_TUBE_ZOMBIE:
elif name == c.SCREEN_DOOR_ZOMBIE: self.zombie_groups[map_y].add(zombie.BucketHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
self.zombie_groups[map_y].add(zombie.ScreenDoorZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group)) case c.SCREEN_DOOR_ZOMBIE:
elif name == c.POLE_VAULTING_ZOMBIE: self.zombie_groups[map_y].add(zombie.ScreenDoorZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
# 本来撑杆跳生成位置不同对齐左端可认为修正了一部分看作移动了70只需要相对修改即可 case c.POLE_VAULTING_ZOMBIE:
self.zombie_groups[map_y].add(zombie.PoleVaultingZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group)) # 本来撑杆跳生成位置不同对齐左端可认为修正了一部分看作移动了70只需要相对修改即可
elif name == c.ZOMBONI: self.zombie_groups[map_y].add(zombie.PoleVaultingZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
# 冰车僵尸生成位置不同 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: 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))
# 潜水僵尸生成位置不同 case c.SNORKELZOMBIE:
self.zombie_groups[map_y].add(zombie.SnorkelZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group)) self.zombie_groups[map_y].add(zombie.SnorkelZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
# 能否种植物的判断: # 能否种植物的判断:
# 先判断位置是否合法 isValid(map_x, map_y) # 先判断位置是否合法 isValid(map_x, map_y)
@ -856,70 +852,70 @@ class Level(tool.State):
map_x, map_y = self.map.getMapIndex(x, y) map_x, map_y = self.map.getMapIndex(x, y)
# 新植物也需要在这里声明 # 新植物也需要在这里声明
if self.plant_name == c.SUNFLOWER: match self.plant_name:
new_plant = plant.SunFlower(x, y, self.sun_group) case c.SUNFLOWER:
elif self.plant_name == c.PEASHOOTER: new_plant = plant.SunFlower(x, y, self.sun_group)
new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y]) case c.PEASHOOTER:
elif self.plant_name == c.SNOWPEASHOOTER: new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y])
new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y]) case c.SNOWPEASHOOTER:
elif self.plant_name == c.WALLNUT: new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y])
new_plant = plant.WallNut(x, y) case c.WALLNUT:
elif self.plant_name == c.CHERRYBOMB: new_plant = plant.WallNut(x, y)
new_plant = plant.CherryBomb(x, y) case c.CHERRYBOMB:
elif self.plant_name == c.THREEPEASHOOTER: new_plant = plant.CherryBomb(x, y)
new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y, self.map.background_type) case c.THREEPEASHOOTER:
elif self.plant_name == c.REPEATERPEA: new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y, self.map.background_type)
new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y]) case c.REPEATERPEA:
elif self.plant_name == c.CHOMPER: new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y])
new_plant = plant.Chomper(x, y) case c.CHOMPER:
elif self.plant_name == c.PUFFSHROOM: new_plant = plant.Chomper(x, y)
new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y]) case c.PUFFSHROOM:
elif self.plant_name == c.POTATOMINE: new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y])
new_plant = plant.PotatoMine(x, y) case c.POTATOMINE:
elif self.plant_name == c.SQUASH: new_plant = plant.PotatoMine(x, y)
new_plant = plant.Squash(x, y, self.map.map[map_y][map_x][c.MAP_PLANT]) case c.SQUASH:
elif self.plant_name == c.SPIKEWEED: new_plant = plant.Squash(x, y, self.map.map[map_y][map_x][c.MAP_PLANT])
new_plant = plant.Spikeweed(x, y) case c.SPIKEWEED:
elif self.plant_name == c.JALAPENO: new_plant = plant.Spikeweed(x, y)
new_plant = plant.Jalapeno(x, y) case c.JALAPENO:
elif self.plant_name == c.SCAREDYSHROOM: new_plant = plant.Jalapeno(x, y)
new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y]) case c.SCAREDYSHROOM:
elif self.plant_name == c.SUNSHROOM: new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y])
new_plant = plant.SunShroom(x, y, self.sun_group) case c.SUNSHROOM:
elif self.plant_name == c.ICESHROOM: new_plant = plant.SunShroom(x, y, self.sun_group)
new_plant = plant.IceShroom(x, y) case c.ICESHROOM:
elif self.plant_name == c.HYPNOSHROOM: new_plant = plant.IceShroom(x, y)
new_plant = plant.HypnoShroom(x, y) case c.HYPNOSHROOM:
elif self.plant_name == c.WALLNUTBOWLING: new_plant = plant.HypnoShroom(x, y)
new_plant = plant.WallNutBowling(x, y, map_y, self) case c.WALLNUTBOWLING:
elif self.plant_name == c.REDWALLNUTBOWLING: new_plant = plant.WallNutBowling(x, y, map_y, self)
new_plant = plant.RedWallNutBowling(x, y) case c.REDWALLNUTBOWLING:
elif self.plant_name == c.LILYPAD: new_plant = plant.RedWallNutBowling(x, y)
new_plant = plant.LilyPad(x, y) case c.LILYPAD:
elif self.plant_name == c.TORCHWOOD: new_plant = plant.LilyPad(x, y)
new_plant = plant.TorchWood(x, y, self.bullet_groups[map_y]) case c.TORCHWOOD:
elif self.plant_name == c.STARFRUIT: new_plant = plant.TorchWood(x, y, self.bullet_groups[map_y])
new_plant = plant.StarFruit(x, y, self.bullet_groups[map_y], self) case c.STARFRUIT:
elif self.plant_name == c.COFFEEBEAN: new_plant = plant.StarFruit(x, y, self.bullet_groups[map_y], self)
new_plant = plant.CoffeeBean(x, y, self.plant_groups[map_y], self.map.map[map_y][map_x], self.map, map_x) case c.COFFEEBEAN:
elif self.plant_name == c.SEASHROOM: new_plant = plant.CoffeeBean(x, y, self.plant_groups[map_y], self.map.map[map_y][map_x], self.map, map_x)
new_plant = plant.SeaShroom(x, y, self.bullet_groups[map_y]) case c.SEASHROOM:
elif self.plant_name == c.TALLNUT: new_plant = plant.SeaShroom(x, y, self.bullet_groups[map_y])
new_plant = plant.TallNut(x, y) case c.TALLNUT:
elif self.plant_name == c.TANGLEKLEP: new_plant = plant.TallNut(x, y)
new_plant = plant.TangleKlep(x, y) case c.TANGLEKLEP:
elif self.plant_name == c.DOOMSHROOM: new_plant = plant.TangleKlep(x, y)
if ((self.map_data[c.BACKGROUND_TYPE] in c.ON_ROOF_BACKGROUNDS) or case c.DOOMSHROOM:
(self.map_data[c.BACKGROUND_TYPE] in c.POOL_EQUIPPED_BACKGROUNDS)): 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) new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=3)
else: else:
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=2) 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) 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]) 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) new_plant = plant.Garlic(x, y)
if new_plant.can_sleep and self.background_type in c.DAYTIME_BACKGROUNDS: if new_plant.can_sleep and self.background_type in c.DAYTIME_BACKGROUNDS:
@ -1015,7 +1011,7 @@ class Level(tool.State):
continue continue
if collided_func(zombie, bullet): if collided_func(zombie, bullet):
if zombie.state != c.DIE: 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() bullet.setExplode()
# 火球有溅射伤害 # 火球有溅射伤害
if bullet.name == c.BULLET_FIREBALL: if bullet.name == c.BULLET_FIREBALL:
@ -1045,7 +1041,7 @@ class Level(tool.State):
# 被攻击对象是植物时才可能刷新 # 被攻击对象是植物时才可能刷新
if zombie.prey_is_plant: 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, "花盆(未实现)"}) if ((zombie.prey.name not in {c.LILYPAD, "花盆(未实现)"})
@ -1102,11 +1098,11 @@ class Level(tool.State):
targetPlant = None targetPlant = None
if targetPlant: 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 zombie.name in {c.POLE_VAULTING_ZOMBIE} and (not zombie.jumped):
if not zombie.jumping: 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.preyMapX), min(self.map_y_len - 1, zombie.preyMapY)
zombie.jumpMap_x, zombie.jumpMap_y = min(c.GRID_X_LEN - 1, zombie.jumpMap_x), min(self.map_y_len - 1, zombie.jumpMap_y)
jumpX = targetPlant.rect.x - c.GRID_X_SIZE * 0.6 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]: if c.TALLNUT in self.map.map[zombie.jumpMap_y][zombie.jumpMap_x][c.MAP_PLANT]:
zombie.setJump(False, jumpX) zombie.setJump(False, jumpX)
@ -1132,6 +1128,9 @@ class Level(tool.State):
elif targetPlant.name == c.REDWALLNUTBOWLING: elif targetPlant.name == c.REDWALLNUTBOWLING:
if targetPlant.state == c.IDLE: if targetPlant.state == c.IDLE:
targetPlant.setAttack() targetPlant.setAttack()
elif zombie.targetYChange:
# 大蒜作用正在生效的僵尸不进行传递
continue
elif targetPlant.name == c.GARLIC: elif targetPlant.name == c.GARLIC:
zombie.setAttack(targetPlant) zombie.setAttack(targetPlant)
# 向吃过大蒜的僵尸传入level # 向吃过大蒜的僵尸传入level
@ -1145,7 +1144,7 @@ class Level(tool.State):
else: else:
_move = random.randint(0, 1)*2 - 1 _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]: 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.targetMapY = i + _move
zombie.targetYChange = _move * self.map.gridHeightSize zombie.targetYChange = _move * self.map.gridHeightSize
else: else:
@ -1185,7 +1184,7 @@ class Level(tool.State):
continue continue
for zombie in self.zombie_groups[i]: for zombie in self.zombie_groups[i]:
if ((abs(zombie.rect.centerx - x) <= x_range) or 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]是在右边的情况 ((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: if effect == c.BULLET_EFFECT_UNICE:
zombie.ice_slow_ratio = 1 zombie.ice_slow_ratio = 1
zombie.setDamage(1800, damageType=c.ZOMBIE_ASH_DAMAGE) zombie.setDamage(1800, damageType=c.ZOMBIE_ASH_DAMAGE)
@ -1277,7 +1276,7 @@ class Level(tool.State):
for zombie in self.zombie_groups[i]: for zombie in self.zombie_groups[i]:
# 双判断:发生碰撞或在攻击范围内 # 双判断:发生碰撞或在攻击范围内
if ((pg.sprite.collide_mask(zombie, targetPlant)) or if ((pg.sprite.collide_mask(zombie, targetPlant)) or
(abs(zombie.rect.centerx - targetPlant.rect.centerx) <= targetPlant.explode_x_range)): (abs(zombie.rect.centerx - targetPlant.rect.centerx) <= targetPlant.explode_x_range)):
zombie.setDamage(1800, damageType=c.ZOMBIE_RANGE_DAMAGE) zombie.setDamage(1800, damageType=c.ZOMBIE_RANGE_DAMAGE)
targetPlant.boomed = True targetPlant.boomed = True
elif targetPlant.name == c.SQUASH: elif targetPlant.name == c.SQUASH:
@ -1351,7 +1350,7 @@ class Level(tool.State):
targetPlant.explode_x_range, effect=c.BULLET_EFFECT_UNICE) targetPlant.explode_x_range, effect=c.BULLET_EFFECT_UNICE)
# 消除冰道 # 消除冰道
for item in self.plant_groups[i]: for item in self.plant_groups[i]:
if item.name == c.ICE_FROZEN_PLOT: if item.name == c.ICEFROZENPLOT:
item.health = 0 item.health = 0
elif targetPlant.name == c.ICESHROOM: elif targetPlant.name == c.ICESHROOM:
self.freezeZombies(targetPlant) self.freezeZombies(targetPlant)
@ -1394,7 +1393,8 @@ class Level(tool.State):
def checkLose(self): def checkLose(self):
for i in range(self.map_y_len): for i in range(self.map_y_len):
for zombie in self.zombie_groups[i]: 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 True
return False return False

View File

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

View File

@ -30,7 +30,7 @@ class Screen(tool.State):
self.rect.y = 0 self.rect.y = 0
def update(self, surface, current_time, mouse_pos, mouse_click): def update(self, surface, current_time, mouse_pos, mouse_click):
if(current_time - self.start_time) < self.end_time: if (current_time - self.start_time) < self.end_time:
surface.fill(c.WHITE) surface.fill(c.WHITE)
surface.blit(self.image, self.rect) surface.blit(self.image, self.rect)
else: else:

View File

@ -14,7 +14,7 @@ class State():
self.done = False # false 代表未做完 self.done = False # false 代表未做完
self.next = None # 表示这个状态退出后要转到的下一个状态 self.next = None # 表示这个状态退出后要转到的下一个状态
self.persist = {} # 在状态间转换时需要传递的数据 self.persist = {} # 在状态间转换时需要传递的数据
# 当从其他状态进入这个状态时,需要进行的初始化操作 # 当从其他状态进入这个状态时,需要进行的初始化操作
@abstractmethod @abstractmethod
def startup(self, current_time, persist): def startup(self, current_time, persist):
@ -90,7 +90,8 @@ class Control():
self.mouse_pos = pg.mouse.get_pos() 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] = pg.mouse.get_pressed()
# self.mouse_click[0]表示左键self.mouse_click[1]表示右键 # 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): def run(self):
@ -188,16 +189,14 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.
# 用于消除文件边框影响 # 用于消除文件边框影响
def loadZombieImageRect(): def loadZombieImageRect():
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'zombie.json') 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) data = json.load(f)
f.close()
return data[c.ZOMBIE_IMAGE_RECT] return data[c.ZOMBIE_IMAGE_RECT]
def loadPlantImageRect(): def loadPlantImageRect():
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'plant.json') 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) data = json.load(f)
f.close()
return data[c.PLANT_IMAGE_RECT] return data[c.PLANT_IMAGE_RECT]
pg.init() pg.init()