植物大战僵尸

Python版植物大战僵尸
已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇,大蒜,南瓜头
已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸,潜水僵尸
支持选择植物卡片
支持白昼模式,夜晚模式,泳池模式,浓雾模式(暂时没有加入雾),传送带模式和坚果保龄球模式
支持背景音乐播放
支持调节音量
支持音效
支持与背景音乐一起调节音量
支持全屏模式
按F键进入全屏模式,按U键恢复至窗口模式
支持用小铲子移除植物
支持分波生成僵尸
支持“关卡进程”进度条显示
夜晚模式支持墓碑以及从墓碑生成僵尸
含有泳池的模式支持在最后一波时从泳池中自动冒出僵尸
环境要求
Python3 (建议 >= 3.10,最好使用最新版)
Python-Pygame (建议 >= 2.0,最好使用最新版)
方法
使用鼠标收集阳光,种植植物
源码分享


初始化页面(部分)
import os
import pygame as pg

# 用户数据及日志存储路径
if os.name == "nt": # Windows系统存储路径
    USERDATA_PATH = os.path.expandvars(os.path.join("%APPDATA%", "pypvz", "userdata.json"))
    USERLOG_PATH = os.path.expandvars(os.path.join("%APPDATA%", "pypvz", "run.log"))
else:   # 非Windows系统存储路径
    USERDATA_PATH = os.path.expanduser(os.path.join("~", ".config", "pypvz", "userdata.json"))
    USERLOG_PATH = os.path.expanduser(os.path.join("~", ".config", "pypvz", "run.log"))

# 游戏图片资源路径
PATH_IMG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "graphics")
# 游戏音乐文件夹路径
PATH_MUSIC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources","music")
# 窗口图标
ORIGINAL_LOGO = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pypvz-exec-logo.png")
# 字体路径
FONT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "DroidSansFallback.ttf")

# 窗口标题
ORIGINAL_CAPTION = "pypvz"

# 游戏模式
GAME_MODE = "mode"
MODE_ADVENTURE = "adventure"
MODE_LITTLEGAME = "littleGame"

# 窗口大小
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)


# 选卡数量
# 最大数量
CARD_MAX_NUM = 10   # 这里以后可以增加解锁功能,从最初的6格逐渐解锁到10格
# 最小数量
CARD_LIST_NUM = CARD_MAX_NUM

# 方格数据
# 一般
GRID_X_LEN = 9
GRID_Y_LEN = 5
GRID_X_SIZE = 80
GRID_Y_SIZE = 100
# 带有泳池
GRID_POOL_X_LEN = GRID_X_LEN
GRID_POOL_Y_LEN = 6
GRID_POOL_X_SIZE = GRID_X_SIZE
GRID_POOL_Y_SIZE = 85
# 屋顶
GRID_ROOF_X_LEN = GRID_X_LEN
GRID_ROOF_Y_LEN = GRID_Y_LEN
GRID_ROOF_X_SIZE = GRID_X_SIZE
GRID_ROOF_Y_SIZE = 85

# 颜色
WHITE        = (255, 255, 255)
NAVYBLUE     = ( 60,  60, 100)
SKY_BLUE     = ( 39, 145, 251)
BLACK        = (  0,   0,   0)
LIGHTYELLOW  = (234, 233, 171)
RED          = (255,   0,   0)
PURPLE       = (255,   0, 255)
GOLD         = (255, 215,   0)
GREEN        = (  0, 255,   0)
YELLOWGREEN  = ( 55, 200,   0)
LIGHTGRAY    = (107, 108, 145)
PARCHMENT_YELLOW = (207, 146, 83)

# 退出游戏按钮
EXIT = "exit"
HELP = "help"
# 游戏界面可选的菜单
LITTLE_MENU = "littleMenu"
BIG_MENU = "bigMenu"
RESTART_BUTTON = "restartButton"
MAINMENU_BUTTON = "mainMenuButton"
LITTLEGAME_BUTTON = "littleGameButton"
OPTION_BUTTON = "optionButton"
SOUND_VOLUME_BUTTON = "volumeButton"
UNIVERSAL_BUTTON = "universalButton"
# 金银向日葵奖杯
TROPHY_SUNFLOWER = "sunflowerTrophy"
# 小铲子
SHOVEL = "shovel"
SHOVEL_BOX = "shovelBox"
# 一大波僵尸来袭图片
HUGE_WAVE_APPROCHING = "Approching"
# 关卡进程图片
LEVEL_PROGRESS_BAR = "LevelProgressBar"
LEVEL_PROGRESS_ZOMBIE_HEAD = "LevelProgressZombieHead"
LEVEL_PROGRESS_FLAG = "LevelProgressFlag"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
地图搭建(部分)
class Map():
    def __init__(self, background_type:int):
        self.background_type = background_type
        # 注意:从0开始编号
        if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
            self.width = c.GRID_POOL_X_LEN
            self.height = c.GRID_POOL_Y_LEN
            self.grid_height_size = c.GRID_POOL_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_WATER) if 2 <= y <= 3
                                else self.initMapGrid(c.MAP_GRASS)
                            for x in range(self.width)
                            ]
                        for y in range(self.height)
                        ]
        elif self.background_type in c.ON_ROOF_BACKGROUNDS:
            self.width = c.GRID_ROOF_X_LEN
            self.height = c.GRID_ROOF_Y_LEN
            self.grid_height_size = c.GRID_ROOF_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_TILE)
                            for x in range(self.width)
                            ]
                        for y in range(self.height)
                        ]
        elif self.background_type == c.BACKGROUND_SINGLE:
            self.width = c.GRID_X_LEN
            self.height = c.GRID_Y_LEN
            self.grid_height_size = c.GRID_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_GRASS) if y ==2
                                else self.initMapGrid(c.MAP_UNAVAILABLE)
                            for x in range(self.width)
                            ]
                        for y in range(self.height)
                        ]
        elif self.background_type == c.BACKGROUND_TRIPLE:
            self.width = c.GRID_X_LEN
            self.height = c.GRID_Y_LEN
            self.grid_height_size = c.GRID_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_GRASS) if 1 <= y <= 3
                                else self.initMapGrid(c.MAP_UNAVAILABLE)
                            for x in range(self.width)
                            ]
                        for y in range(self.height)
                        ]
        else:
            self.width = c.GRID_X_LEN
            self.height = c.GRID_Y_LEN
            self.grid_height_size = c.GRID_Y_SIZE
            self.map =  [   [self.initMapGrid(c.MAP_GRASS)
                            for x in range(self.width)
                            ]
                        for y in range(self.height)
                        ]

    def isValid(self, map_x:int, map_y:int) -> bool:
        if ((0 <= map_x < self.width)
        and (0 <= map_y < self.height)):
            return True
        return False

    # 地图单元格状态
    # 注意是可变对象,不能直接引用
    # 由于同一格显然不可能种两个相同的植物,所以用集合
    def initMapGrid(self, plot_type:str) -> set:
        return {c.MAP_PLANT:set(), c.MAP_SLEEP:False, c.MAP_PLOT_TYPE:plot_type}

    # 判断位置是否可用
    # 暂时没有写紫卡植物的判断方法
    # 由于紫卡植物需要移除以前的植物,所以可用另外定义一个函数
    def isAvailable(self, map_x:int, map_y:int, plant_name:str) -> bool:
        # 咖啡豆和墓碑吞噬者的判别最为特殊
        if plant_name == c.COFFEEBEAN:
            if (self.map[map_y][map_x][c.MAP_SLEEP]
            and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])):
                return True
            else:
                return False
        if plant_name == c.GRAVEBUSTER:
            if (c.GRAVE in self.map[map_y][map_x][c.MAP_PLANT]
            and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])):
                return True
            else:
                return False
        # 被非植物障碍占据的格子对于一般植物不可种植
        if any((i in c.NON_PLANT_OBJECTS) for i in self.map[map_y][map_x][c.MAP_PLANT]):
            return False
        if self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_GRASS:  # 草地
            # 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
            if plant_name not in c.WATER_PLANTS:
                if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植
                    return True
                elif (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])
                and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
                    return True
                elif ((plant_name == c.PUMPKINHEAD)
                and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):   # 没有南瓜头就能种南瓜头
                    return True
                else:
                    return False
            else:
                return False
        elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_TILE: # 屋顶
            # 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
            if plant_name not in c.WATER_PLANTS:
                if "花盆(未实现)" in self.map[map_y][map_x][c.MAP_PLANT]:
                    if (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])
                    and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
                        if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物
                            return False
                        else:
                            return True
                    elif ((plant_name == c.PUMPKINHEAD)
                    and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):    # 有花盆且没有南瓜头就能种南瓜头
                        return True
                    else:
                        return False
                elif plant_name == "花盆(未实现)": # 这一格本来没有花盆而且新来的植物是花盆,可以种
                    return True
                else:
                    return False
            else:
                return False
        elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_WATER:   # 水里
            if plant_name in c.WATER_PLANTS:   # 是水生植物
                if not self.map[map_y][map_x][c.MAP_PLANT]: # 只有无植物时才能在水里种植水生植物
                    return True
                else:
                    return False
            else:   # 非水生植物,依赖睡莲
                if c.LILYPAD in self.map[map_y][map_x][c.MAP_PLANT]:
                    if (all((i in {c.LILYPAD, c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])
                    and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])):
                        if plant_name in {c.SPIKEWEED, c.POTATOMINE, "花盆(未实现)"}: # 不能在睡莲上种植的植物
                            return False
                        else:
                            return True
                    elif ((plant_name == c.PUMPKINHEAD)
                    and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])):   # 在睡莲上且没有南瓜头就能种南瓜头
                        return True
                    else:
                        return False
                else:
                    return False
        else:   # 不可种植区域
            return False
    
    def getMapIndex(self, x:int, y:int) -> tuple[int, int]:
        if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
            x -= c.MAP_POOL_OFFSET_X
            y -= c.MAP_POOL_OFFSET_Y
            return (x // c.GRID_POOL_X_SIZE, y // c.GRID_POOL_Y_SIZE)
        elif self.background_type in c.ON_ROOF_BACKGROUNDS:
            x -= c.MAP_ROOF_OFFSET_X
            y -= c.MAP_ROOF_OFFSET_X
            grid_x = x // c.GRID_ROOF_X_SIZE
            if grid_x >= 5:
                grid_y = y // c.GRID_ROOF_Y_SIZE
            else:
                grid_y = (y - 20*(6 - grid_x)) // 85
            return (grid_x, grid_y)
        else:
            x -= c.MAP_OFFSET_X
            y -= c.MAP_OFFSET_Y
            return (x // c.GRID_X_SIZE, y // c.GRID_Y_SIZE)
    
    def getMapGridPos(self, map_x:int, map_y:int) -> tuple[int, int]:
        if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
            return (map_x * c.GRID_POOL_X_SIZE + c.GRID_POOL_X_SIZE//2 + c.MAP_POOL_OFFSET_X,
                    map_y * c.GRID_POOL_Y_SIZE + c.GRID_POOL_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y)
        elif self.background_type in c.ON_ROOF_BACKGROUNDS:
            return (map_x * c.GRID_ROOF_X_SIZE + c.GRID_ROOF_X_SIZE//2 + c.MAP_ROOF_OFFSET_X,
                    map_y * c.GRID_ROOF_Y_SIZE + 20 * max(0, (6 - map_y)) + c.GRID_ROOF_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y)
        else:
            return (map_x * c.GRID_X_SIZE + c.GRID_X_SIZE//2 + c.MAP_OFFSET_X,
                    map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)
    
    def setMapGridType(self, map_x:int, map_y:int, plot_type:str):
        self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type

    def addMapPlant(self, map_x:int, map_y:int, plant_name:int, sleep:bool=False):
        self.map[map_y][map_x][c.MAP_PLANT].add(plant_name)
        self.map[map_y][map_x][c.MAP_SLEEP] = sleep
    
    def removeMapPlant(self, map_x:int, map_y:int, plant_name:str):
        self.map[map_y][map_x][c.MAP_PLANT].discard(plant_name)

    def getRandomMapIndex(self) -> tuple[int, int]:
        map_x = random.randint(0, self.width-1)
        map_y = random.randint(0, self.height-1)
        return (map_x, map_y)

    def checkPlantToSeed(self, x:int, y:int, plant_name:str) -> tuple[int, int]:
        pos = None
        map_x, map_y = self.getMapIndex(x, y)
        if self.isValid(map_x, map_y) and self.isAvailable(map_x, map_y, plant_name):
            pos = self.getMapGridPos(map_x, map_y)
        return pos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
定义植物类 (部分)


# 豌豆及孢子类普通子弹
class Bullet(pg.sprite.Sprite):
    def __init__(   self, x:int, start_y:int, dest_y:int, name:str, damage:int,
                    effect:str=None, passed_torchwood_x:int=None,
                    damage_type:str=c.ZOMBIE_DEAFULT_DAMAGE):
        pg.sprite.Sprite.__init__(self)

        self.name = name
        self.frames = []
        self.frame_index = 0
        self.load_images()
        self.frame_num = len(self.frames)
        self.image = self.frames[self.frame_index]
        self.mask = pg.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = start_y
        self.dest_y = dest_y
        self.y_vel = 15 if (dest_y > start_y) else -15
        self.x_vel = 10
        self.damage = damage
        self.damage_type = damage_type
        self.effect = effect
        self.state = c.FLY
        self.current_time = 0
        self.animate_timer = 0
        self.animate_interval = 70
        self.passed_torchwood_x = passed_torchwood_x  # 记录最近通过的火炬树横坐标,如果没有缺省为None

    def loadFrames(self, frames, name):
        frame_list = tool.GFX[name]
        if name in c.PLANT_RECT:
            data = c.PLANT_RECT[name]
            x, y, width, height = data["x"], data["y"], data["width"], data["height"]
        else:
            x, y = 0, 0
            rect = frame_list[0].get_rect()
            width, height = rect.w, rect.h

        for frame in frame_list:
            frames.append(tool.get_image(frame, x, y, width, height))

    def load_images(self):
        self.fly_frames = []
        self.explode_frames = []

        fly_name = self.name
        if self.name in c.BULLET_INDEPENDENT_BOOM_IMG:
            explode_name = f"{self.name}Explode"
        else:
            explode_name = "PeaNormalExplode"

        self.loadFrames(self.fly_frames, fly_name)
        self.loadFrames(self.explode_frames, explode_name)

        self.frames = self.fly_frames

    def update(self, game_info):
        self.current_time = game_info[c.CURRENT_TIME]
        if self.state == c.FLY:
            if self.rect.y != self.dest_y:
                self.rect.y += self.y_vel
                if self.y_vel * (self.dest_y - self.rect.y) < 0:
                    self.rect.y = self.dest_y
            self.rect.x += self.x_vel
            if self.rect.x >= c.SCREEN_WIDTH + 20:
                self.kill()
        elif self.state == c.EXPLODE:
            if (self.current_time - self.explode_timer) > 250:
                self.kill()
        if self.current_time - self.animate_timer >= self.animate_interval:
            self.frame_index += 1
            self.animate_timer = self.current_time
            if self.frame_index >= self.frame_num:
                self.frame_index = 0
            self.image = self.frames[self.frame_index]

    def setExplode(self):
        self.state = c.EXPLODE
        self.explode_timer = self.current_time
        self.frames = self.explode_frames
        self.frame_num = len(self.frames)
        self.image = self.frames[0]
        self.mask = pg.mask.from_surface(self.image)

        # 播放子弹爆炸音效
        if self.name == c.BULLET_FIREBALL:
            c.SOUND_FIREPEA_EXPLODE.play()
        else:
            c.SOUND_BULLET_EXPLODE.play()

    def draw(self, surface):
        surface.blit(self.image, self.rect)

# 大喷菇的烟雾
# 仅有动画效果,不参与攻击运算
class Fume(pg.sprite.Sprite):
    def __init__(self, x, y):
        pg.sprite.Sprite.__init__(self)
        self.name = c.FUME
        self.timer = 0
        self.frame_index = 0
        self.load_images()
        self.frame_num = len(self.frames)
        self.image = self.frames[self.frame_index]
        self.mask = pg.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

    def load_images(self):
        self.fly_frames = []

        fly_name = self.name

        self.loadFrames(self.fly_frames, fly_name)

        self.frames = self.fly_frames

    def draw(self, surface):
        surface.blit(self.image, self.rect)

    def update(self, game_info):
        self.current_time = game_info[c.CURRENT_TIME]
        if self.current_time - self.timer >= 100:
            self.frame_index += 1
            if self.frame_index >= self.frame_num:
                self.frame_index = self.frame_num - 1
                self.kill()
            self.timer = self.current_time
        self.image = self.frames[self.frame_index]

    def loadFrames(self, frames, name):
        frame_list = tool.GFX[name]
        x, y = 0, 0
        rect = frame_list[0].get_rect()
        width, height = rect.w, rect.h

        for frame in frame_list:
            frames.append(tool.get_image(frame, x, y, width, height))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
定义僵尸类(部分)


class Zombie(pg.sprite.Sprite):
    def __init__(   self, x, y, name, head_group=None,
                    helmet_health=0,                helmet_type2_health=0,
                    body_health=c.NORMAL_HEALTH,    losthead_health=c.LOSTHEAD_HEALTH,
                    damage=c.ZOMBIE_ATTACK_DAMAGE,  can_swim=False):
        pg.sprite.Sprite.__init__(self)

        self.name = name
        self.frames = []
        self.frame_index = 0
        self.loadImages()
        self.frame_num = len(self.frames)

        self.image = self.frames[self.frame_index]
        self.rect = self.image.get_rect()
        self.mask = pg.mask.from_surface(self.image)
        self.rect.x = x
        self.rect.bottom = y
        # 大蒜换行移动像素值,< 0时向上,= 0时不变,> 0时向上
        self.target_y_change = 0
        self.original_y = y
        self.to_change_group = False

        self.helmet_health = helmet_health
        self.helmet_type2_health = helmet_type2_health
        self.health = body_health + losthead_health
        self.losthead_health = losthead_health
        self.damage = damage
        self.dead = False
        self.losthead = False
        self.can_swim = can_swim
        self.swimming = False
        self.helmet = (self.helmet_health > 0)
        self.helmet_type2 = (self.helmet_type2_health > 0)
        self.head_group = head_group

        self.walk_timer = 0
        self.animate_timer = 0
        self.attack_timer = 0
        self.state = c.WALK
        self.animate_interval = 150
        self.walk_animate_interval = 180
        self.attack_animate_interval = 100
        self.losthead_animate_interval = 180
        self.die_animate_interval = 50
        self.boomDie_animate_interval = 100
        self.ice_slow_ratio = 1
        self.ice_slow_timer = 0
        self.hit_timer = 0
        self.speed = 1
        self.freeze_timer = 0
        self.losthead_timer = 0
        self.is_hypno = False  # the zombie is hypo and attack other zombies when it ate a HypnoShroom

    def loadFrames(self, frames, name, colorkey=c.BLACK):
        frame_list = tool.GFX[name]
        rect = frame_list[0].get_rect()
        width, height = rect.w, rect.h
        if name in c.ZOMBIE_RECT:
            data = c.ZOMBIE_RECT[name]
            x, width = data["x"], data["width"]
        else:
            x = 0
        for frame in frame_list:
            frames.append(tool.get_image(frame, x, 0, width, height, colorkey))

    def update(self, game_info):
        self.current_time = game_info[c.CURRENT_TIME]
        self.handleState()
        self.updateIceSlow()
        self.animation()

    def handleState(self):
        if self.state == c.WALK:
            self.walking()
        elif self.state == c.ATTACK:
            self.attacking()
        elif self.state == c.DIE:
            self.dying()
        elif self.state == c.FREEZE:
            self.freezing()

    # 濒死状态用函数
    def checkToDie(self, framesKind):
        if self.health <= 0:
            self.setDie()
            return True
        elif self.health <= self.losthead_health:
            if not self.losthead:
                self.changeFrames(framesKind)
                self.setLostHead()
                return True
            else:
                self.health -= (self.current_time - self.losthead_timer) / 40
                self.losthead_timer = self.current_time
                return False
        else:
            return False

    def walking(self):
        if self.checkToDie(self.losthead_walk_frames):
            return

        # 能游泳的僵尸
        if self.can_swim:
            # 在水池范围内
            # 在右侧岸左
            if self.rect.right <= c.MAP_POOL_FRONT_X:
                # 在左侧岸右,左侧岸位置为预估
                if self.rect.right - 25 >= c.MAP_POOL_OFFSET_X:
                    # 还未进入游泳状态
                    if not self.swimming:
                        self.swimming = True
                        self.changeFrames(self.swim_frames)
                        # 播放入水音效
                        c.SOUND_ZOMBIE_ENTERING_WATER.play()
                        # 同样没有兼容双防具
                        if self.helmet:
                            if self.helmet_health <= 0:
                                self.helmet = False
                            else:
                                self.changeFrames(self.helmet_swim_frames)
                        if self.helmet_type2:
                            if self.helmet_type2_health <= 0:
                                self.helmet_type2 = False
                            else:
                                self.changeFrames(self.helmet_swim_frames)
                    # 已经进入游泳状态
                    else:
                        if self.helmet:
                            if self.helmet_health <= 0:
                                self.changeFrames(self.swim_frames)
                                self.helmet = False
                        if self.helmet_type2:
                            if self.helmet_type2_health <= 0:
                                self.changeFrames(self.swim_frames)
                                self.helmet_type2 = False
                # 水生僵尸已经接近家门口并且上岸
                else:
                    if self.swimming:
                        self.changeFrames(self.walk_frames)
                        self.swimming = False
                        # 同样没有兼容双防具
                        if self.helmet:
                            if self.helmet_health <= 0:
                                self.helmet = False
                            else:
                                self.changeFrames(self.helmet_walk_frames)
                        if self.helmet_type2:
                            if self.helmet_type2_health <= 0:
                                self.helmet_type2 = False
                            else:
                                self.changeFrames(self.helmet_walk_frames)
                    if self.helmet:
                        if self.helmet_health <= 0:
                            self.helmet = False
                            self.changeFrames(self.walk_frames)
                    if self.helmet_type2:
                        if self.helmet_type2_health <= 0:
                            self.helmet_type2 = False
                            self.changeFrames(self.walk_frames)
            elif self.is_hypno and self.rect.right > c.MAP_POOL_FRONT_X + 55:   # 常数拟合暂时缺乏检验
                if self.swimming:
                    self.changeFrames(self.walk_frames)
                if self.helmet:
                    if self.helmet_health <= 0:
                        self.changeFrames(self.walk_frames)
                        self.helmet = False
                    elif self.swimming: # 游泳状态需要改为步行
                        self.changeFrames(self.helmet_walk_frames)
                if self.helmet_type2:
                    if self.helmet_type2_health <= 0:
                        self.changeFrames(self.walk_frames)
                        self.helmet_type2 = False
                    elif self.swimming: # 游泳状态需要改为步行
                        self.changeFrames(self.helmet_walk_frames)
                self.swimming = False
            # 尚未进入水池
            else:
                if self.helmet_health <= 0 and self.helmet:
                    self.changeFrames(self.walk_frames)
                    self.helmet = False
                if self.helmet_type2_health <= 0 and self.helmet_type2:
                    self.changeFrames(self.walk_frames)
                    self.helmet_type2 = False
        # 不能游泳的一般僵尸
        else:
            if self.helmet_health <= 0 and self.helmet:
                self.changeFrames(self.walk_frames)
                self.helmet = False
            if self.helmet_type2_health <= 0 and self.helmet_type2:
                self.changeFrames(self.walk_frames)
                self.helmet_type2 = False

        if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
            self.handleGarlicYChange()
            self.walk_timer = self.current_time
            if self.is_hypno:
                self.rect.x += 1
            else:
                self.rect.x -= 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
游戏运行入口

#!/usr/bin/env python
import logging
import traceback
import os
import pygame as pg
from logging.handlers import RotatingFileHandler
# 由于在后续本地模块中存在对pygame的调用,在此处必须完成pygame的初始化
os.environ["SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"]="0" # 设置临时环境变量以避免Linux下禁用x11合成器
pg.init()

from source import tool
from source import constants as c
from source.state import mainmenu, screen, level

if __name__ == "__main__":
    # 日志设置
    if not os.path.exists(os.path.dirname(c.USERLOG_PATH)):
        os.makedirs(os.path.dirname(c.USERLOG_PATH))
    logger = logging.getLogger("main")
    formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
    fileHandler = RotatingFileHandler(c.USERLOG_PATH, "a", 1_000_000, 0, "utf-8")
    # 设置日志文件权限,Unix为644,Windows为可读写;Python的os.chmod与Unix chmod相同,但要显式说明8进制
    os.chmod(c.USERLOG_PATH, 0o644)
    fileHandler.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setFormatter(formatter)
    logger.addHandler(fileHandler)
    logger.addHandler(streamHandler)

    try:
        # 控制状态机运行
        game = tool.Control()
        state_dict = {  c.MAIN_MENU:    mainmenu.Menu(),
                        c.GAME_VICTORY: screen.GameVictoryScreen(),
                        c.GAME_LOSE:    screen.GameLoseScreen(),
                        c.LEVEL:        level.Level(),
                        c.AWARD_SCREEN: screen.AwardScreen(),
                        c.HELP_SCREEN:  screen.HelpScreen(),
                        }
        game.setup_states(state_dict, c.MAIN_MENU)
        game.run()
    except:
        print() # 将日志输出与上文内容分隔开,增加可读性
        logger.error(f"\n{traceback.format_exc()}") 

————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_46277553/article/details/137912235

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值