Return to repo list

heart-of-gold

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

commit 5762f91147b69d43d4a95bc149c1c9525c58c950
parent 92e8b7ff43a46d685b01b3f7bb2829256f8b59c0
Author: Erik Letson <hmagellan@hmagellan.com>
Date:   Wed,  4 Nov 2020 15:38:50 -0600

Further refactoring work, checkpoint 2

Diffstat:
Msrc/board.py | 28++++++++++++++--------------
Msrc/constants.py | 1-
Dsrc/cursor.py | 2--
Asrc/entity.py | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/game.py | 22+++++++++++-----------
Msrc/menu.py | 24+++++++-----------------
Msrc/piece.py | 35++++++++++++++++++++++++++---------
Msrc/scene.py | 14+++++++-------
Msrc/turn.py | 10+++++-----
Dsrc/unit.py | 67-------------------------------------------------------------------
Dsrc/vgo.py | 225-------------------------------------------------------------------------------
11 files changed, 295 insertions(+), 358 deletions(-)

diff --git a/src/board.py b/src/board.py @@ -1,5 +1,5 @@ import pygame, pytmx, os, queue -from . import manager, vgo +from . import manager, entity from .constants import BOARD_PATH ############ @@ -60,10 +60,10 @@ class BoardManager(manager.Manager): if isinstance(layer, pytmx.TiledTileLayer): for x, y, gid in layer: if self.current_board.tmx_data.get_tile_properties_by_gid(gid)["Passable"] == 1: - v = vgo.VisibleGameObject(self.game.sheet_manager.loaded_sheets["board_overlays_1"]) - v.set_position((x * self.current_board.tmx_data.tilewidth, y * self.current_board.tmx_data.tileheight)) - v.custom_flags = "OverlayGrid" - self.board_overlay.add(v) + e = entity.Entity(self.game.sheet_manager.loaded_sheets["board_overlays_1"]) + e.set_position((x * self.current_board.tmx_data.tilewidth, y * self.current_board.tmx_data.tileheight)) + e.custom_flags = "OverlayGrid" + self.board_overlay.add(e) def switch_to_board(self, boardname): """ @@ -114,14 +114,14 @@ class BoardManager(manager.Manager): return (x, y, gid) return None - def get_overlay_move_vgo_at_pos(self, pos): + def get_overlay_move_entity_at_pos(self, pos): """ - Return (x, y) if there is a legal overlay move vgo at 'pos', + Return (x, y) if there is a legal overlay move entity at 'pos', and return None otherwise. """ - for v in self.board_overlay: - if "OverlayMove" in v.custom_flags and v.rect.collidepoint(pos): - return v.custom_flags[1] + for e in self.board_overlay: + if "OverlayMove" in e.custom_flags and e.rect.collidepoint(pos): + return e.custom_flags[1] return None def display_as_move_range(self, tile_pos_list): @@ -135,10 +135,10 @@ class BoardManager(manager.Manager): # TEMP!!!! self.create_move_range(self.game.piece_manager.selected_piece) for t in self.move_targets: - v = vgo.VisibleGameObject(self.game.sheet_manager.loaded_sheets["board_overlays_1"], (1, 0)) - v.set_position((t[0] * self.current_board.tmx_data.tilewidth, t[1] * self.current_board.tmx_data.tileheight)) - v.custom_flags = ("OverlayMove", t) - self.board_overlay.add(v) + e = entity.Entity(self.game.sheet_manager.loaded_sheets["board_overlays_1"], (1, 0)) + e.set_position((t[0] * self.current_board.tmx_data.tilewidth, t[1] * self.current_board.tmx_data.tileheight)) + e.custom_flags = ("OverlayMove", t) + self.board_overlay.add(e) def create_move_range(self, piece): """ diff --git a/src/constants.py b/src/constants.py @@ -36,7 +36,6 @@ FONT_PATH = os.path.join(DATA_PATH, "font") BOARD_PATH = os.path.join(DATA_PATH, "map") JSON_PATH = os.path.join(DATA_PATH, "json") ENTITY_JSON_PATH = os.path.join(JSON_PATH, "ents") -STATUS_JSON_PATH = os.path.join(JSON_PATH, "stats") MENU_JSON_PATH = os.path.join(JSON_PATH, "menus") SCENE_JSON_PATH = os.path.join(JSON_PATH, "scenes") diff --git a/src/cursor.py b/src/cursor.py @@ -1,2 +0,0 @@ -import pygame - diff --git a/src/entity.py b/src/entity.py @@ -0,0 +1,225 @@ +import pygame, json, os, math +from . import manager +from .constants import * + +############# +# entity.py # +############# + +# TODO: +# 1. Some serious refactoring needs to happen here. EntityManager needs to +# be brought in line with how managers in general are imagined to work +# now, especially if the generic manager is going to be significantly +# extended and the bus is going to be implemented. The differences +# between a VGO and an Entity are blurred right now. Hypothetically +# speaking, an Entity should be an extended VGO that has features that +# go beyond what a generally-extended pygame Sprite object should have. +# A VGO is a just a sprite that can be drawn and animated by any other +# object, but an Entity should be created and managed ONLY by the +# EntityManager object, because of its extensions. Positioning and +# animating can be VGO functions, but objects you interact with should +# be Entities. This means the EntityManager should be active and working +# in any mode that has on-screen object interaction (e.g. in Menu mode). +# This is NOT how EntityManager currently works and refactoring of the +# EntityManager, Entity, and VGO objects will be required to bring the +# imagined function of this system of objects to realization. One way to +# accomplish some of this would be to try to encapsulate more of what +# EntityManager does each frame into its own update method, and make +# the behavoir therein game_mode-conditional. +# 2. An alternative to the above: Reimagine EntityManager as PieceManager, +# Meaning it is only responsible for board Piece entities. The BoardCursor +# could be handled by a seperate manager of its own, which could manage +# the cursor across multiple modes. This would mean splitting the EntityManager +# and Piece classes off into their own source file (a good option would +# be to combine them with Unit, remove UnitManager, and just make units +# be something PieceManager handles). All of this would be in line with +# the natural path that the code has been taking, whereby there is not +# one single management object for ALL entities, but rather entities and +# other VGOs are drawn and updated by the managers that logically are +# already associated with them. Drawing entities is a pan-Manager thing, +# and one of the important distinctions between managers and subsystems. +# The codebase should probably reflect this by doing away with EntityManager +# entirely and refactoring it into PieceManager. EntityManager's functionality +# involving identifying entities by name/id could be moved to a subsystem +# object called ObjectOracle. This subsystem could be alerted each time +# a new object is created, and could maintain a list of references to +# those objects. It could manage ID by itself, and spread it across any +# VGO, not just Entities. VGOs would know their own ID, and they could be +# accessed from the oracly by ID by any object with a basic call to +# game.oracle. It would be excellent for debug as well. It is possible that +# even the non-VGO objects like the managers should have IDs and be referred +# to by the oracle. + +# This file contains: +# 1. The Entity class that is the extension of pygame.Sprite used throughout the game +# 2. Various kinds of specialized entity classes that don't fit in other source files + +# TODO: This should eventually use LayeredDirty sprites for performance reasons +# TODO: Eventually need to add a UI-element class type (sub of entity? Should entity replace VGO entirely???) + +############################ +# Section 1 - Entity class # +############################ + +class Entity(pygame.sprite.DirtySprite): + """ + The parent of all visible objects. VisibleGameObject (VGO) is + essentially an extended, customised version of the PyGame Sprite + object. Entity supports animations, motions (movement over time), + and tile operations, but none of that is required. + """ + + def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False): + + # Parent initialization + super().__init__() + + # Saved values + self.sheet = sheet + self.image = sheet.sprites[sprite] + self.rect = self.image.get_rect() + self.rect.topleft = (0, 0) + self.custom_flags = None # Used to pass special info to VGOs + + # DirtySprite class defaults + self.visible = 1 + self.dirty = 0 + self.layer = 0 + + # Animation values + self.animated = animated + self.animation = {} + self.animation_timer = 0 + self.animation_frame = 0 + if animated: + self.set_animation(animation, animated) + + # Motion values + self.motion = {} + self.motion_timer = 0 + + # Tile values + self.tile_pos = (-1, -1) + self.tile_gid = None + + def set_sprite(self, sprite_coord): + """ + Set the VGO's sprite to another one on the board. The + argument 'sprite_coord' is a tuple (x, y). This is used + to assign an image from the saved sheet to non-animated + VGO objects. + """ + # TODO: Error-checking + self.image = self.sheet.sprites[sprite_coord] + + def set_animation(self, new_animation, play = False, init_frame = 0): + """ + Assign a new animation to this VGO and configure all + necessary values to get it playing. + """ + self.animation = new_animation + self.animation_frame = init_frame + self.animation_timer = new_animation[init_frame]["timer"] + self.image = self.sheet.sprites[new_animation[init_frame]["sprite"]] + self.animated = play + + def set_motion(self, target_pos, speed): + """ + Assign a new motion to this VGO and start playing it. + Unlike animations, motions must be played if assigned. + Directions go by center rather than topleft. + """ + self.motion = { + "target" : (target_pos[0] + (TILE_WIDTH / 2), target_pos[1] + (TILE_HEIGHT / 2)), + "speed" : speed, + "direction" : ((target_pos[0] - self.rect.topleft[0]) / speed, (target_pos[1] - self.rect.topleft[1]) / speed), + "timer" : speed + } + + def set_position(self, pos): + """ + Assign the rect topleft to a raw position tuple, independent + of e.g. a tile. + """ + self.rect.topleft = pos + + def set_center_position(self, pos): + """ + Assign the rect center to a raw position tuple, independent + of e.g. a tile. + """ + self.rect.center = pos + + def animate(self): + """ + Play the current animation. This method is called as part of + the update() logic. + """ + if self.animation_timer > 0: + self.animation_timer -= 1 + else: + if self.animation_frame < len(self.animation) - 1: + self.animation_frame += 1 + else: + self.animation_frame = 0 + self.animation_timer = self.animation[self.animation_frame]["timer"] + self.image = self.sheet.sprites[self.animation[self.animation_frame]["sprite"]] + + def motion_move(self): + """ + Perform current motion, if it is set. + """ + if self.motion != {} and not self.rect.collidepoint(self.motion["target"]): + if self.motion_timer == 0: + mx = self.rect.topleft[0] + self.motion["direction"][0] + my = self.rect.topleft[1] + self.motion["direction"][1] + self.rect.topleft = (mx, my) + self.motion_timer = self.motion["speed"] + else: + self.motion_timer -= 1 + else: + if self.motion != {}: + self.set_center_position(self.motion["target"]) # Make sure we end up in exactly the right spot + self.motion = {} + + def snap_to_tile(self): + """ + Snap the Entity to its current tile. + """ + if self.tile_pos != (-1, -1): + self.set_position((self.tile_pos[0] * TILE_WIDTH, self.tile_pos[1] * TILE_HEIGHT)) + + def assign_tile(self, tile_def): + """ + Assign a tile as this entity object's occupied tile. + Assume the values are legitimate. + """ + self.tile_pos = (tile_def[0], tile_def[1]) + self.tile_gid = tile_def[2] + + def act(self): + """ + This method is called as part of update() and is meant + to be overwritten in subclasses with any logic that needs + to happen each update. + """ + pass + + def update(self, surface = None): + """ + Draw the VGO to the surface. Also calls act() for update + logic for specific VGO children, and animate() to animate + the sprite image. + """ + self.act() + if surface != None: + self.motion_move() + if self.animated: + self.animate() + surface.blit(self.image, self.rect) + +######################################### +# Section 2 - Various Entity subclasses # +######################################### + +# TODO: diff --git a/src/game.py b/src/game.py @@ -1,5 +1,5 @@ import pygame -from . import subsystem, manager, images, sound, board, vgo, piece, unit, menu, scene +from . import subsystem, manager, images, sound, board, entity, piece, menu, scene from .constants import * ########### @@ -51,7 +51,6 @@ class Game(object): self.board_manager = board.BoardManager(self, self.manager_bus) #self.entity_manager = vgo.EntityManager(self, self.manager_bus) self.piece_manager = piece.PieceManager(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) @@ -62,13 +61,15 @@ class Game(object): # Switch to game control self.switch_mode(STATE_MODES.Main_Menu_Mode) - def switch_mode(self, new_mode): + def switch_mode(self, new_mode, data = None): """ Change the current state_mode, as well as load up the elements of the new mode. This large method should take account of every possible mode in the - game. Such data has no reason to exists e.g. as a - JSON file. + game. Such a structure has no reason to exists e.g. + as a JSON file. The 'data' option contains the info + needed as part of a mode switch. This method handles + a change even if data is None. """ if new_mode == None: return @@ -84,16 +85,15 @@ class Game(object): self.control_mode = CTRL_MODES.Turn_Normal # TODO: Obv this must be made more generic. Currently only # sources from the one map file. - self.board_manager.load_board_from_file("testmap1.tmx") - self.board_manager.switch_to_board("testmap1.tmx") - self.unit_manager.load_stats_from_json("jisella_1.json") - self.entity_manager.load_entities_from_json("testmap1.json") - self.entity_manager.load_tile_cursor("cursor1") + #self.board_manager.load_board_from_file("testmap1.tmx") + #self.board_manager.switch_to_board("testmap1.tmx") + #self.entity_manager.load_entities_from_json("testmap1.json") + #self.entity_manager.load_tile_cursor("cursor1") self.camera.load_camera_surface(self.board_manager.current_board_dimensions) elif new_mode == STATE_MODES.Still_Scene_Mode: self.control_mode = CTRL_MODES.Still_Scene_Normal # TODO: Generic-ify - self.scene_manager.load_still_scene_from_file("testscene.json") + #self.scene_manager.load_still_scene_from_file("testscene.json") self.camera.load_camera_surface((SCREEN_WIDTH, SCREEN_HEIGHT)) def lose_control(self, time = -1, followup_mode = None): diff --git a/src/menu.py b/src/menu.py @@ -1,9 +1,7 @@ import pygame, json, os -from . import images, vgo, manager +from . import images, manager, entity from .constants import * -# TODO: EntityManager should be used to manage menu entities as well, eventually. - ########### # menu.py # ########### @@ -88,9 +86,6 @@ class Menu(object): self.entities_group = pygame.sprite.LayeredDirty() self.definition = json.load(open(os.path.join(MENU_JSON_PATH, menufile))) - # TODO: This will not be permenant - self.contained_entities = 0 - # Load/setup menu self.load_menu() @@ -103,14 +98,9 @@ class Menu(object): if self.definition != None: # Load background, if any - # TODO: Background should probably be a VGO subclass of its own rather than - # a generic entity. It would be better since currently decisions such - # as e.g. being animated are hardcoded here. - self.background = vgo.Entity(self.definition["bg_sheet"] + " _ent", self.contained_entities, - self.manager.game.sheet_manager.loaded_sheets[self.definition["bg_sheet"]], - tuple(self.definition["bg_sprite"]), None, False, True, None) - self.contained_entities += 1 - + self.background = entity.Entity(self.manager.bus.fetch_sheet(self.definition["bg_sheet"]), + tuple(self.definition["bg_sprite"]), None, False) + # Load buttons, if any for b in self.definition["buttons"]: nmb = MenuButton(b, self.contained_entities, @@ -134,17 +124,17 @@ class Menu(object): # Section 3 - Menu Entities # ############################# -class MenuButton(vgo.Entity): +class MenuButton(entity.Entity): """ Object that represents pushable menu buttons in menu mode. Created and managed by Menu objects. """ # TODO: MenuButton should probably eventually be passed info on its draw layer. - def __init__(self, name, ent_id, sheet, sprite = (0, 0), animation = None, animated = False, passable = True, unit = None, effects = []): + def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False, effects = []): # Parent initialization - super().__init__(name, ent_id, sheet, sprite, animation, animated, passable, unit) + super().__init__(sheet, sprite, animation, animated) # Saved values self.effects = effects # A list of effects. Each effect goes with a method in MenuManager, and each one is triggered on a press. diff --git a/src/piece.py b/src/piece.py @@ -1,5 +1,5 @@ import pygame, os, json -from . import manager, unit +from . import manager, entity from .constants import * ############ @@ -20,7 +20,7 @@ class PieceManager(manager.Manager): PieceManager acts as a manager for all Pieces (Entities representing playable characters on the game board), as well as for the TileCursor object that is used to interact - with units and tiles. + with pieces and tiles. """ def __init__(self, game, bus): @@ -212,7 +212,7 @@ class PieceManager(manager.Manager): # Section 2 - The Piece class # ############################### -class Piece(vgo.Entity): +class Piece(entity.Entity): """ Object that represents a playable piece on the board. Mostly only differs from entity in that it expects the standard @@ -234,12 +234,19 @@ class Piece(vgo.Entity): self.passable = passable self.normal_stats = normal_stats self.active_stats = active_stats - self.team = team # TODO: team maybe should be defined in unit + self.team = team self.back_to_stand = False # TODO: This may not be the best way self.current_tile_path = [] self.current_tile_path_index = 0 self.path_moving = False + def set_facing(self, direction): + """ + Set the direction this piece should face. 'direction' should + be a valid enum value matching FACE_DIR. + """ + self.facing = direction + def set_motion(self, target_pos, speed): """ Overwrite of the basic Entity version to support facing and @@ -266,7 +273,7 @@ class Piece(vgo.Entity): # animation should be assigned according to whether or not a board move is # actually taking place. Also, this is a very roundabout way to reference # the sheet's animations. Maybe sheets should be aware of their anims? - self.set_animation(self.sheet.manager.animations[self.sheet.name]["walk_" + self.facing.name], True) + self.set_animation(self.manager.bus.fetch_animation("walk_" + self.facing.name), True) def set_move_along_tile_path(self, tile_seq): """ @@ -276,6 +283,14 @@ class Piece(vgo.Entity): self.current_tile_path_index = 0 self.path_moving = True + # Set facing values + #c = None + #for t in tile_seq: + # if c != None: + # if tile_seq[t][0] < tile_seq[c][0]: + # + # c = t + def execute_tile_path_move(self): """ Execute a move along a tile path. @@ -284,6 +299,8 @@ class Piece(vgo.Entity): if self.motion == {} and self.current_tile_path_index < len(self.current_tile_path): next_tar = (self.current_tile_path[self.current_tile_path_index][0] * TILE_WIDTH, self.current_tile_path[self.current_tile_path_index][1] * TILE_HEIGHT) self.set_motion(next_tar, PIECE_MOVE_SPEED) + #anim = None + #if next_tar[ self.current_tile_path_index += 1 elif self.motion == {}: self.tile_pos = self.current_tile_path[self.current_tile_path_index - 1] @@ -306,18 +323,18 @@ class Piece(vgo.Entity): # Section 3 - TileCursor # ########################## -class TileCursor(vgo.Entity): +class TileCursor(entity.Entity): """ Object that follows the cursor to indicate selected/highlighted tiles. """ - def __init__(self, name, ent_id, sheet, sprite = (0, 0), animation = None, animated = False, passable = True, unit = None): + def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False): # Parent initialization - super().__init__(name, ent_id, sheet, sprite, animation, animated, passable, unit) + super().__init__(sheet, sprite, animation, animated) - # VGO settings + # Entity settings self.custom_flags = "TileCursor" # DirtySprite settings diff --git a/src/scene.py b/src/scene.py @@ -1,5 +1,5 @@ import pygame, os, json -from . import manager, vgo +from . import manager, entity from .constants import * ############ @@ -114,19 +114,19 @@ class StillScene(object): Load a scene from file. """ # Load univeral elements - self.name_box = vgo.VisibleGameObject(self.manager.game.sheet_manager.loaded_sheets["still_scene_name_box_1"]) + self.name_box = entity.Entity(self.manager.bus.fetch_sheet("still_scene_name_box_1")) self.name_box.set_position(self.name_box_pos) - self.text_box = vgo.VisibleGameObject(self.manager.game.sheet_manager.loaded_sheets["still_scene_text_box_1"]) + self.text_box = entity.Entity(self.manager.bus.fetch_sheet("still_scene_text_box_1")) self.text_box.set_position(self.text_box_pos) - self.continue_prompt = vgo.VisibleGameObject(self.manager.game.sheet_manager.loaded_sheets["continue_prompt_1"], (0, 0), - self.manager.game.sheet_manager.animations["continue_prompt_1"]["shimmer"], True) + self.continue_prompt = entity.Entity(self.manager.bus.fetch_sheet("continue_prompt_1"), (0, 0), + self.manager.bus.fetch_animation("continue_prompt_1", "shimmer")) self.continue_prompt.set_position(self.continue_prompt_pos) # Load from scene definition JSON scenedef = json.load(open(os.path.join(SCENE_JSON_PATH, scenefile))) self.name = scenedef["name"] - self.background = vgo.VisibleGameObject(self.manager.game.sheet_manager.loaded_sheets[scenedef["bg_sheet"]], tuple(scenedef["bg_sprite"])) + self.background = entity.Entity(self.manager.bus.fetch_sheet(scenedef["bg_sheet"]), tuple(scenedef["bg_sprite"])) self.script = scenedef["script"] for f in scenedef["fonts"]: self.fonts[f] = pygame.font.Font(os.path.join(FONT_PATH, scenedef["fonts"][f][0]), scenedef["fonts"][f][1]) @@ -157,7 +157,7 @@ class StillScene(object): self.current_voice = self.script[self.script_index]["voice"] self.current_text_string = self.script[self.script_index]["line"] for c in self.script[self.script_index]["characters"]: - nc = vgo.VisibleGameObject(self.manager.game.sheet_manager.loaded_sheets[c["sheet"]], tuple(c["sprite"])) + nc = entity.Entity(self.manager.bus.fetch_sheet(c["sheet"]), tuple(c["sprite"])) nc.set_position(tuple(c["pos"])) self.displayed_characters.add(nc) if self.script[self.script_index]["speaker"] != None: diff --git a/src/turn.py b/src/turn.py @@ -1,5 +1,5 @@ import pygame -from . import manager, vgo +from . import manager, entity from .constants import * ########### @@ -64,7 +64,7 @@ class TurnManager(manager.Manager): # Section 2 - Turn Entities # ############################# -class TurnTray(vgo.Entity): +class TurnTray(entity.Entity): """ The TurnTray is the on-screen object where icons and names representing the next Pieces to take @@ -72,19 +72,19 @@ class TurnTray(vgo.Entity): mostly because it must write specific text. """ - def __init__(self, name, ent_id, sheet, sprite = (0, 0), animation = None, animated = False): + def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False): # Parent initialization super().__init__(sheet, sprite, animation, animated) -class TurnButton(vgo.Entity): +class TurnButton(entity.Entity): """ Class representing the buttons with icons and names that appear in order inside the TurnTray object. Clicking one will lock the cursor on that Piece. """ - def __init__(self, name, ent_id, sheet, sprite = (0, 0), animation = None, animated = False): + def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False): # Parent initialization super().__init__(sheet, sprite, animation, animated) diff --git a/src/unit.py b/src/unit.py @@ -1,67 +0,0 @@ -import pygame, json, os -from . import manager -from .constants import STATUS_JSON_PATH - -########### -# unit.py # -########### - -# TODO: -# 1. The utility/appropriateness of this entire file is suspect. Especially seems like Unit does not need its own manager. - -# This file contains -# 1. The UnitManager class, which loads Unit JSONs and keeps track of them (like SheetManager) -# 2. The Unit class, which represents the statistics of a charachter unit - -################################# -# Section 1. UnitManager Object # -################################# - -class UnitManager(manager.Manager): - """ - Loads and manages Unit status JSONs. This manager is - called whenever a Unit must be created and assigned to - an Entity, and it gives back a loaded dict representing - the stats of that Unit. - """ - - def __init__(self, game, bus): - - # Parent initialization - super().__init__(game, bus) - - # Saved values - self.loaded_stats = {} - - def load_stats_from_json(self, stats_json): - """ - Load the stats from a given stats_json file. - """ - j = json.load(open(os.path.join(STATUS_JSON_PATH, stats_json))) - self.loaded_stats[j["name"]] = j - - def get_stats(self, name): - """ - Return stats if it exists. - """ - if name in self.loaded_stats.keys(): - return self.loaded_stats[name] - -########################## -# Section 2. Unit Object # -########################## - -class Unit(object): - """ - Unit represents a turn-taking Entity's statistics that - are important for gameplay. An Entity can have a Unit, - and that Unit can be used to compare against the other - Unit's owned by other Entities. - """ - - def __init__(self, entity, stats_definition): - - # Saved values - self.entity = entity - self.static_stats = stats_definition - self.active_stats = stats_definition diff --git a/src/vgo.py b/src/vgo.py @@ -1,225 +0,0 @@ -import pygame, json, os, math -from . import manager, unit -from .constants import * - -########## -# vgo.py # -########## - -# TODO: -# 1. Some serious refactoring needs to happen here. EntityManager needs to -# be brought in line with how managers in general are imagined to work -# now, especially if the generic manager is going to be significantly -# extended and the bus is going to be implemented. The differences -# between a VGO and an Entity are blurred right now. Hypothetically -# speaking, an Entity should be an extended VGO that has features that -# go beyond what a generally-extended pygame Sprite object should have. -# A VGO is a just a sprite that can be drawn and animated by any other -# object, but an Entity should be created and managed ONLY by the -# EntityManager object, because of its extensions. Positioning and -# animating can be VGO functions, but objects you interact with should -# be Entities. This means the EntityManager should be active and working -# in any mode that has on-screen object interaction (e.g. in Menu mode). -# This is NOT how EntityManager currently works and refactoring of the -# EntityManager, Entity, and VGO objects will be required to bring the -# imagined function of this system of objects to realization. One way to -# accomplish some of this would be to try to encapsulate more of what -# EntityManager does each frame into its own update method, and make -# the behavoir therein game_mode-conditional. -# 2. An alternative to the above: Reimagine EntityManager as PieceManager, -# Meaning it is only responsible for board Piece entities. The BoardCursor -# could be handled by a seperate manager of its own, which could manage -# the cursor across multiple modes. This would mean splitting the EntityManager -# and Piece classes off into their own source file (a good option would -# be to combine them with Unit, remove UnitManager, and just make units -# be something PieceManager handles). All of this would be in line with -# the natural path that the code has been taking, whereby there is not -# one single management object for ALL entities, but rather entities and -# other VGOs are drawn and updated by the managers that logically are -# already associated with them. Drawing entities is a pan-Manager thing, -# and one of the important distinctions between managers and subsystems. -# The codebase should probably reflect this by doing away with EntityManager -# entirely and refactoring it into PieceManager. EntityManager's functionality -# involving identifying entities by name/id could be moved to a subsystem -# object called ObjectOracle. This subsystem could be alerted each time -# a new object is created, and could maintain a list of references to -# those objects. It could manage ID by itself, and spread it across any -# VGO, not just Entities. VGOs would know their own ID, and they could be -# accessed from the oracly by ID by any object with a basic call to -# game.oracle. It would be excellent for debug as well. It is possible that -# even the non-VGO objects like the managers should have IDs and be referred -# to by the oracle. - -# This file contains: -# 1. The Entity class that is the extension of pygame.Sprite used throughout the game -# 2. Various kinds of specialized entity classes that don't fit in other source files - -# TODO: This should eventually use LayeredDirty sprites for performance reasons -# TODO: Eventually need to add a UI-element class type (sub of entity? Should entity replace VGO entirely???) - -############################ -# Section 1 - Entity class # -############################ - -class Entity(pygame.sprite.DirtySprite): - """ - The parent of all visible objects. VisibleGameObject (VGO) is - essentially an extended, customised version of the PyGame Sprite - object. Entity supports animations, motions (movement over time), - and tile operations, but none of that is required. - """ - - def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False): - - # Parent initialization - super().__init__() - - # Saved values - self.sheet = sheet - self.image = sheet.sprites[sprite] - self.rect = self.image.get_rect() - self.rect.topleft = (0, 0) - self.custom_flags = None # Used to pass special info to VGOs - - # DirtySprite class defaults - self.visible = 1 - self.dirty = 0 - self.layer = 0 - - # Animation values - self.animated = animated - self.animation = {} - self.animation_timer = 0 - self.animation_frame = 0 - if animated: - self.set_animation(animation, animated) - - # Motion values - self.motion = {} - self.motion_timer = 0 - - # Tile values - self.tile_pos = (-1, -1) - self.tile_gid = None - - def set_sprite(self, sprite_coord): - """ - Set the VGO's sprite to another one on the board. The - argument 'sprite_coord' is a tuple (x, y). This is used - to assign an image from the saved sheet to non-animated - VGO objects. - """ - # TODO: Error-checking - self.image = self.sheet.sprites[sprite_coord] - - def set_animation(self, new_animation, play = False, init_frame = 0): - """ - Assign a new animation to this VGO and configure all - necessary values to get it playing. - """ - self.animation = new_animation - self.animation_frame = init_frame - self.animation_timer = new_animation[init_frame]["timer"] - self.image = self.sheet.sprites[new_animation[init_frame]["sprite"]] - self.animated = play - - def set_motion(self, target_pos, speed): - """ - Assign a new motion to this VGO and start playing it. - Unlike animations, motions must be played if assigned. - Directions go by center rather than topleft. - """ - self.motion = { - "target" : (target_pos[0] + (TILE_WIDTH / 2), target_pos[1] + (TILE_HEIGHT / 2)), - "speed" : speed, - "direction" : ((target_pos[0] - self.rect.topleft[0]) / speed, (target_pos[1] - self.rect.topleft[1]) / speed), - "timer" : speed - } - - def set_position(self, pos): - """ - Assign the rect topleft to a raw position tuple, independent - of e.g. a tile. - """ - self.rect.topleft = pos - - def set_center_position(self, pos): - """ - Assign the rect center to a raw position tuple, independent - of e.g. a tile. - """ - self.rect.center = pos - - def animate(self): - """ - Play the current animation. This method is called as part of - the update() logic. - """ - if self.animation_timer > 0: - self.animation_timer -= 1 - else: - if self.animation_frame < len(self.animation) - 1: - self.animation_frame += 1 - else: - self.animation_frame = 0 - self.animation_timer = self.animation[self.animation_frame]["timer"] - self.image = self.sheet.sprites[self.animation[self.animation_frame]["sprite"]] - - def motion_move(self): - """ - Perform current motion, if it is set. - """ - if self.motion != {} and not self.rect.collidepoint(self.motion["target"]): - if self.motion_timer == 0: - mx = self.rect.topleft[0] + self.motion["direction"][0] - my = self.rect.topleft[1] + self.motion["direction"][1] - self.rect.topleft = (mx, my) - self.motion_timer = self.motion["speed"] - else: - self.motion_timer -= 1 - else: - if self.motion != {}: - self.set_center_position(self.motion["target"]) # Make sure we end up in exactly the right spot - self.motion = {} - - def snap_to_tile(self): - """ - Snap the Entity to its current tile. - """ - if self.tile_pos != (-1, -1): - self.set_position((self.tile_pos[0] * TILE_WIDTH, self.tile_pos[1] * TILE_HEIGHT)) - - def assign_tile(self, tile_def): - """ - Assign a tile as this entity object's occupied tile. - Assume the values are legitimate. - """ - self.tile_pos = (tile_def[0], tile_def[1]) - self.tile_gid = tile_def[2] - - def act(self): - """ - This method is called as part of update() and is meant - to be overwritten in subclasses with any logic that needs - to happen each update. - """ - pass - - def update(self, surface = None): - """ - Draw the VGO to the surface. Also calls act() for update - logic for specific VGO children, and animate() to animate - the sprite image. - """ - self.act() - if surface != None: - self.motion_move() - if self.animated: - self.animate() - surface.blit(self.image, self.rect) - -######################################### -# Section 2 - Various Entity subclasses # -######################################### - -# TODO: