Las imágenes y los ficheros de audio
Para nuestro juego necesitaremos varias imágenes y ficheros de audio. Para poder incluirlas en nuestro proyecto, primero debemos descargarlas en nuestro ordenador, y a continuación las arrastraremos a la zona de «Sistema de Archivos» de Godot. Las tienes disponibles en este archivo ZIP.
Pasos a seguir
Esqueleto del juego (todas las funciones vacías)
extends Node2D
# ==============================================================================
# CONFIGURACIÓN Y CONSTANTES
# ==============================================================================
# --- TAMAÑOS Y AJUSTES ---
const TAM_CELDA = 100 # Píxeles por bloque
const MARGEN = 5 # Píxeles de separación interna
# --- VELOCIDADES ---
const VEL_JUGADOR = 350
const VEL_ENEMIGO = 150
# --- CARGA DE IMÁGENES ---
const TEX_FONDO = preload("res://assets/fondo.png")
const TEX_JUGADOR = preload("res://assets/jugador.png")
const TEX_ENEMIGO = preload("res://assets/enemigo.png")
const TEX_PARED = preload("res://assets/bloque.png")
const TEX_META = preload("res://assets/meta.png")
const TEX_CERRAR = preload("res://assets/boton_cerrar.png")
const TEX_REINICIAR = preload("res://assets/boton_reiniciar.png")
const TEX_GAMEOVER = preload("res://assets/game_over.png")
const TEX_SIGUIENTE = preload("res://assets/siguiente_nivel.png")
# --- CARGA DE AUDIO ---
const AUDIO_FONDO = preload("res://assets/musica_fondo.mp3")
const AUDIO_MUERTE = preload("res://assets/sfx_muerte.mp3")
# ==============================================================================
# VARIABLES GLOBALES
# ==============================================================================
var pantalla: Vector2
var tam_tablero: Vector2
var celda_dim: Vector2
var paredes: Array[Rect2] = []
var enemigos: Array[Dictionary] = []
var jugador: Rect2
var meta: Rect2
var nivel_actual: int = 1
var game_over: bool = false
var siguiente_nivel: bool = false
var btn_cerrar: Rect2
var btn_reiniciar: Rect2
var music_player: AudioStreamPlayer
var sfx_player: AudioStreamPlayer
# ==============================================================================
# 1. HERRAMIENTAS BÁSICAS
# ==============================================================================
func _crear_rect(x_grid, y_grid, margen_interno):
# Ayuda a convertir coordenadas de la cuadrícula (ej: 2,3) a píxeles reales en pantalla.
pass
func _es_zona_segura(pos: Vector2):
# Devuelve true si la coordenada es inicio, fin o esquinas clave.
pass
func _colisiona_con_paredes(rect: Rect2):
# Comprueba si un rectángulo intercepta alguna pared del array.
pass
func _reproducir_sonido(stream):
# Función auxiliar para reproducir un efecto de sonido puntual.
pass
func _es_posicion_valida(rect: Rect2):
# Valida si un objeto está dentro de la pantalla y libre de paredes.
pass
# ==============================================================================
# 2. GENERACIÓN DE DATOS (Lógica de Nivel)
# ==============================================================================
func _generar_paredes(cantidad):
# Coloca obstáculos aleatorios asegurándose de no bloquear el inicio o fin.
pass
func _generar_enemigos(cantidad):
# Genera enemigos solo en la mitad inferior para dar tiempo al jugador.
pass
func _iniciar_nivel():
# Resetea las variables y genera un nuevo mapa limpio.
# Calcula la dificultad basada en el nivel actual.
pass
# ==============================================================================
# 3. CONFIGURACIÓN Y VISUALIZACIÓN
# ==============================================================================
func _ready():
# Inicialización básica: Configura el tamaño de pantalla, la cuadrícula y el audio.
# Se ejecuta una única vez al arrancar la escena.
pass
func _draw():
# Dibuja texturas, enemigos, jugador e interfaz en pantalla.
# IMPORTANTE: El orden importa. Lo primero que se dibuja queda al fondo.
pass
# ==============================================================================
# 4. MOVIMIENTO Y FÍSICAS
# ==============================================================================
func _mover_jugador(delta):
# Calcula el movimiento del jugador y evita que atraviese paredes.
pass
func _mover_enemigos(delta):
# Mueve enemigos verticalmente y los hace rebotar al chocar.
pass
func _comprobar_colisiones():
# Verifica condiciones de victoria o derrota por colisión.
pass
# ==============================================================================
# 5. BUCLE PRINCIPAL E INPUT
# ==============================================================================
func _process(delta):
# Bucle principal: Gestiona movimiento y colisiones en cada frame.
pass
func _input(event):
# Gestiona clics del ratón para botones y reinicio de juego.
pass
crear_rect()
func _crear_rect(x_grid, y_grid, margen_interno) -> Rect2: # Ayuda a convertir coordenadas de la cuadrícula (ej: 2,3) a píxeles reales en pantalla. # Retorna un Rect2. La posición X es (columna * ancho_celda) + margen. # El tamaño es (ancho_celda - doble margen) para que quede centrado. return Rect2( x_grid * celda_dim.x + margen_interno, y_grid * celda_dim.y + margen_interno, celda_dim.x - margen_interno * 2, celda_dim.y - margen_interno * 2 )
es_zona_segura()
func _es_zona_segura(pos: Vector2) -> bool: # Devuelve true si la coordenada es inicio, fin o esquinas clave. if pos == Vector2(0, 0): return true # Esquina Jugador if pos == Vector2(tam_tablero.x - 1, tam_tablero.y - 1): return true # Esquina Meta # Protegemos las otras dos esquinas para evitar bloqueos imposibles en mapas pequeños if pos == Vector2(tam_tablero.x - 1, 0): return true if pos == Vector2(0, tam_tablero.y - 1): return true return false
colisiona_con_paredes()
func _colisiona_con_paredes(rect: Rect2) -> bool: # Comprueba si un rectángulo intercepta alguna pared del array. for pared in paredes: # Usamos .grow(-1) para reducir un píxel el hitbox de chequeo. # Esto evita que detecte colisión si solo se están rozando lado con lado. if rect.grow(-1).intersects(pared): return true return false
reproducir_sonido()
func _reproducir_sonido(stream): # Función auxiliar para reproducir un efecto de sonido puntual. sfx_player.stream = stream sfx_player.play()
es_posicion_valida()
func _es_posicion_valida(rect: Rect2) -> bool: # Valida si un objeto está dentro de la pantalla y libre de paredes. # 'encloses' devuelve true si el rect está TOTALMENTE dentro de la pantalla if not get_viewport_rect().encloses(rect): return false # Si choca con pared, devuelve false (posición no válida) return not _colisiona_con_paredes(rect)
generar_paredes()
func _generar_paredes(cantidad): # Coloca obstáculos aleatorios asegurándose de no bloquear el inicio o fin. var intentos = 0 # Usamos un while con límite de intentos para evitar bucles infinitos si no hay sitio while paredes.size() < cantidad and intentos < 1000: intentos += 1 # Elegimos una columna y fila al azar var pos = Vector2(randi() % int(tam_tablero.x), randi() % int(tam_tablero.y)) # Si la posición elegida es vital (inicio/fin), la descartamos y probamos otra if _es_zona_segura(pos): continue # Creamos el rectángulo provisional var nueva_pared = _crear_rect(pos.x, pos.y, 0) # Verificamos que no caiga encima de otra pared ya existente if not _colisiona_con_paredes(nueva_pared): paredes.append(nueva_pared)
generar_enemigos()
func _generar_enemigos(cantidad):
# Genera enemigos solo en la mitad inferior para dar tiempo al jugador.
var intentos = 0
var fila_minima = int(tam_tablero.y / 2) # Calculamos la mitad del tablero
while enemigos.size() < cantidad and intentos < 1000:
intentos += 1
# Coordenada X aleatoria (cualquier columna)
var cx = randi() % int(tam_tablero.x)
# Coordenada Y aleatoria (solo desde la mitad hacia abajo)
var cy = randi_range(fila_minima, int(tam_tablero.y) - 1)
if _es_zona_segura(Vector2(cx, cy)): continue
var rect_temp = _crear_rect(cx, cy, 0)
# Evitamos que un enemigo nazca dentro de una pared
if _colisiona_con_paredes(rect_temp): continue
# Añadimos el enemigo como un diccionario con sus propiedades
enemigos.append({
"rect": rect_temp.grow(-15), # Hacemos la hitbox más pequeña para perdonar roces
"dir": 1 if randf() > 0.5 else -1, # 50% probabilidad de ir arriba o abajo
"velocidad": VEL_ENEMIGO
})
iniciar_nivel()
func _iniciar_nivel(): # Resetea las variables y genera un nuevo mapa limpio. # Calcula la dificultad basada en el nivel actual. # Limpiamos los arrays para borrar el nivel anterior paredes.clear() enemigos.clear() # Reiniciamos estados lógicos game_over = false siguiente_nivel = false # Creamos al jugador en la posición (0,0) (Arriba-Izquierda) jugador = _crear_rect(0, 0, MARGEN) # Creamos la meta en la última celda disponible (Abajo-Derecha) meta = _crear_rect(tam_tablero.x - 1, tam_tablero.y - 1, 0) # Fórmula de dificultad: Más nivel = más paredes y enemigos var cant_paredes = 5 + (nivel_actual * 3) var cant_enemigos = 2 + (nivel_actual * 1) # Ejecutamos los bucles de generación _generar_paredes(cant_paredes) _generar_enemigos(cant_enemigos)
ready()
func _ready(): # Inicialización básica: Configura el tamaño de pantalla, la cuadrícula y el audio. # Se ejecuta una única vez al arrancar la escena. # Inicializa la semilla aleatoria para que cada partida sea distinta randomize() # Obtenemos el tamaño visible de la ventana del juego pantalla = get_viewport_rect().size # Calculamos cuántas columnas y filas caben dividiendo el ancho/alto por el tamaño de celda var cols = round(pantalla.x / TAM_CELDA) var filas = round(pantalla.y / TAM_CELDA) tam_tablero = Vector2(cols, filas) # Recalculamos el tamaño exacto de la celda para que se ajuste perfectamente a la pantalla celda_dim = Vector2(pantalla.x / cols, pantalla.y / filas) # Definimos la geometría de los botones UI # Botón cerrar: Esquina superior derecha btn_cerrar = Rect2(pantalla.x - celda_dim.x, 0, celda_dim.x, celda_dim.y) # Botón reiniciar: Esquina inferior izquierda btn_reiniciar = Rect2(0, (tam_tablero.y - 1) * celda_dim.y, celda_dim.x, celda_dim.y) # Instanciamos y configuramos el nodo de música music_player = AudioStreamPlayer.new() music_player.stream = AUDIO_FONDO music_player.volume_db = -10 # Bajamos un poco el volumen add_child(music_player) # Lo añadimos al árbol de nodos music_player.play() # Instanciamos el nodo para efectos de sonido (SFX) sfx_player = AudioStreamPlayer.new() add_child(sfx_player) # Llamamos a la función que prepara el primer nivel _iniciar_nivel()
draw()
func _draw(): # Dibuja texturas, enemigos, jugador e interfaz en pantalla. # IMPORTANTE: El orden importa. Lo primero que se dibuja queda al fondo. # 1. Fondo (Capa más baja) draw_texture_rect(TEX_FONDO, Rect2(Vector2.ZERO, pantalla), false) # 2. Objetivos draw_texture_rect(TEX_META, meta, false) # 3. Obstáculos for p in paredes: draw_texture_rect(TEX_PARED, p, false, Color(1,1,1,0.5)) # Transparencia 0.5 # 4. Entidades (enemigos y jugador) for e in enemigos: draw_texture_rect(TEX_ENEMIGO, e.rect, false) draw_texture_rect(TEX_JUGADOR, jugador, false) # 5. Interfaz de Usuario (Capa superior) draw_texture_rect(TEX_CERRAR, btn_cerrar, false) draw_texture_rect(TEX_REINICIAR, btn_reiniciar, false) # Texto informativo draw_string(ThemeDB.fallback_font, Vector2(0, 50), "NIVEL " + str(nivel_actual), HORIZONTAL_ALIGNMENT_CENTER, pantalla.x, 40) # 6. Pantallas Superpuestas (Overlays) if game_over: # Dibuja game over con un tinte ligeramente transparente (0.8) draw_texture_rect(TEX_GAMEOVER, Rect2(Vector2.ZERO, pantalla), false, Color(1,1,1,0.8)) elif siguiente_nivel: draw_texture_rect(TEX_SIGUIENTE, Rect2(Vector2.ZERO, pantalla), false, Color(1,1,1,0.8))
mover_jugador()
func _mover_jugador(delta):
# Calcula el movimiento del jugador y evita que atraviese paredes.
# Obtiene un vector (-1, 0, 1) según las teclas pulsadas
var dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
# Si no se pulsa nada, salimos para ahorrar cálculos
if dir == Vector2.ZERO: return
# Calculamos cuánto desplazarse en este frame (Dirección * Velocidad * Tiempo)
var velocidad = dir * VEL_JUGADOR * delta
# --- MOVIMIENTO EJE X ---
# Creamos una copia temporal del jugador movida en X
var test_x = jugador
test_x.position.x += velocidad.x
# Si la copia no choca, movemos al jugador real
if _es_posicion_valida(test_x): jugador.position.x = test_x.position.x
# --- MOVIMIENTO EJE Y ---
# Hacemos lo mismo para el eje Y por separado.
# (Hacerlo separado permite deslizarse por las paredes en lugar de atascarse)
var test_y = jugador
test_y.position.y += velocidad.y
if _es_posicion_valida(test_y): jugador.position.y = test_y.position.y
mover_enemigos()
func _mover_enemigos(delta): # Mueve enemigos verticalmente y los hace rebotar al chocar. for e in enemigos: # Calculamos el paso vertical var paso = e.dir * e.velocidad * delta e.rect.position.y += paso # Verificamos si se salió de la pantalla (Arriba < 0, Abajo > pantalla) var choca_borde = e.rect.position.y < 0 or e.rect.end.y > pantalla.y # Si toca borde o pared... if choca_borde or _colisiona_con_paredes(e.rect): e.rect.position.y -= paso # Revertimos el movimiento para que no se quede pegado e.dir *= -1 # Invertimos la dirección (1 a -1, o viceversa)
comprobar_colisiones()
func _comprobar_colisiones(): # Verifica condiciones de victoria o derrota por colisión. # 1. VICTORIA: ¿El jugador toca la meta? if jugador.intersects(meta): siguiente_nivel = true _reproducir_sonido(AUDIO_FONDO) # 2. DERROTA: ¿El jugador toca algún enemigo? for e in enemigos: # Reducimos un poco el hitbox del enemigo (grow -4) para ser benévolos con el jugador if jugador.intersects(e.rect.grow(-4)): game_over = true music_player.stop() # Silenciamos música _reproducir_sonido(AUDIO_MUERTE) # Sonido de choque return
process()
func _process(delta): # Bucle principal: Gestiona movimiento y colisiones en cada frame. # Si el juego acabó, salimos de la función inmediatamente (no procesamos nada más) if game_over or siguiente_nivel: return # Actualizamos la lógica _mover_jugador(delta) _mover_enemigos(delta) _comprobar_colisiones() # Solicitamos redibujar la pantalla (llama a _draw automáticamente) queue_redraw()
input()
func _input(event): # Gestiona clics del ratón para botones y reinicio de juego. # Solo nos interesan los eventos que sean Clics de ratón y que estén Presionados if not (event is InputEventMouseButton and event.pressed): return # Chequeo de botones de UI if btn_cerrar.has_point(event.position): get_tree().quit() # Cierra el juego return if btn_reiniciar.has_point(event.position): _iniciar_nivel() # Reinicia el nivel actual return # Lógica para continuar tras ganar o perder if game_over: music_player.play() _iniciar_nivel() # Reintenta el mismo nivel elif siguiente_nivel: nivel_actual += 1 # Sube la dificultad _iniciar_nivel()
El resultado
Desde Godot podemos exportar este mismo proyecto para poder jugar en cualquier navegador. Puedes ver el resultado y jugar directamente mediante el siguiente enlace: