Enhance marine unit functionality: update visibility logic, improve movement handling, and refactor ray casting for better performance and clarity.
This commit is contained in:
+85
-23
@@ -11,7 +11,8 @@ class Marine(Entity):
|
||||
|
||||
def update(self):
|
||||
self.centered_position = (self.iso_x, self.iso_y)
|
||||
self.cast_rays_to_distance(6)
|
||||
# Aggiorna il campo visivo dell'unità (fog of war)
|
||||
self.cast_rays_to_distance(7)
|
||||
|
||||
self.move()
|
||||
super().update()
|
||||
@@ -40,8 +41,8 @@ class Marine(Entity):
|
||||
# Controlla se l'unità èha raggiunto la cella di destinazione
|
||||
if self.target_cell != (self.x, self.y):
|
||||
# Contolla che la cella target non sia occupata da una unità che abbia quella cella come target
|
||||
if self.engine.entities_positions.get(self.target_cell):
|
||||
if self.engine.entities_positions.get(self.target_cell).target_cell == self.target_cell and self.engine.entities_positions.get(self.target_cell) != self:
|
||||
if self.game.entities_positions.get(self.target_cell):
|
||||
if self.game.entities_positions.get(self.target_cell).target_cell == self.target_cell and self.game.entities_positions.get(self.target_cell) != self:
|
||||
#calcola un nuovo target
|
||||
self.target_cell = self.graphics.get_next_cell(self.target_cell, self.position, ignore_units=True)
|
||||
if not self.target_cell:
|
||||
@@ -58,7 +59,7 @@ class Marine(Entity):
|
||||
self.next_cell = self.graphics.get_next_cell(self.next_cell, self.target_cell)
|
||||
# Se l'algoritmo ha efettivamente trovato una cella, occupa la posizione
|
||||
if self.next_cell:
|
||||
self.engine.entities_positions[self.next_cell] = self
|
||||
self.game.entities_positions[self.next_cell] = self
|
||||
# Se la cella di destinazione è occupata da un'altra entità, fermati
|
||||
else:
|
||||
self.action = "idle"
|
||||
@@ -115,23 +116,84 @@ class Marine(Entity):
|
||||
self.graphics.play_sound(f"marine/tmayes0{random.randint(0, 3)}.wav")
|
||||
self.game.cmd_sound_effects = True
|
||||
|
||||
def cast_rays_to_distance(self, distance):
|
||||
# Cast rays to check for visibility
|
||||
done_cells = {}
|
||||
for angle in range(0, 360, 20):
|
||||
for i in range(distance, 0, -1):
|
||||
ray_x = int(self.centered_position[0] + (distance -i) * int(self.engine.graphics.cell_size/3) * math.sin(math.radians(angle)))
|
||||
ray_y = int(self.centered_position[1] - (distance - i)* int(self.engine.graphics.cell_size/6) * math.cos(math.radians(angle)))
|
||||
|
||||
v_x, v_y = self.graphics.inv_iso_transform(ray_x, ray_y)
|
||||
if v_x>= 0 and v_x < len(self.engine.map[0]) and v_y >= 0 and v_y < len(self.engine.map):
|
||||
if (v_x, v_y) not in done_cells.keys():
|
||||
done_cells.update({(v_x, v_y): self.game.map_shadow[v_y][v_x]})
|
||||
self.engine.map[v_y][v_x]["visited"] = True
|
||||
self.game.map_shadow[v_y][v_x] = 0
|
||||
if i == 1:
|
||||
def cast_rays_to_distance(self, view_distance):
|
||||
"""
|
||||
Rivela tutte le celle nel raggio di vista dell'unità usando ray casting.
|
||||
Le celle vengono illuminate indipendentemente dagli ostacoli,
|
||||
ma quelle più lontane hanno maggiore ombreggiatura.
|
||||
"""
|
||||
# Pre-calcolo dei valori trigonometrici per ottimizzazione
|
||||
if not hasattr(self, '_precomputed_angles'):
|
||||
self._precompute_ray_angles()
|
||||
|
||||
revealed_cells = {}
|
||||
|
||||
# Lancia raggi in 18 direzioni (ogni 20 gradi)
|
||||
for angle_idx, (sin_val, cos_val) in enumerate(self._ray_directions):
|
||||
if angle_idx == 0:
|
||||
self._cast_visibility_ray(angle_idx, sin_val, cos_val, view_distance+1, revealed_cells)
|
||||
else:
|
||||
self._cast_visibility_ray(angle_idx, sin_val, cos_val, view_distance, revealed_cells)
|
||||
|
||||
self.game.map_shadow[v_y][v_x] = max(done_cells.get((v_x, v_y), 0)-0.5, 0)
|
||||
print(done_cells.get((v_x, v_y), 0))
|
||||
#self.graphics.draw_line((self.centered_position[0], self.centered_position[1], ray_x, ray_y), color=(0, 255, 0, 255))
|
||||
continue
|
||||
def _precompute_ray_angles(self):
|
||||
"""Pre-calcola i valori sin/cos per evitare calcoli ripetuti ogni frame"""
|
||||
self._ray_directions = []
|
||||
for angle in range(0, 360, 20):
|
||||
radians = math.radians(angle)
|
||||
self._ray_directions.append((math.sin(radians), math.cos(radians)))
|
||||
self._precomputed_angles = True
|
||||
|
||||
def _cast_visibility_ray(self, angle_idx, sin_val, cos_val, max_distance, revealed_cells):
|
||||
"""
|
||||
Lancia un singolo raggio di visibilità dalla posizione dell'unità.
|
||||
Rivela tutte le celle lungo il percorso, applicando ombreggiatura crescente con la distanza.
|
||||
"""
|
||||
cell_size_x = int(self.engine.graphics.cell_size/3)
|
||||
cell_size_y = int(self.engine.graphics.cell_size/6)
|
||||
|
||||
# Calcola le posizioni lungo il raggio partendo dalla distanza massima verso il centro
|
||||
for step in range(max_distance, 0, -1):
|
||||
current_distance = max_distance - step
|
||||
|
||||
# Calcola la posizione del punto sul raggio
|
||||
ray_x = int(self.centered_position[0] + current_distance * cell_size_x * sin_val)
|
||||
ray_y = int(self.centered_position[1] - current_distance * cell_size_y * cos_val)
|
||||
|
||||
# Converte le coordinate schermo in coordinate griglia
|
||||
grid_x, grid_y = self.graphics.inv_iso_transform(ray_x, ray_y)
|
||||
|
||||
# Verifica che le coordinate siano valide
|
||||
if self._is_valid_grid_position(grid_x, grid_y):
|
||||
self._reveal_cell(grid_x, grid_y, step, revealed_cells)
|
||||
|
||||
# Disegna il raggio solo per il punto più distante (visualizzazione debug)
|
||||
# if step == 1:
|
||||
# self.graphics.draw_line(
|
||||
# (self.centered_position[0], self.centered_position[1], ray_x, ray_y),
|
||||
# color=(0, 255, 0, 255)
|
||||
# )
|
||||
|
||||
def _is_valid_grid_position(self, grid_x, grid_y):
|
||||
"""Controlla se le coordinate della griglia sono valide"""
|
||||
map_width = len(self.engine.map[0])
|
||||
map_height = len(self.engine.map)
|
||||
return 0 <= grid_x < map_width and 0 <= grid_y < map_height
|
||||
|
||||
def _reveal_cell(self, grid_x, grid_y, distance_step, revealed_cells):
|
||||
"""
|
||||
Rivela una singola cella della mappa e applica l'ombreggiatura appropriata.
|
||||
Le celle più vicine (distance_step == 1) hanno ombra ridotta.
|
||||
"""
|
||||
cell_key = (grid_x, grid_y)
|
||||
|
||||
# Salva il valore originale dell'ombra solo se non già processata
|
||||
if cell_key not in revealed_cells:
|
||||
revealed_cells[cell_key] = self.game.map_shadow[grid_y][grid_x]
|
||||
# Marca la cella come visitata e rimuovi l'ombra base
|
||||
self.engine.map[grid_y][grid_x]["visited"] = True
|
||||
self.game.map_shadow[grid_y][grid_x] = 0
|
||||
|
||||
# Applica ombreggiatura speciale per le celle ai bordi del campo visivo
|
||||
if distance_step == 1:
|
||||
original_shadow = revealed_cells.get(cell_key, 0)
|
||||
self.game.map_shadow[int(grid_y)][int(grid_x)] = max(original_shadow - 0.5, 0)
|
||||
|
||||
@@ -6,10 +6,10 @@ import json
|
||||
|
||||
|
||||
|
||||
class GameEngine(UserControls):
|
||||
class GameEngine(UserControls,SDL2Renderer):
|
||||
def __init__(self, map, game):
|
||||
super().__init__()
|
||||
self.map = map
|
||||
self.game = game
|
||||
self.graphics = SDL2Renderer(self)
|
||||
self.graphics = SDL2Renderer(game, self)
|
||||
|
||||
|
||||
@@ -12,16 +12,17 @@ from .sdl2_utils.isogeometry import IsometricGeometry
|
||||
from .sdl2_utils.gui import SDL2Gui
|
||||
|
||||
class SDL2Renderer(IsometricGeometry, SDL2Gui):
|
||||
def __init__(self, engine):
|
||||
def __init__(self, game, engine):
|
||||
self.engine = engine
|
||||
self.game = game
|
||||
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.height = len(self.engine.map)
|
||||
self.width = len(self.engine.map[0])
|
||||
self.view_size = (800, 600)
|
||||
self.target_size = (800, 600)
|
||||
self.base_cell_size = 132 # Original/base cell size
|
||||
|
||||
@@ -62,7 +62,7 @@ class IsometricGeometry:
|
||||
continue
|
||||
if not ignore_units:
|
||||
# Skip occupied cells
|
||||
if self.engine.entities_positions.get((new_x, new_y)) is not None:
|
||||
if self.game.entities_positions.get((new_x, new_y)) is not None:
|
||||
continue
|
||||
neighbors.append((new_x, new_y))
|
||||
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
from enne2engine import engine
|
||||
from Entities.Units.marine import Marine
|
||||
|
||||
class Game:
|
||||
def __init__(self):
|
||||
self.engine = engine.GameEngine(self.load_map(), self)
|
||||
|
||||
self.frame_time = 0
|
||||
self.cursor_pos = (0, 0)
|
||||
|
||||
self.load_assets()
|
||||
self.entities = []
|
||||
self.entities_positions = {}
|
||||
|
||||
def run(self):
|
||||
running = True
|
||||
# Set a custom scale if needed
|
||||
self.engine.graphics.set_scaling_factor(1.0) # 50% scale
|
||||
marine = Marine("knight", 0, 0, "idle", 1, 1, self)
|
||||
self.entities.append(marine)
|
||||
# self.entities.append(Marine("knight", 5, 5, "idle", 1, 1, self))
|
||||
# #5 more marines
|
||||
# self.entities.append(Marine("knight", 0, 5, "idle", 1, 1, self))
|
||||
# self.entities.append(Marine("knight", 5, 5, "idle", 1, 1, self))
|
||||
# self.entities.append(Marine("knight", 1,1, "idle", 1, 1, self))
|
||||
# self.entities.append(Marine("knight", 2,2, "idle", 1, 1, self))
|
||||
# self.entities.append(Marine("knight", 3,3, "idle", 1, 1, self))
|
||||
while running:
|
||||
self.cmd_sound_effects = False
|
||||
# Start the frame timer
|
||||
perf_counter = self.engine.graphics.get_perf_counter()
|
||||
# Initialize the map shadow and entities positions
|
||||
self.map_shadow = [ [1 for _ in range(len(self.engine.map[0]))] for _ in range(len(self.engine.map)) ]
|
||||
self.entities_positions.clear()
|
||||
for entity in self.entities:
|
||||
self.entities_positions[entity.next_cell] = entity
|
||||
self.entities_positions[(entity.x, entity.y)] = entity
|
||||
|
||||
# Handle events
|
||||
event = self.engine.graphics.handle_events()
|
||||
if event:
|
||||
#print(f"Event detected: {event}")
|
||||
if event.startswith("MOUSEDOWN"):
|
||||
print(f"Mouse down event: {event}")
|
||||
print(f"Button pressed: {event[-1]}")
|
||||
if event[-1] == "3":
|
||||
print("Right mouse button pressed")
|
||||
for entity in self.entities:
|
||||
if entity.selected:
|
||||
entity.set_target_cell(self.cursor_pos)
|
||||
elif event.startswith("SELECTION"):
|
||||
# Handle multiple unit selection
|
||||
self.select_units_in_area(event)
|
||||
elif event.startswith("MOUSEUP"):
|
||||
if event[-1] == "1":
|
||||
|
||||
self.select_entity_at_cursor()
|
||||
self.engine.handle_events("keymap_game", event)
|
||||
|
||||
running = False if event == "QUIT" else True
|
||||
|
||||
self.engine.graphics.clear_screen()
|
||||
|
||||
for entity in self.entities:
|
||||
entity.update()
|
||||
# Create the map background texture with tiles
|
||||
self.engine.graphics.create_background(self.engine.map, "tiles", self.map_shadow)
|
||||
self.cursor_pos = self.engine.graphics.draw_cursor()
|
||||
|
||||
self.engine.graphics.render_background()
|
||||
self.engine.graphics.render_sprites()
|
||||
self.engine.graphics.draw_selection_rectangle()
|
||||
self.engine.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.engine.graphics.present_renderer()
|
||||
self.frame_time = self.engine.graphics.get_frame_time(perf_counter)
|
||||
self.engine.graphics.delay_frame(self.frame_time,50)
|
||||
self.engine.graphics.quit()
|
||||
|
||||
def set_cursor(self, x, y):
|
||||
self.engine.graphics.cursor = (x, y)
|
||||
|
||||
def load_assets(self):
|
||||
self.engine.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.engine.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.engine.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}")
|
||||
|
||||
def load_map(self):
|
||||
# Load map from JSON file
|
||||
try:
|
||||
with open("assets/maps/map.json", "r") as map_file:
|
||||
return json.load(map_file)
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
print(f"Error loading map file: {e}")
|
||||
print("Exiting program.")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
game = Game()
|
||||
game.run()
|
||||
Reference in New Issue
Block a user