Return to repo list

heart-of-gold

Tactical RPG written in python, using pygame.
Return to HMagellan.com

commit 5d338784416de295f706106f4776916e147bbfc8
parent 1246f52201a78677f16e7e7ac87ae9e940bb2c41
Author: Erik Letson <hmagellan@hmagellan.com>
Date:   Sat, 24 Oct 2020 16:37:20 -0500

Reimagined subsystems and managers

Diffstat:
Mdata/json/menus/mainmenu.json | 6+++---
Mdata/json/scenes/testscene.json | 2+-
Msrc/board.py | 4++--
Msrc/game.py | 103++++++++-----------------------------------------------------------------------
Msrc/images.py | 4++--
Msrc/manager.py | 69++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/menu.py | 8++++----
Msrc/scene.py | 8++++----
Msrc/sound.py | 4++--
Asrc/subsystem.py | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/unit.py | 4++--
Msrc/vgo.py | 4++--
12 files changed, 216 insertions(+), 138 deletions(-)

diff --git a/data/json/menus/mainmenu.json b/data/json/menus/mainmenu.json @@ -10,7 +10,7 @@ "intlayer" : 0, "effects" : [ { - "call" : "me_switch_mode", + "call" : "ef_switch_mode", "data" : "Battle_Mode" } ] @@ -22,7 +22,7 @@ "intlayer" : 0, "effects" : [ { - "call" : "me_switch_mode", + "call" : "ef_switch_mode", "data" : "Still_Scene_Mode" } ] @@ -34,7 +34,7 @@ "intlayer" : 0, "effects" : [ { - "call" : "me_quit", + "call" : "ef_quit", "data" : null } ] diff --git a/data/json/scenes/testscene.json b/data/json/scenes/testscene.json @@ -41,7 +41,7 @@ ], "effects" : [ { - "call" : "se_switch_mode", + "call" : "ef_switch_mode", "data" : "Battle_Mode" } ] diff --git a/src/board.py b/src/board.py @@ -29,9 +29,9 @@ class BoardManager(manager.Manager): switching between Boards. """ - def __init__(self, game): + def __init__(self, game, bus): - super().__init__(game) + super().__init__(game, bus) # Board values self.current_board = None diff --git a/src/game.py b/src/game.py @@ -1,5 +1,5 @@ import pygame -from . import manager, images, sound, board, vgo, unit, menu, scene +from . import subsystem, manager, images, sound, board, vgo, unit, menu, scene from .constants import * ########### @@ -8,7 +8,6 @@ from .constants import * # This file contains: # 1. The 'Game' class, which defines the overarching game object -# 2. The 'GameInterface' subsystem, which handles all pygame events ########################### # Section 1 - Game object # @@ -39,16 +38,17 @@ class Game(object): self.control_mode = CTRL_MODES.Main_Menu_Normal # Subsystems - self.interface = GameInterface(self) + self.interface = subsystem.GameInterface(self) + self.manager_bus = subsystem.ManagerBus(self) # Managers - self.sheet_manager = images.SheetManager(self) - self.sound_manager = sound.SoundManager(self) - self.menu_manager = menu.MenuManager(self) - self.board_manager = board.BoardManager(self) - self.entity_manager = vgo.EntityManager(self) - self.unit_manager = unit.UnitManager(self) - self.scene_manager = scene.SceneManager(self) + self.sheet_manager = images.SheetManager(self, self.manager_bus) + self.sound_manager = sound.SoundManager(self, self.manager_bus) + self.menu_manager = menu.MenuManager(self, self.manager_bus) + self.board_manager = board.BoardManager(self, self.manager_bus) + self.entity_manager = vgo.EntityManager(self, self.manager_bus) + self.unit_manager = unit.UnitManager(self, self.manager_bus) + self.scene_manager = scene.SceneManager(self, self.manager_bus) # Setup (This is WIP) self.sheet_manager.load_sheets_from_json("sheets.json") @@ -144,86 +144,3 @@ class Game(object): # When we turn off, cleanup and end pygame.quit() -############################################## -# Section 2 - GameInterface Subsystem Object # -############################################## - -class GameInterface(manager.Manager): - """ - GameInterface handles all PyGame events, meaning it is - responsible for all input and the responses to that - input. - """ - - def __init__(self, game): - - # Parent init - super().__init__(game) - - def handle_events(self, events): - """ - Handle any kind of PyGame event and react appropriately. - """ - for event in events: - if event.type == pygame.KEYDOWN: - self.handle_key_press(event) - elif event.type == pygame.KEYUP: - self.handle_key_release(event) - elif event.type == pygame.MOUSEBUTTONDOWN: - self.handle_mouse_click(event) - elif event.type == pygame.QUIT: - self.game.quit_game() - - def handle_key_press(self, event): - """ - React to a key being pressed. - """ - # TODO: This is irregular compared to mouseclick. Desireable??? - if self.game.control_mode == CTRL_MODES.Turn_Select_Move: - if event.key == pygame.K_q: - self.game.control_mode = CTRL_MODES.Turn_Normal - # TODO: Should this really be done here??? - self.game.board_manager.load_overlay() - - def handle_key_release(self, event): - """ - React to a key being released. - """ - pass - - def handle_mouse_click(self, event): - """ - React to a mousebutton being clicked. - """ - if event.button == 1: - if self.game.state_mode == STATE_MODES.Main_Menu_Mode: - if self.game.control_mode == CTRL_MODES.Main_Menu_Normal: - self.game.menu_manager.trigger_button_at_pos(pygame.mouse.get_pos()) - elif self.game.state_mode == STATE_MODES.Battle_Mode: - if self.game.control_mode == CTRL_MODES.Turn_Normal: - if self.game.entity_manager.select_entities_with_tile_cursor(self.game.board_manager.get_tile_at_position(pygame.mouse.get_pos())): - self.game.control_mode = CTRL_MODES.Turn_Select_Move - elif self.game.control_mode == CTRL_MODES.Turn_Select_Move: - if self.game.entity_manager.set_entity_move_to_tile_pos(self.game.entity_manager.selected_entity, self.game.board_manager.get_overlay_move_vgo_at_pos(pygame.mouse.get_pos())): - self.game.control_mode = CTRL_MODES.Turn_Normal - # TODO: Should this really be done here??? - self.game.board_manager.load_overlay() - elif self.game.state_mode == STATE_MODES.Still_Scene_Mode: - if self.game.control_mode == CTRL_MODES.Still_Scene_Normal: - self.game.scene_manager.current_scene.continue_script() - - def handle_mouse_release(self, event): - """ - React to a mousebutton being released. - """ - pass - - def update_interface(self): - """ - Update interface elements (such as the cursor) once - per frame. This is not the same as a drawing update for - e.g. an Entity, and is logic-only. - """ - # Update cursor position - if self.game.state_mode == STATE_MODES.Battle_Mode: - self.game.entity_manager.position_tile_cursor(self.game.board_manager.get_tile_at_position(pygame.mouse.get_pos())) diff --git a/src/images.py b/src/images.py @@ -35,9 +35,9 @@ class SheetManager(manager.Manager): them. """ - def __init__(self, game): + def __init__(self, game, bus): - super().__init__(game) + super().__init__(game, bus) # Important values self.sheets_def = {} diff --git a/src/manager.py b/src/manager.py @@ -1,4 +1,5 @@ import pygame +from . import subsystem ############## # manager.py # @@ -32,40 +33,62 @@ import pygame # This file contains: # 1. The Manager object, which is the parent of other managers that perform functions directly subordinate to the Game object. -# 2. The ManagerBus object, which handles inter-manager communication and exposes manager/game object attributes ################################## # Section 1 - The Manager Object # ################################## -class Manager(object): +class Manager(subsystem.GameSubsystem): """ - Extremely simple parent type for the various + Fairly simple parent type for the various managers of different game elements, such as the play board or the UI. """ - def __init__(self, game): + # Properties + @property + def activated(self): + return self._activated + @activated.setter + def activated(self, x): + self._activated = x - self.game = game + @property + def effectual(self): + return self._effectual + @effectual.setter + def effectual(self, x): + self._effectual = x -##################################### -# Section 2 - The ManagerBus Object # -##################################### + # Initialization + def __init__(self, game, bus): -class ManagerBus(object): - """ - Shared communication object that ALL interaction - between manager objects should go through. - """ - # What SHOULD go through ManagerBus: - # * Manager-to-Manager communication - # * Manager-to-external-object communication - # * ANY object talking to a manager BESIDES Game - # What SHOULD NOT go through ManagerBus: - # * Direct communication from Game to a manager - # * A Manager talking to an object it manages - # * An object talking to its own manager - def __init__(self, game): + # Initial values + super().__init__(game) + self.bus = bus + + # Property defaults + self._activated = True + self._effectual = True + + # Other methods + def trigger_effects(self, effect_list): + """ + Triggers game effects as defined in JSONs. + This method can be extended in child objects + that need to be able to trigger unique + effects. + """ + # effect_list is ALWAYS a LIST of DICTS + for ef in effect_list: + if ef["call"] == "ef_quit": + self.game.quit_game() + elif ef["call"] == "ef_switch_mode": + self.game.switch_mode(STATE_MODES[ef["data"]]) - self.game = game + def update_managed(self, surface): + """ + Abstract method to be overwritten with + update logic in child objects. + """ + pass diff --git a/src/menu.py b/src/menu.py @@ -24,9 +24,9 @@ class MenuManager(manager.Manager): access those groups to draw them. """ - def __init__(self, game): + def __init__(self, game, bus): - super().__init__(game) + super().__init__(game, bus) # MenuManager values self.current_menu = None @@ -54,9 +54,9 @@ class MenuManager(manager.Manager): """ # TODO: Regularize this with the similar event in SceneManager for e in effects: - if e["call"] == "me_quit": # Quit the game + if e["call"] == "ef_quit": # Quit the game self.game.quit_game() - elif e["call"] == "me_switch_mode": # Switch to another game mode + elif e["call"] == "ef_switch_mode": # Switch to another game mode self.game.switch_mode(STATE_MODES[e["data"]]) def trigger_button_at_pos(self, pos): diff --git a/src/scene.py b/src/scene.py @@ -22,10 +22,10 @@ class SceneManager(manager.Manager): complicated scenes. """ - def __init__(self, game): + def __init__(self, game, bus): # Parent initialization - super().__init__(game) + super().__init__(game, bus) # Important values self.current_scene = None @@ -44,9 +44,9 @@ class SceneManager(manager.Manager): """ # TODO: Regularize this with the similar event in MenuManager for ef in effects: - if ef["call"] == "se_quit": + if ef["call"] == "ef_quit": self.game.quit_game() - elif ef["call"] == "se_switch_mode": + elif ef["call"] == "ef_switch_mode": self.game.switch_mode(STATE_MODES[ef["data"]]) def update_scene(self, surface = None): diff --git a/src/sound.py b/src/sound.py @@ -19,10 +19,10 @@ class SoundManager(manager.Manager): files and is called to play them. """ - def __init__(self, game): + def __init__(self, game, bus): # Parent initialization - super().__init__(game) + super().__init__(game, bus) # Important values self.sounds = {} diff --git a/src/subsystem.py b/src/subsystem.py @@ -0,0 +1,138 @@ +import pygame +from .constants import * + +################ +# subsystem.py # +################ + +# This file contains: +# 1. The generic GameSubsystem class, which abstract subsystems (like Interface) are children of, as well as all Managers +# 2. The GameInterface class, which handles game events such as keyboard and mouse input +# 3. The ManagerBus class, which is a communication mechanism used by Managers as well as other subsystems and ever game objects + +####################################### +# Section 1 - The GameSubsystem class # +####################################### + +class GameSubsystem(object): + """ + Abstract class subordinate to game which + Managers and other subsystems are specialized + versions of. GameSubsystem is pretty much the + most abstract possible parent for these kinds + of objects and is very simple. + """ + + def __init__(self, game): + + self.game = game + +####################################### +# Section 2 - The GameInterface class # +####################################### + +class GameInterface(GameSubsystem): + """ + GameInterface handles all PyGame events, meaning + it is responsible for all input and the responses + to that input. + """ + + def __init__(self, game): + + # Parent init + super().__init__(game) + + def handle_events(self, events): + """ + Handle any kind of PyGame event and react appropriately. + """ + for event in events: + if event.type == pygame.KEYDOWN: + self.handle_key_press(event) + elif event.type == pygame.KEYUP: + self.handle_key_release(event) + elif event.type == pygame.MOUSEBUTTONDOWN: + self.handle_mouse_click(event) + elif event.type == pygame.QUIT: + self.game.quit_game() + + def handle_key_press(self, event): + """ + React to a key being pressed. + """ + # TODO: This is irregular compared to mouseclick. Desireable??? + if self.game.control_mode == CTRL_MODES.Turn_Select_Move: + if event.key == pygame.K_q: + self.game.control_mode = CTRL_MODES.Turn_Normal + # TODO: Should this really be done here??? + self.game.board_manager.load_overlay() + + def handle_key_release(self, event): + """ + React to a key being released. + """ + pass + + def handle_mouse_click(self, event): + """ + React to a mousebutton being clicked. + """ + if event.button == 1: + if self.game.state_mode == STATE_MODES.Main_Menu_Mode: + if self.game.control_mode == CTRL_MODES.Main_Menu_Normal: + self.game.menu_manager.trigger_button_at_pos(pygame.mouse.get_pos()) + elif self.game.state_mode == STATE_MODES.Battle_Mode: + if self.game.control_mode == CTRL_MODES.Turn_Normal: + if self.game.entity_manager.select_entities_with_tile_cursor(self.game.board_manager.get_tile_at_position(pygame.mouse.get_pos())): + self.game.control_mode = CTRL_MODES.Turn_Select_Move + elif self.game.control_mode == CTRL_MODES.Turn_Select_Move: + if self.game.entity_manager.set_entity_move_to_tile_pos(self.game.entity_manager.selected_entity, self.game.board_manager.get_overlay_move_vgo_at_pos(pygame.mouse.get_pos())): + self.game.control_mode = CTRL_MODES.Turn_Normal + # TODO: Should this really be done here??? + self.game.board_manager.load_overlay() + elif self.game.state_mode == STATE_MODES.Still_Scene_Mode: + if self.game.control_mode == CTRL_MODES.Still_Scene_Normal: + self.game.scene_manager.current_scene.continue_script() + + def handle_mouse_release(self, event): + """ + React to a mousebutton being released. + """ + pass + + def update_interface(self): + """ + Update interface elements (such as the cursor) once + per frame. This is not the same as a drawing update for + e.g. an Entity, and is logic-only. + """ + # Update cursor position + if self.game.state_mode == STATE_MODES.Battle_Mode: + self.game.entity_manager.position_tile_cursor(self.game.board_manager.get_tile_at_position(pygame.mouse.get_pos())) + +#################################### +# Section 3 - The ManagerBus class # +#################################### + +class ManagerBus(GameSubsystem): + """ + Communication bus that managers talk to each + other and their own subordinate objects through. + """ + # What SHOULD go through ManagerBus: + # * Manager-to-Manager communication + # * Manager-to-external-object communication + # * ANY object talking to a manager BESIDES Game + # What SHOULD NOT go through ManagerBus: + # * Direct communication from Game to a manager + # * A Manager talking to an object it manages + # * An object talking to its own manager + + def __init__(self, game): + + # Parent init + super().__init__(game) + + # Module references + self.managers = {} diff --git a/src/unit.py b/src/unit.py @@ -25,10 +25,10 @@ class UnitManager(manager.Manager): the stats of that Unit. """ - def __init__(self, game): + def __init__(self, game, bus): # Parent initialization - super().__init__(game) + super().__init__(game, bus) # Saved values self.loaded_stats = {} diff --git a/src/vgo.py b/src/vgo.py @@ -327,9 +327,9 @@ class EntityManager(manager.Manager): entities. Menus manage their own entities in menu modes. """ # TODO: EntityManager should eventually be managing menu entities as well. - def __init__(self, game): + def __init__(self, game, bus): - super().__init__(game) + super().__init__(game, bus) # Entity values self.loaded_entities = pygame.sprite.LayeredDirty()