Project Script


""" Hitscan laser

Cuando queremos tener disparos en nuestros juegos hay varias formas de programarlo.

Las dos clases más generales de tipos de disparos son HITSCAN y PROJECTILE

En este ejemplo vamos a implementar un disparo HITSCAN, ya que es más sencillo y nos va a permitir
programar que aparezcan enemigos.

"""

import pyxel

# Tamaño de la pantalla en píxeles
WIDTH, HEIGHT = 160, 120

# Altura del suelo en todas las habitaciones
FLOOR_HEIGHT = 10

# Como el suelo es igual siempre, nos lo guardamos en esta lista para reutilizarlo
FLOOR_PLATFORM = [0, HEIGHT - FLOOR_HEIGHT, WIDTH, FLOOR_HEIGHT]

ALTURA_PLATAFORMA = 2
# Aquí definimos las habitaciones de nuestro nivel
ROOMS = [
    [FLOOR_PLATFORM, [40, 80, 40, ALTURA_PLATAFORMA], [100, 60, 40, ALTURA_PLATAFORMA], [150, 15 , 10, ALTURA_PLATAFORMA]],
    [FLOOR_PLATFORM, [50, 90, 80, ALTURA_PLATAFORMA], [ 40, 50, 20, ALTURA_PLATAFORMA]],
]

# Aquí definimos donde está el tesoro (final del nivel)
DOOR = {
    "room": 0,
    "position": (150, 7),
    "collision": False,
}

# --- NUEVO: Enemigos por habitación ---
# Cada enemigo es [x, y, vivo (True/False)]
ENEMIES = [
    [[60, 72, True], [120, 52, True]], # Enemigos habitación 0
    [[80, 82, True]],                  # Enemigos habitación 1
]

# Aquí le ponemos nombre a los distintos huecos para sonidos que tenemos
JUMP_SOUND_INDEX = 0
LANDING_SOUND_INDEX = 1

# Aquí asumimos que el personaje es siempre cuadrado y de este
# tamaño en pixels por lado
CHARACTER_SIDE_PIXELS = 8

class SimplePlatformer:
    def __init__(self):
        pyxel.init(WIDTH, HEIGHT, title="Lesson 7: Habitaciones")
        pyxel.load("my_resource.pyxres")
        
        # Define Jump Sound (Sound 0)
        pyxel.sounds[JUMP_SOUND_INDEX].set("c3c4", "S", "7", "N", 5)
        
        # Define Landing Sound (Sound 1)
        pyxel.sounds[LANDING_SOUND_INDEX].set("g1", "N", "5", "F", 10)

        self.active_room = 0
        self.x, self.y, self.dy = 80, 0, 0
        self.facing_sign = 1
        self.is_jumping = True
        self.on_platform = False
        
        # --- NUEVO: Estado del láser ---
        self.laser_timer = 0 # Para que el láser brille un momento

        pyxel.run(self.update, self.draw)

    def _update_movement(self):
        if pyxel.btn(pyxel.KEY_LEFT):
            self.x -= 2
            self.facing_sign = -1
        if pyxel.btn(pyxel.KEY_RIGHT):
            self.x += 2
            self.facing_sign = 1
        if self.x < 0:
            self.active_room -= 1
        elif self.x >= WIDTH:
            self.active_room += 1
            
    def _update_room(self):
        
        # Calculamos en qué habitación estamos
        self.active_room %= len(ROOMS)
        
        # Calculamos en que posición horizontal estamos (posición X)
        self.x %= WIDTH
        
        # Cargamos las plataformas de la habitación en la que estamos
        self.platforms = ROOMS[self.active_room]
        
        # NUEVO: Mostramos los enemigos vivos en la sala
        self.current_enemies = ENEMIES[self.active_room] # Cargar enemigos de la sala

    def _update_gravity(self):
        
        # Calculamos la gravedad por si estuviéramos saltando
        self.dy += 0.3
        self.on_platform = False
        
        # Devolvemos la posición vertical que resultaría
        # si aplicásemos la gravedad
        return self.y + self.dy
    
    # NUEVO! Lógica hitscan del laser
    def _update_laser(self):
        if self.laser_timer > 0:
            self.laser_timer -= 1

        if pyxel.btnp(pyxel.KEY_X):
            self.laser_timer = 5 # El láser se verá durante 5 frames
            
            # Lógica Hitscan:
            # El láser sale del centro del personaje (self.y + 4)
            laser_y = self.y + 4
            
            for enemy in self.current_enemies:
                ex, ey, alive = enemy
                if alive:
                    # Comprobamos si el láser está a la altura del enemigo
                    if ey <= laser_y <= ey + 8:
                        # Comprobamos si el enemigo está en la dirección hacia donde miramos
                        if self.facing_sign == 1 and ex > self.x: # Derecha
                            enemy[2] = False # ¡Muerto!
                        elif self.facing_sign == -1 and ex < self.x: # Izquierda
                            enemy[2] = False
    
    def update(self):
        
        self._update_movement()
        self._update_room()
        self._update_laser()

        new_y = self._update_gravity()
        
        # Miramos todas las plataformas para ver si chocamos
        for plat in self.platforms:
            px, py, pw, _ = plat
            
            # Comprobamos si nuestro personaje colisiona con la plataforma saltando
            if self.x + CHARACTER_SIDE_PIXELS > px and self.x < px + pw:
                if self.y + CHARACTER_SIDE_PIXELS <= py and new_y + CHARACTER_SIDE_PIXELS >= py:
                    # Reproducimos el sonido de tocar el suelo
                    # Pero sólo si estábamos saltando
                    if self.is_jumping:
                        pyxel.play(0, 1) # (channel, sound_no)
                    
                    # Ajustamos la posición para que caiga exactamente en la plataforma
                    self.y = py - CHARACTER_SIDE_PIXELS
                    self.dy = 0
                    self.is_jumping = False
                    self.on_platform = True
                    
                    break
        
        # Si resulta que no hemos tocado plataforma,
        # seguimos cayendo y seguimos saltando
        if not self.on_platform:
            self.y = new_y
            self.is_jumping = True

        # Jump Trigger
        if pyxel.btnp(pyxel.KEY_SPACE) and not self.is_jumping:
            self.dy = -5
            self.is_jumping = True
            # Trigger Jump Sound
            pyxel.play(0, 0)
        
        x_door = DOOR["position"][0]
        y_door = DOOR["position"][1]
        
        bounding_box_side = 4
        x_collision = self.x < x_door + bounding_box_side and self.x > x_door - bounding_box_side
        y_collision = self.y < y_door + bounding_box_side and self.y > y_door - bounding_box_side
        
        DOOR["collision"] = x_collision and y_collision and not self.is_jumping
        
    def draw(self):
        pyxel.cls(0)
        for plat in self.platforms:
            pyxel.rect(*plat, 11)
        
        # NUEVO: Dibujar enemigos
        for ex, ey, alive in self.current_enemies:
            if alive:
                pyxel.rect(ex, ey, 8, 8, 8) # Cuadrado rojo para enemigos

        # NUEVO: Dibujar Láser
        if self.laser_timer > 0:
            color = pyxel.frame_count % 16 # Efecto de parpadeo
            lx = self.x + 4
            ly = self.y + 4
            target_x = WIDTH if self.facing_sign == 1 else 0
            pyxel.line(lx, ly, target_x, ly, 10 if color < 8 else 7)
        
        if self.is_jumping:
            u, v = (0, CHARACTER_SIDE_PIXELS) if self.dy < 0 else (CHARACTER_SIDE_PIXELS, 0)
        else:
            u, v = 0, 0
 
         # Si estamos en la habitación de la puerta, la pintamos
        if self.active_room == DOOR["room"]:
            # ¿Sabes explicar qué hace esta línea?
            pyxel.blt(DOOR["position"][0], DOOR["position"][1], 0, 8, 8, 8, 8, 2)
            
    
        pyxel.blt(self.x, self.y, 0, u, v, self.facing_sign * 8, 8, 2)
        
        if DOOR["collision"]:
            pyxel.text(20, 20, "Wiiii!!!", pyxel.rndi(1, 15))

SimplePlatformer()

"""Ejercicios

- Reemplazar los enemigos por un sprite chulo
- Añadir un sonido cuando se dispara el láser
- Añadir un sonido cuando los enemigos mueren
- Animar a los enemigos
- Que la puerta esté cerrada hasta que matemos a todos los enemigos
- Alternativa: cuando matamos al último enemigo, suelta una llave
- Que nos muramos al tocar un enemigo
- Que los enemigos se muevan
- Anota todas las mejoras que le quieras hacer a tu juego en una lista TODO
"""