Enemigos y disparos

This commit is contained in:
luisgulo 2026-10-04 15:15:48 +02:00
parent 1b272ba47e
commit ac215e0e37
2 changed files with 219 additions and 120 deletions

View File

@ -1,11 +1,11 @@
import sys
import math import math
import os
import sys
import pygame import pygame
# Inicialización de Pygame
pygame.init() pygame.init()
# Configuración de pantalla # Configuración de Pantalla
SCREEN_WIDTH = 800 SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600 SCREEN_HEIGHT = 600
FPS = 60 FPS = 60
@ -16,106 +16,148 @@ TILE_HEIGHT = 32
GRID_SIZE_X = 8 GRID_SIZE_X = 8
GRID_SIZE_Y = 120 GRID_SIZE_Y = 120
# Colores de apoyo # Colores de Respaldo / Suelo
COLOR_BG = (15, 15, 25) COLOR_BG = (15, 15, 25)
COLOR_ROAD = (50, 50, 65) COLOR_ROAD = (50, 50, 65)
COLOR_GRID = (30, 30, 45) COLOR_GRID = (30, 30, 45)
def grid_to_iso(gx, gy, gz=0): 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_x = (gx - gy) * (TILE_WIDTH // 2)
iso_y = (gx + gy) * (TILE_HEIGHT // 2) - (gz * 16) iso_y = (gx + gy) * (TILE_HEIGHT // 2) - (gz * 16)
return iso_x, iso_y return iso_x, iso_y
# ============================================================================== def create_placeholder_sprites():
# GENERADOR DE SPRITES AUTOMÁTICO (O Carga desde PNG si existen) """Genera sprites vectoriales en memoria si no existen archivos PNG locales."""
# ============================================================================== sprites = {}
def load_or_create_sprite(filename, create_func):
"""Intenta cargar una imagen PNG. Si no existe, la crea dinámicamente.""" # 1. Jugador (Lasertron)
try: surf_player = pygame.Surface((64, 64), pygame.SRCALPHA)
image = pygame.image.load(filename).convert_alpha() pygame.draw.polygon(
return image surf_player,
except (pygame.error, FileNotFoundError): (255, 200, 0),
return create_func() [(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(): class SpriteManager:
surf = pygame.Surface((64, 64), pygame.SRCALPHA)
# Cuerpo principal Lasertron def __init__(self):
pygame.draw.ellipse(surf, (255, 200, 0), (16, 20, 32, 28)) self.placeholders = create_placeholder_sprites()
pygame.draw.ellipse(surf, (255, 255, 200), (20, 12, 24, 20)) self.cache = {}
pygame.draw.circle(surf, (0, 200, 255), (32, 20), 6) # Visor
# Cañón de disparo def get_sprite(self, name, filename):
pygame.draw.rect(surf, (150, 150, 150), (30, 6, 4, 10)) if name in self.cache:
return surf 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: 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.gx = float(gx)
self.gy = float(gy) self.gy = float(gy)
self.gz = 0 self.gz = 0
self.sprite = sprite self.sprite_name = sprite_name
self.filename = filename
self.is_solid = is_solid 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) 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) sprite = sprite_mgr.get_sprite(self.sprite_name, self.filename)
draw_y = screen_y + camera_offset_y - (self.sprite.get_height() // 2)
surface.blit(self.sprite, (draw_x, draw_y)) # 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): 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_x = dir_x
self.dir_y = dir_y self.dir_y = dir_y
self.speed = 0.25 self.speed = 0.25
@ -131,7 +173,10 @@ class Bullet(Entity):
for entity in entities: for entity in entities:
if entity != self and entity.is_solid: 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 self.alive = False
if isinstance(entity, (PushBlock, Enemy)): if isinstance(entity, (PushBlock, Enemy)):
entity.health -= 1 entity.health -= 1
@ -139,8 +184,9 @@ class Bullet(Entity):
class PushBlock(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 self.health = 2
def push(self, dx, dy, entities): def push(self, dx, dy, entities):
@ -152,7 +198,10 @@ class PushBlock(Entity):
for entity in entities: for entity in entities:
if entity != self and entity.is_solid: 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 return False
self.gx = new_gx self.gx = new_gx
@ -161,15 +210,18 @@ class PushBlock(Entity):
class Enemy(Entity): class Enemy(Entity):
def __init__(self, gx, gy, sprite):
super().__init__(gx, gy, sprite, is_solid=True) def __init__(self, gx, gy):
self.speed = 0.03 super().__init__(gx, gy, "enemy", "enemy.png", is_solid=True)
self.speed = 0.04
self.direction = 1 self.direction = 1
self.health = 1 self.health = 1
def update(self, entities): def update(self, entities):
# Movimiento de patrulla horizontal
target_gx = self.gx + (self.speed * self.direction) 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: if target_gx <= 0.5 or target_gx >= GRID_SIZE_X - 0.5:
self.direction *= -1 self.direction *= -1
target_gx = self.gx + (self.speed * self.direction) target_gx = self.gx + (self.speed * self.direction)
@ -178,9 +230,9 @@ class Enemy(Entity):
class Player(Entity): class Player(Entity):
def __init__(self, gx, gy, sprite, bullet_sprite):
super().__init__(gx, gy, sprite, is_solid=True) def __init__(self, gx, gy):
self.bullet_sprite = bullet_sprite super().__init__(gx, gy, "player", "player.png", is_solid=True)
self.speed = 0.09 self.speed = 0.09
self.facing_x = 0.0 self.facing_x = 0.0
self.facing_y = -1.0 self.facing_y = -1.0
@ -209,10 +261,17 @@ class Player(Entity):
blocked = False blocked = False
for entity in entities: for entity in entities:
if entity != self and entity.is_solid: 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): if isinstance(entity, PushBlock):
push_dx = 0.15 if dx > 0 else (-0.15 if dx < 0 else 0) push_dx = (
push_dy = 0.15 if dy > 0 else (-0.15 if dy < 0 else 0) 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): if not entity.push(push_dx, push_dy, entities):
blocked = True blocked = True
else: else:
@ -227,13 +286,16 @@ class Player(Entity):
if keys[pygame.K_SPACE] and self.shoot_cooldown == 0: if keys[pygame.K_SPACE] and self.shoot_cooldown == 0:
spawn_x = self.gx + (self.facing_x * 0.7) spawn_x = self.gx + (self.facing_x * 0.7)
spawn_y = self.gy + (self.facing_y * 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 self.shoot_cooldown = 15
class Vortex(Entity): 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 self.speed = 0.012
def update(self, entities): def update(self, entities):
@ -242,7 +304,10 @@ class Vortex(Entity):
blocked = False blocked = False
for entity in entities: for entity in entities:
if entity != self and entity.is_solid: 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 blocked = True
break break
@ -251,53 +316,65 @@ class Vortex(Entity):
class Game: class Game:
def __init__(self): def __init__(self):
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 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.clock = pygame.time.Clock()
self.font = pygame.font.SysFont("monospace", 18, bold=True) self.font = pygame.font.SysFont("monospace", 18, bold=True)
self.sprite_mgr = SpriteManager()
# 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.lives = 4
self.score = 0 self.score = 0
self.reset_level() self.reset_level()
def reset_level(self): def reset_level(self):
self.vortex = Vortex(3.5, GRID_SIZE_Y - 5, self.spr_vortex) self.vortex = Vortex(3.5, GRID_SIZE_Y - 5)
self.player = Player(3.5, GRID_SIZE_Y - 8, self.spr_player, self.spr_bullet) self.player = Player(3.5, GRID_SIZE_Y - 8)
self.entities = [self.vortex, self.player] self.entities = [self.vortex, self.player]
self.bullets = [] self.bullets = []
# Escuadrón de reserva (vidas)
self.spares = [] self.spares = []
for i in range(self.lives - 1): 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) self.spares.append(spare)
for y in range(10, GRID_SIZE_Y - 15, 6): # Generar bloques y enemigos alternados a lo largo de la autopista
self.entities.append(PushBlock(y % GRID_SIZE_X, y, self.spr_block)) for y in range(10, GRID_SIZE_Y - 15, 5):
if y % 12 == 0: # Agregar bloques
self.entities.append(Enemy(3.5, y - 2, self.spr_enemy)) 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): def handle_collisions(self):
# Colisión con enemigos
for entity in self.entities: for entity in self.entities:
if isinstance(entity, Enemy): 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 self.lives -= 1
if self.lives > 0: if self.lives > 0:
self.reset_level() self.reset_level()
return return
# Limpiar entidades destruidas (bloques o enemigos con salud <= 0)
for entity in self.entities[:]: for entity in self.entities[:]:
if hasattr(entity, "health") and entity.health <= 0: if hasattr(entity, "health") and entity.health <= 0:
self.entities.remove(entity) self.entities.remove(entity)
self.score += 100 self.score += 150
def draw_hud(self): def draw_hud(self):
zone = int((GRID_SIZE_Y - self.vortex.gy) / 4) zone = int((GRID_SIZE_Y - self.vortex.gy) / 4)
@ -317,8 +394,12 @@ class Game:
if self.lives <= 0: if self.lives <= 0:
self.screen.fill((0, 0, 0)) self.screen.fill((0, 0, 0))
msg = self.font.render("GAME OVER - Press R to Restart", True, (255, 50, 50)) msg = self.font.render(
self.screen.blit(msg, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2)) "GAME OVER - Press R to Restart", True, (255, 50, 50)
)
self.screen.blit(
msg, (SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2)
)
pygame.display.flip() pygame.display.flip()
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
@ -328,6 +409,7 @@ class Game:
self.reset_level() self.reset_level()
continue continue
# Actualización
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
self.player.update(keys, self.entities, self.bullets) self.player.update(keys, self.entities, self.bullets)
self.vortex.update(self.entities) self.vortex.update(self.entities)
@ -343,32 +425,49 @@ class Game:
self.handle_collisions() self.handle_collisions()
# Desplazamiento de cámara centrado en el jugador
iso_x, iso_y = grid_to_iso(self.player.gx, self.player.gy) iso_x, iso_y = grid_to_iso(self.player.gx, self.player.gy)
camera_offset_x = SCREEN_WIDTH // 2 - iso_x camera_offset_x = SCREEN_WIDTH // 2 - iso_x
camera_offset_y = SCREEN_HEIGHT // 2 - iso_y + 100 camera_offset_y = SCREEN_HEIGHT // 2 - iso_y + 100
# Renderizado
self.screen.fill(COLOR_BG) self.screen.fill(COLOR_BG)
# Dibujar mosaico de la carretera
for gy in range(0, GRID_SIZE_Y): for gy in range(0, GRID_SIZE_Y):
for gx in range(0, GRID_SIZE_X): for gx in range(0, GRID_SIZE_X):
sx, sy = grid_to_iso(gx, gy) sx, sy = grid_to_iso(gx, gy)
draw_x = sx + camera_offset_x draw_x = sx + camera_offset_x
draw_y = sy + camera_offset_y 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 = [ pts = [
(draw_x, draw_y), (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, 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_ROAD, pts)
pygame.draw.polygon(self.screen, COLOR_GRID, pts, 1) pygame.draw.polygon(self.screen, COLOR_GRID, pts, 1)
# Dibujar elementos ordenados por profundidad (Y descendente)
all_drawables = self.entities + self.bullets + self.spares 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: 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() self.draw_hud()
pygame.display.flip() pygame.display.flip()

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB