From cea5693b3e6258851d71290d1ff804820c647b13 Mon Sep 17 00:00:00 2001 From: justinbeetle <30003866+justinbeetle@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:25:51 -0600 Subject: [PATCH 1/2] Fix issue where PyscrollDataAdapter.reload_animations didn't clear self._animated_tile leading to a frame of stale animations after reload_animations is called. --- pyscroll/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyscroll/data.py b/pyscroll/data.py index 860f031..d80c50f 100644 --- a/pyscroll/data.py +++ b/pyscroll/data.py @@ -156,6 +156,7 @@ def reload_animations(self) -> None: """ self._update_time() self._animation_queue = list() + self._animated_tile = dict() self._tracked_gids = set() self._animation_map = dict() From 91367d12aa919d5b1aa4f060c1bdbe6769aeb5d0 Mon Sep 17 00:00:00 2001 From: justinbeetle <30003866+justinbeetle@users.noreply.github.com> Date: Sat, 17 Jan 2026 20:37:59 -0600 Subject: [PATCH 2/2] Add set_blit_list_sort_key method to BufferedRenderer allowing the sorting behavior of sprites and overlaying tiles to be tuned. For my purpose, instead of drawing sprites over tiles in the same layer I want to favor the surface with higher bottom coordinate and then favor sprite over tile. The default behavior is retained but can now be tuned to for different appilcations by setting an alternate key function for the sort. This ability to tailer the drawing order may address https://github.com/bitcraft/pyscroll/issues/47. --- pyscroll/orthographic.py | 68 +++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/pyscroll/orthographic.py b/pyscroll/orthographic.py index e0fa07e..8981f6d 100644 --- a/pyscroll/orthographic.py +++ b/pyscroll/orthographic.py @@ -5,7 +5,7 @@ import time from collections.abc import Callable from itertools import chain, product -from typing import TYPE_CHECKING +from typing import Any, Callable, Optional, TYPE_CHECKING, Union import pygame from pygame import Rect, Surface @@ -16,6 +16,7 @@ if TYPE_CHECKING: from .data import PyscrollDataAdapter + log = logging.getLogger(__file__) @@ -74,6 +75,9 @@ def __init__( self.tall_sprites = tall_sprites self.sprite_damage_height = sprite_damage_height self.map_rect = None + self.blit_list_sort_key: Optional[ + Callable[[tuple[int, int, int, int, int, Surface, Optional[int]]], Any] + ] = None # internal private defaults if colorkey and alpha: @@ -216,7 +220,14 @@ def center(self, coords: Vector2D) -> None: self._tile_view.move_ip(dx, dy) self.redraw_tiles(self._buffer) - def draw(self, surface: Surface, rect: RectLike, surfaces: list[Surface] = None): + def draw( + self, + surface: Surface, + rect: RectLike, + surfaces: Optional[ + list[Union[tuple[Surface, Rect, int], tuple[Surface, Rect, int, int]]] + ] = None, + ): """ Draw the map onto a surface. @@ -399,7 +410,12 @@ def translate_rects(self, rects: list[Rect]) -> list[Rect]: return retval def _render_map( - self, surface: Surface, rect: RectLike, surfaces: list[Surface] + self, + surface: Surface, + rect: RectLike, + surfaces: Optional[ + list[Union[tuple[Surface, Rect, int], tuple[Surface, Rect, int, int]]] + ], ) -> None: """ Render the map and optional surfaces to destination surface. @@ -439,7 +455,14 @@ def _clear_surface(self, surface: Surface, area: RectLike = None) -> None: ) surface.fill(clear_color, area) - def _draw_surfaces(self, surface: Surface, offset: Vector2DInt, surfaces) -> None: + def _draw_surfaces( + self, + surface: Surface, + offset: Vector2DInt, + surfaces: list[ + Union[tuple[Surface, Rect, int], tuple[Surface, Rect, int, int]] + ], + ) -> None: """ Draw surfaces while correcting overlapping tile layers. @@ -495,30 +518,26 @@ def _draw_surfaces(self, surface: Surface, offset: Vector2DInt, surfaces) -> Non # equal to the damaged layer, then add the entire column of # tiles to the blit_list. if not, then discard the column and # do not update the screen when the damage was done. - column = list() - is_over = False + column = [] for dl, damage_rect in sprite_damage: x, y, w, h = damage_rect tx = x // w + left ty = y // h + top # TODO: heightmap - for l in tile_layers: + for l in [l for l in tile_layers if dl <= l]: tile = get_tile(tx, ty, l) if tile: sx = x - ox sy = y - oy - if dl <= l: - is_over = True blit_op = l, 0, sx, sy, order, tile, None column.append(blit_op) order += 1 - if is_over: + if len(column): blit_list.extend(column) column.clear() - is_over = False # finally sort and do the thing - blit_list.sort() + blit_list.sort(key=self.blit_list_sort_key) draw_list2 = list() for l, priority, x, y, order, s, blend in blit_list: if blend is not None: @@ -528,6 +547,31 @@ def _draw_surfaces(self, surface: Surface, offset: Vector2DInt, surfaces) -> Non draw_list2.append(blit_op) surface.blits(draw_list2, doreturn=False) + def set_blit_list_sort_key( + self, + key: Optional[ + Callable[[tuple[int, int, int, int, int, Surface, Optional[int]]], Any] + ], + ) -> None: + """Set the key function for sorting list of blit operations on sprites + and tiles overlaying sprites in the _draw_surfaces method. The elements + in the list are 7-tuples with the following contents: + element 0 (int): layer number + element 1 (int): priority - 0 for tile, 1 for sprite + element 2 (int): x coordinate for the left corner of the surface + element 3 (int): y coordinate for the top-left corner of the surface + element 4 (int): the pre-sorted order of elements in the list + element 5 (Surface): the source image for the blit + element 6 (int): Special flags for blit operation + + The default key is None, yeilding a sort first on element 0, then 1, 2, ..., 6. + + To alternatinely sort first on layer, then bottom y coordinate, then priority, + the key function could be set to the following lambda function: + lambda x: (x[0], x[3]+x[5].get_height(), x[1]) + """ + self.blit_list_sort_key = key + def _queue_edge_tiles(self, dx: int, dy: int) -> None: """ Queue edge tiles and clear edge areas on buffer if needed.