#!~/.pyenv/versions/3.11.6/bin/python # # Copyright (c) 2024 Cutieguwu | Olivia Brooks # # -*- coding:utf-8 -*- # @Title: Intro to Pygame. # @Author: Cutieguwu | Olivia Brooks # @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca # @Description: Getting started with using PyGame engine. # # @Script: main.py # @Date Created: 22 Mar, 2024 # @Last Modified: 19 Jun, 2024 # @Last Modified by: Cutieguwu | Olivia Brooks # ---------------------------------------------------------- # Load lib.system to ensure everything is good to go dependency and system config wise. from lib import system # Knowing that all external dependencies are acessible, import the rest. import pygame, json, threading from pygame import freetype, mixer # Requires explicit importing. from icecream import ic from time import sleep from lib import interface, level, entity, section # Configure IceCream a bit. # Use for release version. def debug(msg, file="debug.txt"): open(system.DIRWORKING + "/logs/" + file, "w") # Build a debug file if one doesn't exist. with open(system.DIRWORKING + "/logs/" + file, "a") as f: # Append each debugging report to file f.write(msg + "\n") ic.configureOutput(prefix="DEBUG | ", outputFunction=debug, includeContext=True) #ic.configureOutput(prefix="DEBUG | ") class Game(): def __init__(self): self.initOn = True self.initSkip = False self.clock = pygame.time.Clock() # Set game icon for system. gameIcon = pygame.image.load(system.DIRWORKING + "/data/image/game/icon.png") pygame.display.set_icon(gameIcon) self.VERSION = "0.0.1-alpha" pygame.display.set_caption(f"Shroom of Doom: The Massacring Mushroom v{self.VERSION}") self.SETTINGS = {} self.load_settings(reset=False) # Initialise the window. self.WINDOW = self.Window( self, self.SETTINGS["window"]["size"]["width"], self.SETTINGS["window"]["size"]["height"] ) self.bgFill = (255, 255, 255) self.WINDOW.window.fill(self.bgFill) self.SCALE = self.Scale(game=self) self.LEVEL = level.Level(self) # Set up a font freetype.init() self.REPORTFONT = freetype.Font(system.DIRWORKING + "/data/font/Domestic_Manners.ttf", self.WINDOW.height * 0.03) # Start audio mixer mixer.init() # Prep for BS loading screen. self.gameProgressBars = [] self.bgImages = [] self.bgImagesIndex = 0 # Add more images to animate bg. for imageDir in [ "/data/image/game/blocks/wool_colored_gray.png" ]: image = pygame.image.load(system.DIRWORKING + imageDir) image = pygame.transform.scale(image, (self.WINDOW.width, self.WINDOW.height)) self.bgImages.append(image) # The following variables are created so that the progress bar is initialized to display immediately with the loading screen. # If not listed, can run into a condition where these are undefined as the thread hasn't reached defining them fast enough. self.initJobsComplete = 0 self.initJobsTotal = 1 self.initJobsImportIncomplete = 3 # Variable posessing the total number of BS Import report groups to fix scaling on progress bar if init is sped up. self.loadStatus = "" self.is_initSkipFirst = False # Variable describing if it is the first time that update_report() has seen the initSkip. self.initProgressBar = interface.Progress_bar( self, 5, 90, 90, [ pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/mushroom_silhouette_1.png"), pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/mushroom_silhouette_2.png") ], pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_left.png"), pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_right.png"), pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_clear.png"), pygame.image.load(system.DIRWORKING + "/data/image/ui/screen_init/init_bar_fill.png") ) windowRefresh = threading.Thread(target=self.init_jobs) # Create a thread to run the mostly bs readouts. windowRefresh.start() # Start that thread. p = 0 # Number of times that pygame.K_p has been pressed. # Event handler and display rendering must be done on master thread due to pygame structure. while self.initOn: self.clock.tick(30) # Run at a lower framerate to reduce system resource consumption when not needed. self.draw_background() # Update objects on screen. pygame.display.flip() # Event handler for event in pygame.event.get(): # Short event handler to allow the game to be exited during startup. if event.type == pygame.QUIT: # Cannot currently kill thread. Must let it run out, otherwise employ spaghetti code in its target function. self.initOn = False self.gameOn = False raise SystemExit elif event.type == pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) elif event.type == pygame.KEYDOWN: if event.key == pygame.K_p: p = p + 1 if p == 2: # Cannot currently kill thread. Must let it run out, otherwise employ spaghetti code in its target function. self.initSkip = True # Clean memory del self.initJobsComplete, self.initJobsTotal, self.loadStatus, self.is_initSkipFirst, self.initSkip, self.initProgressBar, self.bgImagesIndex, self.bgImages, self.textRendered def init_jobs(self): """ The actual init while the main thread handles the window rendering. """ # Dicts in list to alow iteration as dicts not iterable in python 3.3.1 self.reportsImports1 = [ {"report": "pygame", "time": 0.25}, {"report": "pygame.freetype", "time": 0.25}, {"report": "icecream.ic", "time": 0.25}, {"report": "json", "time": 0.25}, {"report": "threading", "time": 0.5}, {"report": "math", "time": 0.5}, {"report": "time.sleep ;)", "time": 0.5}, {"report": "levels", "time": 0.25}, {"report": "entities", "time": 0.25}, {"report": "system", "time": 0.25}, {"report": "urmom", "time": 0.5}, {"report": "sections.Section", "time": 0.4}, {"report": "scenes.Scene", "time": 0.25}, {"report": "smingusdinglebus.Dinglebob", "time": 0.5}, {"report": "pills.BLUE - Yes, this is all a lie.", "time": 1}, {"report": "thebritish.Food.OldeTimeFishandChips - Wait until I'm done lying.", "time": 1}, {"report": "Cannot find randos-free-ram-bin in system repos", "time": 2}, {"report": "Cannot find randos-free-ram-git in system repos", "time": 2} ] self.reportsImports2 = [ {"report": "001%", "time": 2}, {"report": "002%", "time": 1}, {"report": "003%", "time": 2}, {"report": "030%", "time": 0.5}, {"report": "031%", "time": 0.5}, {"report": "099%", "time": 4}, {"report": "105%", "time": 2}, {"report": "400%", "time": 3}, {"report": "666%", "time": 3}, {"report": "6546588795009842089420845126960248328684206489564465987951613174159489636682%", "time": 0.75} ] self.reportsImports3 = [ {"report": "Decompressing with gzip", "time": 1}, {"report": "Running build", "time": 1}, {"report": "Aquiring additional permissions without permission", "time": 2}, {"report": "Running makefile", "time": 1}, {"report": "Build Complete.", "time": 2} ] self.initJobsTotal = len(self.reportsImports1) + len(self.reportsImports2) + len(self.reportsImports3) + 7 # 7 additional config reports. self.initJobsComplete = 0 for i in range(0, 3): # Report all import lies. self.update_init_load("import", i) self.update_init_load("config", 0) self.update_init_load("config", 1) # Bad practice, but overwrite the class to load the textures once only. section.Textures = section.Textures(self.SCALE.unitBlockPx) self.update_init_load("config", 2) self.PLAYER = entity.Player(self) self.update_init_load("config", 3) self.entityList = [] self.update_init_load("config", 4) # Temp hardcoded player control buttons. self.controlsPlayerMovement = { "run_u": [pygame.K_UP, pygame.K_w], "run_d": [pygame.K_DOWN, pygame.K_s], "run_l": [pygame.K_LEFT, pygame.K_a], "run_r": [pygame.K_RIGHT, pygame.K_d], "dash": [pygame.K_LSHIFT, pygame.K_RSHIFT] } self.controlsPlayerInteraction = { "trigger": [pygame.K_RETURN, pygame.K_e] } self.update_init_load("config", 5) self.MENU_FUNCTIONS = interface.Interface_Functions(game=self) self.update_init_load("config", 6) self.gameInteractables = [] # Contains button classes that must have their conditions updated. self.LEVEL.load_layout(level.Scenes.Intro) # Temp override for user settings self.WINDOW.auto_configure() self.update_init_load("config", 7) self.gameOn = True self.initOn = False def update_init_load(self, type:str, item:int = 0): """ Initial load reports and lies. """ try: if not self.initSkip: if system.FEATURES["matchcase"]["is_active"]: match type: case "import": match item: case 0: reports = self.reportsImports1 msg = "Importing Libraries: " case 1: reports = self.reportsImports2 msg = "Importing Libraries: ftp://www.ram.sus/downloads/nightlybuilds - Cloning... " case 2: reports = self.reportsImports3 msg = "Importing Libraries: randos-free-ram - " case _: pass self.initJobsImportIncomplete = self.initJobsImportIncomplete - 1 case "config": msg = "Configuration: " match item: case 0: reports = [{"report": "Not entirely a lie... if only.", "time": 2}] case 1: reports = [{"report": "Section - Textures", "time": 0.25}] case 2: reports = [{"report": "Entity - Player", "time": 0.25}] case 3: reports = [{"report": "Entity - Other", "time": 0.25}] case 4: reports = [{"report": "Game - Controls", "time": 0.25}] case 5: reports = [{"report": "Interface - Control Functions", "time": 0.25}] case 6: reports = [{"report": "Loading Menu Interface...", "time": 0.25}] case 7: # Final report time is very quick as a manual wait is applied afterwards so that there is time for reader to register the "Complete." on display. reports = [{"report": "Complete.", "time": 2}] case _: pass case _: reports = [{"report": "Skipping BS", "time": 0.1}] msg = "ha ha, you typed pp: " self.update_report(reports, msg) else: if type == "import": if item == 0: reports = self.reportsImports1 msg = "Importing Libraries: " elif item == 1: reports = self.reportsImports2 msg = "Importing Libraries: ftp://www.ram.sus/downloads/nightlybuilds - Cloning... " elif item == 2: reports = self.reportsImports3 msg = "Importing Libraries: randos-free-ram - " self.initJobsImportIncomplete = self.initJobsImportIncomplete - 1 elif type == "config": msg = "Configuration: " if item == 0: reports = [{"report": "Not entirely a lie... if only.", "time": 2}] elif item == 1: reports = [{"report": "Section - Textures", "time": 0.5}] elif item == 2: reports = [{"report": "Entity - Player", "time": 0.5}] elif item == 3: reports = [{"report": "Entity - Other", "time": 0.25}] elif item == 4: reports = [{"report": "Game - Controls", "time": 0.5}] elif item == 5: reports = [{"report": "Interface - Control Functions", "time": 1}] elif item == 6: reports = [{"report": "Loading Menu Interface...", "time": 1}] elif item == 7: # Final report time is very quick as a manual wait is applied afterwards so that there is time for reader to register the "Complete." on display. reports = [{"report": "Complete.", "time": 1}] else: reports = [{"report": "Skipping BS", "time": 0.1}] msg = "ha ha, you typed pp: " self.update_report(reports, msg) except AttributeError: # If game stopped during init, avoid erroring out. pass def update_report(self, reports, msg): """ Updates the displayed reports. """ for report in reports: if self.is_initSkipFirst: # If skipping and done the action below, just pass. Nothing to waste time processing. pass elif self.initSkip and not self.is_initSkipFirst: # If supposed to skip BS, but in the middle of a series of reports, recalculate and skip. Only ic skipping report to sdtout once. self.initJobsTotal = self.initJobsComplete + 8 + self.initJobsImportIncomplete # Clean memory del self.initJobsImportIncomplete, self.reportsImports1, self.reportsImports2, self.reportsImports3 report = {"report": "Skipping BS", "time": 0.1} msg = "ha ha, you typed pp: " self.loadStatus = msg + report["report"] ic(self.loadStatus) self.is_initSkipFirst = True break else: # Otherwise ic the bs reports. self.loadStatus = msg + report["report"] ic(self.loadStatus) sleep(report["time"]) self.initJobsComplete = self.initJobsComplete + 1 def draw_background(self): """ Sets and redraws the background. """ if self.initOn: self.bgImagesIndex = self.bgImagesIndex + 1 if self.bgImagesIndex >= len(self.bgImages): self.bgImagesIndex = 0 self.WINDOW.window.blit(self.bgImages[self.bgImagesIndex], (0, 0)) self.initProgressBar.draw(self.initJobsComplete, self.initJobsTotal) # Render and display the mostly bs reports. self.textRendered = self.REPORTFONT.render(self.loadStatus, (255, 255, 255))[0] self.WINDOW.window.blit( self.textRendered, ( self.initProgressBar.x + self.initProgressBar.barImages["left"].get_width(), self.initProgressBar.y - self.textRendered.get_height() ) ) else: # Clear window if self.bgFill != self.LEVEL.layout.BG_FILL: # If the background has changed (may or may not change between levels) self.bgFill = self.LEVEL.layout.BG_FILL # Update it locally. if isinstance(self.bgFill, pygame.surface.Surface): # If it's an image, scale it accordingly. self.bgFill = pygame.transform.scale(self.bgFill, (self.WINDOW.width, self.WINDOW.height)) if isinstance(self.bgFill, tuple): # If background is a solid colour fill self.WINDOW.window.fill(self.bgFill) else: # Else background is an image fill. self.WINDOW.window.blit(self.bgFill, (0, 0)) def load_settings(self, reset:bool = False): """ Loads user settings from file. """ if reset is False: # If not told to reset settings to default, load user settings. with open(system.DIRWORKING + "/config/settings/user.json") as f: self.SETTINGS = json.load(f) else: with open(system.DIRWORKING + "/config/settings/default.json") as f: self.INFO["settings"] = json.load(f) ic(self.SETTINGS) def run(self): """ Main run loop. """ self.playerActionMove = "neutral" self.levelLoad = False self.menuOn = True # Required for self.update_framerate_average #self.framerates = [] while self.gameOn: # For menus that pause the game, self.levelLoad must be made True on resume. if self.levelLoad: self.LEVEL.load_level_state() self.levelLoad = False if system.FEATURES["matchcase"]["is_active"]: match self.LEVEL.layout.TYPE: case "level": self.levelOn = True while self.levelOn: self.clock.tick(self.WINDOW.framerate) if self.PLAYER.health <= 0: # Player died, end game. self.levelOn = False self.LEVEL.load_layout(level.Scenes.Player_Died) break self.draw_background() self.LEVEL.draw() self.PLAYER.draw(self.playerActionMove) if len(self.entityList) != 0: # There are still entities to kill. for entity in self.entityList: if entity.health > 0: # Draw entities that are alive.. entity.draw() else: entity.destroy() # Remove entities that have died. else: # All entities killed, end game. self.LEVEL.load_layout(self.LEVEL.layout.FOLLOW_UP_LEVEL) # Render Framerate textRendered = self.REPORTFONT.render(str(self.clock.get_fps()), (255, 255, 255))[0] self.WINDOW.window.blit(textRendered, (0, 0)) # Update objects on screen pygame.display.flip() # Event handler for event in pygame.event.get(): match event.type: case pygame.QUIT: self.MENU_FUNCTIONS.exit_to_desktop() case pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) case pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: # Pause game on ESC self.LEVEL.load_layout(level.Menus.Pause) else: for controlsDict in [ self.controlsPlayerMovement, self.controlsPlayerInteraction ]: for action, keyList in controlsDict.items(): # Check movement triggers if event.key in keyList: # Key pressed is associated with a movement action. match controlsDict: case self.controlsPlayerMovement: if action in ["run_l", "run_r", "run_u", "run_d"]: self.playerActionMove = action elif action == "dash": self.PLAYER.dash(self.playerActionMove) case self.controlsPlayerInteraction: if action == "trigger": self.PLAYER.interact() case pygame.KEYUP: if self.LEVEL.layout.HAS_PLAYER: # If layout shows a player for controlsDict in [ self.controlsPlayerMovement, self.controlsPlayerInteraction ]: for action, keyList in controlsDict.items(): # Check movement triggers if event.key in keyList: # Key released is associated with a movement action. if controlsDict == self.controlsPlayerMovement: if action == self.playerActionMove: # Only if the most recent movement action key was released self.playerActionMove = "neutral" # stop the player. case pygame.MOUSEBUTTONDOWN: if 1 in pygame.mouse.get_pressed(): self.PLAYER.attack() case pygame.MOUSEWHEEL: # Scale the level if self.SETTINGS["controls"]["mouse"]["is_inverted"]: scaleNew = self.SCALE.scaleGame + (event.y * 2) else: scaleNew = self.SCALE.scaleGame + (event.y * 2) if scaleNew in self.SCALE.scaleRange: # Limit the scaling range (limit level zoom). self.SCALE.scaleGame = scaleNew ic(self.SCALE.update_units_game()) ic(self.PLAYER.update_scale()) for entity in self.entityList: entity.update_scale() section.Textures.update_scale(self.SCALE.unitBlockPx) self.LEVEL.update_deltas_relative_to_player() if isinstance(self.bgFill, pygame.surface.Surface): # Update the scaling of the bgFill if it's an image fill. width = self.WINDOW.width / self.GAME.gameX height = self.WINDOW.height / self.GAME.gameY pygame.transform.scale(self.bgFill, (width, height)) case _: # Except all other events. pass case "menu": self.menuOn = True while self.menuOn: self.clock.tick(self.WINDOW.framerate) self.draw_background() self.LEVEL.draw() if self.LEVEL.layout.HAS_PLAYER: self.PLAYER.draw() # Refresh interactables. for interactable in self.gameInteractables: interactable.draw() pygame.display.flip() for event in pygame.event.get(): match event.type: case pygame.QUIT: # Exit game. self.MENU_FUNCTIONS.exit_to_desktop() case pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) case pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: if isinstance(self.LEVEL.layout, level.Menus.Pause): self.MENU_FUNCTIONS.return_to_game() case pygame.MOUSEBUTTONDOWN: if 1 in pygame.mouse.get_pressed(): for interactable in self.gameInteractables: if interactable.is_hovered(): if interactable.functionArgs is not None: interactable.function(interactable.functionArgs) else: interactable.function() case _: pass case "scene": self.sceneOn = True escapes = 0 while self.sceneOn: self.clock.tick(self.WINDOW.framerate) self.LEVEL.FRAMES.draw() pygame.display.flip() for event in pygame.event.get(): match event.type: case pygame.QUIT: # Exit game. self.MENU_FUNCTIONS.exit_to_desktop() case pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) self.LEVEL.FRAMES.update_scale() case pygame.KEYDOWN: if self.LEVEL.layout.IS_SKIPPABLE and event.key == pygame.K_ESCAPE: if escapes == 1: self.LEVEL.FRAMES.skip() else: escapes = escapes + 1 case _: pass elif self.LEVEL.layout.TYPE == "level": # Run the general level rederer and event handler. self.levelOn = True while self.levelOn: self.clock.tick(self.WINDOW.framerate) if self.PLAYER.health <= 0: # Player died, end game. self.LEVEL.load_layout(level.Scenes.Player_Died) self.draw_background() self.LEVEL.draw() self.PLAYER.draw(self.playerActionMove) if len(self.entityList) != 0: for entity in self.entityList: if entity.health > 0: entity.draw() else: entity.destroy() else: # All entities killed, end game. self.LEVEL.load_layout(self.LEVEL.layout.FOLLOW_UP_LEVEL) # Render Framerate textRendered = self.REPORTFONT.render(str(self.clock.get_fps()), (255, 255, 255))[0] self.WINDOW.window.blit(textRendered, (0, 0)) # Update objects on screen pygame.display.flip() # Event handler for event in pygame.event.get(): if event.type == pygame.QUIT: self.MENU_FUNCTIONS.exit_to_desktop() elif event.type == pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: # Pause game on ESC self.LEVEL.load_layout(level.Menus.Pause) else: for controlsDict in [ self.controlsPlayerMovement, self.controlsPlayerInteraction ]: for action, keyList in controlsDict.items(): # Check movement triggers if event.key in keyList: # Key pressed is associated with a movement action. if controlsDict == self.controlsPlayerMovement: if action in ["run_l", "run_r", "run_u", "run_d"]: self.playerActionMove = action # Keep track of the current movement action. ic(self.playerActionMove) elif action == "dash": self.PLAYER.dash(self.playerActionMove) elif controlsDict == self.controlsPlayerInteraction: if action == "trigger": self.PLAYER.interact() elif event.type == pygame.KEYUP: if self.LEVEL.layout.HAS_PLAYER: # If layout shows a player for controlsDict in [ self.controlsPlayerMovement, self.controlsPlayerInteraction ]: for action, keyList in controlsDict.items(): # Check movement triggers if event.key in keyList: # Key released is associated with a movement action. if controlsDict == self.controlsPlayerMovement: if action == self.playerActionMove: # Only if the most recent movement action key was released self.playerActionMove = "neutral" # stop the player. elif event.type == pygame.MOUSEBUTTONDOWN: if 1 in pygame.mouse.get_pressed(): self.PLAYER.attack() elif event.type == pygame.MOUSEWHEEL: # Scale the level if self.SETTINGS["controls"]["mouse"]["is_inverted"]: scaleNew = self.SCALE.scaleGame + (event.y * 2) else: scaleNew = self.SCALE.scaleGame + (event.y * 2) if scaleNew in self.SCALE.scaleRange: # Limit the scaling range (limit level zoom). self.SCALE.scaleGame = scaleNew ic(self.SCALE.update_units_game()) ic(self.PLAYER.update_scale()) for entity in self.entityList: entity.update_scale() section.Textures.update_scale(self.SCALE.unitBlockPx) self.LEVEL.FRAMES.update_scale() self.LEVEL.update_deltas_relative_to_player() if isinstance(self.bgFill, pygame.surface.Surface): # Update the scaling of the bgFill if it's an image fill. width = self.WINDOW.width / self.GAME.gameX height = self.WINDOW.height / self.GAME.gameY pygame.transform.scale(self.bgFill, (width, height)) elif self.LEVEL.layout.TYPE == "menu": # Run the general menu renderer and event handler. self.menuOn = True while self.menuOn: self.clock.tick(self.WINDOW.framerate) self.draw_background() self.LEVEL.draw() if self.LEVEL.layout.HAS_PLAYER: self.PLAYER.draw() # Refresh interactables. for interactable in self.gameInteractables: interactable.draw() pygame.display.flip() for event in pygame.event.get(): if event.type == pygame.QUIT: # Exit game. self.MENU_FUNCTIONS.exit_to_desktop() elif event.type == pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: if isinstance(self.LEVEL.layout, level.Menus.Pause): self.MENU_FUNCTIONS.return_to_game() elif event.type == pygame.MOUSEBUTTONDOWN: if 1 in pygame.mouse.get_pressed(): for interactable in self.gameInteractables: if interactable.is_hovered(): if interactable.functionArgs is not None: interactable.function(interactable.functionArgs) else: interactable.function() elif self.LEVEL.layout.TYPE == "scene": # Run the general scene renderer and event handler. self.sceneOn = True escapes = 0 while self.sceneOn: self.clock.tick(self.WINDOW.framerate) self.LEVEL.FRAMES.draw() pygame.display.flip() for event in pygame.event.get(): if event.type == pygame.QUIT: # Exit game. self.MENU_FUNCTIONS.exit_to_desktop() elif event.type == pygame.VIDEORESIZE: self.WINDOW.update_window(event.w, event.h) elif event.type == pygame.KEYDOWN: if self.LEVEL.layout.IS_SKIPPABLE and event.key == pygame.K_ESCAPE: if escapes == 1: self.LEVEL.FRAMES.skip() else: escapes = escapes + 1 def update_framerate_average(self): framerateAverage = 0 for i in self.framerates: framerateAverage += i return framerateAverage / len(self.framerates) class Window(): """ Creates a window. """ def __init__(self, game, width:int = 1440, height:int = 720, framerate:int = 30): self.GAME = game self.width = width self.height = height self.framerate = framerate self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE) def auto_configure(self): """ Autoconfigures display settings. """ system.update_framerate() framerate = system.DISPLAY["framerate"] ic("Detected highest framerate for this window as", framerate) ic("Applying...") self.framerate = framerate self.GAME.clock.tick(self.framerate) ic("Done.") def toggle_fullscreen(self): """ Toggles window fullscreening. Returns False if cannot. """ # Isolate Linux platforms as this method has always worked as get_driver has supported x11 and wayland since pagame v1. if system.PLATFORM["kernel"] == "Linux" or system.PLATFORM["libraries"]["pygame"] >= (2, 0, 0): pygame.display.toggle_fullscreen() return True else: ic("Cannot enter fullscreen. Please update pygame to v2.0.0 or newer.") return False def toggle_vsync(self): """ Sets up vsync if possible. Falls back to pseudo vsync from system library. """ if system.FEATURES["vsync"]["is_active"]: self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE, vsync=1) else: system.update_framerate() def update_window(self, width, height): """ Updates the window. """ # Window resizing handling changed in pygame v2 to be handled automatically. if system.FEATURES["resizeable"]["is_active"]: self.width = self.window.get_width() self.height = self.window.get_height() else: self.width = width self.height = height if system.FEATURES["vsync"]["is_active"]: self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE, vsync=1) else: self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE) self.GAME.SCALE.update_units_game() class Scale(): """ Handles all globally referenced scaling. """ def __init__(self, game): self.GAME = game self.WINDOW = self.GAME.WINDOW self.scaleGlobal = 2 # Adjusts other scales for HiDPI screens. self.scaleGame = 36 * self.scaleGlobal self.scaleUi = 1 * self.scaleGlobal self.scaleRange = range(int("%.0f" % (self.scaleGame * (1/2))), int("%.0f" % (self.scaleGame * 2))) self.update_units_game() def update_units_game(self): """ Gets game units. """ self.gameY = (1 / self.scaleGame) * self.WINDOW.height # Blocks along Y if (self.WINDOW.height / self.gameY) % 2 != 0: # If not filling full screen, add a block. self.gameY = int((self.gameY // 1) + 2) # Produce a clean integer. else: self.gameY = int((self.gameY // 1) + 1) self.unitBlockPx = self.WINDOW.height / self.gameY # Defines the edge length in pixels of a section. if (self.unitBlockPx % 2) != 0: # Is not an integer. self.unitBlockPx = (self.unitBlockPx // 1) + 1 # Round up. self.gameX = self.WINDOW.width / self.unitBlockPx # Blocks along X if (self.WINDOW.width / self.gameX) % 2 != 0: # If not filling full screen, add a block. self.gameX = int((self.gameX // 1) + 2) # Produce a clean integer. else: self.gameX = int((self.gameX // 1) + 1) game = Game() game.run() pygame.quit()