diff --git a/highway_vector.py b/highway_vector.py index 4b7a36d..1852e38 100644 --- a/highway_vector.py +++ b/highway_vector.py @@ -16,94 +16,124 @@ TILE_HEIGHT = 32 GRID_SIZE_X = 8 GRID_SIZE_Y = 120 -# Colores de Respaldo / Suelo +# Colores del Entorno COLOR_BG = (15, 15, 25) COLOR_ROAD = (50, 50, 65) COLOR_GRID = (30, 30, 45) +# Paleta de Colores de Entidades +COLOR_YELLOW = (255, 215, 0) +COLOR_WHITE = (240, 240, 240) +COLOR_RED = (220, 40, 40) +COLOR_BROWN = (139, 69, 19) +COLOR_ORANGE = (255, 140, 0) + def grid_to_iso(gx, gy, gz=0): - """Convierte coordenadas de la cuadrícula a coordenadas isométricas de pantalla.""" + """Convierte coordenadas 3D de rejilla a coordenadas 2D de pantalla.""" iso_x = (gx - gy) * (TILE_WIDTH // 2) iso_y = (gx + gy) * (TILE_HEIGHT // 2) - (gz * 16) return iso_x, iso_y +def draw_isometric_prism(surface, color, width=40, height=35): + """Genera una superficie isométrica con forma de prisma o cubo.""" + surf = pygame.Surface((64, 64), pygame.SRCALPHA) + cx, cy = 32, 32 + + w2 = width // 2 + h2 = width // 4 + + # Cara Superior + top_pts = [ + (cx, cy - height), + (cx + w2, cy - height + h2), + (cx, cy - height + 2 * h2), + (cx - w2, cy - height + h2), + ] + + # Cara Izquierda + left_pts = [ + (cx - w2, cy - height + h2), + (cx, cy - height + 2 * h2), + (cx, cy + 2 * h2), + (cx - w2, cy + h2), + ] + + # Cara Derecha + right_pts = [ + (cx, cy - height + 2 * h2), + (cx + w2, cy - height + h2), + (cx + w2, cy + h2), + (cx, cy + 2 * h2), + ] + + # Sombra/Sombreado para volumen + c_top = color + c_left = tuple(max(0, c - 40) for c in color) + c_right = tuple(max(0, c - 70) for c in color) + + pygame.draw.polygon(surf, c_top, top_pts) + pygame.draw.polygon(surf, c_left, left_pts) + pygame.draw.polygon(surf, c_right, right_pts) + + # Bordes para definición + pygame.draw.polygon(surf, (20, 20, 20), top_pts, 1) + pygame.draw.polygon(surf, (20, 20, 20), left_pts, 1) + pygame.draw.polygon(surf, (20, 20, 20), right_pts, 1) + + return surf + + def create_placeholder_sprites(): - """Genera sprites vectoriales en memoria si no existen archivos PNG locales.""" + """Crea las formas geométricas vectoriales solicitadas.""" sprites = {} - # 1. Jugador (Lasertron) + # 1. Jugador: Pirámide Amarilla surf_player = pygame.Surface((64, 64), pygame.SRCALPHA) + base_pts = [(32, 48), (52, 38), (32, 28), (12, 38)] + apex = (32, 8) + + # Caras de la pirámide pygame.draw.polygon( - surf_player, - (255, 200, 0), - [(32, 8), (56, 24), (32, 56), (8, 24)], - ) + surf_player, (200, 160, 0), [base_pts[3], base_pts[0], apex] + ) # Izquierda pygame.draw.polygon( - surf_player, - (255, 255, 255), - [(32, 8), (56, 24), (32, 56), (8, 24)], - 2, - ) - pygame.draw.circle(surf_player, (0, 255, 255), (32, 28), 6) + surf_player, (255, 215, 0), [base_pts[0], base_pts[1], apex] + ) # Derecha + pygame.draw.polygon(surf_player, (255, 235, 100), base_pts) # Base + pygame.draw.line(surf_player, (50, 50, 0), apex, base_pts[0], 2) sprites["player"] = surf_player - # 2. VORTEX - surf_vortex = pygame.Surface((64, 64), pygame.SRCALPHA) - pygame.draw.polygon( - surf_vortex, - (220, 50, 50), - [(32, 4), (60, 20), (52, 52), (12, 52)], - ) - pygame.draw.polygon( - surf_vortex, - (255, 255, 255), - [(32, 4), (60, 20), (52, 52), (12, 52)], - 2, - ) - pygame.draw.circle(surf_vortex, (255, 255, 0), (32, 28), 10) + # 2. VORTEX: Cubo amarillo con "bola" encima + surf_vortex = draw_isometric_prism(None, COLOR_YELLOW, width=38, height=25) + # Bola superior + pygame.draw.circle(surf_vortex, (255, 255, 200), (32, 12), 8) + pygame.draw.circle(surf_vortex, (0, 0, 0), (32, 12), 8, 1) sprites["vortex"] = surf_vortex - # 3. Bloque empujable - surf_block = pygame.Surface((64, 64), pygame.SRCALPHA) - pygame.draw.polygon( - surf_block, - (180, 70, 40), - [(32, 12), (58, 26), (32, 40), (6, 26)], - ) - pygame.draw.polygon( - surf_block, - (120, 40, 20), - [(6, 26), (32, 40), (32, 58), (6, 44)], - ) - pygame.draw.polygon( - surf_block, - (150, 50, 30), - [(58, 26), (32, 40), (32, 58), (58, 44)], - ) - sprites["block"] = surf_block + # 3. Bloque Empujable: Cubo Blanco + sprites["block"] = draw_isometric_prism(None, COLOR_WHITE, width=40, height=25) - # 4. Enemigo Alienígena (Drone) - surf_enemy = pygame.Surface((64, 64), pygame.SRCALPHA) - pygame.draw.polygon( - surf_enemy, - (50, 200, 100), - [(32, 10), (54, 28), (32, 46), (10, 28)], + # 4. Enemigo Indestructible: Prisma Blanco + sprites["enemy_indestructible"] = draw_isometric_prism( + None, COLOR_WHITE, width=32, height=35 ) - pygame.draw.polygon( - surf_enemy, - (255, 255, 255), - [(32, 10), (54, 28), (32, 46), (10, 28)], - 2, - ) - pygame.draw.circle(surf_enemy, (255, 0, 0), (32, 28), 5) # Ojo rojo - sprites["enemy"] = surf_enemy - # 5. Bala + # 5. Enemigos Destructibles (Prismas por estado de vida) + sprites["enemy_red"] = draw_isometric_prism( + None, COLOR_RED, width=32, height=35 + ) + sprites["enemy_brown"] = draw_isometric_prism( + None, COLOR_BROWN, width=32, height=35 + ) + sprites["enemy_orange"] = draw_isometric_prism( + None, COLOR_ORANGE, width=32, height=35 + ) + + # 6. Proyectil surf_bullet = pygame.Surface((32, 32), pygame.SRCALPHA) - pygame.draw.circle(surf_bullet, (0, 255, 255), (16, 16), 5) - pygame.draw.circle(surf_bullet, (255, 255, 255), (16, 16), 2) + pygame.draw.circle(surf_bullet, (0, 255, 255), (16, 16), 4) sprites["bullet"] = surf_bullet return sprites @@ -115,19 +145,17 @@ class SpriteManager: self.placeholders = create_placeholder_sprites() self.cache = {} - def get_sprite(self, name, filename): + def get_sprite(self, name, filename=None): if name in self.cache: return self.cache[name] - if os.path.exists(filename): + if filename and os.path.exists(filename): try: img = pygame.image.load(filename).convert_alpha() self.cache[name] = img return img except Exception as e: - print( - f"Error al cargar {filename}, usando respaldo vectorial: {e}" - ) + print(f"Error cargando {filename}: {e}") self.cache[name] = self.placeholders[name] return self.cache[name] @@ -135,7 +163,7 @@ class SpriteManager: class Entity: - def __init__(self, gx, gy, sprite_name, filename, is_solid=True): + def __init__(self, gx, gy, sprite_name, filename=None, is_solid=True): self.gx = float(gx) self.gy = float(gy) self.gz = 0 @@ -147,7 +175,6 @@ class Entity: screen_x, screen_y = grid_to_iso(self.gx, self.gy, self.gz) sprite = sprite_mgr.get_sprite(self.sprite_name, self.filename) - # Centrar la imagen con respecto a la casilla isométrica draw_x = screen_x + camera_offset_x - (sprite.get_width() // 2) draw_y = screen_y + camera_offset_y - (sprite.get_height() // 2) @@ -178,8 +205,10 @@ class Bullet(Entity): and abs(entity.gy - self.gy) < 0.5 ): self.alive = False - if isinstance(entity, (PushBlock, Enemy)): + if isinstance(entity, PushBlock): entity.health -= 1 + elif isinstance(entity, Enemy) and entity.destructible: + entity.take_damage() break @@ -211,39 +240,43 @@ class PushBlock(Entity): class Enemy(Entity): - def __init__(self, gx, gy): - super().__init__(gx, gy, "enemy", "enemy.png", is_solid=True) + def __init__(self, gx, gy, destructible=True): + sprite = "enemy_red" if destructible else "enemy_indestructible" + super().__init__(gx, gy, sprite, "enemy.png", is_solid=True) self.speed = 0.04 self.direction = 1 - self.health = 1 + self.destructible = destructible + self.health = 3 if destructible else 999 + + def take_damage(self): + if not self.destructible: + return + + self.health -= 1 + if self.health == 2: + self.sprite_name = "enemy_brown" + elif self.health == 1: + self.sprite_name = "enemy_orange" def update(self, entities): - # Calcular la posición objetiva horizontal target_gx = self.gx + (self.speed * self.direction) - # 1. Rebotar si llega a los límites físicos de la carretera + # Rebotar en los bordes de la carretera if target_gx <= 0.5 or target_gx >= GRID_SIZE_X - 0.5: self.direction *= -1 return - # 2. Comprobar colisión con bloques u otras entidades sólidas - blocked = False + # Comprobar bloqueos con bloques u otras entidades sólidas for entity in entities: if entity != self and entity.is_solid: - # Comprobar la distancia con el bloque en la rejilla if ( abs(entity.gx - target_gx) < 0.6 and abs(entity.gy - self.gy) < 0.6 ): - blocked = True - break + self.direction *= -1 + return - if blocked: - # Si se topa con un bloque empujable u otro obstáculo, invierte su rumbo - self.direction *= -1 - else: - # Si el camino está despejado, avanza - self.gx = target_gx + self.gx = target_gx class Player(Entity): @@ -336,7 +369,7 @@ class Game: def __init__(self): self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) - pygame.display.set_caption("Highway Encounter - Full Sprites & Enemies") + pygame.display.set_caption("Highway Encounter - Custom Enemies & Shapes") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont("monospace", 18, bold=True) self.sprite_mgr = SpriteManager() @@ -353,7 +386,7 @@ class Game: self.entities = [self.vortex, self.player] self.bullets = [] - # Escuadrón de reserva (vidas) + # Escuadrón de reserva self.spares = [] for i in range(self.lives - 1): spare = Entity( @@ -365,17 +398,17 @@ class Game: ) self.spares.append(spare) - # Generar bloques y enemigos alternados a lo largo de la autopista - for y in range(10, GRID_SIZE_Y - 15, 5): - # Agregar bloques + # Generar bloques y ambos tipos de enemigos a lo largo del mapa + for y in range(10, GRID_SIZE_Y - 15, 6): self.entities.append(PushBlock(y % GRID_SIZE_X, y)) - # Agregar enemigos cada 10 casillas - if y % 10 == 0: - self.entities.append(Enemy(3.5, y - 2)) + # Alternar entre enemigos destructibles e indestructibles + if y % 12 == 0: + destructible = (y % 24) == 0 + self.entities.append(Enemy(3.5, y - 2, destructible=destructible)) def handle_collisions(self): - # Colisión con enemigos + # Muerte por contacto con enemigo for entity in self.entities: if isinstance(entity, Enemy): if ( @@ -387,11 +420,11 @@ class Game: self.reset_level() return - # Limpiar entidades destruidas (bloques o enemigos con salud <= 0) + # Limpiar bloques destruidos y enemigos muertos for entity in self.entities[:]: if hasattr(entity, "health") and entity.health <= 0: self.entities.remove(entity) - self.score += 150 + self.score += 200 def draw_hud(self): zone = int((GRID_SIZE_Y - self.vortex.gy) / 4) @@ -426,7 +459,6 @@ class Game: self.reset_level() continue - # Actualización keys = pygame.key.get_pressed() self.player.update(keys, self.entities, self.bullets) self.vortex.update(self.entities) @@ -447,10 +479,9 @@ class Game: camera_offset_x = SCREEN_WIDTH // 2 - iso_x camera_offset_y = SCREEN_HEIGHT // 2 - iso_y + 100 - # Renderizado self.screen.fill(COLOR_BG) - # Dibujar mosaico de la carretera + # Dibujar suelo for gy in range(0, GRID_SIZE_Y): for gx in range(0, GRID_SIZE_X): sx, sy = grid_to_iso(gx, gy) @@ -476,7 +507,7 @@ class Game: pygame.draw.polygon(self.screen, COLOR_ROAD, pts) pygame.draw.polygon(self.screen, COLOR_GRID, pts, 1) - # Dibujar elementos ordenados por profundidad (Y descendente) + # Orden de renderizado por profundidad all_drawables = self.entities + self.bullets + self.spares sorted_entities = sorted( all_drawables, key=lambda e: e.gy, reverse=True diff --git a/img-versiones/version_2.0.png b/img-versiones/version_2.0.png new file mode 100644 index 0000000..ea7f88b Binary files /dev/null and b/img-versiones/version_2.0.png differ