Return to repo list

heart-of-gold

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

commit 04a12b28880194ae97de3019d3bb63b9479ab553
parent 145eb531b57dc6da62051dec4fed8b2bc6b5c76a
Author: Erik Letson <hmagellan@hmagellan.com>
Date:   Wed, 11 Nov 2020 13:55:06 -0600

split off the largest subsystems to their own files

Diffstat:
Asrc/camera.py | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/game.py | 6+++---
Asrc/interface.py | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/subsystem.py | 200++-----------------------------------------------------------------------------
4 files changed, 219 insertions(+), 199 deletions(-)

diff --git a/src/camera.py b/src/camera.py @@ -0,0 +1,73 @@ +import pygame +from . import subsystem +from .constants import * + +############# +# camera.py # +############# + +# This file contains: +# 1. The GameCamera class, which manages the camera surface + +#################################### +# Section 1 - The GameCamera class # +#################################### + +class GameCamera(subsystem.GameSubsystem): + """ + Object that controls the camera surface and + moves it in relation to input. The camera + surface is then drawn onto the game's real + screen at an offset determined by input. + """ + + def __init__(self, game): + + # Parent init + super().__init__(game) + + # Camera surface values + self.camera_surface = pygame.Surface((0, 0)).convert() # This surface is initialized whenever a new map or other game area is loaded + self.camera_surface_rect = self.camera_surface.get_rect() + self.camera_surface_offset = (0, 0) # The rect topleft is never changed; rather, this value is used by game to draw the camera surface at an offset + self.scroll_rects = {(1, 0) : None, (-1, 0) : None, (0, 1) : None, (0, -1) : None} + + def load_camera_surface(self, dimensions, init_offset = (0, 0)): + """ + Load up the camera surface as a new PyGame + surface object of the necessary size. Also + resets the camera offset. + """ + self.camera_surface = pygame.Surface(dimensions).convert() + self.camera_surface_rect = self.camera_surface.get_rect() + self.camera_surface_offset = init_offset + self.scroll_rects = { + (1, 0) : pygame.Rect(0, 0, SCREEN_WIDTH / 4, SCREEN_HEIGHT), + (-1, 0) : pygame.Rect(SCREEN_WIDTH - (SCREEN_WIDTH / 4), 0, SCREEN_WIDTH / 4, SCREEN_HEIGHT), + (0, 1) : pygame.Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 4), + (0, -1) : pygame.Rect(0, SCREEN_HEIGHT - (SCREEN_HEIGHT / 4), SCREEN_WIDTH, SCREEN_HEIGHT / 4) + } + + def move_offset(self, rel_offset, speed_multiple = 1): + """ + Move the offset by the given relative offset. + Speed can also be increased or decreased by + passing a multiple here. Handles not moving + the camera view outside of game screen + completely. + """ + changex = 0 + changey = 0 + if not ((self.camera_surface_offset[0] <= -(SCREEN_WIDTH / 2) and rel_offset[0] < 0) or (self.camera_surface_offset[0] >= (SCREEN_WIDTH / 2) and rel_offset[0] > 0)): + changex = rel_offset[0] * speed_multiple + if not ((self.camera_surface_offset[1] <= -SCREEN_HEIGHT and rel_offset[1] < 0) or (self.camera_surface_offset[1] >= (SCREEN_HEIGHT / 2) and rel_offset[1] > 0)): + changey = rel_offset[1] * speed_multiple + self.camera_surface_offset = (self.camera_surface_offset[0] + changex, self.camera_surface_offset[1] + changey) + + def update_camera(self, surface = None): + """ + Update and draw the camera contents. + """ + if surface != None: + surface.blit(self.camera_surface, self.camera_surface_offset) + diff --git a/src/game.py b/src/game.py @@ -1,5 +1,5 @@ import pygame -from . import subsystem, bus, manager, images, sound, board, entity, piece, menu, scene +from . import subsystem, bus, camera, interface, manager, images, sound, board, entity, piece, menu, scene from .constants import * ########### @@ -43,10 +43,10 @@ class Game(object): # Subsystems self.manager_bus = bus.ManagerBus(self) - self.camera = subsystem.GameCamera(self) + self.camera = camera.GameCamera(self) self.object_oracle = subsystem.ObjectOracle(self) self.save_system = subsystem.SaveSystem(self) - self.interface = subsystem.GameInterface(self, self.manager_bus, self.camera) + self.interface = interface.GameInterface(self, self.manager_bus, self.camera) # Managers self.sheet_manager = images.SheetManager(self, self.manager_bus, "sheet_manager") diff --git a/src/interface.py b/src/interface.py @@ -0,0 +1,139 @@ +import pygame +from . import subsystem +from .constants import * + +################ +# interface.py # +################ + +# This file contains: +# 1. The GameInterface class, which handles all kinds of interaction from the player + +####################################### +# Section 1 - The GameInterface class # +####################################### + +class GameInterface(subsystem.GameSubsystem): + """ + GameInterface handles all PyGame events, meaning + it is responsible for all input and the responses + to that input. GameInterface takes extra arguments + compared to other subsystems, representing its need + to directly access manager calls via bus and the + game camera. + """ + + def __init__(self, game, bus, camera): + + # Parent init + super().__init__(game) + + # Others + self.bus = bus + self.camera = camera + + 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.MOUSEBUTTONUP: + self.handle_mouse_release(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 + self.bus.perform_load_board_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 we clicked the left mouse button... + if event.button == 1: + + # First, get important mouse positional info, namely unoffset mouse position and camera-offset mouse position + mouseraw = pygame.mouse.get_pos() + mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1]) + + # State checker + # Main menu mode behavior + if self.game.state_mode == STATE_MODES.Main_Menu_Mode: + + # Normal main menu control + if self.game.control_mode == CTRL_MODES.Main_Menu_Normal: + self.bus.perform_trigger_menu_button_at_pos(mousepos) + + # Battle mode behavior + elif self.game.state_mode == STATE_MODES.Battle_Mode: + + # Normal in-battle turn control + if self.game.control_mode == CTRL_MODES.Turn_Normal: + self.bus.perform_select_piece_with_tile_cursor() + sp = self.bus.fetch("piece_manager", "selected_piece") + if sp != None: + self.bus.perform_display_move_range_of_piece(sp) + self.game.control_mode = CTRL_MODES.Turn_Select_Move + + # Selecting a move for the active piece control + elif self.game.control_mode == CTRL_MODES.Turn_Select_Move: + if self.bus.check_for_overlay_move_entity_by_screen_pos(mousepos) != None: + targ = self.bus.check_for_tile_pos_by_screen_pos(mousepos) + to_path = self.bus.check_for_piece_path_by_previous_moves(self.bus.fetch("piece_manager", "selected_piece").tile_pos, targ) + if to_path != None: + self.bus.perform_set_piece_move_along_tile_path(self.bus.fetch("piece_manager", "selected_piece"), to_path) + # TODO: Not like this + self.game.lose_control(len(to_path) * PIECE_MOVE_DELAY, CTRL_MODES.Turn_Normal) + self.bus.perform_load_board_overlay() + + # Still-scene mode behavior + elif self.game.state_mode == STATE_MODES.Still_Scene_Mode: + + # Normal still-scene control + if self.game.control_mode == CTRL_MODES.Still_Scene_Normal: + self.bus.perform_continue_current_scene_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: + mouseraw = pygame.mouse.get_pos() + mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1]) + tilepos = self.bus.check_for_tile_by_screen_pos(mousepos) + if tilepos != None: + self.bus.perform_position_tile_cursor((tilepos[0], tilepos[1])) + + if self.game.control_mode == CTRL_MODES.Turn_Normal: + for r in self.camera.scroll_rects: + if self.camera.scroll_rects[r].collidepoint(mouseraw): + self.camera.move_offset(r, SCROLL_SPEED) + diff --git a/src/subsystem.py b/src/subsystem.py @@ -7,10 +7,8 @@ from .constants import * # This file contains: # 1. The generic GameSubsystem class, which abstract subsystems (like GameInterface) are children of, as well as all Managers -# 2. The GameInterface class, which handles game events such as keyboard and mouse input -# 3. The GameCamera class, which controls camera-oriented logic and drawing -# 4. The ObjectOracle class, which keeps track of all Entity objects and can refer to them, but does not manage or draw them -# 5. The SaveSystem class, which handles saving and loading games +# 2. The ObjectOracle class, which keeps track of all Entity objects and can refer to them, but does not manage or draw them +# 3. The SaveSystem class, which handles saving and loading games ####################################### # Section 1 - The GameSubsystem class # @@ -39,198 +37,8 @@ class GameSubsystem(object): 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. GameInterface takes extra arguments - compared to other subsystems, representing its need - to directly access manager calls via bus and the - game camera. - """ - - def __init__(self, game, bus, camera): - - # Parent init - super().__init__(game) - - # Others - self.bus = bus - self.camera = camera - - 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.MOUSEBUTTONUP: - self.handle_mouse_release(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 - self.bus.perform_load_board_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 we clicked the left mouse button... - if event.button == 1: - - # First, get important mouse positional info, namely unoffset mouse position and camera-offset mouse position - mouseraw = pygame.mouse.get_pos() - mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1]) - - # State checker - # Main menu mode behavior - if self.game.state_mode == STATE_MODES.Main_Menu_Mode: - - # Normal main menu control - if self.game.control_mode == CTRL_MODES.Main_Menu_Normal: - self.bus.perform_trigger_menu_button_at_pos(mousepos) - - # Battle mode behavior - elif self.game.state_mode == STATE_MODES.Battle_Mode: - - # Normal in-battle turn control - if self.game.control_mode == CTRL_MODES.Turn_Normal: - self.bus.perform_select_piece_with_tile_cursor() - sp = self.bus.fetch("piece_manager", "selected_piece") - if sp != None: - self.bus.perform_display_move_range_of_piece(sp) - self.game.control_mode = CTRL_MODES.Turn_Select_Move - - # Selecting a move for the active piece control - elif self.game.control_mode == CTRL_MODES.Turn_Select_Move: - if self.bus.check_for_overlay_move_entity_by_screen_pos(mousepos) != None: - targ = self.bus.check_for_tile_pos_by_screen_pos(mousepos) - to_path = self.bus.check_for_piece_path_by_previous_moves(self.bus.fetch("piece_manager", "selected_piece").tile_pos, targ) - if to_path != None: - self.bus.perform_set_piece_move_along_tile_path(self.bus.fetch("piece_manager", "selected_piece"), to_path) - # TODO: Not like this - self.game.lose_control(len(to_path) * PIECE_MOVE_DELAY, CTRL_MODES.Turn_Normal) - self.bus.perform_load_board_overlay() - - # Still-scene mode behavior - elif self.game.state_mode == STATE_MODES.Still_Scene_Mode: - - # Normal still-scene control - if self.game.control_mode == CTRL_MODES.Still_Scene_Normal: - self.bus.perform_continue_current_scene_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: - mouseraw = pygame.mouse.get_pos() - mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1]) - tilepos = self.bus.check_for_tile_by_screen_pos(mousepos) - if tilepos != None: - self.bus.perform_position_tile_cursor((tilepos[0], tilepos[1])) - - if self.game.control_mode == CTRL_MODES.Turn_Normal: - for r in self.camera.scroll_rects: - if self.camera.scroll_rects[r].collidepoint(mouseraw): - self.camera.move_offset(r, SCROLL_SPEED) - -#################################### -# Section 3 - The GameCamera class # -#################################### - -class GameCamera(GameSubsystem): - """ - Object that controls the camera surface and - moves it in relation to input. The camera - surface is then drawn onto the game's real - screen at an offset determined by input. - """ - - def __init__(self, game): - - # Parent init - super().__init__(game) - - # Camera surface values - self.camera_surface = pygame.Surface((0, 0)).convert() # This surface is initialized whenever a new map or other game area is loaded - self.camera_surface_rect = self.camera_surface.get_rect() - self.camera_surface_offset = (0, 0) # The rect topleft is never changed; rather, this value is used by game to draw the camera surface at an offset - self.scroll_rects = {(1, 0) : None, (-1, 0) : None, (0, 1) : None, (0, -1) : None} - - def load_camera_surface(self, dimensions, init_offset = (0, 0)): - """ - Load up the camera surface as a new PyGame - surface object of the necessary size. Also - resets the camera offset. - """ - self.camera_surface = pygame.Surface(dimensions).convert() - self.camera_surface_rect = self.camera_surface.get_rect() - self.camera_surface_offset = init_offset - self.scroll_rects = { - (1, 0) : pygame.Rect(0, 0, SCREEN_WIDTH / 4, SCREEN_HEIGHT), - (-1, 0) : pygame.Rect(SCREEN_WIDTH - (SCREEN_WIDTH / 4), 0, SCREEN_WIDTH / 4, SCREEN_HEIGHT), - (0, 1) : pygame.Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 4), - (0, -1) : pygame.Rect(0, SCREEN_HEIGHT - (SCREEN_HEIGHT / 4), SCREEN_WIDTH, SCREEN_HEIGHT / 4) - } - - def move_offset(self, rel_offset, speed_multiple = 1): - """ - Move the offset by the given relative offset. - Speed can also be increased or decreased by - passing a multiple here. Handles not moving - the camera view outside of game screen - completely. - """ - changex = 0 - changey = 0 - if not ((self.camera_surface_offset[0] <= -(SCREEN_WIDTH / 2) and rel_offset[0] < 0) or (self.camera_surface_offset[0] >= (SCREEN_WIDTH / 2) and rel_offset[0] > 0)): - changex = rel_offset[0] * speed_multiple - if not ((self.camera_surface_offset[1] <= -SCREEN_HEIGHT and rel_offset[1] < 0) or (self.camera_surface_offset[1] >= (SCREEN_HEIGHT / 2) and rel_offset[1] > 0)): - changey = rel_offset[1] * speed_multiple - self.camera_surface_offset = (self.camera_surface_offset[0] + changex, self.camera_surface_offset[1] + changey) - - def update_camera(self, surface = None): - """ - Update and draw the camera contents. - """ - if surface != None: - surface.blit(self.camera_surface, self.camera_surface_offset) - ###################################### -# Section 4 - The ObjectOracle class # +# Section 2 - The ObjectOracle class # ###################################### class ObjectOracle(GameSubsystem): @@ -250,7 +58,7 @@ class ObjectOracle(GameSubsystem): super().__init__(game) #################################### -# Section 5 - The SaveSystem class # +# Section 3 - The SaveSystem class # #################################### class SaveSystem(GameSubsystem):