commit 8a40f24d653d25b8bf0634fe62fa799e38b95587 Author: Wobbl Date: Thu Jul 18 10:53:51 2024 +0200 Got the engine working. diff --git a/assets/stacked_sprites/Building.png b/assets/stacked_sprites/Building.png new file mode 100644 index 0000000..8268a98 Binary files /dev/null and b/assets/stacked_sprites/Building.png differ diff --git a/assets/stacked_sprites/Robot.png b/assets/stacked_sprites/Robot.png new file mode 100644 index 0000000..5c59f36 Binary files /dev/null and b/assets/stacked_sprites/Robot.png differ diff --git a/sprite_stacking_engine/cache.py b/sprite_stacking_engine/cache.py new file mode 100644 index 0000000..03cde37 --- /dev/null +++ b/sprite_stacking_engine/cache.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 +import pygame.draw + +from settings import * + + +class Cache: + def __init__(self): + self.stacked_sprite_cache = {} + self.viewing_angle = 360 // NUM_ANGLES + self.outline_thickness = 4 + self.get_stacked_sprite_cache() + + def get_stacked_sprite_cache(self): + for object_name in SPRITE_ATTRS: + self.stacked_sprite_cache[object_name] = { + "rotated_sprites": {} + } + + attrs = SPRITE_ATTRS[object_name] + layer_array = self.get_layer_array(attrs) + self.run_prerender(object_name, layer_array, attrs) + + def run_prerender(self, object_name, layer_array, attrs): + outline = attrs.get("outline", True) + + for angle in range(NUM_ANGLES): + surface = pygame.Surface(layer_array[0].get_size()) + surface = pygame.transform.rotate(surface, angle * self.viewing_angle) + sprite_surface = pygame.Surface([ + surface.get_width(), + surface.get_height() + attrs["num_layers"] * attrs["scale"] + ]) + + sprite_surface.fill(TRANSP_COLOR) + sprite_surface.set_colorkey(TRANSP_COLOR) + + for i, layer in enumerate(layer_array): + layer = pygame.transform.rotate(layer, angle * self.viewing_angle) + sprite_surface.blit(layer, (0, i * attrs["scale"])) + + # get outline + if outline: + outline_coords = pygame.mask.from_surface(sprite_surface).outline() + pygame.draw.polygon(sprite_surface, "black", outline_coords, self.outline_thickness) + + image = pygame.transform.flip(sprite_surface, True, True) + self.stacked_sprite_cache[object_name]["rotated_sprites"][angle] = image + + def get_layer_array(self, attrs): + # load sprite sheet + sprite_sheet = pygame.image.load(attrs["path"]).convert_alpha() + # scaling + sprite_sheet = pygame.transform.scale(sprite_sheet, vec2(sprite_sheet.get_size()) * attrs["scale"]) + sheet_width, sheet_height = sprite_sheet.get_size() + sprite_width = sheet_width // attrs["num_layers"] + # new width to prevent error + sheet_width = sprite_width * attrs["num_layers"] + + # get sprites + layer_array = [] + for x in range(0, sheet_width, sprite_width): + sprite = sprite_sheet.subsurface((x, 0, sprite_width, sheet_height)) + layer_array.append(sprite) + + return layer_array \ No newline at end of file diff --git a/sprite_stacking_engine/engine.py b/sprite_stacking_engine/engine.py new file mode 100755 index 0000000..737633e --- /dev/null +++ b/sprite_stacking_engine/engine.py @@ -0,0 +1,53 @@ +#!/usr/bin/python3 + +import sys +from settings import * +from stacked_sprite import StackedSprite +from cache import Cache +from player import Player +from scene import Scene + + +class App: + def __init__(self): + self.screen = pygame.display.set_mode(RES) + self.clock = pygame.time.Clock() + self.time = 0 + self.delta_time = 0.01 + # groups + self.main_group = pygame.sprite.LayeredUpdates() + # game objects + self.cache = Cache() + self.player = Player(self) + self.scene = Scene(self) + + def update(self): + self.main_group.update() + pygame.display.set_caption(f"{self.clock.get_fps(): .1f}") + self.delta_time = self.clock.tick(60) + + def draw(self): + self.screen.fill(BG_COLOR) + self.main_group.draw(self.screen) + pygame.display.flip() + + def check_events(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): + pygame.quit() + sys.exit() + + def get_time(self): + self.time = pygame.time.get_ticks() * 0.001 + + def run(self): + while True: + self.check_events() + self.get_time() + self.update() + self.draw() + + +if __name__ == "__main__": + app = App() + app.run() diff --git a/sprite_stacking_engine/player.py b/sprite_stacking_engine/player.py new file mode 100644 index 0000000..d49deee --- /dev/null +++ b/sprite_stacking_engine/player.py @@ -0,0 +1,58 @@ +#!/usr/bin/python3 + +from settings import * +import math + + +class Player(pygame.sprite.Sprite): + def __init__(self, app): + self.app = app + self.group = app.main_group + super().__init__(self.group) + + self.group.change_layer(self, CENTER.y) + + size = vec2([50, 50]) + self.image = pygame.Surface(size, pygame.SRCALPHA) + pygame.draw.circle(self.image, "red", size / 2, size[0] / 2) + self.rect = self.image.get_rect(center=CENTER) + + self.offset = vec2(0) + self.inc = vec2(0) + self.angle = 0 + self.diag_move_corr = 1 / math.sqrt(2) + + def control(self): + self.inc = vec2(0) + speed = PLAYER_SPEED * self.app.delta_time + rot_speed = PLAYER_ROT_SPEED * self.app.delta_time + + key_state = pygame.key.get_pressed() + + if key_state[pygame.K_LEFT]: + self.angle -= rot_speed + + if key_state[pygame.K_RIGHT]: + self.angle += rot_speed + + if key_state[pygame.K_w]: + self.inc += vec2(0, -speed).rotate_rad(-self.angle) + + if key_state[pygame.K_s]: + self.inc += vec2(0, speed).rotate_rad(-self.angle) + + if key_state[pygame.K_a]: + self.inc += vec2(-speed, 0).rotate_rad(-self.angle) + + if key_state[pygame.K_d]: + self.inc += vec2(speed, 0).rotate_rad(-self.angle) + + if self.inc.x and self.inc.y: + self.inc *= self.diag_move_corr + + def update(self): + self.control() + self.move() + + def move(self): + self.offset += self.inc \ No newline at end of file diff --git a/sprite_stacking_engine/scene.py b/sprite_stacking_engine/scene.py new file mode 100644 index 0000000..fae4082 --- /dev/null +++ b/sprite_stacking_engine/scene.py @@ -0,0 +1,42 @@ +#!/usr/bin/python3 + +from stacked_sprite import * +from random import uniform + +P = "player" +A, B = "Robot", "Building" + +MAP = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, B, B, B, B], + [0, 0, A, A, 0, B, B, B, B], + [0, 0, A, A, P, B, B, B, B], + [0, 0, A, A, 0, B, B, B, B], + [0, 0, 0, 0, 0, B, B, B, B], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], +] + +MAP_SIZE = MAP_WIDTH, MAP_HEIGHT = vec2(len(MAP), len(MAP[0])) +MAP_CENTER = MAP_SIZE / 2 + + +class Scene: + def __init__(self, app): + self.app = app + self.load_scene() + + def load_scene(self): + rand_rot = lambda: uniform(0, 360) + rand_pos = lambda pos: pos + vec2(uniform(-0.25, 0.25)) + + for j, row in enumerate(MAP): + for i, name in enumerate(row): + pos = vec2(i, j) + vec2(0.5) + + if name == "player": + self.app.player.offset = pos * TILE_SIZE + + elif name: + StackedSprite(self.app, name=name, pos=rand_pos(pos), rot=rand_rot()) diff --git a/sprite_stacking_engine/settings.py b/sprite_stacking_engine/settings.py new file mode 100644 index 0000000..011fb09 --- /dev/null +++ b/sprite_stacking_engine/settings.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 + +import pygame + +vec2 = pygame.math.Vector2 + +RES = WIDTH, HEIGHT = vec2(1400, 800) +CENTER = H_WIDTH, H_HEIGHT = RES // 2 +TILE_SIZE = 250 + +PLAYER_SPEED = 0.4 +PLAYER_ROT_SPEED = 0.0015 + +BG_COLOR = "olivedrab" +NUM_ANGLES = 180 # multiple of 360 +TRANSP_COLOR = (43, 64, 36) + +SPRITE_ATTRS = { + "Robot": { + "path": "../assets/stacked_sprites/Robot.png", + "num_layers": 16, + "scale": 8, + "y_offset": -8 + }, + "Building": { + "path": "../assets/stacked_sprites/Building.png", + "num_layers": 80, + "scale": 4, + "y_offset": -40 + } +} \ No newline at end of file diff --git a/sprite_stacking_engine/stacked_sprite.py b/sprite_stacking_engine/stacked_sprite.py new file mode 100644 index 0000000..1726319 --- /dev/null +++ b/sprite_stacking_engine/stacked_sprite.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +import pygame + +from settings import * +import math +from wobbl_tools import pg + + +class StackedSprite(pygame.sprite.Sprite): + def __init__(self, app, name, pos, rot=0): + self.app = app + self.name = name + self.pos = vec2(pos) * TILE_SIZE + self.player = self.app.player + self.group = self.app.main_group + super().__init__(self.group) + + self.attrs = SPRITE_ATTRS[name] + self.y_offset = vec2(0, -self.attrs["num_layers"] / 2 * self.attrs["scale"]) + self.cache = self.app.cache.stacked_sprite_cache + self.viewing_angle = app.cache.viewing_angle + self.rotated_sprites = self.cache[name]["rotated_sprites"] + self.angle = 0 + self.screen_position = vec2(0) + self.rot = (rot % 360) // self.viewing_angle + + def transform(self): + pos = self.pos - self.player.offset + pos = pos.rotate_rad(self.player.angle) + self.screen_position = pos + CENTER + + def change_layer(self): + self.group.change_layer(self, self.screen_position.y) + + def get_angle(self): + self.angle = -math.degrees(self.player.angle) // self.viewing_angle + self.rot + self.angle = int(self.angle % NUM_ANGLES) + + def update(self): + self.transform() + self.get_angle() + self.get_image() + self.change_layer() + + def get_image(self): + self.image = self.rotated_sprites[self.angle] + self.rect = self.image.get_rect(center=self.screen_position + self.y_offset)