From 1b272ba47e19632fc922cfb2acf98dc591552025 Mon Sep 17 00:00:00 2001 From: luisgulo Date: Sun, 4 Oct 2026 15:13:14 +0200 Subject: [PATCH] Bloques en 3D y volumen --- highway_vector.py | 248 ++++++++++++++++------------------ img-versiones/version_1.0.png | Bin 0 -> 18164 bytes 2 files changed, 120 insertions(+), 128 deletions(-) create mode 100644 img-versiones/version_1.0.png 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 0000000000000000000000000000000000000000..8c18e9bd6baae80d4b9e1723540dc0ff229f12e7 GIT binary patch literal 18164 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