[PyGame] Best way to pass data to the few game states with the same superclass? - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: Game Development (https://python-forum.io/forum-11.html) +--- Thread: [PyGame] Best way to pass data to the few game states with the same superclass? (/thread-35283.html) |
Best way to pass data to the few game states with the same superclass? - Milosz - Oct-16-2021 Hello, I wonder what is the best way to pass data to the few game states with the same superclass? I'm gonna use state machine with control class. States class is a superclass for Game and MainMenu(and there'll be few more) because they gonna use attributes with the same names. In the project I'm gonna use only one instance of Game and MainMenu class. GameData is a class which loads(from files) and store data like images, sounds etc. and it's attributes are read-only. I'd like to pass GameData object to the Game and MainMenu instances and I wonder, if my solution where I put it in the superclass is correct or there's maybe better way? game_data module: import os import pickle import pygame class GameData: """Loads and share game data: images, sounds and enemies""" def __init__(self) -> None: self.__images = {} self.__sounds = {} pygame.init() # OGARNĄĆ TO !!!!!!!! pygame.mixer.set_num_channels(50) # Dictionary where key is a game level, values are tuples with parameters(x, y, style) # used for creating each 'Enemy' spaceship object self.__enemies_args = {} self.__load_images() self.__load_sounds() self.__load_level_enemies() @property def textures(self): return self.__images @property def sounds(self): return self.__sounds @property def enemies_args(self): return self.__enemies_args def __load_images(self) -> None: """Loads images""" print(os.listdir()) for img in os.listdir('../../resources/img/Other'): if img.endswith('.png'): self.__images[img.replace('.png', '')] = pygame.image.load(f'../../resources/img/Other/{img}') # => surface for img in os.listdir('../../resources/img/Background'): self.__images[img.replace('.png', '')] = pygame.image.load(f'../../resources/img/background/{img}') # => surface def __load_sounds(self) -> None: """Loads sounds""" for sound in os.listdir('../../resources/sounds'): self.__sounds[sound.replace('.wav', '')] = pygame.mixer.Sound(f'../../resources/sounds/{sound}') # => sound def __load_level_enemies(self) -> None: """Loads level enemies from pickle file""" with open('../game_levels.pickle', 'rb') as handle: self.__enemies_args = pickle.load(handle)states module: from game_data import GameData class States: """Superclass for each state""" game_data = GameData() # Is it correct and 'pythonic'? def __init__(self): self.done = False self.next = None self.quit = False self.previous = None class Game(States): def __init__(self): States.__init__(self) def some_method(self): pass # Here I'm gonna use game_data class MainMenu(States): def __init__(self): States.__init__(self) def some_method(self): pass # Here I'm gonna use game_dataOther solution but I think it's incorrect because it's not optimal(2 instances of GameData()): from game_data import GameData class States: """Superclass for each state""" def __init__(self): self.done = False self.next = None self.quit = False self.previous = None class Game(States): def __init__(self): States.__init__(self) self.game_data = GameData() # First instance def some_method(self): pass # Here I'm gonna use self.game_data class MainMenu(States): def __init__(self): States.__init__(self) self.game_data = GameData() # Second instance def some_method(self): pass # Here I'm gonna use self.game_dataOr maybe it should be passed as an argument?: from game_data import GameData class States: """Superclass for each state""" def __init__(self): self.done = False self.next = None self.quit = False self.previous = None class Game(States): def __init__(self, game_data): # As arg States.__init__(self) self.game_data = game_data # Assignment def some_method(self): pass # Here I'm gonna use self.game_data class MainMenu(States): def __init__(self, game_data): # As arg States.__init__(self) self.game_data = game_data # Assignment def some_method(self): pass # Here I'm gonna use self.game_data game_data = GameData() game = Game(game_data) main_menu = MainMenu(game_data)Is one of these solutions correct or it should be done in another way? Maybe just use cfg module and import it in each module? Or maybe i just made a mistake in the beginning and game data shouldn't be stored in a class? RE: Best way to pass data to the few game states with the same superclass? - Windspar - Oct-16-2021 I say everything in a class unless it make sense to use function. Avoid global variables like the plague. Keep it simple. Refactor to code to smallest component. Have it do one job. Have it do it well. You can always use class to store data then pass it to state. I been playing with the idea of GameManager. You just pass it to state/scene. My State/Scene from .timer_engine import TimerEngine class Scene: scenes = {} def __init__(self, manager, name=""): self.manager = manager self.engine = manager.engine self.timer = TimerEngine() if name == "": name = self.__class__.__name__ if name: Scene.scenes[name] = self def engine_update(self): self.timer.update() def on_draw(self, surface): pass def on_event(self, event): pass def on_focus(self, last_scene): pass def on_update(self, delta, ticks): pass def on_quit(self): self.engine.running = False def set_scene(self, scene): if isinstance(scene, Scene): self.engine.next_scene = scene else: self.engine.next_scene = Scene.scenes[scene]My Display Engine import pygame from .timer_engine import Timer class DisplayEngine: def __init__(self, caption, width, height, flags=0): pygame.display.set_caption(caption) self.surface = pygame.display.set_mode((width, height), flags) self.rect = self.surface.get_rect() self.clock = pygame.time.Clock() self.running = False self.delta = 0 self.fps = 60 self._current_scene = None self.next_scene = None self.extension = [] self.ticks = 0 def set_scene(self, scene): self._current_scene = scene def mainloop(self): self.running = True while self.running: if self.next_scene: self.next_scene.on_focus(self._current_scene) self._current_scene = self.next_scene self.next_scene = None if self._current_scene is not None: for event in pygame.event.get(): if event.type == pygame.QUIT: self._current_scene.on_quit() else: self._current_scene.on_event(event) Timer.update_ticks() self.ticks = Timer.ticks self._current_scene.engine_update() self._current_scene.on_draw(self.surface) self._current_scene.on_update(self.delta, self.ticks) else: self.surface.fill('gray10') for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False for ext in self.extension: ext.update(self) pygame.display.flip() self.delta = self.clock.tick(self.fps) * 0.001My Game Manager import pygame from .display_engine import DisplayEngine from .image_manager import ImageManager class GameManager: def __init__(self, caption, width, height, flags=0): pygame.init() self.engine = DisplayEngine(caption, width, height, flags) self.image = None self.data = None def data(self, data): self.data = data def images(self, image_path): self.images = ImageManager(image_path) def run(self): self.engine.mainloop() pygame.quit() def scene(self, scene): if self.engine: self.engine.set_scene(scene(self)) RE: Best way to pass data to the few game states with the same superclass? - Milosz - Oct-16-2021 Thanks for the answer (Oct-16-2021, 03:59 PM)Windspar Wrote: Avoid global variables like the plagueIs it called global variables if I store some stuff in a module and just import it in almost each module? Because I made something like this and for example i use 'SCREEN' variable in almost every other module. Is it wrong way? Why? import pygame from game_data import GameData WIN_SIZE = (1024, 768) FRAMERATE = 80 SCREEN = pygame.display.set_mode(WIN_SIZE) SCREEN_RECT = SCREEN.get_rect() MOVE_RATIO = 30 START_TIME = 140 # Time of 'intro' before each level END_TIME = 180 # Time of 'outro' after each level pygame.init() FONT = pygame.font.Font('../resources/fonts/OpenSans-Bold.ttf', 100) MOVE = pygame.USEREVENT # User event for moving enemy pygame.time.set_timer(MOVE, MOVE_RATIO) game_data = GameData() GFX = game_data.textures SFX = game_data.sounds ENEMIES_ARGS = game_data.enemies_args RE: Best way to pass data to the few game states with the same superclass? - Windspar - Oct-17-2021 Doesn't make it wrong. Just make program less flexible. Frame rate shouldn't be a global or a constant. I Would just wrap it as Setting class. Then make variable in share data class. Then you can just pass one class to handle all data. Good thing about classes. They are reference. So passing is low memory and fast. class Setting: def __init__(self): self.end_time = 180Then I put it in my game manger. class GameManager: def __init__(self, *args, **kwargs): self.setting = Setting() self.game_data = GameData() self.gfx = self.game_data.texturesThen I would pass manager to scene/state. This way every state that inherit will already have access to data. class Scene: def __init__(self, manager): self.manager = manager self.gfx = manager.gfxHope this help you to find your style. Anyone can program. Only a few can program well. RE: Best way to pass data to the few game states with the same superclass? - Milosz - Oct-20-2021 Great idea, thank you. I'm gonna do like you said, but I always think about all possible solutions and compare them. What about using there an inheritance? class Setting: def __init__(self): self.end_time = 180 class GameManager(Setting, GameData): def __init__(self, *args, **kwargs): Setting.__init__() GameData.__init__() RE: Best way to pass data to the few game states with the same superclass? - Windspar - Oct-20-2021 I avoid inheritance unless I'm extending the class. Common gotcha is same variable and/or method name. Last one rules. They also been big discussing about multi inheritance. Most programming language don't even allow it. Like java and d. Example of extending class. Game class need to be refactor thou. |