You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
18 KiB
390 lines
18 KiB
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 .sdl2_utils.isogeometry import IsometricGeometry |
|
from .sdl2_utils.gui import SDL2Gui |
|
|
|
class SDL2Renderer(IsometricGeometry, SDL2Gui): |
|
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.height = len(engine.map) |
|
self.width = len(engine.map[0]) |
|
self.view_size = (800, 600) |
|
self.target_size = (800, 600) |
|
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.ground_level_offset = self.base_cell_size // 4 |
|
self.view_offset_x = 400 |
|
self.view_offset_y = 0 |
|
self.surface_width = 0 |
|
self.surface_height = 0 |
|
self.window = sdl2.ext.Window("Enne2EngineDEMO", size=self.view_size) |
|
self.window.show() # Mostra la finestra |
|
self.renderer = sdl2.ext.Renderer(self.window, flags=sdl2.SDL_RENDERER_ACCELERATED) |
|
self.tile_managers = {} |
|
self.sprite_managers = {} |
|
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()) |
|
|
|
def handle_events(self): |
|
for event in sdl2.ext.get_events(): |
|
if event.type == sdl2.SDL_QUIT: |
|
return "QUIT" |
|
elif event.type == sdl2.SDL_KEYUP: |
|
key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') |
|
return f"KEYUP:{key}".lower() |
|
elif event.type == sdl2.SDL_KEYDOWN: |
|
key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') |
|
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) |
|
self.selection_start = (x, y) |
|
self.selection_current = (x, y) |
|
if event.button.button == 1: |
|
self.selecting = True |
|
return f"MOUSEDOWN:{x}:{y}:{event.button.button}" |
|
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}:{event.button.button}" |
|
# 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) |
|
|
|
def present_renderer(self): |
|
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.base_cell_size) |
|
|
|
def load_tilesheet(self, name, path): |
|
surface = sdl2.ext.load_image(path) |
|
texture = sdl2.ext.Texture(self.renderer, surface) |
|
|
|
self.tile_managers[name] = TileManager(path.replace('.png', '.xml'), surface, texture, self.cell_size) |
|
|
|
def load_sprite(self, spritesheet, name): |
|
return self.tile_managers[spritesheet].get_tile_rect(name) |
|
|
|
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) |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) |
|
return (frame + 1) % total_frames |
|
|
|
def create_background(self, map, spritesheet_name, map_shadow): |
|
# 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) |
|
self.map_shadow = map_shadow |
|
|
|
tilesheet_texture = self.tile_managers[spritesheet_name].get_tilesheet_texture() |
|
|
|
def get_shadow(x, y): |
|
shadow = min(self.map_shadow[y][x], 1) |
|
if shadow == 1 and self.engine.map[y][x].get('visited'): |
|
shadow = 0.8 |
|
return shadow |
|
|
|
def blit_tile(tile, x, y): |
|
tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile) |
|
if tile_rect is not None: |
|
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] * self.scaling_factor - self.ground_level_offset * self.scaling_factor) |
|
# Adjusted horizontal offset calculation for half-sized cells |
|
horizontal_offset = (self.cell_size - tile_rect[2] * self.scaling_factor ) |
|
|
|
iso_x, iso_y = self.iso_transform(x, y) |
|
iso_y += vertical_offset |
|
iso_x += (horizontal_offset - tile_rect[2] * self.scaling_factor // 2) |
|
|
|
# Scale the actual tile rectangle based on the current scaling factor |
|
dst_rect = (int(iso_x), int(iso_y), |
|
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) |
|
# Reset render target to the default (window) |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) |
|
|
|
|
|
def render_background(self): |
|
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 |
|
|
|
def delay_frame(self, frame_time, target): |
|
self.performance = (frame_time) |
|
if self.performance < target: |
|
delay = target - round(self.performance) |
|
else: |
|
delay = 0 |
|
sdl2.SDL_Delay(delay) |
|
|
|
def draw_cursor(self): |
|
x, y = self.cursor |
|
c_x, c_y = self.inv_iso_transform(x, y) |
|
iso_x, iso_y = self.iso_transform(c_x, c_y) |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture) |
|
|
|
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)) |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) |
|
return c_x, c_y |
|
|
|
def draw_square(self, iso_x, iso_y, color=(0, 255, 0, 255), margin=6): |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture) |
|
|
|
# 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)) |
|
color_mod_b = int(255 * (1 - occlusion)) |
|
original_color_mods_r_g_b_a = [0] * 3 |
|
r_ptr = ctypes.pointer(ctypes.c_ubyte()) |
|
g_ptr = ctypes.pointer(ctypes.c_ubyte()) |
|
b_ptr = ctypes.pointer(ctypes.c_ubyte()) |
|
sdl_texture = texture.texture if hasattr(texture, 'texture') else texture.tx |
|
sdl2.SDL_GetTextureColorMod(sdl_texture, |
|
r_ptr, |
|
g_ptr, |
|
b_ptr) |
|
original_color_mods_r_g_b_a[0] = r_ptr.contents.value |
|
original_color_mods_r_g_b_a[1] = g_ptr.contents.value |
|
original_color_mods_r_g_b_a[2] = b_ptr.contents.value |
|
sdl2.SDL_SetTextureColorMod(sdl_texture, |
|
color_mod_r, |
|
color_mod_g, |
|
color_mod_b) |
|
return original_color_mods_r_g_b_a |
|
|
|
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.) |
|
""" |
|
if factor > 0: |
|
print(f"Scaling factor set to {factor}") |
|
self.scaling_factor = factor |
|
self.cell_size = int(self.base_cell_size * self.scaling_factor) |
|
|
|
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 |
|
|
|
def set_opacity(self, x, y, opacity): |
|
self.engine.map_shadow[y][x] = max(self.engine.map_shadow[y][x], opacity) |
|
|
|
def draw_line(self, points, color=(255, 0, 0, 255)): |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture) |
|
self.renderer.draw_line(points=points, color=color) |
|
sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None) |