from typing import Optional import pygame from level.Level import Level from level.LevelManager import LevelManager from level.selection.LevelScreenManager import LevelScreenManager from level.selection.LevelSelectionScreenManager import LevelSelectionScreenManager from level.selection.MainMenuScreenManager import MainMenuScreenManager from level.selection.ScreenManager import ScreenManager from physics import ConstantsParser from physics.SpriteManager import SpriteManager from physics.TickData import TickData from sprite.PositionScale import PositionScale from sprite.SpritesheetManager import SpritesheetManager from ui_elements.ClickEvent import ClickEvent from ui_elements.KeyManager import KeyManager BACKGROUND_IMAGES_UNSCALED = { 'tutorial': pygame.image.load('data/sprites/tutorial_bg.png'), 'castle': pygame.image.load('data/sprites/castle_bg.png'), 'cave': pygame.image.load('data/sprites/cave_bg.png'), } BACKGROUND_IMAGES_SCALED = { 'tutorial': pygame.transform.scale(BACKGROUND_IMAGES_UNSCALED['tutorial'], (1, 1)), 'castle': pygame.transform.scale(BACKGROUND_IMAGES_UNSCALED['castle'], (1, 1)), 'cave': pygame.transform.scale(BACKGROUND_IMAGES_UNSCALED['cave'], (1, 1)), } def apply_frame_rate(number: float, frame_rate: float = 30): """ this function calculates a factor that will be multiplied with the physics of the game to provide a constant speed :param frame_rate: the frame rate of the game :param number: The number to scale by the factor :return: The scaled number """ return number / (frame_rate / 30) class MainLoop: def __init__(self): self.GAME_STATE_MENU = 'main_menu' self.GAME_STATE_LEVEL_SELECTION = 'level_selection' self.GAME_STATE_LEVEL = 'level' self.screen_transform: PositionScale = PositionScale((0, 0), (1.5, 1.5)) self.window_size: tuple[float, float] = (1, 1) pygame.init() pygame.display.set_caption("PM GAME") self.update_position_scale(self.screen_transform) self.screen = self.screen self.clock = pygame.time.Clock() self.frame_rate = 30 self.spritesheet_manager = SpritesheetManager("data/sprites", "data/sprites/sprites.json") self.sprite_manager = SpriteManager() self.key_manager = KeyManager() self.parsed_levels_manager = LevelManager('data/levels') self.parsed_levels_manager.load_from_config('data/levels/levels.json') self.screen_manager: Optional[ScreenManager] = None self.game_state = self.GAME_STATE_MENU self.set_game_state(self.GAME_STATE_MENU) self.level: Optional[Level] = None def update_position_scale(self, position_scale: PositionScale): self.screen_transform = position_scale self.window_size = ( self.screen_transform.scale[0] * ConstantsParser.CONFIG.block_size[0] * ConstantsParser.CONFIG.level_size[0], self.screen_transform.scale[1] * ConstantsParser.CONFIG.block_size[1] * ConstantsParser.CONFIG.level_size[1] ) self.screen = pygame.display.set_mode((self.window_size[0], self.window_size[1])) for key in BACKGROUND_IMAGES_UNSCALED: BACKGROUND_IMAGES_SCALED[key] = pygame.transform.scale( BACKGROUND_IMAGES_UNSCALED[key], self.window_size ) def select_level(self, level: Level): print('Loading level', level.name) self.level = level self.set_game_state(self.GAME_STATE_LEVEL) def select_level_selection(self, theme: str = 'tutorial'): self.set_game_state(self.GAME_STATE_LEVEL_SELECTION) if self.screen_manager is not None and isinstance(self.screen_manager, LevelSelectionScreenManager): self.screen_manager.select_theme(theme) def select_main_menu(self): self.set_game_state(self.GAME_STATE_MENU) def set_game_state(self, game_state: str): self.game_state = game_state if self.screen_manager is not None: self.screen_manager.destroy() if self.game_state == self.GAME_STATE_MENU: self.screen_manager = MainMenuScreenManager( self.sprite_manager, self.spritesheet_manager, self ) elif self.game_state == self.GAME_STATE_LEVEL: self.screen_manager = LevelScreenManager( self.sprite_manager, self.spritesheet_manager, self, self.level ) elif self.game_state == self.GAME_STATE_LEVEL_SELECTION: self.screen_manager = LevelSelectionScreenManager( self.sprite_manager, self.spritesheet_manager, self, self.parsed_levels_manager ) else: print('Invalid game state', self.game_state) if self.screen_manager is not None: self.screen_manager.initialize() def main_loop(self): while True: self.clock.tick(self.frame_rate) pygame_events: list[pygame.event.Event] = pygame.event.get() self.key_manager.update_key_events(pygame_events) click_events: list[ClickEvent] = ClickEvent.create_events(pygame_events, self.screen_transform) for event in click_events: for layer in self.sprite_manager.layers: for sprite in self.sprite_manager.layers[layer]: if sprite.get_bounding_box().contains_point(event.world_position): sprite.click(event) for event in pygame_events: if event.type == pygame.QUIT: pygame.quit() exit() self.screen.fill((0, 0, 0)) tick_data = TickData(apply_frame_rate(1, self.frame_rate), pygame_events, self.key_manager, click_events, self.screen_transform) self.screen_manager.tick(tick_data) self.sprite_manager.tick(tick_data) if self.level is not None and self.game_state == self.GAME_STATE_LEVEL: self.screen.blit(BACKGROUND_IMAGES_SCALED[self.level.theme], (0, 0)) self.sprite_manager.draw(self.screen, self.screen_transform) pygame.display.update() main_loop: MainLoop = MainLoop() main_loop.main_loop()