diff --git a/highway_vector.py b/highway_vector.py index 0933950..5623c98 100644 --- a/highway_vector.py +++ b/highway_vector.py @@ -1,7 +1,8 @@ -import math import sys +import math import pygame +# Inicialización de Pygame pygame.init() # Configuración de pantalla @@ -13,19 +14,12 @@ FPS = 60 TILE_WIDTH = 64 TILE_HEIGHT = 32 GRID_SIZE_X = 8 -GRID_SIZE_Y = 120 # Distancia total de la autopista +GRID_SIZE_Y = 120 -# Paleta de Colores +# Colores de apoyo COLOR_BG = (15, 15, 25) COLOR_ROAD = (50, 50, 65) COLOR_GRID = (30, 30, 45) -COLOR_VORTEX = (220, 50, 50) -COLOR_PLAYER = (255, 220, 0) -COLOR_SPARE = (180, 160, 0) -COLOR_BLOCK = (180, 70, 40) -COLOR_WALL = (80, 90, 110) -COLOR_ENEMY = (50, 200, 100) -COLOR_BULLET = (0, 255, 255) def grid_to_iso(gx, gy, gz=0): @@ -35,64 +29,109 @@ def grid_to_iso(gx, gy, gz=0): return iso_x, iso_y -class Entity: +# ============================================================================== +# 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 __init__(self, gx, gy, color, is_solid=True): + +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 + + +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): self.gx = float(gx) self.gy = float(gy) self.gz = 0 - self.color = color + self.sprite = sprite self.is_solid = is_solid - self.radius = 0.4 - self.height = 20 def draw(self, surface, camera_offset_x, camera_offset_y): screen_x, screen_y = grid_to_iso(self.gx, self.gy, self.gz) - draw_x = screen_x + camera_offset_x - draw_y = screen_y + camera_offset_y - - points = [ - (draw_x, draw_y - self.height), - ( - draw_x + TILE_WIDTH // 2, - draw_y - self.height + TILE_HEIGHT // 2, - ), - (draw_x, draw_y - self.height + TILE_HEIGHT), - ( - draw_x - TILE_WIDTH // 2, - draw_y - self.height + TILE_HEIGHT // 2, - ), - ] - pygame.draw.polygon(surface, self.color, points) - pygame.draw.polygon(surface, (255, 255, 255), points, 2) + 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)) class Bullet(Entity): - - def __init__(self, gx, gy, dir_x, dir_y): - super().__init__(gx, gy, COLOR_BULLET, is_solid=False) + def __init__(self, gx, gy, dir_x, dir_y, sprite): + super().__init__(gx, gy, sprite, is_solid=False) self.dir_x = dir_x self.dir_y = dir_y self.speed = 0.25 - self.height = 10 self.alive = True def update(self, entities): self.gx += self.dir_x * self.speed self.gy += self.dir_y * self.speed - # Comprobar límites if not (0 <= self.gx < GRID_SIZE_X and 0 <= self.gy < GRID_SIZE_Y): self.alive = False return - # Impacto con entidades 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 @@ -100,10 +139,9 @@ class Bullet(Entity): class PushBlock(Entity): - - def __init__(self, gx, gy): - super().__init__(gx, gy, COLOR_BLOCK, is_solid=True) - self.health = 2 # Se puede destruir tras 2 disparos + def __init__(self, gx, gy, sprite): + super().__init__(gx, gy, sprite, is_solid=True) + self.health = 2 def push(self, dx, dy, entities): new_gx = self.gx + dx @@ -114,10 +152,7 @@ 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 @@ -126,9 +161,8 @@ class PushBlock(Entity): class Enemy(Entity): - - def __init__(self, gx, gy): - super().__init__(gx, gy, COLOR_ENEMY, is_solid=True) + def __init__(self, gx, gy, sprite): + super().__init__(gx, gy, sprite, is_solid=True) self.speed = 0.03 self.direction = 1 self.health = 1 @@ -136,7 +170,6 @@ class Enemy(Entity): def update(self, entities): 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) @@ -145,12 +178,12 @@ class Enemy(Entity): class Player(Entity): - - def __init__(self, gx, gy): - super().__init__(gx, gy, COLOR_PLAYER, is_solid=True) + def __init__(self, gx, gy, sprite, bullet_sprite): + super().__init__(gx, gy, sprite, is_solid=True) + self.bullet_sprite = bullet_sprite self.speed = 0.09 self.facing_x = 0.0 - self.facing_y = -1.0 # Mirando hacia adelante por defecto + self.facing_y = -1.0 self.shoot_cooldown = 0 def update(self, keys, entities, bullets): @@ -165,7 +198,6 @@ class Player(Entity): dy += self.speed if dx != 0 or dy != 0: - # Actualizar la dirección en la que mira el jugador norm = math.sqrt(dx * dx + dy * dy) self.facing_x = dx / norm self.facing_y = dy / norm @@ -177,17 +209,10 @@ 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: @@ -196,24 +221,19 @@ class Player(Entity): self.gx = target_gx self.gy = target_gy - # Gestión del enfriamiento de disparo if self.shoot_cooldown > 0: self.shoot_cooldown -= 1 - # Disparar al pulsar la barra espaciadora if keys[pygame.K_SPACE] and self.shoot_cooldown == 0: - # Generar la bala desplazada por delante del jugador para evitar la auto-colisión 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.shoot_cooldown = 15 # Cadencia de tiro + bullets.append(Bullet(spawn_x, spawn_y, self.facing_x, self.facing_y, self.bullet_sprite)) + self.shoot_cooldown = 15 class Vortex(Entity): - - def __init__(self, gx, gy): - super().__init__(gx, gy, COLOR_VORTEX, is_solid=True) + def __init__(self, gx, gy, sprite): + super().__init__(gx, gy, sprite, is_solid=True) self.speed = 0.012 def update(self, entities): @@ -222,10 +242,7 @@ 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 @@ -234,53 +251,49 @@ class Vortex(Entity): class Game: - def __init__(self): self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) - pygame.display.set_caption("Highway Encounter - Full Game") + pygame.display.set_caption("Highway Encounter - PNG Sprites Version") 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.lives = 4 self.score = 0 - self.reset_level() def reset_level(self): - self.vortex = Vortex(3.5, GRID_SIZE_Y - 5) - self.player = Player(3.5, GRID_SIZE_Y - 8) + 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.entities = [self.vortex, self.player] self.bullets = [] - # Crear escuadrón de reserva (Vidas restantes) self.spares = [] for i in range(self.lives - 1): - spare = Entity( - 3.5, GRID_SIZE_Y - 3 + (i * 1.2), COLOR_SPARE, is_solid=False - ) + spare = Entity(3.5, GRID_SIZE_Y - 3 + (i * 1.2), self.spr_player, is_solid=False) self.spares.append(spare) - # Generar obstáculos y enemigos en la autopista for y in range(10, GRID_SIZE_Y - 15, 6): - self.entities.append(PushBlock(y % GRID_SIZE_X, y)) + 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.entities.append(Enemy(3.5, y - 2, self.spr_enemy)) def handle_collisions(self): - # Muerte del jugador por contacto 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 - # Eliminar bloques/enemigos destruidos for entity in self.entities[:]: if hasattr(entity, "health") and entity.health <= 0: self.entities.remove(entity) @@ -304,12 +317,8 @@ 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() @@ -319,7 +328,6 @@ class Game: self.reset_level() continue - # Actualizaciones keys = pygame.key.get_pressed() self.player.update(keys, self.entities, self.bullets) self.vortex.update(self.entities) @@ -335,45 +343,30 @@ class Game: self.handle_collisions() - # Posicionamiento 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 suelo 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) - # Renderizado en orden de profundidad (Z-Sorting por Y) 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) @@ -385,5 +378,4 @@ class Game: if __name__ == "__main__": - Game().run() - + Game().run() \ No newline at end of file diff --git a/img-versiones/version_1.0.png b/img-versiones/version_1.0.png new file mode 100644 index 0000000..8c18e9b Binary files /dev/null and b/img-versiones/version_1.0.png differ