Last active
May 6, 2025 21:37
-
-
Save nitori/a2f2efcc3dd30ba5c4c4385a0d12cb74 to your computer and use it in GitHub Desktop.
render order
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
from pygame import Vector2 | |
from dataclasses import dataclass | |
@dataclass | |
class Tile: | |
name: str | |
image: pygame.Surface | |
position: Vector2 | |
@dataclass | |
class SingleTile(Tile): | |
# origin is local | |
origin: Vector2 | |
@property | |
def world_origin(self): | |
return self.position + self.origin | |
@dataclass | |
class MultiTile(Tile): | |
# origins are local | |
origins: tuple[Vector2, Vector2] | |
@property | |
def world_origins(self): | |
return ( | |
self.position + self.origins[0], | |
self.position + self.origins[1], | |
) | |
def __post_init__(self): | |
""" | |
Make sure first vector has the smaller x-value. | |
So the line goes from left to right. | |
""" | |
a, b = self.origins | |
self.origins = (a, b) if a.x < b.x else (b, a) | |
class CompareFunc: | |
""" | |
Python does not have a "compare func" for sorting like other languages (not anymore). | |
But you can effectively get the same behaviour by using a class | |
as key-func, that implements __lt__ for comparison. | |
""" | |
def __init__(self, tile: SingleTile | MultiTile): | |
self.tile = tile | |
@staticmethod | |
def compare(single: SingleTile, multi: MultiTile): | |
a, b = multi.world_origins | |
v = b - a | |
p = single.world_origin - a | |
return v.cross(p) | |
def __lt__(self, other: 'CompareFunc'): | |
if isinstance(self.tile, SingleTile) and isinstance(other.tile, SingleTile): | |
# simple y-sort | |
return self.tile.world_origin.y < other.tile.world_origin.y | |
if isinstance(self.tile, SingleTile) and isinstance(other.tile, MultiTile): | |
# do the cross-product sort | |
return self.compare(self.tile, other.tile) < 0 | |
if isinstance(self.tile, MultiTile) and isinstance(other.tile, SingleTile): | |
# do the cross-product sort (just different order) | |
return self.compare(other.tile, self.tile) >= 0 | |
# multi-on-multi case, not sure yet, just sort by y. | |
return self.tile.world_origins[0].y < other.tile.world_origins[0].y | |
class Player: | |
def __init__(self, pos, speed=150): | |
self.image = pygame.image.load('player.png') | |
self.speed = speed | |
self.rect = pygame.FRect(self.image.get_rect(center=pos)) | |
def update(self, delta: float): | |
keys = pygame.key.get_pressed() | |
direction = Vector2( | |
keys[pygame.K_d] - keys[pygame.K_a], | |
keys[pygame.K_s] - keys[pygame.K_w], | |
) | |
if direction.length_squared() > 0: | |
direction.normalize_ip() | |
movement = direction * self.speed * delta | |
self.rect.x += movement.x | |
self.rect.y += movement.y | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((800, 600)) | |
clock = pygame.Clock() | |
player = Player((50, 300)) | |
player_tile = SingleTile( | |
name='PLAYER', | |
image=player.image, | |
position=Vector2(player.rect.topleft), | |
origin=Vector2(14, 94), | |
) | |
entities = [ | |
player_tile, | |
SingleTile( | |
name='other_player', | |
image=pygame.image.load('player.png').convert_alpha(), | |
position=Vector2(450, 270), | |
origin=Vector2(14, 94), | |
), | |
SingleTile( | |
name='other_player2', | |
image=pygame.image.load('player.png').convert_alpha(), | |
position=Vector2(400, 470), | |
origin=Vector2(14, 94), | |
), | |
MultiTile( | |
name='house', | |
image=pygame.image.load('house.png').convert_alpha(), | |
position=Vector2(250, 250), | |
origins=(Vector2(1, 225), Vector2(255, 224)), | |
), | |
MultiTile( | |
name='bed', | |
image=pygame.image.load('bed.png').convert_alpha(), | |
position=Vector2(150, 150), | |
origins=(Vector2(3, 47), Vector2(104, 60)), | |
), | |
] | |
print() | |
while True: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
pygame.quit() | |
return | |
if event.type == pygame.MOUSEBUTTONDOWN: | |
print(event.pos) | |
delta = clock.tick(60) / 1000 | |
player.update(delta) | |
player_tile.position = Vector2(player.rect.topleft) | |
# SORT!!! | |
entities.sort(key=CompareFunc) | |
print('\r', *[e.name for e in entities], end='') | |
screen.fill('black') | |
# render sorted list | |
for entity in entities: | |
screen.blit(entity.image, entity.position) | |
# Debug lines | |
if isinstance(entity, SingleTile): | |
pygame.draw.circle(screen, 'red', entity.world_origin, 3, 3) | |
elif isinstance(entity, MultiTile): | |
pygame.draw.line(screen, 'red', entity.world_origins[0], entity.world_origins[1], 3) | |
pygame.display.flip() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment