Python Forum
[PyGame] Best way to pass data to the few game states with the same superclass?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] Best way to pass data to the few game states with the same superclass?
#1
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_data
Other 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_data
Or 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?
Reply
#2
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.001
My 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))
Milosz likes this post
99 percent of computer problems exists between chair and keyboard.
Reply
#3
Thanks for the answer Smile
(Oct-16-2021, 03:59 PM)Windspar Wrote: Avoid global variables like the plague
Is 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
Reply
#4
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 = 180
Then 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.textures
Then 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.gfx
Hope this help you to find your style.
Anyone can program. Only a few can program well.
Milosz likes this post
99 percent of computer problems exists between chair and keyboard.
Reply
#5
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__()
Reply
#6
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.
99 percent of computer problems exists between chair and keyboard.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyGame] Variables shared between game states, how to? michael1789 5 3,398 Dec-10-2019, 10:09 PM
Last Post: metulburr

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020