From e80a88cb1344bcc45a942c6923a93923ea97b417 Mon Sep 17 00:00:00 2001 From: luisgulo Date: Sun, 4 Oct 2026 15:19:07 +0200 Subject: [PATCH] Mejora de player y vortex -sin enemigos- --- highway_vector.py | 235 +++++++++++++++++++--------------- img-versiones/version_2.0.png | Bin 0 -> 18327 bytes 2 files changed, 133 insertions(+), 102 deletions(-) create mode 100644 img-versiones/version_2.0.png 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 0000000000000000000000000000000000000000..ea7f88b8b9067d93fdee01066eb07af2f4dcfb3e GIT binary patch literal 18327 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