399 lines
16 KiB
Python
399 lines
16 KiB
Python
import os
|
||
|
||
import pygame as pg
|
||
|
||
from .. import constants as c
|
||
from .. import tool
|
||
|
||
|
||
class Menu(tool.State):
|
||
def __init__(self):
|
||
tool.State.__init__(self)
|
||
|
||
def startup(self, current_time: int, persist):
|
||
self.next = c.LEVEL
|
||
self.persist = persist
|
||
self.game_info = persist
|
||
self.setupBackground()
|
||
self.setupOptions()
|
||
self.setupOptionMenu()
|
||
self.setupSunflowerTrophy()
|
||
pg.mixer.music.stop()
|
||
pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, 'intro.opus'))
|
||
pg.mixer.music.play(-1, 0)
|
||
pg.display.set_caption(c.ORIGINAL_CAPTION)
|
||
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
|
||
for i in c.SOUNDS:
|
||
i.set_volume(self.game_info[c.SOUND_VOLUME])
|
||
|
||
def setupBackground(self):
|
||
frame_rect = (80, 0, 800, 600)
|
||
# 1、形参中加单星号,即f(*x)则表示x为元组,所有对x的操作都应将x视为元组类型进行。
|
||
# 2、双星号同上,区别是x视为字典。
|
||
# 3、在变量前加单星号表示将元组(列表、集合)拆分为单个元素。
|
||
# 4、双星号同上,区别是目标为字典,字典前加单星号的话可以得到“键”。
|
||
self.bg_image = tool.get_image(
|
||
tool.GFX[c.MAIN_MENU_IMAGE], *frame_rect
|
||
)
|
||
self.bg_rect = self.bg_image.get_rect()
|
||
self.bg_rect.x = 0
|
||
self.bg_rect.y = 0
|
||
|
||
def setupOptions(self):
|
||
# 冒险模式
|
||
frame_rect = (0, 0, 330, 144)
|
||
# 写成列表生成器方便IDE识别与自动补全
|
||
self.adventure_frames = [
|
||
tool.get_image_alpha(
|
||
tool.GFX[f'{c.OPTION_ADVENTURE}_{i}'], *frame_rect
|
||
)
|
||
for i in range(2)
|
||
]
|
||
self.adventure_image = self.adventure_frames[0]
|
||
self.adventure_rect = self.adventure_image.get_rect()
|
||
self.adventure_rect.x = 400
|
||
self.adventure_rect.y = 60
|
||
self.adventure_highlight_time = 0
|
||
|
||
# 小游戏
|
||
littleGame_frame_rect = (0, 7, 317, 135)
|
||
self.littleGame_frames = [
|
||
tool.get_image_alpha(
|
||
tool.GFX[f'{c.LITTLEGAME_BUTTON}_{i}'], *littleGame_frame_rect
|
||
)
|
||
for i in range(2)
|
||
]
|
||
self.littleGame_image = self.littleGame_frames[0]
|
||
self.littleGame_rect = self.littleGame_image.get_rect()
|
||
self.littleGame_rect.x = 397
|
||
self.littleGame_rect.y = 175
|
||
self.littleGame_highlight_time = 0
|
||
|
||
# 退出按钮
|
||
exit_frame_rect = (0, 0, 47, 27)
|
||
self.exit_frames = [
|
||
tool.get_image_alpha(
|
||
tool.GFX[f'{c.EXIT}_{i}'], *exit_frame_rect, scale=1.1
|
||
)
|
||
for i in range(2)
|
||
]
|
||
self.exit_image = self.exit_frames[0]
|
||
self.exit_rect = self.exit_image.get_rect()
|
||
self.exit_rect.x = 730
|
||
self.exit_rect.y = 507
|
||
self.exit_highlight_time = 0
|
||
|
||
# 选项按钮
|
||
option_button_frame_rect = (0, 0, 81, 31)
|
||
self.option_button_frames = [
|
||
tool.get_image_alpha(
|
||
tool.GFX[f'{c.OPTION_BUTTON}_{i}'], *option_button_frame_rect
|
||
)
|
||
for i in range(2)
|
||
]
|
||
self.option_button_image = self.option_button_frames[0]
|
||
self.option_button_rect = self.option_button_image.get_rect()
|
||
self.option_button_rect.x = 560
|
||
self.option_button_rect.y = 490
|
||
self.option_button_highlight_time = 0
|
||
|
||
# 帮助菜单
|
||
help_frame_rect = (0, 0, 48, 22)
|
||
self.help_frames = [
|
||
tool.get_image_alpha(tool.GFX[f'{c.HELP}_{i}'], *help_frame_rect)
|
||
for i in range(2)
|
||
]
|
||
self.help_image = self.help_frames[0]
|
||
self.help_rect = self.help_image.get_rect()
|
||
self.help_rect.x = 653
|
||
self.help_rect.y = 520
|
||
self.help_hilight_time = 0
|
||
|
||
# 计时器与点击信号记录器
|
||
self.adventure_start = 0
|
||
self.adventure_timer = 0
|
||
self.adventure_clicked = False
|
||
self.option_button_clicked = False
|
||
|
||
def checkHilight(self, x: int, y: int):
|
||
# 高亮冒险模式按钮
|
||
if self.inArea(self.adventure_rect, x, y):
|
||
self.adventure_highlight_time = self.current_time
|
||
# 高亮小游戏按钮
|
||
elif self.inArea(self.littleGame_rect, x, y):
|
||
self.littleGame_highlight_time = self.current_time
|
||
# 高亮退出按钮
|
||
elif self.inArea(self.exit_rect, x, y):
|
||
self.exit_highlight_time = self.current_time
|
||
# 高亮选项按钮
|
||
elif self.inArea(self.option_button_rect, x, y):
|
||
self.option_button_highlight_time = self.current_time
|
||
# 高亮帮助按钮
|
||
elif self.inArea(self.help_rect, x, y):
|
||
self.help_hilight_time = self.current_time
|
||
|
||
# 处理按钮高亮情况
|
||
self.adventure_image = self.chooseHilightImage(
|
||
self.adventure_highlight_time, self.adventure_frames
|
||
)
|
||
self.exit_image = self.chooseHilightImage(
|
||
self.exit_highlight_time, self.exit_frames
|
||
)
|
||
self.option_button_image = self.chooseHilightImage(
|
||
self.option_button_highlight_time, self.option_button_frames
|
||
)
|
||
self.littleGame_image = self.chooseHilightImage(
|
||
self.littleGame_highlight_time, self.littleGame_frames
|
||
)
|
||
self.help_image = self.chooseHilightImage(
|
||
self.help_hilight_time, self.help_frames
|
||
)
|
||
|
||
def chooseHilightImage(self, hilightTime: int, frames):
|
||
if (self.current_time - hilightTime) < 80:
|
||
index = 1
|
||
else:
|
||
index = 0
|
||
return frames[index]
|
||
|
||
def respondAdventureClick(self):
|
||
self.adventure_clicked = True
|
||
self.adventure_timer = self.adventure_start = self.current_time
|
||
self.persist[c.GAME_MODE] = c.MODE_ADVENTURE
|
||
# 播放进入音效
|
||
pg.mixer.music.stop()
|
||
c.SOUND_EVILLAUGH.play()
|
||
c.SOUND_LOSE.play()
|
||
|
||
# 按到小游戏
|
||
def respondLittleGameClick(self):
|
||
self.done = True
|
||
self.persist[c.GAME_MODE] = c.MODE_LITTLEGAME
|
||
# 播放点击音效
|
||
c.SOUND_BUTTON_CLICK.play()
|
||
|
||
# 点击到退出按钮,修改转态的done属性
|
||
def respondExitClick(self):
|
||
self.done = True
|
||
self.next = c.EXIT
|
||
|
||
# 帮助按钮点击
|
||
def respondHelpClick(self):
|
||
self.done = True
|
||
self.next = c.HELP_SCREEN
|
||
|
||
def setupOptionMenu(self):
|
||
# 选项菜单框
|
||
frame_rect = (0, 0, 500, 500)
|
||
self.big_menu = tool.get_image_alpha(
|
||
tool.GFX[c.BIG_MENU], *frame_rect, c.BLACK, 1.1
|
||
)
|
||
self.big_menu_rect = self.big_menu.get_rect()
|
||
self.big_menu_rect.x = 150
|
||
self.big_menu_rect.y = 0
|
||
|
||
# 返回按钮,用字体渲染实现,增强灵活性
|
||
# 建立一个按钮大小的surface对象
|
||
self.return_button = pg.Surface((376, 96))
|
||
self.return_button.set_colorkey(c.BLACK) # 避免多余区域显示成黑色
|
||
self.return_button_rect = self.return_button.get_rect()
|
||
self.return_button_rect.x = 220
|
||
self.return_button_rect.y = 440
|
||
font = pg.font.Font(c.FONT_PATH, 40)
|
||
font.bold = True
|
||
text = font.render('返回游戏', True, c.YELLOWGREEN)
|
||
text_rect = text.get_rect()
|
||
text_rect.x = 105
|
||
text_rect.y = 18
|
||
self.return_button.blit(text, text_rect)
|
||
|
||
# 音量+、音量-
|
||
frame_rect = (0, 0, 39, 41)
|
||
font = pg.font.Font(c.FONT_PATH, 35)
|
||
font.bold = True
|
||
# 音量+
|
||
self.sound_volume_plus_button = tool.get_image_alpha(
|
||
tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
|
||
)
|
||
sign = font.render('+', True, c.YELLOWGREEN)
|
||
sign_rect = sign.get_rect()
|
||
sign_rect.x = 8
|
||
sign_rect.y = -4
|
||
self.sound_volume_plus_button.blit(sign, sign_rect)
|
||
self.sound_volume_plus_button_rect = (
|
||
self.sound_volume_plus_button.get_rect()
|
||
)
|
||
self.sound_volume_plus_button_rect.x = 500
|
||
# 音量-
|
||
self.sound_volume_minus_button = tool.get_image_alpha(
|
||
tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
|
||
)
|
||
sign = font.render('-', True, c.YELLOWGREEN)
|
||
sign_rect = sign.get_rect()
|
||
sign_rect.x = 12
|
||
sign_rect.y = -6
|
||
self.sound_volume_minus_button.blit(sign, sign_rect)
|
||
self.sound_volume_minus_button_rect = (
|
||
self.sound_volume_minus_button.get_rect()
|
||
)
|
||
self.sound_volume_minus_button_rect.x = 450
|
||
# 音量+、-应当处于同一高度
|
||
self.sound_volume_minus_button_rect.y = (
|
||
self.sound_volume_plus_button_rect.y
|
||
) = 250
|
||
|
||
def setupSunflowerTrophy(self):
|
||
# 设置金银向日葵图片信息
|
||
if (
|
||
self.game_info[c.LEVEL_COMPLETIONS]
|
||
or self.game_info[c.LITTLEGAME_COMPLETIONS]
|
||
):
|
||
if (
|
||
self.game_info[c.LEVEL_COMPLETIONS]
|
||
and self.game_info[c.LITTLEGAME_COMPLETIONS]
|
||
):
|
||
frame_rect = (157, 0, 157, 269)
|
||
else:
|
||
frame_rect = (0, 0, 157, 269)
|
||
self.sunflower_trophy = tool.get_image_alpha(
|
||
tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, c.BLACK
|
||
)
|
||
self.sunflower_trophy_rect = self.sunflower_trophy.get_rect()
|
||
self.sunflower_trophy_rect.x = 0
|
||
self.sunflower_trophy_rect.y = 280
|
||
self.sunflower_trophy_show_info_time = 0
|
||
|
||
def checkSunflowerTrophyInfo(self, surface: pg.Surface, x: int, y: int):
|
||
if self.inArea(self.sunflower_trophy_rect, x, y):
|
||
self.sunflower_trophy_show_info_time = self.current_time
|
||
if (self.current_time - self.sunflower_trophy_show_info_time) < 80:
|
||
font = pg.font.Font(c.FONT_PATH, 14)
|
||
if (
|
||
self.game_info[c.LEVEL_COMPLETIONS]
|
||
and self.game_info[c.LITTLEGAME_COMPLETIONS]
|
||
):
|
||
infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮,玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮'
|
||
elif self.game_info[c.LEVEL_COMPLETIONS]:
|
||
infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
|
||
else:
|
||
infoText = f'目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
|
||
infoImg = font.render(infoText, True, c.BLACK, c.LIGHTYELLOW)
|
||
infoImg_rect = infoImg.get_rect()
|
||
infoImg_rect.x = self.sunflower_trophy_rect.x
|
||
infoImg_rect.y = self.sunflower_trophy_rect.bottom - 14
|
||
surface.blit(infoImg, infoImg_rect)
|
||
|
||
def respondOptionButtonClick(self):
|
||
self.option_button_clicked = True
|
||
# 播放点击音效
|
||
c.SOUND_BUTTON_CLICK.play()
|
||
|
||
def showCurrentVolumeImage(self, surface: pg.Surface):
|
||
# 由于音量可变,因此这一内容不能在一开始就结束加载,而应当不断刷新不断显示
|
||
font = pg.font.Font(c.FONT_PATH, 30)
|
||
volume_tips = font.render(
|
||
f'音量:{round(self.game_info[c.SOUND_VOLUME]*100):3}%',
|
||
True,
|
||
c.LIGHTGRAY,
|
||
)
|
||
volume_tips_rect = volume_tips.get_rect()
|
||
volume_tips_rect.x = 275
|
||
volume_tips_rect.y = 247
|
||
surface.blit(volume_tips, volume_tips_rect)
|
||
|
||
def update(
|
||
self,
|
||
surface: pg.Surface,
|
||
current_time: int,
|
||
mouse_pos: list,
|
||
mouse_click,
|
||
):
|
||
self.current_time = self.game_info[c.CURRENT_TIME] = current_time
|
||
|
||
surface.blit(self.bg_image, self.bg_rect)
|
||
surface.blit(self.adventure_image, self.adventure_rect)
|
||
surface.blit(self.littleGame_image, self.littleGame_rect)
|
||
surface.blit(self.exit_image, self.exit_rect)
|
||
surface.blit(self.option_button_image, self.option_button_rect)
|
||
surface.blit(self.help_image, self.help_rect)
|
||
if (
|
||
self.game_info[c.LEVEL_COMPLETIONS]
|
||
or self.game_info[c.LITTLEGAME_COMPLETIONS]
|
||
):
|
||
surface.blit(self.sunflower_trophy, self.sunflower_trophy_rect)
|
||
|
||
# 点到冒险模式后播放动画
|
||
if self.adventure_clicked:
|
||
# 乱写一个不用信号标记的循环播放 QwQ
|
||
if ((self.current_time - self.adventure_timer) // 150) % 2:
|
||
self.adventure_image = self.adventure_frames[1]
|
||
else:
|
||
self.adventure_image = self.adventure_frames[0]
|
||
if (self.current_time - self.adventure_start) > 3200:
|
||
self.done = True
|
||
# 点到选项按钮后显示菜单
|
||
elif self.option_button_clicked:
|
||
surface.blit(self.big_menu, self.big_menu_rect)
|
||
surface.blit(self.return_button, self.return_button_rect)
|
||
surface.blit(
|
||
self.sound_volume_plus_button,
|
||
self.sound_volume_plus_button_rect,
|
||
)
|
||
surface.blit(
|
||
self.sound_volume_minus_button,
|
||
self.sound_volume_minus_button_rect,
|
||
)
|
||
self.showCurrentVolumeImage(surface)
|
||
if mouse_pos:
|
||
# 返回
|
||
if self.inArea(self.return_button_rect, *mouse_pos):
|
||
self.option_button_clicked = False
|
||
c.SOUND_BUTTON_CLICK.play()
|
||
# 音量+
|
||
elif self.inArea(
|
||
self.sound_volume_plus_button_rect, *mouse_pos
|
||
):
|
||
self.game_info[c.SOUND_VOLUME] = round(
|
||
min(self.game_info[c.SOUND_VOLUME] + 0.05, 1), 2
|
||
)
|
||
# 一般不会有人想把音乐和音效分开设置,故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用
|
||
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
|
||
for i in c.SOUNDS:
|
||
i.set_volume(self.game_info[c.SOUND_VOLUME])
|
||
c.SOUND_BUTTON_CLICK.play()
|
||
self.saveUserData()
|
||
# 音量-
|
||
elif self.inArea(
|
||
self.sound_volume_minus_button_rect, *mouse_pos
|
||
):
|
||
self.game_info[c.SOUND_VOLUME] = round(
|
||
max(self.game_info[c.SOUND_VOLUME] - 0.05, 0), 2
|
||
)
|
||
# 一般不会有人想把音乐和音效分开设置,故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用
|
||
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
|
||
for i in c.SOUNDS:
|
||
i.set_volume(self.game_info[c.SOUND_VOLUME])
|
||
c.SOUND_BUTTON_CLICK.play()
|
||
self.saveUserData()
|
||
# 没有点到前两者时常规行检测所有按钮的点击和高亮
|
||
else:
|
||
# 先检查选项高亮预览
|
||
x, y = pg.mouse.get_pos()
|
||
self.checkHilight(x, y)
|
||
if (
|
||
self.game_info[c.LEVEL_COMPLETIONS]
|
||
or self.game_info[c.LITTLEGAME_COMPLETIONS]
|
||
):
|
||
self.checkSunflowerTrophyInfo(surface, x, y)
|
||
if mouse_pos:
|
||
if self.inArea(self.adventure_rect, *mouse_pos):
|
||
self.respondAdventureClick()
|
||
elif self.inArea(self.littleGame_rect, *mouse_pos):
|
||
self.respondLittleGameClick()
|
||
elif self.inArea(self.option_button_rect, *mouse_pos):
|
||
self.respondOptionButtonClick()
|
||
elif self.inArea(self.exit_rect, *mouse_pos):
|
||
self.respondExitClick()
|
||
elif self.inArea(self.help_rect, *mouse_pos):
|
||
self.respondHelpClick()
|