From ac215e0e3749623b6ae3ceaf985c44a131f66a0f Mon Sep 17 00:00:00 2001 From: luisgulo Date: Sun, 4 Oct 2026 15:15:48 +0200 Subject: [PATCH] Enemigos y disparos --- highway_vector.py | 339 ++++++++++++++++++++++------------ img-versiones/version_1.1.png | Bin 0 -> 19121 bytes 2 files changed, 219 insertions(+), 120 deletions(-) create mode 100644 img-versiones/version_1.1.png diff --git a/highway_vector.py b/highway_vector.py index 5623c98..ce2de08 100644 --- a/highway_vector.py +++ b/highway_vector.py @@ -1,11 +1,11 @@ -import sys import math +import os +import sys import pygame -# Inicialización de Pygame pygame.init() -# Configuración de pantalla +# Configuración de Pantalla SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 FPS = 60 @@ -16,106 +16,148 @@ TILE_HEIGHT = 32 GRID_SIZE_X = 8 GRID_SIZE_Y = 120 -# Colores de apoyo +# Colores de Respaldo / Suelo COLOR_BG = (15, 15, 25) COLOR_ROAD = (50, 50, 65) COLOR_GRID = (30, 30, 45) def grid_to_iso(gx, gy, gz=0): - """Convierte coordenadas 3D de rejilla en coordenadas 2D en pantalla.""" + """Convierte coordenadas de la cuadrícula a coordenadas isométricas de pantalla.""" iso_x = (gx - gy) * (TILE_WIDTH // 2) iso_y = (gx + gy) * (TILE_HEIGHT // 2) - (gz * 16) return iso_x, iso_y -# ============================================================================== -# GENERADOR DE SPRITES AUTOMÁTICO (O Carga desde PNG si existen) -# ============================================================================== -def load_or_create_sprite(filename, create_func): - """Intenta cargar una imagen PNG. Si no existe, la crea dinámicamente.""" - try: - image = pygame.image.load(filename).convert_alpha() - return image - except (pygame.error, FileNotFoundError): - return create_func() +def create_placeholder_sprites(): + """Genera sprites vectoriales en memoria si no existen archivos PNG locales.""" + sprites = {} + + # 1. Jugador (Lasertron) + surf_player = pygame.Surface((64, 64), pygame.SRCALPHA) + pygame.draw.polygon( + surf_player, + (255, 200, 0), + [(32, 8), (56, 24), (32, 56), (8, 24)], + ) + 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) + 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) + 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 + + # 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)], + ) + 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 + 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) + sprites["bullet"] = surf_bullet + + return sprites -def create_player_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Cuerpo principal Lasertron - pygame.draw.ellipse(surf, (255, 200, 0), (16, 20, 32, 28)) - pygame.draw.ellipse(surf, (255, 255, 200), (20, 12, 24, 20)) - pygame.draw.circle(surf, (0, 200, 255), (32, 20), 6) # Visor - # Cañón de disparo - pygame.draw.rect(surf, (150, 150, 150), (30, 6, 4, 10)) - return surf +class SpriteManager: + + def __init__(self): + self.placeholders = create_placeholder_sprites() + self.cache = {} + + def get_sprite(self, name, filename): + if name in self.cache: + return self.cache[name] + + if 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}" + ) + + self.cache[name] = self.placeholders[name] + return self.cache[name] -def create_vortex_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Base pesada VORTEX - pygame.draw.ellipse(surf, (180, 40, 40), (8, 24, 48, 30)) - pygame.draw.ellipse(surf, (220, 60, 60), (14, 16, 36, 22)) - pygame.draw.circle(surf, (255, 255, 255), (32, 24), 8) - pygame.draw.circle(surf, (255, 0, 0), (32, 24), 4) - return surf - - -def create_block_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Cara superior - pts_top = [(32, 8), (56, 20), (32, 32), (8, 20)] - pygame.draw.polygon(surf, (210, 90, 50), pts_top) - # Cara izquierda - pts_left = [(8, 20), (32, 32), (32, 52), (8, 40)] - pygame.draw.polygon(surf, (150, 60, 30), pts_left) - # Cara derecha - pts_right = [(32, 32), (56, 20), (56, 40), (32, 52)] - pygame.draw.polygon(surf, (180, 75, 40), pts_right) - # Bordes - pygame.draw.polygon(surf, (255, 255, 255), pts_top, 1) - return surf - - -def create_enemy_sprite(): - surf = pygame.Surface((64, 64), pygame.SRCALPHA) - # Dron Alienígena - pygame.draw.ellipse(surf, (50, 220, 100), (12, 16, 40, 32)) - pygame.draw.circle(surf, (255, 0, 100), (32, 30), 8) - pygame.draw.line(surf, (50, 220, 100), (20, 48), (12, 58), 3) - pygame.draw.line(surf, (50, 220, 100), (44, 48), (52, 58), 3) - return surf - - -def create_bullet_sprite(): - surf = pygame.Surface((32, 32), pygame.SRCALPHA) - pygame.draw.circle(surf, (0, 255, 255), (16, 16), 6) - pygame.draw.circle(surf, (255, 255, 255), (16, 16), 3) - return surf - - -# ============================================================================== -# CLASES DEL JUEGO -# ============================================================================== class Entity: - def __init__(self, gx, gy, sprite, is_solid=True): + + def __init__(self, gx, gy, sprite_name, filename, is_solid=True): self.gx = float(gx) self.gy = float(gy) self.gz = 0 - self.sprite = sprite + self.sprite_name = sprite_name + self.filename = filename self.is_solid = is_solid - def draw(self, surface, camera_offset_x, camera_offset_y): + def draw(self, surface, camera_offset_x, camera_offset_y, sprite_mgr): screen_x, screen_y = grid_to_iso(self.gx, self.gy, self.gz) - draw_x = screen_x + camera_offset_x - (self.sprite.get_width() // 2) - draw_y = screen_y + camera_offset_y - (self.sprite.get_height() // 2) - surface.blit(self.sprite, (draw_x, draw_y)) + 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) + + surface.blit(sprite, (draw_x, draw_y)) class Bullet(Entity): - def __init__(self, gx, gy, dir_x, dir_y, sprite): - super().__init__(gx, gy, sprite, is_solid=False) + + def __init__(self, gx, gy, dir_x, dir_y): + super().__init__(gx, gy, "bullet", "bullet.png", is_solid=False) self.dir_x = dir_x self.dir_y = dir_y self.speed = 0.25 @@ -131,7 +173,10 @@ class Bullet(Entity): for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - self.gx) < 0.5 and abs(entity.gy - self.gy) < 0.5: + if ( + abs(entity.gx - self.gx) < 0.5 + and abs(entity.gy - self.gy) < 0.5 + ): self.alive = False if isinstance(entity, (PushBlock, Enemy)): entity.health -= 1 @@ -139,8 +184,9 @@ class Bullet(Entity): class PushBlock(Entity): - def __init__(self, gx, gy, sprite): - super().__init__(gx, gy, sprite, is_solid=True) + + def __init__(self, gx, gy): + super().__init__(gx, gy, "block", "block.png", is_solid=True) self.health = 2 def push(self, dx, dy, entities): @@ -152,7 +198,10 @@ class PushBlock(Entity): for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - new_gx) < 0.5 and abs(entity.gy - new_gy) < 0.5: + if ( + abs(entity.gx - new_gx) < 0.5 + and abs(entity.gy - new_gy) < 0.5 + ): return False self.gx = new_gx @@ -161,15 +210,18 @@ class PushBlock(Entity): class Enemy(Entity): - def __init__(self, gx, gy, sprite): - super().__init__(gx, gy, sprite, is_solid=True) - self.speed = 0.03 + + def __init__(self, gx, gy): + super().__init__(gx, gy, "enemy", "enemy.png", is_solid=True) + self.speed = 0.04 self.direction = 1 self.health = 1 def update(self, entities): + # Movimiento de patrulla horizontal target_gx = self.gx + (self.speed * self.direction) + # Rebotar en los bordes de la carretera if target_gx <= 0.5 or target_gx >= GRID_SIZE_X - 0.5: self.direction *= -1 target_gx = self.gx + (self.speed * self.direction) @@ -178,9 +230,9 @@ class Enemy(Entity): class Player(Entity): - def __init__(self, gx, gy, sprite, bullet_sprite): - super().__init__(gx, gy, sprite, is_solid=True) - self.bullet_sprite = bullet_sprite + + def __init__(self, gx, gy): + super().__init__(gx, gy, "player", "player.png", is_solid=True) self.speed = 0.09 self.facing_x = 0.0 self.facing_y = -1.0 @@ -209,10 +261,17 @@ class Player(Entity): blocked = False for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - target_gx) < 0.5 and abs(entity.gy - target_gy) < 0.5: + if ( + abs(entity.gx - target_gx) < 0.5 + and abs(entity.gy - target_gy) < 0.5 + ): if isinstance(entity, PushBlock): - push_dx = 0.15 if dx > 0 else (-0.15 if dx < 0 else 0) - push_dy = 0.15 if dy > 0 else (-0.15 if dy < 0 else 0) + push_dx = ( + 0.15 if dx > 0 else (-0.15 if dx < 0 else 0) + ) + push_dy = ( + 0.15 if dy > 0 else (-0.15 if dy < 0 else 0) + ) if not entity.push(push_dx, push_dy, entities): blocked = True else: @@ -227,13 +286,16 @@ class Player(Entity): if keys[pygame.K_SPACE] and self.shoot_cooldown == 0: spawn_x = self.gx + (self.facing_x * 0.7) spawn_y = self.gy + (self.facing_y * 0.7) - bullets.append(Bullet(spawn_x, spawn_y, self.facing_x, self.facing_y, self.bullet_sprite)) + bullets.append( + Bullet(spawn_x, spawn_y, self.facing_x, self.facing_y) + ) self.shoot_cooldown = 15 class Vortex(Entity): - def __init__(self, gx, gy, sprite): - super().__init__(gx, gy, sprite, is_solid=True) + + def __init__(self, gx, gy): + super().__init__(gx, gy, "vortex", "vortex.png", is_solid=True) self.speed = 0.012 def update(self, entities): @@ -242,7 +304,10 @@ class Vortex(Entity): blocked = False for entity in entities: if entity != self and entity.is_solid: - if abs(entity.gx - self.gx) < 0.6 and abs(entity.gy - target_gy) < 0.6: + if ( + abs(entity.gx - self.gx) < 0.6 + and abs(entity.gy - target_gy) < 0.6 + ): blocked = True break @@ -251,53 +316,65 @@ class Vortex(Entity): class Game: + def __init__(self): self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) - pygame.display.set_caption("Highway Encounter - PNG Sprites Version") + pygame.display.set_caption("Highway Encounter - Full Sprites & Enemies") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont("monospace", 18, bold=True) - - # Carga o Generación de Sprites - self.spr_player = load_or_create_sprite("player.png", create_player_sprite) - self.spr_vortex = load_or_create_sprite("vortex.png", create_vortex_sprite) - self.spr_block = load_or_create_sprite("block.png", create_block_sprite) - self.spr_enemy = load_or_create_sprite("enemy.png", create_enemy_sprite) - self.spr_bullet = load_or_create_sprite("bullet.png", create_bullet_sprite) + self.sprite_mgr = SpriteManager() self.lives = 4 self.score = 0 + self.reset_level() def reset_level(self): - self.vortex = Vortex(3.5, GRID_SIZE_Y - 5, self.spr_vortex) - self.player = Player(3.5, GRID_SIZE_Y - 8, self.spr_player, self.spr_bullet) + self.vortex = Vortex(3.5, GRID_SIZE_Y - 5) + self.player = Player(3.5, GRID_SIZE_Y - 8) self.entities = [self.vortex, self.player] self.bullets = [] + # Escuadrón de reserva (vidas) self.spares = [] for i in range(self.lives - 1): - spare = Entity(3.5, GRID_SIZE_Y - 3 + (i * 1.2), self.spr_player, is_solid=False) + spare = Entity( + 3.5, + GRID_SIZE_Y - 3 + (i * 1.2), + "player", + "player.png", + is_solid=False, + ) self.spares.append(spare) - for y in range(10, GRID_SIZE_Y - 15, 6): - self.entities.append(PushBlock(y % GRID_SIZE_X, y, self.spr_block)) - if y % 12 == 0: - self.entities.append(Enemy(3.5, y - 2, self.spr_enemy)) + # Generar bloques y enemigos alternados a lo largo de la autopista + for y in range(10, GRID_SIZE_Y - 15, 5): + # Agregar bloques + 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)) def handle_collisions(self): + # Colisión con enemigos for entity in self.entities: if isinstance(entity, Enemy): - if abs(entity.gx - self.player.gx) < 0.5 and abs(entity.gy - self.player.gy) < 0.5: + if ( + abs(entity.gx - self.player.gx) < 0.5 + and abs(entity.gy - self.player.gy) < 0.5 + ): self.lives -= 1 if self.lives > 0: self.reset_level() return + # Limpiar entidades destruidas (bloques o enemigos con salud <= 0) for entity in self.entities[:]: if hasattr(entity, "health") and entity.health <= 0: self.entities.remove(entity) - self.score += 100 + self.score += 150 def draw_hud(self): zone = int((GRID_SIZE_Y - self.vortex.gy) / 4) @@ -317,8 +394,12 @@ class Game: if self.lives <= 0: self.screen.fill((0, 0, 0)) - msg = self.font.render("GAME OVER - Press R to Restart", True, (255, 50, 50)) - self.screen.blit(msg, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2)) + msg = self.font.render( + "GAME OVER - Press R to Restart", True, (255, 50, 50) + ) + self.screen.blit( + msg, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2) + ) pygame.display.flip() keys = pygame.key.get_pressed() @@ -328,6 +409,7 @@ 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) @@ -343,32 +425,49 @@ class Game: self.handle_collisions() + # Desplazamiento de cámara centrado en el jugador iso_x, iso_y = grid_to_iso(self.player.gx, self.player.gy) 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 for gy in range(0, GRID_SIZE_Y): for gx in range(0, GRID_SIZE_X): sx, sy = grid_to_iso(gx, gy) draw_x = sx + camera_offset_x draw_y = sy + camera_offset_y - if -100 < draw_x < SCREEN_WIDTH + 100 and -100 < draw_y < SCREEN_HEIGHT + 100: + if ( + -100 < draw_x < SCREEN_WIDTH + 100 + and -100 < draw_y < SCREEN_HEIGHT + 100 + ): pts = [ (draw_x, draw_y), - (draw_x + TILE_WIDTH // 2, draw_y + TILE_HEIGHT // 2), + ( + draw_x + TILE_WIDTH // 2, + draw_y + TILE_HEIGHT // 2, + ), (draw_x, draw_y + TILE_HEIGHT), - (draw_x - TILE_WIDTH // 2, draw_y + TILE_HEIGHT // 2), + ( + draw_x - TILE_WIDTH // 2, + draw_y + TILE_HEIGHT // 2, + ), ] pygame.draw.polygon(self.screen, COLOR_ROAD, pts) pygame.draw.polygon(self.screen, COLOR_GRID, pts, 1) + # Dibujar elementos ordenados por profundidad (Y descendente) all_drawables = self.entities + self.bullets + self.spares - sorted_entities = sorted(all_drawables, key=lambda e: e.gy, reverse=True) + sorted_entities = sorted( + all_drawables, key=lambda e: e.gy, reverse=True + ) for entity in sorted_entities: - entity.draw(self.screen, camera_offset_x, camera_offset_y) + entity.draw( + self.screen, camera_offset_x, camera_offset_y, self.sprite_mgr + ) self.draw_hud() pygame.display.flip() @@ -378,4 +477,4 @@ class Game: if __name__ == "__main__": - Game().run() \ No newline at end of file + Game().run() diff --git a/img-versiones/version_1.1.png b/img-versiones/version_1.1.png new file mode 100644 index 0000000000000000000000000000000000000000..cb6ccd53f1a7764b81fc6c829757d7595e046027 GIT binary patch literal 19121 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 LX`TFI_PGB80(&_H literal 0 HcmV?d00001