Return to repo list

heart-of-gold

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

commit cceaaaa865a8e243c1d12c46e4121c4e063f9cc9
parent bed90a4b9717102f5a9d383502e06ddf788ffa24
Author: Erik Letson <hmagellan@hmagellan.com>
Date:   Fri, 13 Nov 2020 21:42:57 -0600

Initial, non-working version of turn display

Diffstat:
Mdata/board/testmap1/testmap1.json | 4++++
Adata/img/pictures/jisella_1/icons_small.png | 0
Adata/img/pictures/jisella_2/icons_small.png | 0
Adata/img/pictures/jisella_3/icons_small.png | 0
Adata/img/pictures/jisella_4/icons_small.png | 0
Adata/img/turn_icon_holder_1.png | 0
Mdata/json/sheets.json | 32+++++++++++++++++++++++++++++++-
Mmain.py | 25++++++++++++++++---------
Msrc/camera.py | 13+++++++++++++
Msrc/constants.py | 1+
Msrc/game.py | 10+++++-----
Msrc/piece.py | 19+++++++++++++++++--
Msrc/turn.py | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
13 files changed, 155 insertions(+), 26 deletions(-)

diff --git a/data/board/testmap1/testmap1.json b/data/board/testmap1/testmap1.json @@ -4,6 +4,7 @@ "type" : "Piece", "sheet" : "jisella_1", "sprite" : [0, 0], + "pictures" : "jisella_pictures_1", "visible" : true, "animation" : "stand_L", "animated" : true, @@ -61,6 +62,7 @@ "type" : "Piece", "sheet" : "jisella_2", "sprite" : [0, 0], + "pictures" : "jisella_pictures_2", "visible" : true, "animation" : "stand_R", "animated" : true, @@ -118,6 +120,7 @@ "type" : "Piece", "sheet" : "jisella_4", "sprite" : [0, 0], + "pictures" : "jisella_pictures_4", "visible" : true, "animation" : "stand_R", "animated" : true, @@ -175,6 +178,7 @@ "type" : "Piece", "sheet" : "jisella_3", "sprite" : [0, 0], + "pictures" : "jisella_pictures_3", "visible" : true, "animation" : "stand_R", "animated" : true, diff --git a/data/img/pictures/jisella_1/icons_small.png b/data/img/pictures/jisella_1/icons_small.png Binary files differ. diff --git a/data/img/pictures/jisella_2/icons_small.png b/data/img/pictures/jisella_2/icons_small.png Binary files differ. diff --git a/data/img/pictures/jisella_3/icons_small.png b/data/img/pictures/jisella_3/icons_small.png Binary files differ. diff --git a/data/img/pictures/jisella_4/icons_small.png b/data/img/pictures/jisella_4/icons_small.png Binary files differ. diff --git a/data/img/turn_icon_holder_1.png b/data/img/turn_icon_holder_1.png Binary files differ. diff --git a/data/json/sheets.json b/data/json/sheets.json @@ -54,6 +54,26 @@ "dimensions" : [64, 64], "total_sprites" : 72 }, + "jisella_pictures_1_icons_small" : { + "filename" : "pictures/jisella_1/icons_small.png", + "dimensions" : [24, 24], + "total_sprites" : 2 + }, + "jisella_pictures_2_icons_small" : { + "filename" : "pictures/jisella_2/icons_small.png", + "dimensions" : [24, 24], + "total_sprites" : 2 + }, + "jisella_pictures_3_icons_small" : { + "filename" : "pictures/jisella_3/icons_small.png", + "dimensions" : [24, 24], + "total_sprites" : 2 + }, + "jisella_pictures_4_icons_small" : { + "filename" : "pictures/jisella_4/icons_small.png", + "dimensions" : [24, 24], + "total_sprites" : 2 + }, "cursor1" : { "filename" : "cursor1.png", "dimensions" : [64, 64], @@ -63,5 +83,15 @@ "filename" : "board_overlays_1.png", "dimensions" : [64, 64], "total_sprites" : 4 - } + }, + "turn_order_tray_1" : { + "filename" : "turn_order_tray_1.png", + "dimensions" : [135, 128], + "total_sprites" : 1 + }, + "turn_icon_holder_1" : { + "filename" : "turn_icon_holder_1.png", + "dimensions" : [128, 28], + "total_sprites" : 1 + } } diff --git a/main.py b/main.py @@ -2,15 +2,22 @@ import pygame from src import game ## GLOBAL TODO: -# 2. The JSON loading methods are all over the place. Need to decide about whether or not things like units should be defined in individual JSONs and -# referenced by a big meta JSON or if there should just be many individual JSONs for that. One problem with it is that when saving is implemented -# there will have to be a way to distinguish between the basic versions of a unit and the ones that are leveled up. Maybe JSONs are not the correct -# choice altogether. Perhaps there should be one huge save.json file for all changes. If that is so, then there could be several JSONs in the dir -# located at data/json that are loaded first, then the save.json file is parsed by a save-load subsystem and any changes are applied, at which point -# the game would be in a loaded state. This seems the ideal solution, and it can be put off a bit. -# 3. Implement the "draw()" functionality of DirtySprite for all VGOs, which will be done in anything that manages a LayeredDirty group, e.g. in the -# EntityManager object. -# 6. Implement the pre-rendered cutscene playing methodology using Pyglet as described here: http://www.sbirch.net/tidbits/pygame_video.html +# 2. The JSON loading methods are all over the place. Need to decide about whether or not things like units should be defined in individual JSONs and +# referenced by a big meta JSON or if there should just be many individual JSONs for that. One problem with it is that when saving is implemented +# there will have to be a way to distinguish between the basic versions of a unit and the ones that are leveled up. Maybe JSONs are not the correct +# choice altogether. Perhaps there should be one huge save.json file for all changes. If that is so, then there could be several JSONs in the dir +# located at data/json that are loaded first, then the save.json file is parsed by a save-load subsystem and any changes are applied, at which point +# the game would be in a loaded state. This seems the ideal solution, and it can be put off a bit. +# 3. Implement the "draw()" functionality of DirtySprite for all VGOs, which will be done in anything that manages a LayeredDirty group, e.g. in the +# EntityManager object. +# 6. Implement the pre-rendered cutscene playing methodology using Pyglet as described here: http://www.sbirch.net/tidbits/pygame_video.html +# 7. ALL character-related art could go in the 'data/img/pictures' directory, and it could have common names (all icons called icon.png, all +# in-battle spritesheets called battle_sprites.png, etc.) The sheet/piece/other manager(s) could be made to search these dirs instead of +# looking for single specific files. This would greatly simplify animation. Animations could simply be made to match one of these common file +# names, and thus only a fraction of the animations would have to be loaded, and it would simplify fetch calls too (desirable since fetch calls +# for sheets and animations are looking like they will be very common). The 'pictures' directory should maybe be renamed to 'chars' or something. +# Scene character art could also be stored in this dir, but scene def JSONs will still just point to specific files (already should support +# subdir pathing at this point since it is loaded with os.join). # Initialization of pygame and submodules pygame.mixer.pre_init(44100, -16, 4, 1024) diff --git a/src/camera.py b/src/camera.py @@ -64,6 +64,19 @@ class GameCamera(subsystem.GameSubsystem): changey = rel_offset[1] * speed_multiple self.camera_surface_offset = (self.camera_surface_offset[0] + changex, self.camera_surface_offset[1] + changey) + def lock_to_position(self, position): + """ + Lock the camera to a position, relative + to the screen. The given position should + be coordinates on the camera surface, and + this method will compute that to produce + an offset such that the camera surface + appears centered to that position in + relation to the game screen. + """ + # TODO: Work here + self.camera_surface_offset = position + def update_camera(self, surface = None): """ Update and draw the camera contents. diff --git a/src/constants.py b/src/constants.py @@ -27,6 +27,7 @@ COLORKEY = (255, 0, 255) PIECE_MOVE_SPEED = 2 PIECE_MOVE_DELAY = PIECE_MOVE_SPEED * 4 SCROLL_SPEED = 4 +UI_FONT = "ArchivoNarrow-Regular.otf" # File paths DATA_PATH = os.path.join(os.getcwd(), "data") diff --git a/src/game.py b/src/game.py @@ -136,18 +136,18 @@ class Game(object): self.interface.update_interface() # State_Mode-specific actions (can be further subdivided by Control_Mode) + # Camera draw occurs in all these modes if self.state_mode == STATE_MODES.Main_Menu_Mode: self.menu_manager.update(self.camera.camera_surface) + self.camera.update_camera(self.screen) elif self.state_mode == STATE_MODES.Battle_Mode: self.board_manager.update(self.camera.camera_surface) self.piece_manager.update(self.camera.camera_surface) - self.turn_manager.update(self.camera.camera_surface) + self.camera.update_camera(self.screen) + self.turn_manager.update(self.screen) # Draw to the screen since it manages UI elements elif self.state_mode == STATE_MODES.Still_Scene_Mode: self.scene_manager.update(self.camera.camera_surface) - - # Draw the camera to the screen - #self.screen.blit(self.camera.camera_surface, self.camera.camera_surface_offset) - self.camera.update_camera(self.screen) + self.camera.update_camera(self.screen) # State_Mode agnostic, Control_Mode specific actions if self.control_mode == CTRL_MODES.No_Control: diff --git a/src/piece.py b/src/piece.py @@ -57,12 +57,13 @@ class PieceManager(manager.Manager): n_anim = self.bus.fetch("sheet_manager", "animations")[definition[p]["sheet"]][definition[p]["animation"]] n_animated = definition[p]["animated"] n_name = definition[p]["name"] + n_pictures = definition[p]["pictures"] n_passable = definition[p]["passable"] n_nstats = definition[p]["normal_stats"] n_astats = definition[p]["active_stats"] n_team = definition[p]["team"] n_equip = definition[p]["equipment"] - np = Piece(n_sheet, n_sprite, n_anim, n_animated, self, n_name, n_passable, n_nstats, n_astats, n_team, n_equip) + np = Piece(n_sheet, n_sprite, n_anim, n_animated, self, n_name, n_pictures, n_passable, n_nstats, n_astats, n_team, n_equip) np.assign_tile(tuple(definition[p]["tile"]), self.bus.fetch("board_manager", "board_tile_layer")[tuple(definition[p]["tile"])]) np.snap_to_tile() self.add_piece(np) @@ -190,7 +191,8 @@ class Piece(entity.Entity): """ def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False, - manager = None, name = None, passable = False, normal_stats = None, active_stats = None, team = None, equipment = None): + manager = None, name = None, pictures = None, passable = False, normal_stats = None, active_stats = None, + team = None, equipment = None): # Parent initialization super().__init__(sheet, sprite, animation, animated) @@ -201,6 +203,12 @@ class Piece(entity.Entity): # Others self.manager = manager self.name = name + self.pictures = { + "icons_small" : None, + "icons_large" : None, + "portrait" : None + } + self.load_pictures(pictures) self.passable = passable self.normal_stats = normal_stats self.active_stats = active_stats @@ -217,6 +225,13 @@ class Piece(entity.Entity): self.taking_turn = False self.haste_mod = 3 # 3 - normal, 1 - minimum (slow 2), 5 - maximum (haste 2) + def load_pictures(self, pictures): + """ + Load universal pictures from a given pictures + directory. + """ + self.pictures["icons_small"] = pictures + "_icons_small" + def set_facing(self, direction): """ Set the direction this piece should face. 'direction' should diff --git a/src/turn.py b/src/turn.py @@ -43,6 +43,10 @@ class TurnManager(manager.Manager): Load up the turns for the first time. """ self.in_play_pieces = pieces + self.turn_tray = TurnTray(self.bus.fetch("sheet_manager", "sheets")["turn_order_tray_1"]) + # TODO: Probably shouldn't be hardcoded + self.turn_tray.set_position((898, 28)) + self.turn_icons = [] self.shift_turns() def shift_turns(self): @@ -82,6 +86,9 @@ class TurnManager(manager.Manager): self.current_active_piece.taking_turn = True self.current_active_piece.readiness -= 100 + # Predict the next turn order + self.project_turn_order() + def project_turn_order(self): """ Make a projection of the upcoming turn order. @@ -89,27 +96,59 @@ class TurnManager(manager.Manager): turn_depth value (accounting for that many projected turns). """ + # Initial setup pt = self.current_tick - self.turn_projection = [] + self.turn_projection = [self.current_active_piece] + self.turn_icons = [] next_turn_candidates = [] - rds = { self.in_play_pieces[i] : self.in_play_pieces[i].readiness for i in self.in_play_pieces } + rds = { i : i.readiness for i in self.in_play_pieces } + + # If anybody has readiness >= 100 already, go ahead and + # account for them + for fc in rds: + if rds[fc] >= 100: + self.turn_projection.append(fc) + rds[fc] -= 100 + + # If we did the above, reverse-sort the turn projection + if len(self.turn_projection) > 1: + self.turn_projection.sort(reverse = True, key = lambda g: (g.readiness, g.active_stats["SPD"])) + + # Until we have the desired amount of projected turns, keep trying + # to add new ones while len(self.turn_projection) < self.turn_depth: pt += 1 for p in rds: rds[p] += p.active_stats["INIT"] + min(1, (p.active_stats["SPD"] // 3)) - if rds[p] >= 100: - next_turn_candidates.append[p] + if rds[p] >= 100 and len(self.turn_projection) < self.turn_depth: + next_turn_candidates.append(p) rds[p] -= 100 next_turn_candidates.sort(reverse = True, key = lambda can: (can.readiness, can.active_stats["SPD"])) for ntc in next_turn_candidates: self.turn_projection.append(ntc) + # Fill up the turn icons + j = 0 + for pj in self.turn_projection: + self.construct_turn_icon(pj, j) + j += 1 + + def construct_turn_icon(self, piece, index): + """ + Create a turn icon for the given piece and append + it to the turn_icons list. + """ + ni = TurnIcon(self.bus.fetch("sheet_manager", "sheets")["turn_icon_holder_1"], (0, 0), None, False, self, piece, index) + self.turn_icons.append(ni) + def update_managed(self, surface): """ Update the entities this manager controls. """ if surface != None: - pass + self.turn_tray.update(surface) + for i in self.turn_icons: + i.update(surface) ############################# # Section 2 - Turn Entities # @@ -120,7 +159,8 @@ class TurnTray(entity.Entity): The TurnTray is the on-screen object where icons and names representing the next Pieces to take their turns are drawn to. It is a seperate class - mostly because it must write specific text. + mostly because it must write specific text and + it can grow to show more turns. """ def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False): @@ -128,16 +168,35 @@ class TurnTray(entity.Entity): # Parent initialization super().__init__(sheet, sprite, animation, animated) -class TurnButton(entity.Entity): +class TurnIcon(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, sheet, sprite = (0, 0), animation = None, animated = False): + def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False, + manager = None, piece = None, index = -1): # Parent initialization super().__init__(sheet, sprite, animation, animated) - + # Initialize important settings based on piece + self.manager = manager + self.piece = piece + self.piece_name = piece.name + self.piece_picture = entity.Entity(self.manager.bus.fetch("sheet_manager", "sheets")[piece.pictures["icons_small"]]) + # TODO: Font size should be dynamically determined from screen size + self.font = pygame.font.Font(os.path.join(FONT_PATH, UI_FONT), 12) + self.index = index + # TODO: Set this in settings??? Should at least dynamically derive y multiple from screen size + self.set_position((900, 32 + (32 * index))) + self.piece_picture.set_position(self.rect.topleft) + + def update(self, surface = None): + """ + Overwrite of parent update() since this entity + draws sub-sprites. + """ + super().update(surface) + self.piece_picture.update(surface)