diff --git a/doc/levels/demo.txt b/doc/levels/demo.txt new file mode 100644 index 0000000..6544f65 --- /dev/null +++ b/doc/levels/demo.txt @@ -0,0 +1,48 @@ +1-1 +ghost-house +dash + + + + + + + + + + + + + + + + + + + + + + + + + + + + +########## +########## +########## +########## +########## +########## +########## +########## +########## +########## +########## +########## +########## + +3, 4, HEBEL-1, 0 +23, 34, HEBEL-2, 0 +10, 10, HEBEL-1; HEBEL-2 diff --git a/project/PositionScale.py b/project/PositionScale.py new file mode 100644 index 0000000..90fde85 --- /dev/null +++ b/project/PositionScale.py @@ -0,0 +1,14 @@ +class PositionScale: + def __init__(self, position=(0, 0), scale=(1, 1)): + self.position = position + self.scale = scale + + def apply_scale_to_position(self): + return self.position[0] * self.scale[0], self.position[1] * self.scale[1] + + @staticmethod + def combine(a, b): + return PositionScale( + (a.position[0] + b.position[0], a.position[1] + b.position[1]), + (a.scale[0] * b.scale[0], a.scale[1] * b.scale[1]) + ) diff --git a/project/Sprite.py b/project/Sprite.py new file mode 100644 index 0000000..9ace5b2 --- /dev/null +++ b/project/Sprite.py @@ -0,0 +1,93 @@ +import pygame + +from PositionScale import PositionScale + + +class Sprite: + def __init__(self, spritesheet): + self.spritesheet = spritesheet + + self.animation_state = list(self.spritesheet.animations.keys())[0] + self.animation_frame = 0 + self.animation_delay = 0 + + self.position_scale = PositionScale() + + self.visible = True + self.animated = True + + self.image = None + + def tick(self, dt): + animation = self.spritesheet.animations[self.animation_state] + + if self.animated: + self.animation_delay += dt + + while self.animation_delay >= animation["delays"][self.animation_frame % len(animation["delays"])]: + self.animation_delay -= animation["delays"][self.animation_frame % len(animation["delays"])] + self.animation_frame = (self.animation_frame + 1) % len(animation["images"]) + + self.image = animation["images"][self.animation_frame % len(animation["images"])] + + def set_animation_state(self, state): + if state in self.spritesheet.animations: + self.animation_state = state + self.animation_delay = 0 + self.tick(0) + + def set_animation_frame(self, frame): + self.animation_frame = frame + self.tick(0) + + def draw(self, screen, screen_transform): + if not self.visible: + return + + if self.image is not None: + target_position = screen_transform.apply_scale_to_position() + + target_scale = ( + screen_transform.scale[0] * self.position_scale.scale[0], + screen_transform.scale[1] * self.position_scale.scale[1] + ) + target_size = ( + int(target_scale[0] * self.image.get_width()), + int(target_scale[1] * self.image.get_height()) + ) + + target_image = self.get_scaled_image(self.image, target_size) + + screen.blit(target_image, target_position) + + def get_scaled_image(self, image, resize): + return pygame.transform.scale(image, resize) + + def dump(self, file): + # re-attach all the images to a single surface and save it to a file + width = 0 + height = 0 + + for animation in self.spritesheet.animations.values(): + max_height = 0 + total_width = 0 + for image in animation["images"]: + total_width += image.get_width() + max_height = max(max_height, image.get_height()) + width = max(width, total_width) + height += max_height + + sheet = pygame.Surface((width, height)) + + x = 0 + y = 0 + for animation in self.spritesheet.animations.values(): + max_height = 0 + for image in animation["images"]: + sheet.blit(image, (x, y)) + x += image.get_width() + max_height = max(max_height, image.get_height()) + y += max_height + x = 0 + + pygame.image.save(sheet, file) diff --git a/project/Spritesheet.py b/project/Spritesheet.py new file mode 100644 index 0000000..cedd8f2 --- /dev/null +++ b/project/Spritesheet.py @@ -0,0 +1,33 @@ +import pygame + + +class Spritesheet: + def __init__(self, sprites_dir, sprite_data): + self.sprite_id = sprite_data["id"] + self.subsheets = sprite_data["subsheets"] + + self.sheet_file = sprites_dir + "/" + self.sprite_id + ".png" + self.sheet = pygame.image.load(self.sheet_file).convert_alpha() + + self.animations = {} + self.load_animations() + + def load_animations(self): + subsheet_y = 0 + + for subsheet_data in self.subsheets: + subsheet_id = subsheet_data["id"] + delays = subsheet_data["delays"] + subsheet_width = subsheet_data["width"] + subsheet_height = subsheet_data["height"] + subsheet_x = 0 + + subsheet_images = [] + for i in range(len(delays)): + subsheet_image = pygame.Surface((subsheet_width, subsheet_height), pygame.SRCALPHA) + subsheet_image.blit(self.sheet, (0, 0), (subsheet_x, subsheet_y, subsheet_width, subsheet_height)) + subsheet_images.append(subsheet_image) + subsheet_x += subsheet_width + + subsheet_y += subsheet_height + self.animations[subsheet_id] = {"images": subsheet_images, "delays": delays} diff --git a/project/SpritesheetManager.py b/project/SpritesheetManager.py new file mode 100644 index 0000000..50abcb4 --- /dev/null +++ b/project/SpritesheetManager.py @@ -0,0 +1,26 @@ +import pygame +import json + +from Spritesheet import Spritesheet + + +# This class is used to load named sprite sheets from the img folder. +class SpritesheetManager: + def __init__(self, sprites_dir, config_file): + self.sprites_dir = sprites_dir + self.spritesheets = {} + self.load_from_config(config_file) + + def load_from_config(self, config_file): + print("Loading sprites from sprite sheet config file", config_file) + config = json.load(open(config_file)) + + for sprite_data in config: + sprite_sheet = Spritesheet(self.sprites_dir, sprite_data) + self.spritesheets[sprite_sheet.sprite_id] = sprite_sheet + print("Loaded", len(self.spritesheets), "sprite sheet(s)") + + def get_sheet(self, sheet): + if sheet not in self.spritesheets: + raise ValueError("Invalid/Missing sprite sheet " + sheet) + return self.spritesheets[sheet] diff --git a/project/main.py b/project/main.py new file mode 100644 index 0000000..c7d06a2 --- /dev/null +++ b/project/main.py @@ -0,0 +1,35 @@ +import random + +import pygame + +from PositionScale import PositionScale +from SpritesheetManager import SpritesheetManager +from Sprite import Sprite + +pygame.init() +screen = pygame.display.set_mode((300, 300)) +pygame.display.set_caption("PE GAME") +clock = pygame.time.Clock() + +spritesheet_manager = SpritesheetManager("sprites", "sprites/sprites.json") + +test_1_sprite = Sprite(spritesheet_manager.get_sheet("test_1")) + +test_1_sprite.dump("debug.png") + +while True: + clock.tick(5) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + + screen.fill((0, 0, 0)) + + test_1_sprite.tick(1) + test_1_sprite.draw(screen, PositionScale((40, 40), (3, 3))) + pygame.display.update() + + if random.randint(1, 10) == 1: + test_1_sprite.set_animation_state(random.choice(["walk_r", "walk_l", "idle", "other_test"])) + print(test_1_sprite.animation_state) diff --git a/project/sprites/sprites.json b/project/sprites/sprites.json new file mode 100644 index 0000000..f7e3e93 --- /dev/null +++ b/project/sprites/sprites.json @@ -0,0 +1,51 @@ +[ + { + "id": "test_1", + "subsheets": [ + { + "id": "walk_l", + "delays": [ + 1, + 2, + 3, + 1 + ], + "height": 16, + "width": 16 + }, + { + "id": "idle", + "delays": [ + 1, + 1, + 3, + 1, + 1 + ], + "height": 16, + "width": 16 + }, + { + "id": "walk_r", + "delays": [ + 1, + 1, + 1, + 1 + ], + "height": 16, + "width": 16 + }, + { + "id": "other_test", + "delays": [ + 1, + 1, + 1 + ], + "height": 16, + "width": 16 + } + ] + } +] \ No newline at end of file diff --git a/project/sprites/test_1.png b/project/sprites/test_1.png new file mode 100644 index 0000000..c6e43f4 Binary files /dev/null and b/project/sprites/test_1.png differ