Bloques en 3D y volumen

This commit is contained in:
luisgulo 2026-10-04 15:13:14 +02:00
parent ce640e1d93
commit 1b272ba47e
2 changed files with 120 additions and 128 deletions

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB