diff --git a/.gitignore b/.gitignore index 0e5ac79..259a506 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,36 @@ -.venv -__pycache__ \ No newline at end of file +# Virtual Environment +.venv/ +venv/ +ENV/ + +# Python cache files +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ + +# Distribution / packaging +dist/ +build/ +*.egg-info/ +*.egg + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo + +# OS specific files +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Local configuration +.env +.env.local +*.local.py +engine_demo \ No newline at end of file diff --git a/enne2engine/Entities/__init__.py b/Entities/Units/__init__.py similarity index 100% rename from enne2engine/Entities/__init__.py rename to Entities/Units/__init__.py diff --git a/Entities/Units/marine.py b/Entities/Units/marine.py new file mode 100644 index 0000000..2e77c2b --- /dev/null +++ b/Entities/Units/marine.py @@ -0,0 +1,43 @@ +import random +from Entities.entity import Entity + +class Marine(Entity): + + next_cell = (1,1) + movement = 0 + + + def update(self): + self.move() + super().update() + + def select_unit(self): + self.selected = True + # Play a random voice response when selected + sound_file = f"marine/tmawht0{random.randint(0, 4)}.wav" + print(f"Playing sound: {sound_file}") + self.graphics.play_sound(sound_file) + + def move(self): + if (self.x, self.y) != self.next_cell: + # Set walking animation and direction + self.action = "walk" + self.direction = self.graphics.get_direction((self.x, self.y), self.next_cell) + self.moving = True + + # Calculate target coordinates + target_x, target_y = self.graphics.iso_transform(self.next_cell[0], self.next_cell[1]) + + # Increment movement counter + self.movement += 0.01 + + # Calculate how far we've moved (0.0 to 1.0) + move_progress = min(self.movement, 1.0) + # Calculate new position based on progress between cells + self.iso_x = self.iso_x + (target_x - self.iso_x) * move_progress + self.iso_y = self.iso_y + (target_y - self.iso_y) * move_progress + print(f"Moving to {self.iso_x}, {self.iso_y} with progress {move_progress}") + if self.movement >= 1.0: + # Reset movement and set to idle + self.movement = 0 + self.iso_x, self.iso_y = self.graphics.iso_transform(self.x, self.y) \ No newline at end of file diff --git a/enne2engine/Entities/unit.py b/Entities/__init__.py similarity index 100% rename from enne2engine/Entities/unit.py rename to Entities/__init__.py diff --git a/enne2engine/Entities/entity.py b/Entities/entity.py similarity index 59% rename from enne2engine/Entities/entity.py rename to Entities/entity.py index f96d3e9..5f1f27f 100644 --- a/enne2engine/Entities/entity.py +++ b/Entities/entity.py @@ -2,21 +2,28 @@ class Entity: def __init__(self, asset, x, y, action, direction, speed, engine): self.asset = asset + self.graphics = engine.graphics self.x = x self.y = y + self.iso_x, self.iso_y = self.graphics.iso_transform(self.x, self.y) self.action = action self.direction = direction self.speed = speed self.frame = 0 - self.graphics = engine.graphics self.engine = engine + self.selected = True + self.movement = 0 def update(self): x, y = self.graphics.iso_transform(self.x, self.y) occlusion = self.graphics.get_distance((self.x, self.y), self.engine.cursor_pos) / 4 + + # Set color based on selection status + color = (255, 255, 0, 255) if self.selected else (0, 255, 0, 255) + self.graphics.draw_square(self.x, self.y, color=color, margin=4) + if occlusion >= 0.8: return - self.frame = self.graphics.render_sprite(f"{self.asset}_{self.action}_dir{self.direction}", x,y, self.frame, occlusion) - - \ No newline at end of file + self.frame = self.graphics.render_sprite(f"{self.asset}_{self.action}_dir{self.direction}", self.iso_x, self.iso_y, self.frame, occlusion) + \ No newline at end of file diff --git a/assets/Sounds/marine/tmadth00.wav b/assets/Sounds/marine/tmadth00.wav new file mode 100644 index 0000000..45a9753 Binary files /dev/null and b/assets/Sounds/marine/tmadth00.wav differ diff --git a/assets/Sounds/marine/tmadth01.wav b/assets/Sounds/marine/tmadth01.wav new file mode 100644 index 0000000..d3cb11e Binary files /dev/null and b/assets/Sounds/marine/tmadth01.wav differ diff --git a/assets/Sounds/marine/tmapss01.wav b/assets/Sounds/marine/tmapss01.wav new file mode 100644 index 0000000..33f79e1 Binary files /dev/null and b/assets/Sounds/marine/tmapss01.wav differ diff --git a/assets/Sounds/marine/tmapss02.wav b/assets/Sounds/marine/tmapss02.wav new file mode 100644 index 0000000..9fb9b5c Binary files /dev/null and b/assets/Sounds/marine/tmapss02.wav differ diff --git a/assets/Sounds/marine/tmapss03.wav b/assets/Sounds/marine/tmapss03.wav new file mode 100644 index 0000000..44442af Binary files /dev/null and b/assets/Sounds/marine/tmapss03.wav differ diff --git a/assets/Sounds/marine/tmapss04.wav b/assets/Sounds/marine/tmapss04.wav new file mode 100644 index 0000000..3d26c79 Binary files /dev/null and b/assets/Sounds/marine/tmapss04.wav differ diff --git a/assets/Sounds/marine/tmapss05.wav b/assets/Sounds/marine/tmapss05.wav new file mode 100644 index 0000000..d3e7808 Binary files /dev/null and b/assets/Sounds/marine/tmapss05.wav differ diff --git a/assets/Sounds/marine/tmapss06.wav b/assets/Sounds/marine/tmapss06.wav new file mode 100644 index 0000000..03ec671 Binary files /dev/null and b/assets/Sounds/marine/tmapss06.wav differ diff --git a/assets/Sounds/marine/tmasti00.wav b/assets/Sounds/marine/tmasti00.wav new file mode 100644 index 0000000..fa25d9f Binary files /dev/null and b/assets/Sounds/marine/tmasti00.wav differ diff --git a/assets/Sounds/marine/tmasti01.wav b/assets/Sounds/marine/tmasti01.wav new file mode 100644 index 0000000..1222d7e Binary files /dev/null and b/assets/Sounds/marine/tmasti01.wav differ diff --git a/assets/Sounds/marine/tmawht00.wav b/assets/Sounds/marine/tmawht00.wav new file mode 100644 index 0000000..316cf0d Binary files /dev/null and b/assets/Sounds/marine/tmawht00.wav differ diff --git a/assets/Sounds/marine/tmawht01.wav b/assets/Sounds/marine/tmawht01.wav new file mode 100644 index 0000000..2f48646 Binary files /dev/null and b/assets/Sounds/marine/tmawht01.wav differ diff --git a/assets/Sounds/marine/tmawht02.wav b/assets/Sounds/marine/tmawht02.wav new file mode 100644 index 0000000..eb48ec1 Binary files /dev/null and b/assets/Sounds/marine/tmawht02.wav differ diff --git a/assets/Sounds/marine/tmawht03.wav b/assets/Sounds/marine/tmawht03.wav new file mode 100644 index 0000000..5e1787c Binary files /dev/null and b/assets/Sounds/marine/tmawht03.wav differ diff --git a/assets/Sounds/marine/tmayes00.wav b/assets/Sounds/marine/tmayes00.wav new file mode 100644 index 0000000..f75bf78 Binary files /dev/null and b/assets/Sounds/marine/tmayes00.wav differ diff --git a/assets/Sounds/marine/tmayes01.wav b/assets/Sounds/marine/tmayes01.wav new file mode 100644 index 0000000..dc0a9a5 Binary files /dev/null and b/assets/Sounds/marine/tmayes01.wav differ diff --git a/assets/Sounds/marine/tmayes02.wav b/assets/Sounds/marine/tmayes02.wav new file mode 100644 index 0000000..7b6442a Binary files /dev/null and b/assets/Sounds/marine/tmayes02.wav differ diff --git a/assets/Sounds/marine/tmayes03.wav b/assets/Sounds/marine/tmayes03.wav new file mode 100644 index 0000000..6a15c90 Binary files /dev/null and b/assets/Sounds/marine/tmayes03.wav differ diff --git a/engine_demo.py b/engine_demo.py index d743efc..088e70b 100644 --- a/engine_demo.py +++ b/engine_demo.py @@ -3,8 +3,8 @@ from enne2engine.pyglet_wrapper import PygletWrapper from enne2engine.controls import UserControls import sys import os +from Entities.Units.marine import Marine -from enne2engine.Entities.entity import Entity class GameEngine(UserControls): def __init__(self): @@ -12,14 +12,14 @@ class GameEngine(UserControls): if "--pyglet" in sys.argv: self.graphics = PygletWrapper() else: - self.graphics = SDL2Wrapper() + self.graphics = SDL2Wrapper(self) self.map = [ [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], [{ 'wall': True, 'tile': "landscapeTiles_066" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': True, 'tile': "landscapeTiles_066" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': True, 'tile': "landscapeTiles_066" }], [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], + [{ 'wall': False, 'tile': "landscapeTiles_001" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': True, 'tile': "landscapeTiles_066" }] ] @@ -28,50 +28,51 @@ class GameEngine(UserControls): self.load_assets() self.entities = [] + self.entities_positions = {} def run(self): running = True - self.entities.append(Entity("knight", 0, 0, "idle", 1, 1, self)) - self.graphics.create_background(self.map, "tiles") + # Set a custom scale if needed + self.graphics.set_scaling_factor(0.5) # 50% scale + self.entities.append(Marine("knight", 0, 0, "idle", 1, 1, self)) + self.graphics.create_background(self.map, "tiles") while running: perf_counter = self.graphics.get_perf_counter() + for entity in self.entities: + self.entities_positions[(entity.x, entity.y)] = entity + self.graphics.create_background(self.map, "tiles") event = self.graphics.handle_events() if event: + #print(f"Event detected: {event}") + if event.startswith("MOUSEDOWN"): + pass + elif event.startswith("SELECTION"): + # Handle multiple unit selection + self.select_units_in_area(event) + elif event.startswith("MOUSEUP"): + self.select_entity_at_cursor() self.handle_events("keymap_game", event) running = False if event == "QUIT" else True self.graphics.clear_screen() - self.render_background() + self.graphics.render_background() + #self.graphics.render_tile(spritesheet_name="tiles", tile="landscapeTiles_064", x=0, y=0) + #self.graphics.draw_square(0,0) self.cursor_pos = self.graphics.draw_cursor() + # Draw the selection rectangle if selecting + if self.graphics.is_mouse_button_pressed(1): + self.graphics.draw_selection_rectangle() for entity in self.entities: entity.update() + self.graphics.render_sprites() self.graphics.update_status(f"Frame time: {round(self.frame_time)}ms - FPS: {round(1000/self.frame_time if self.frame_time != 0 else 1)}") self.graphics.present_renderer() self.frame_time = self.graphics.get_frame_time(perf_counter) self.graphics.delay_frame(self.frame_time,50) self.graphics.quit() - def set_cursor(self, x, y): - self.graphics.cursor = (x, y) - def render_background(self): - for y, row in enumerate(self.map): - for x, cell in enumerate(row): - cell["occlusion"] = self.get_occlusion(x, y) - if cell['occlusion']==1: - continue - self.graphics.render_tile("tiles", cell, x, y) - - def get_occlusion(self, x, y): - if (x, y) == self.cursor_pos: - return 0 - distance = self.graphics.get_distance((x, y), self.cursor_pos) - if distance <2: - return 0.2 - elif distance < 3: - return 0.5 - else: - return 0.8 - + def set_cursor(self, x, y): + self.graphics.cursor = (x, y) def load_assets(self): self.graphics.load_tilesheet("tiles", "assets/tiles/landscapeTiles_sheet.png") @@ -80,7 +81,46 @@ class GameEngine(UserControls): if file.endswith(".json"): self.graphics.load_spritesheet(file[:-5].lower(), f"assets/KnightBasic/{dir}/{file}") + def select_entity_at_cursor(self): + cursor_x, cursor_y = self.cursor_pos + print(f"Cursor position: {cursor_x}, {cursor_y}") + # First deselect all entities + for entity in self.entities: + entity.selected = False + + # Then select the entity at cursor position, if any + entity = self.entities_positions.get((cursor_x, cursor_y)) + if entity: + entity.select_unit() + print(f"Selected entity at cursor: {entity.asset} at position {entity.x}, {entity.y}") + else: + print("No entity selected at cursor position.") + + def select_units_in_area(self, selection_event): + """Select all units within the specified selection area.""" + # Parse selection coordinates + _, start_x, start_y, end_x, end_y = selection_event.split(":") + start_x, start_y, end_x, end_y = int(start_x), int(start_y), int(end_x), int(end_y) + + # Calculate selection rectangle in screen coordinates + min_x = min(start_x, end_x) + max_x = max(start_x, end_x) + min_y = min(start_y, end_y) + max_y = max(start_y, end_y) + + # First deselect all entities + for entity in self.entities: + entity.selected = False + # Select entities within the rectangle + for entity in self.entities: + # Convert entity position to screen coordinates + screen_x, screen_y = self.graphics.iso_transform(entity.x, entity.y) + + # Check if entity is within selection rectangle + if min_x <= screen_x <= max_x and min_y <= screen_y <= max_y: + entity.select_unit() + print(f"Selected entity in area: {entity.asset} at position {entity.x}, {entity.y}") if __name__ == "__main__": engine = GameEngine() diff --git a/enne2engine/engine_demo.py b/enne2engine/engine_demo.py deleted file mode 100644 index d8aafb6..0000000 --- a/enne2engine/engine_demo.py +++ /dev/null @@ -1,87 +0,0 @@ -from sdl2_wrapper import SDL2Wrapper -from pyglet_wrapper import PygletWrapper -from controls import UserControls -import sys -import os - -from Entities.entity import Entity - -class GameEngine(UserControls): - def __init__(self): - super().__init__() - if "--pyglet" in sys.argv: - self.graphics = PygletWrapper() - else: - self.graphics = SDL2Wrapper() - self.map = [ - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': True, 'tile': "landscapeTiles_066" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': True, 'tile': "landscapeTiles_066" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': True, 'tile': "landscapeTiles_066" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }], - [{ 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': False, 'tile': "landscapeTiles_067" }, { 'wall': True, 'tile': "landscapeTiles_066" }] - ] - self.frame_time = 0 - self.cursor_pos = (0, 0) - - self.load_assets() - self.entities = [] - - def run(self): - running = True - self.graphics.create_background(self.map, "tiles") - self.entities.append(Entity("knight", 0, 0, "idle", 1, 1, self)) - - while running: - perf_counter = self.graphics.get_perf_counter() - event = self.graphics.handle_events() - if event: - self.handle_events("keymap_game", event) - running = False if event == "QUIT" else True - self.graphics.clear_screen() - self.graphics.render_background() - self.cursor_pos = self.graphics.draw_cursor() - for entity in self.entities: - entity.update() - self.graphics.update_status(f"Frame time: {round(self.frame_time)}ms - FPS: {round(1000/self.frame_time if self.frame_time != 0 else 1)}") - self.graphics.present_renderer() - self.frame_time = self.graphics.get_frame_time(perf_counter) - self.graphics.delay_frame(self.frame_time,50) - self.graphics.quit() - def set_cursor(self, x, y): - self.graphics.cursor = (x, y) - - def render_background(self): - for y, row in enumerate(self.map): - for x, cell in enumerate(row): - cell["occlusion"] = self.get_occlusion(x, y) - if cell['occlusion']==1: - continue - self.graphics.render_tile("tiles", cell, x, y) - - def get_occlusion(self, x, y): - if (x, y) == self.cursor_pos: - return 0 - distance = self.graphics.get_distance((x, y), self.cursor_pos) - if distance <2: - return 0.2 - elif distance < 3: - return 0.5 - else: - return 0.8 - - - def load_assets(self): - self.graphics.load_tilesheet("tiles", "assets/tiles/landscapeTiles_sheet.png") - for dir in os.listdir("assets/KnightBasic"): - for file in os.listdir(f"assets/KnightBasic/{dir}"): - if file.endswith(".json"): - self.graphics.load_spritesheet(file[:-5].lower(), f"assets/KnightBasic/{dir}/{file}") - - - -if __name__ == "__main__": - engine = GameEngine() - engine.run() \ No newline at end of file diff --git a/enne2engine/isogeometry.py b/enne2engine/isogeometry.py index 7739c39..f94d781 100644 --- a/enne2engine/isogeometry.py +++ b/enne2engine/isogeometry.py @@ -83,8 +83,15 @@ class IsometricGeometry: return screen_x, screen_y def inv_iso_transform(self, screen_x, screen_y): - x = (2 * screen_y + screen_x - 2 * self.view_offset_x) // self.cell_size - 1 - y = (2 * screen_y - screen_x + 2 * self.view_offset_x) // self.cell_size + # Adjust screen coordinates by removing view offsets + adj_x = screen_x - self.view_offset_x + adj_y = screen_y - self.view_offset_y + + # Inverse isometric transformation formulas: + # Convert adjusted screen coordinates back to grid/world coordinates + x = (adj_x + 2 * adj_y) // self.cell_size + y = (2 * adj_y - adj_x) // self.cell_size + #print(f"adj_x: {adj_x}, adj_y: {adj_y}, x: {x}, y: {y}") return x, y def get_distance(self, position1, position2): @@ -124,4 +131,4 @@ class IsometricGeometry: visited.add((nx, ny)) steps += 1 - return -1 \ No newline at end of file + return -1 \ No newline at end of file diff --git a/enne2engine/sdl2_wrapper.py b/enne2engine/sdl2_wrapper.py index 9a9bd61..f532b11 100644 --- a/enne2engine/sdl2_wrapper.py +++ b/enne2engine/sdl2_wrapper.py @@ -1,22 +1,32 @@ import random import math +import os from math import sqrt import ctypes import sdl2 import sdl2.ext +import sdl2.sdlmixer as sdlmixer from .tilemanager import TileManager from .spritemanager import SpriteManager from .isogeometry import IsometricGeometry from .sdl2_utils.gui import SDL2Gui class SDL2Wrapper(IsometricGeometry, SDL2Gui): - def __init__(self): + def __init__(self, engine): + self.engine = engine sdl2.ext.init() + # Initialize SDL2 mixer + sdlmixer.Mix_Init(sdlmixer.MIX_INIT_OGG | sdlmixer.MIX_INIT_MP3) + sdlmixer.Mix_OpenAudio(44100, sdlmixer.MIX_DEFAULT_FORMAT, 2, 1024) + sdlmixer.Mix_AllocateChannels(16) # Allocate channels for multiple sounds + self.view_size = (800, 600) self.target_size = (800, 600) - self.cell_size = 132 - self.view_offset_x = 400 - self.view_offset_y = 200 + self.base_cell_size = 132 # Original/base cell size + self.scaling_factor = 0.5 # Default scaling factor (can be changed) + self.cell_size = int(self.base_cell_size * self.scaling_factor) # Effective cell size + self.view_offset_x = 0 + self.view_offset_y = 0 self.surface_width = 0 self.surface_height = 0 self.window = sdl2.ext.Window("My Game", size=self.view_size) @@ -27,7 +37,21 @@ class SDL2Wrapper(IsometricGeometry, SDL2Gui): self.factory = sdl2.ext.SpriteFactory(renderer=self.renderer) self.fonts = self.generate_fonts("assets/decterm.ttf") self.cursor = (0, 0) - + # Add selection rectangle tracking variables + self.selection_start = None + self.selection_current = None + self.selecting = False + self.tiles_texture = sdl2.SDL_CreateTexture(self.renderer.renderer, + sdl2.SDL_PIXELFORMAT_RGBA8888, + sdl2.SDL_TEXTUREACCESS_TARGET, + self.view_size[0], + self.view_size[1]) + self.sprite_texture = sdl2.SDL_CreateTexture(self.renderer.renderer, + sdl2.SDL_PIXELFORMAT_RGBA8888, + sdl2.SDL_TEXTUREACCESS_TARGET, + self.view_size[0], + self.view_size[1]) + sdl2.SDL_SetTextureBlendMode(self.sprite_texture, sdl2.SDL_BLENDMODE_BLEND) def get_perf_counter(self): return int(sdl2.SDL_GetPerformanceCounter()) @@ -43,10 +67,43 @@ class SDL2Wrapper(IsometricGeometry, SDL2Gui): return f"KEYDOWN:{key}".lower() elif event.type == sdl2.SDL_MOUSEMOTION: x, y = event.motion.x, event.motion.y + # Update current selection point if dragging + if self.selecting: + self.selection_current = (x, y) return f"MOUSEMOTION:{x}:{y}" + elif event.type == sdl2.SDL_MOUSEBUTTONDOWN: + x, y = event.button.x, event.button.y + # Left mouse button (button 1) + if event.button.button == 1: + self.selection_start = (x, y) + self.selection_current = (x, y) + self.selecting = True + return f"MOUSEDOWN:{x}:{y}" + elif event.type == sdl2.SDL_MOUSEBUTTONUP: + x, y = event.button.x, event.button.y + # Left mouse button (button 1) + if event.button.button == 1: + result = f"MOUSEUP:{x}:{y}" + # Return the selection area if it's large enough and we were selecting + if self.selecting and self.selection_start and self.get_selection_size() > 5: + result = f"SELECTION:{self.selection_start[0]}:{self.selection_start[1]}:{x}:{y}" + + # Always reset selection state on mouse up, regardless of selecting flag + self.selecting = False + self.selection_start = None + self.selection_current = None + return result + return f"MOUSEUP:{x}:{y}" return False def clear_screen(self, color=(0, 0, 0, 255)): # Aggiunto valore alfa di default + sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, *color) + # Clear sprite texture with transparent color + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture) + sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, 0, 0, 0, 0) # Transparent color (alpha=0) + sdl2.SDL_RenderClear(self.renderer.sdlrenderer) + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) + # Return to main renderer with the specified color sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, *color) sdl2.SDL_RenderClear(self.renderer.sdlrenderer) @@ -54,12 +111,15 @@ class SDL2Wrapper(IsometricGeometry, SDL2Gui): sdl2.SDL_RenderPresent(self.renderer.sdlrenderer) def quit(self): + # Clean up mixer resources + sdlmixer.Mix_CloseAudio() + sdlmixer.Mix_Quit() sdl2.SDL_Quit() def load_spritesheet(self, name, path): surface = sdl2.ext.load_image(path.replace('.json', '.png')) texture = sdl2.ext.Texture(self.renderer, surface) - self.sprite_managers[name] = SpriteManager(path, surface, texture, self.cell_size) + self.sprite_managers[name] = SpriteManager(path, surface, texture, self.base_cell_size) def load_tilesheet(self, name, path): surface = sdl2.ext.load_image(path) @@ -72,81 +132,87 @@ class SDL2Wrapper(IsometricGeometry, SDL2Gui): def render_sprite(self, name, x, y, frame, occlusion=0): srcrect, total_frames = self.sprite_managers[name].get_frame_rect(frame) + x -= self.cell_size // 2 + y -= self.cell_size // 4 if occlusion: original_color_mods_r_g_b_a = self.apply_texture_color_mdod(self.sprite_managers[name].spritesheet_texture, occlusion) + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture) self.renderer.copy(self.sprite_managers[name].spritesheet_texture, dstrect=(x, y, self.cell_size, self.cell_size), srcrect=srcrect) if occlusion: sdl2.SDL_SetTextureColorMod(self.sprite_managers[name].spritesheet_texture.tx, *original_color_mods_r_g_b_a) - return (frame + 1) % total_frames - - def render_tile(self, spritesheet_name, cell, x, y): - tile_name = cell.get('tile') - occlusion = cell.get('occlusion', 0) - texture = self.tile_managers[spritesheet_name].get_tilesheet_texture() - tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile_name) - - if tile_rect is not None: - iso_x, iso_y = self.iso_transform(x, y) - y_offset = self.cell_size - tile_rect[3] - dst_rect = (iso_x, iso_y + y_offset, - tile_rect[2], tile_rect[3]) - - # Salva i valori originali dei moduli di colore - original_color_mods_r_g_b_a = self.apply_texture_color_mdod(texture, occlusion) - - # Copia la texture con il nuovo modulo di colore - self.renderer.copy(self.tile_managers[spritesheet_name].get_tilesheet_texture(), - sdl2.SDL_Rect(*tile_rect), - sdl2.SDL_Rect(*dst_rect)) - - sdl2.SDL_SetTextureColorMod(texture.tx, *original_color_mods_r_g_b_a) - - def render_tile_shadow(self, spritesheet_name, cell, x, y): - tile_name = cell.get('tile') - occlusion = cell.get('occlusion', 0) + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) + return (frame + 1) % total_frames def create_background(self, map, spritesheet_name): - self.surface_width = round(self.cell_size * len(map[0])*sqrt(2)) - self.surface_height = round(self.cell_size//2 * len(map)*sqrt(2)) - tilesheet_surface = self.tile_managers[spritesheet_name].get_tilesheet_surface() - bg_surface = sdl2.SDL_CreateRGBSurface(0, self.surface_width, self.surface_height, 32, 0, 0, 0, 0) + # Set page as render target and initialize it + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.tiles_texture) + sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, 0, 0, 0, 255) # Green background + sdl2.SDL_RenderClear(self.renderer.sdlrenderer) + + tilesheet_texture = self.tile_managers[spritesheet_name].get_tilesheet_texture() + def get_shadow(x, y): + if (x, y) == self.engine.cursor_pos: + return 0 + distance = self.get_distance((x, y), self.engine.cursor_pos) + if distance <2: + self.engine.map[y][x]['visited'] = True + return 0.2 + elif distance < 3: + self.engine.map[y][x]['visited'] = True + return 0.5 + else: + if self.engine.map[y][x].get('visited', False): + return 0.8 + else: + return 1 + def blit_tile(tile, x, y): tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile) if tile_rect is not None: - vertical_offset = self.cell_size - tile_rect[3] - horizontal_offset = (self.cell_size - tile_rect[2]) + shadow = get_shadow(x, y) + if shadow==1: + return + self.apply_texture_color_mdod(tilesheet_texture, shadow) + + # Adjusted vertical offset calculation for half-sized cells + vertical_offset = self.cell_size - tile_rect[3] // 2 - self.cell_size//4 + if vertical_offset < 0: + vertical_offset = 0 + + # Adjusted horizontal offset calculation for half-sized cells + horizontal_offset = (self.cell_size - tile_rect[2] // 2) + iso_x, iso_y = self.iso_transform(x, y) iso_y += vertical_offset - iso_x += horizontal_offset + iso_x += horizontal_offset - tile_rect[2] // 4 # Divided by 4 instead of 2 + + # Scale the actual tile rectangle based on the current scaling factor dst_rect = (iso_x, iso_y, - tile_rect[2], tile_rect[3]) - sdl2.SDL_BlitSurface(tilesheet_surface, - sdl2.SDL_Rect(*tile_rect), - bg_surface, - sdl2.SDL_Rect(*dst_rect)) - #sdl2.SDL_FillRect(bg_surface, sdl2.SDL_Rect(*dst_rect), sdl2.SDL_MapRGB(bg_surface.contents.format, 0, 0, 255)) + int(tile_rect[2] * self.scaling_factor), + int(tile_rect[3] * self.scaling_factor)) + + sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, tilesheet_texture.tx, + sdl2.SDL_Rect(*tile_rect), + sdl2.SDL_Rect(*dst_rect)) for y, row in enumerate(map): for x, cell in enumerate(row): tile_name = cell.get('tile') blit_tile(tile_name, x, y) - - self.background_texture = self.factory.from_surface(bg_surface) - self.apply_texture_color_mdod(self.background_texture, 0.8) + # Reset render target to the default (window) + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) - - def render_texture_to_texture(self, src_texture, dst_texture, srcrect, dstrect): - self.renderer.copy(src_texture, srcrect, dstrect) def render_background(self): - self.renderer.copy(self.background_texture, - dstrect=(0, 0, self.surface_width, self.surface_height), - srcrect=(0,0, self.surface_width, self.surface_height)) - + sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, self.tiles_texture, None, None) + + def render_sprites(self): + sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, self.sprite_texture, None, None) + def get_frame_time(self, perf_counter): return (sdl2.SDL_GetPerformanceCounter() - perf_counter) / sdl2.SDL_GetPerformanceFrequency() *1000 @@ -160,17 +226,39 @@ class SDL2Wrapper(IsometricGeometry, SDL2Gui): def draw_cursor(self): x, y = self.cursor - x += self.view_offset_x - y -= self.view_offset_y c_x, c_y = self.inv_iso_transform(x, y) + #print(f"Cursor: {c_x}, {c_y}") iso_x, iso_y = self.iso_transform(c_x, c_y) - iso_y += self.cell_size // 2 - self.renderer.draw_line(points=[(iso_x, iso_y), (iso_x + self.cell_size//2, iso_y + self.cell_size//4)], color=(255, 0, 0, 255)) - self.renderer.draw_line(points=[(iso_x + self.cell_size//2, iso_y + self.cell_size//4), (iso_x + self.cell_size, iso_y)], color=(255, 0, 0, 255)) - self.renderer.draw_line(points=[(iso_x + self.cell_size, iso_y), (iso_x + self.cell_size//2, iso_y - self.cell_size//4)], color=(255, 0, 0, 255)) - self.renderer.draw_line(points=[(iso_x + self.cell_size//2, iso_y - self.cell_size//4), (iso_x, iso_y)], color=(255, 0, 0, 255)) + self.renderer.draw_line(points=[(iso_x, iso_y), (iso_x + self.cell_size//2, iso_y + self.cell_size//4)], color=(255 + , 0, 0, 255)) + self.renderer.draw_line(points=[(iso_x + self.cell_size//2, iso_y + self.cell_size//4), (iso_x, iso_y + self.cell_size//2)], color=(255, 0, 0, 255)) + self.renderer.draw_line(points=[(iso_x, iso_y + self.cell_size//2), (iso_x - self.cell_size//2, iso_y + self.cell_size//4)], color=(255, 0, 0, 255)) + self.renderer.draw_line(points=[(iso_x - self.cell_size//2, iso_y + self.cell_size//4), (iso_x, iso_y)], color=(255, 0, 0, 255)) return c_x, c_y + def draw_square(self, x, y, color=(0, 255, 0, 255), margin=6): + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture) + iso_x, iso_y = self.iso_transform(x, y) + + # Apply margin to reduce the size of the square + adjusted_size = self.cell_size - margin*2 + + # Calculate half-sizes with margin applied + half_width = adjusted_size // 2 + half_height = adjusted_size // 4 + + # Draw an isometric square with margin + self.renderer.draw_line(points=[(iso_x, iso_y + margin), + (iso_x + half_width-margin, iso_y + half_height +margin//2)], color=color) + self.renderer.draw_line(points=[(iso_x + half_width-margin, iso_y + half_height +margin//2), + (iso_x, iso_y + half_height*2+margin//4)], color=color) + self.renderer.draw_line(points=[(iso_x, iso_y + half_height*2+margin//4), + (iso_x - half_width+margin, iso_y + half_height +margin//2)], color=color) + self.renderer.draw_line(points=[(iso_x - half_width+margin, iso_y + half_height +margin//2), + (iso_x, iso_y + margin)], color=color) + + sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) + def apply_texture_color_mdod(self, texture, occlusion): color_mod_r = int(255 * (1 - occlusion)) color_mod_g = int(255 * (1 - occlusion)) @@ -193,9 +281,111 @@ class SDL2Wrapper(IsometricGeometry, SDL2Gui): color_mod_b) return original_color_mods_r_g_b_a - def render_tile_shadow(self, x, y, darkness=0): - iso_x, iso_y = self.iso_transform(x, y) - iso_y += self.cell_size // 2 - self.renderer.copy(self.shadow_texture, - dstrect=(iso_x, iso_y, self.cell_size, self.cell_size), - srcrect=(0, 0, self.cell_size, self.cell_size)) \ No newline at end of file + def render_tile(self, spritesheet_name, x, y, tile): + tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile) + if tile_rect is not None: + vertical_offset = self.cell_size - tile_rect[3] + horizontal_offset = (self.cell_size - tile_rect[2]) + iso_x, iso_y = self.iso_transform(x, y) + iso_y += vertical_offset - tile_rect[3]//4 + iso_x += horizontal_offset - tile_rect[2] // 2 + dst_rect = (iso_x, iso_y, + tile_rect[2], tile_rect[3]) + sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, self.tile_managers[spritesheet_name].get_tilesheet_texture().tx, + sdl2.SDL_Rect(*tile_rect), + sdl2.SDL_Rect(*dst_rect)) + + def set_scaling_factor(self, factor): + """ + Set a new scaling factor and recalculate cell_size. + + Args: + factor (float): The scaling factor (0.5 = half size, 1.0 = original size, etc.) + """ + self.scaling_factor = factor + self.cell_size = int(self.base_cell_size * self.scaling_factor) + # Regenerate background if needed + if hasattr(self.engine, 'map'): + self.create_background(self.engine.map, "tiles") + + def play_sound(self, sound_file): + """ + Play a sound file using SDL2 mixer. + + Args: + sound_file (str): Name of the sound file to play + """ + sound_path = os.path.join("assets", "Sounds", sound_file) + if os.path.exists(sound_path): + sound = sdlmixer.Mix_LoadWAV(sound_path.encode('utf-8')) + if sound: + channel = sdlmixer.Mix_PlayChannel(-1, sound, 0) + if channel == -1: + print(f"Error playing sound: {sdlmixer.Mix_GetError().decode('utf-8')}") + # Free the sound when finished (after a delay or in a callback) + # For simplicity, we're not handling this here, but in a real app you might want to + else: + print(f"Sound file not found: {sound_path}") + + def draw_selection_rectangle(self): + """Draw the selection rectangle if currently selecting.""" + # Only draw if we're in a valid selection state + if not self.selecting or not self.selection_start or not self.selection_current: + return + + # Get coordinates for the rectangle + start_x, start_y = self.selection_start + current_x, current_y = self.selection_current + + # Calculate the rectangle dimensions + x = min(start_x, current_x) + y = min(start_y, current_y) + width = abs(current_x - start_x) + height = abs(current_y - start_y) + + # Only draw if we have a meaningful selection area + if width > 1 and height > 1: + # Draw an empty rectangle in green + self.draw_rectangle(x, y, width, height, "selection", outline=(0, 255, 0), filling=None) + + def get_selection_size(self): + """Get the size (diagonal length) of the selection rectangle.""" + if not self.selection_start or not self.selection_current: + return 0 + + start_x, start_y = self.selection_start + current_x, current_y = self.selection_current + + # Calculate diagonal length using Pythagorean theorem + dx = current_x - start_x + dy = current_y - start_y + return int(math.sqrt(dx*dx + dy*dy)) + + def is_mouse_button_pressed(self, button=1): + """ + Check if a specific mouse button is currently pressed. + + Args: + button (int): Button number (1=left, 2=middle, 3=right) + + Returns: + bool: True if the button is pressed, False otherwise + """ + # Create ctypes variables to store the mouse position + x = ctypes.c_int(0) + y = ctypes.c_int(0) + + # Get the current mouse state (returns a bitmask of buttons) + button_state = sdl2.SDL_GetMouseState(ctypes.byref(x), ctypes.byref(y)) + + # Map button numbers to SDL constants + button_masks = { + 1: sdl2.SDL_BUTTON_LMASK, # Left button + 2: sdl2.SDL_BUTTON_MMASK, # Middle button + 3: sdl2.SDL_BUTTON_RMASK, # Right button + } + + # Check if the requested button is pressed + if button in button_masks: + return bool(button_state & button_masks[button]) + return False \ No newline at end of file