1. Fases del desarrollo de un juego
Hacer un juego es como rodar una película 🎥
- Concepto: género, público, plataforma y la mecánica principal (lo que el jugador hace una y otra vez).
- Documento de diseño (GDD): historia, mecánicas, controles, niveles, personajes, estilo gráfico, sonido, interfaz y monetización.
- Prototipo: la mecánica principal con formas simples (cajas y círculos) para comprobar que es divertida antes de invertir en arte.
- Producción: arte, animaciones, niveles, sonido, interfaz, guardado.
- Pruebas: errores (QA), equilibrio de dificultad, pruebas con jugadores, rendimiento en dispositivos reales.
- Publicación y mantenimiento: tiendas, marketing, actualizaciones y corrección de errores.
2. Un juego 2D paso a paso con Godot
Construimos un juego de recoger monedas y esquivar enemigos. En Godot cada elemento es una escena que se instancia dentro de otras.
| Escena | Nodos | Función |
|---|---|---|
| Jugador.tscn | CharacterBody2D → AnimatedSprite2D, CollisionShape2D, Camera2D | Se mueve con el teclado o la pantalla táctil |
| Moneda.tscn | Area2D → AnimatedSprite2D, CollisionShape2D, AudioStreamPlayer2D | Suma puntos al tocarla y desaparece |
| Enemigo.tscn | CharacterBody2D → Sprite2D, CollisionShape2D, Area2D (zona de visión) | Patrulla y persigue al jugador |
| Nivel.tscn | Node2D → TileMapLayer, instancias de Jugador, Monedas y Enemigos | El escenario |
| HUD.tscn | CanvasLayer → Label (puntos), TextureRect (vidas) | Interfaz por encima del juego |
| Main.tscn | Node → Nivel, HUD, AudioStreamPlayer (música) | Controla el estado del juego |
1. Entrada y movimiento
En Proyecto → Configuración → Mapa de entrada se crean las acciones izquierda, derecha, arriba, abajo asignando teclas, botones del mando o toques.
1extends CharacterBody2D
2
3@export var velocidad := 200.0
4signal golpeado
5
6func _physics_process(_delta):
7 var direccion := Input.get_vector("izquierda", "derecha", "arriba", "abajo")
8 velocity = direccion * velocidad
9 move_and_slide()
10 # Animación según el movimiento
11 if direccion == Vector2.ZERO:
12 $AnimatedSprite2D.play("quieto")
13 else:
14 $AnimatedSprite2D.play("andar")
15 $AnimatedSprite2D.flip_h = direccion.x < 02. Monedas con señales
1extends Area2D
2
3signal recogida(valor)
4@export var valor := 10
5
6func _on_body_entered(body): # señal body_entered conectada en el editor
7 if body.is_in_group("jugador"):
8 recogida.emit(valor)
9 $AudioStreamPlayer2D.play()
10 $CollisionShape2D.set_deferred("disabled", true)
11 hide()
12 await $AudioStreamPlayer2D.finished
13 queue_free() # eliminar el nodo de la escena3. Enemigo con máquina de estados
1extends CharacterBody2D
2
3enum Estado { PATRULLAR, PERSEGUIR }
4var estado := Estado.PATRULLAR
5var jugador: Node2D
6@export var puntos_ruta: Array[Vector2] = []
7var indice := 0
8
9func _physics_process(_delta):
10 match estado:
11 Estado.PATRULLAR:
12 var destino = puntos_ruta[indice]
13 velocity = global_position.direction_to(destino) * 80
14 if global_position.distance_to(destino) < 5:
15 indice = (indice + 1) % puntos_ruta.size()
16 Estado.PERSEGUIR:
17 velocity = global_position.direction_to(jugador.global_position) * 140
18 move_and_slide()
19
20func _on_vision_body_entered(body):
21 if body.is_in_group("jugador"):
22 jugador = body
23 estado = Estado.PERSEGUIR
24
25func _on_vision_body_exited(body):
26 if body == jugador:
27 estado = Estado.PATRULLAR4. Estados de la partida, marcador y récord
1extends Node
2
3var puntos := 0
4var record := 0
5const FICHERO := "user://record.save"
6
7func _ready():
8 if FileAccess.file_exists(FICHERO):
9 record = FileAccess.open(FICHERO, FileAccess.READ).get_32()
10 for moneda in get_tree().get_nodes_in_group("monedas"):
11 moneda.recogida.connect(_on_moneda_recogida)
12 $Nivel/Jugador.golpeado.connect(fin_partida)
13
14func _on_moneda_recogida(valor):
15 puntos += valor
16 $HUD/Puntos.text = "Puntos: %d" % puntos
17
18func fin_partida():
19 get_tree().paused = true
20 if puntos > record:
21 record = puntos
22 FileAccess.open(FICHERO, FileAccess.WRITE).store_32(record)
23 $HUD/GameOver.show() # botón «Otra vez» → get_tree().reload_current_scene()(sin mensajes)Completa la lógica de un juego 2D (el dibujado ya está hecho): • colisionan(a, b): true si los rectángulos {x, y, ancho, alto} se solapan (AABB). Si solo se tocan por el borde, no colisionan. • recoger(jugador, monedas): elimina del array monedas las que colisionan con el jugador y devuelve cuántas ha recogido. • Cada vez que se recoge alguna, suma 10 puntos por moneda a la variable global puntos y escribe «Puntos: N» en #marcador.
Implementa siguienteEstado(estado, distancia, vida), que decide qué hace un enemigo en cada fotograma. Los estados son «patrullar», «perseguir», «atacar» y «huir». Reglas, por orden de prioridad: 1. Con vida menor que 20 siempre «huir». 2. Si la distancia al jugador es menor o igual que 30, «atacar». 3. Si está persiguiendo, sigue persiguiendo mientras la distancia sea menor o igual que 250 (así no cambia de estado continuamente en el límite); si no, vuelve a «patrullar». 4. En cualquier otro estado, empieza a «perseguir» si la distancia es menor o igual que 150; si no, «patrullar». 5. Si estaba huyendo y ya tiene 20 o más de vida, aplica las reglas 2 a 4 como si estuviera patrullando.
3. Un juego 3D sencillo
En 3D las ideas son las mismas, con una dimensión más y algunos elementos nuevos:
- Mallas (
MeshInstance3D) con materiales PBR (color, rugosidad, metalicidad, texturas). Los modelos se importan en glTF o FBX desde Blender. - Cámara en perspectiva (
Camera3D) que sigue al jugador, a menudo sobre un brazo (SpringArm3D) para no atravesar paredes. - Luces: direccional (el sol), puntual (una lámpara) y focal (una linterna), con sombras; y un
WorldEnvironmentcon cielo, niebla y efectos. - Física 3D:
CharacterBody3D,RigidBody3D, colisionadores de caja, esfera o cápsula. - Navegación:
NavigationRegion3DyNavigationAgent3Dpara que los enemigos encuentren el camino (A*).
1extends CharacterBody3D
2
3const VELOCIDAD := 6.0
4const SALTO := 5.0
5var gravedad: float = ProjectSettings.get_setting("physics/3d/default_gravity")
6
7func _physics_process(delta):
8 if not is_on_floor():
9 velocity.y -= gravedad * delta
10 if Input.is_action_just_pressed("saltar") and is_on_floor():
11 velocity.y = SALTO
12 var entrada := Input.get_vector("izquierda", "derecha", "adelante", "atras")
13 # La dirección se calcula respecto a hacia dónde mira el personaje
14 var direccion := (transform.basis * Vector3(entrada.x, 0, entrada.y)).normalized()
15 velocity.x = direccion.x * VELOCIDAD
16 velocity.z = direccion.z * VELOCIDAD
17 move_and_slide()
18
19func _unhandled_input(evento):
20 if evento is InputEventMouseMotion: # girar con el ratón
21 rotate_y(-evento.relative.x * 0.003)
22 $Camara.rotate_x(-evento.relative.y * 0.003)Rendimiento en móviles
4. Sonido, interfaz y pulido
| Elemento | Técnica |
|---|---|
| Música | AudioStreamPlayer en bucle, con volumen ajustable en un bus de audio «Música» |
| Efectos | AudioStreamPlayer2D/3D: el volumen depende de la distancia a la cámara |
| Interfaz (HUD) | CanvasLayer por encima del juego; anclajes para adaptarse a cualquier pantalla |
| Menús | Escenas propias: principal, pausa (get_tree().paused), opciones, game over |
| Controles táctiles | TouchScreenButton o joystick virtual en móvil |
| Sensación («game feel») | Partículas, vibración de cámara, pequeñas pausas al golpear, animaciones con Tween |
| Guardado | Ficheros en user:// (JSON o ConfigFile) con récords, opciones y progreso |
| Localización | Traducciones en CSV/PO con tr("CLAVE") |
5. Exportar, probar y publicar
- En Godot: Editor → Administrar plantillas de exportación (se descargan una vez).
- Android: instalar el SDK de Android y un JDK, crear un keystore y configurarlos en Configuración del editor; en Proyecto → Exportar, añadir Android, indicar el paquete (
com.estudio.monedas), iconos, orientación y permisos, y exportar el AAB firmado. - Web (HTML5): se exporta a una carpeta con el
index.htmly el.wasm; se puede subir a itch.io para que cualquiera lo juegue en el navegador. - Escritorio: Windows, macOS y Linux desde el mismo proyecto.
- Probar en dispositivos reales (varios tamaños y gamas), con el depurador remoto y el perfilador.
- Publicar: Google Play (AAB, ficha, clasificación por edades PEGI/IARC), itch.io, Steam; política de privacidad si hay anuncios o analítica.
6. Ejercicios
Documento de diseño
Escribe un documento de diseño (GDD) de una página para un juego móvil 2D sencillo de tu invención: concepto en una frase, público, mecánica principal, controles, objetivo y condiciones de victoria y derrota, progresión (niveles o dificultad), estilo gráfico y sonoro, interfaz (pantallas) y monetización.
Tu primer juego en Godot
Siguiendo el tema, crea en Godot el juego de recoger monedas: jugador con animación de andar, al menos 10 monedas repartidas en un nivel hecho con TileMap, un enemigo que patrulla y persigue al jugador al verlo, marcador de puntos, pantalla de fin de partida con botón para reintentar y récord guardado. Exporta a web y súbelo a itch.io (o a Android).