赞
踩
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"
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
# 豌豆及孢子类普通子弹
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))
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
#!/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()}")
关注vx公众号【苏凉闲谈社】回复“777””即可免费领取游戏源码,同时还为大家准备了相关图书资料、视频资料以及其他python小游戏源码等等。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。