mejora movimiento

This commit is contained in:
luisgulo 2026-10-04 15:11:10 +02:00
parent 7dd9fac0c8
commit ce640e1d93
2 changed files with 219 additions and 69 deletions

View File

@ -1,32 +1,35 @@
import math
import sys import sys
import pygame import pygame
# Inicialización de Pygame
pygame.init() pygame.init()
# Constantes de Pantalla # Configuración de pantalla
SCREEN_WIDTH = 800 SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600 SCREEN_HEIGHT = 600
FPS = 60 FPS = 60
# Configuración de Cuadrícula e Isometría # Isometría y Cuadrícula
TILE_WIDTH = 64 TILE_WIDTH = 64
TILE_HEIGHT = 32 TILE_HEIGHT = 32
GRID_SIZE_X = 8 GRID_SIZE_X = 8
GRID_SIZE_Y = 100 # Largo de la autopista GRID_SIZE_Y = 120 # Distancia total de la autopista
# Colores (Paleta Retro Spectrum/Amstrad) # Paleta de Colores
COLOR_BG = (10, 10, 15) COLOR_BG = (15, 15, 25)
COLOR_GRID = (40, 40, 60) COLOR_ROAD = (50, 50, 65)
COLOR_ROAD = (60, 60, 80) COLOR_GRID = (30, 30, 45)
COLOR_VORTEX = (0, 255, 200) COLOR_VORTEX = (220, 50, 50)
COLOR_PLAYER = (255, 200, 0) COLOR_PLAYER = (255, 220, 0)
COLOR_BLOCK = (200, 50, 50) COLOR_SPARE = (180, 160, 0)
COLOR_WALL = (100, 100, 120) 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): def grid_to_iso(gx, gy, gz=0):
"""Convierte coordenadas de cuadrícula 3D (x, y, z) a coordenadas de pantalla 2D.""" """Convierte coordenadas 3D de rejilla en coordenadas 2D en 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
@ -41,44 +44,79 @@ class Entity:
self.color = color self.color = color
self.is_solid = is_solid self.is_solid = is_solid
self.radius = 0.4 self.radius = 0.4
self.height = 20
def draw(self, surface, camera_offset_x, camera_offset_y): def draw(self, surface, camera_offset_x, camera_offset_y):
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 draw_x = screen_x + camera_offset_x
draw_y = screen_y + camera_offset_y draw_y = screen_y + camera_offset_y
# Dibujar un bloque o personaje estilo isométrico
points = [ points = [
(draw_x, draw_y - 20), (draw_x, draw_y - self.height),
(draw_x + TILE_WIDTH // 2, draw_y - 20 + TILE_HEIGHT // 2), (
(draw_x, draw_y - 20 + TILE_HEIGHT), draw_x + TILE_WIDTH // 2,
(draw_x - TILE_WIDTH // 2, draw_y - 20 + TILE_HEIGHT // 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, self.color, points)
pygame.draw.polygon( pygame.draw.polygon(surface, (255, 255, 255), points, 2)
surface, (255, 255, 255), points, 2
) # Borde brillante
class Bullet(Entity):
def __init__(self, gx, gy, dir_x, dir_y):
super().__init__(gx, gy, COLOR_BULLET, 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
):
self.alive = False
if isinstance(entity, (PushBlock, Enemy)):
entity.health -= 1
break
class PushBlock(Entity): class PushBlock(Entity):
def __init__(self, gx, gy): def __init__(self, gx, gy):
super().__init__(gx, gy, COLOR_BLOCK, is_solid=True) super().__init__(gx, gy, COLOR_BLOCK, is_solid=True)
self.health = 2 # Se puede destruir tras 2 disparos
def push(self, dx, dy, entities): def push(self, dx, dy, entities):
new_gx = self.gx + dx new_gx = self.gx + dx
new_gy = self.gy + dy new_gy = self.gy + dy
# Comprobar límites de la carretera
if not (0 <= new_gx < GRID_SIZE_X and 0 <= new_gy < GRID_SIZE_Y): if not (0 <= new_gx < GRID_SIZE_X and 0 <= new_gy < GRID_SIZE_Y):
return False return False
# Comprobar colisión con otras entidades
for entity in entities: for entity in entities:
if entity != self and entity.is_solid: if entity != self and entity.is_solid:
if ( if (
abs(entity.gx - new_gx) < 0.6 abs(entity.gx - new_gx) < 0.5
and abs(entity.gy - new_gy) < 0.6 and abs(entity.gy - new_gy) < 0.5
): ):
return False return False
@ -87,13 +125,35 @@ class PushBlock(Entity):
return True return True
class Enemy(Entity):
def __init__(self, gx, gy):
super().__init__(gx, gy, COLOR_ENEMY, is_solid=True)
self.speed = 0.03
self.direction = 1
self.health = 1
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)
self.gx = target_gx
class Player(Entity): class Player(Entity):
def __init__(self, gx, gy): def __init__(self, gx, gy):
super().__init__(gx, gy, COLOR_PLAYER, is_solid=True) super().__init__(gx, gy, COLOR_PLAYER, is_solid=True)
self.speed = 0.08 self.speed = 0.09
self.facing_x = 0.0
self.facing_y = -1.0 # Mirando hacia adelante por defecto
self.shoot_cooldown = 0
def update(self, keys, entities): def update(self, keys, entities, bullets):
dx, dy = 0, 0 dx, dy = 0, 0
if keys[pygame.K_LEFT] or keys[pygame.K_a]: if keys[pygame.K_LEFT] or keys[pygame.K_a]:
dx -= self.speed dx -= self.speed
@ -105,51 +165,66 @@ class Player(Entity):
dy += self.speed dy += self.speed
if dx != 0 or dy != 0: 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
target_gx = self.gx + dx target_gx = self.gx + dx
target_gy = self.gy + dy target_gy = self.gy + dy
# Limites horizontales if 0 <= target_gx < GRID_SIZE_X and 0 <= target_gy < GRID_SIZE_Y:
if not (0 <= target_gx < GRID_SIZE_X): blocked = False
dx = 0
if not (0 <= target_gy < GRID_SIZE_Y):
dy = 0
# Intento de movimiento y colisión con bloques
for entity in entities: for entity in entities:
if entity != self and entity.is_solid: if entity != self and entity.is_solid:
if ( if (
abs(entity.gx - (self.gx + dx)) < 0.6 abs(entity.gx - target_gx) < 0.5
and abs(entity.gy - (self.gy + dy)) < 0.6 and abs(entity.gy - target_gy) < 0.5
): ):
# Intentar empujar si es un PushBlock
if isinstance(entity, PushBlock): if isinstance(entity, PushBlock):
push_dx = 0.1 if dx > 0 else (-0.1 if dx < 0 else 0) push_dx = (
push_dy = 0.1 if dy > 0 else (-0.1 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):
return blocked = True
else: else:
return # Bloqueado blocked = True
if not blocked:
self.gx = target_gx
self.gy = target_gy
self.gx += dx # Gestión del enfriamiento de disparo
self.gy += dy 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
class Vortex(Entity): class Vortex(Entity):
def __init__(self, gx, gy): def __init__(self, gx, gy):
super().__init__(gx, gy, COLOR_VORTEX, is_solid=True) super().__init__(gx, gy, COLOR_VORTEX, is_solid=True)
self.speed = 0.015 # Avance constante automático self.speed = 0.012
def update(self, entities): def update(self, entities):
target_gy = self.gy - self.speed target_gy = self.gy - self.speed
# Verificar si hay algún obstáculo al avanzar
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 ( if (
abs(entity.gx - self.gx) < 0.7 abs(entity.gx - self.gx) < 0.6
and abs(entity.gy - target_gy) < 0.7 and abs(entity.gy - target_gy) < 0.6
): ):
blocked = True blocked = True
break break
@ -162,51 +237,123 @@ 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 Python Remake") pygame.display.set_caption("Highway Encounter - Full Game")
self.clock = pygame.time.Clock() self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont("monospace", 18, bold=True)
self.vortex = Vortex(3.5, 95) self.lives = 4
self.player = Player(3.5, 92) 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.entities = [self.vortex, self.player] self.entities = [self.vortex, self.player]
self.bullets = []
# Generar bloques de prueba a lo largo de la autopista # Crear escuadrón de reserva (Vidas restantes)
for y in range(10, 90, 5): 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
)
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.entities.append(PushBlock((y + 3) % GRID_SIZE_X, y + 2)) if y % 12 == 0:
self.entities.append(Enemy(3.5, y - 2))
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
):
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)
self.score += 100
def draw_hud(self):
zone = int((GRID_SIZE_Y - self.vortex.gy) / 4)
hud_text = f"ZONE: {zone:02d} | LIVES: {self.lives} | SCORE: {self.score:06d}"
text_surface = self.font.render(hud_text, True, (255, 255, 255))
pygame.draw.rect(self.screen, (0, 0, 0), (0, 0, SCREEN_WIDTH, 40))
self.screen.blit(text_surface, (20, 10))
def run(self): def run(self):
running = True running = True
while running: while running:
self.clock.tick(FPS) self.clock.tick(FPS)
# 1. Procesar Eventos
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
running = False running = False
# 2. Actualizar Estado 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)
)
pygame.display.flip()
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
self.player.update(keys, self.entities) if keys[pygame.K_r]:
self.lives = 4
self.score = 0
self.reset_level()
continue
# Actualizaciones
keys = pygame.key.get_pressed()
self.player.update(keys, self.entities, self.bullets)
self.vortex.update(self.entities) self.vortex.update(self.entities)
# 3. Calcular desplazamiento de cámara (centrada en el VORTEX o el jugador) for entity in self.entities:
if isinstance(entity, Enemy):
entity.update(self.entities)
for bullet in self.bullets[:]:
bullet.update(self.entities)
if not bullet.alive:
self.bullets.remove(bullet)
self.handle_collisions()
# Posicionamiento 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
# 4. Renderizado # Renderizado
self.screen.fill(COLOR_BG) self.screen.fill(COLOR_BG)
# Dibujar suelo (Autopista) # Dibujar suelo
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
# Solo dibujar si está visible en pantalla if (
if -100 < draw_x < SCREEN_WIDTH + 100 and -100 < draw_y < SCREEN_HEIGHT + 100: -100 < draw_x < SCREEN_WIDTH + 100
and -100 < draw_y < SCREEN_HEIGHT + 100
):
pts = [ pts = [
(draw_x, draw_y), (draw_x, draw_y),
( (
@ -222,13 +369,15 @@ class Game:
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)
# Ordenar entidades por coordenada 'gy' descendente para renderizar correctamente la profundidad # Renderizado en orden de profundidad (Z-Sorting por Y)
all_drawables = self.entities + self.bullets + self.spares
sorted_entities = sorted( sorted_entities = sorted(
self.entities, key=lambda e: e.gy, reverse=True 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.draw_hud()
pygame.display.flip() pygame.display.flip()
pygame.quit() pygame.quit()
@ -237,3 +386,4 @@ class Game:
if __name__ == "__main__": if __name__ == "__main__":
Game().run() Game().run()

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB