Parte: 7 — Multijugador y networking · Fuente: Documentación de Godot 4 (Exporting for dedicated servers, command line tutorial) + prácticas de despliegue en VPS/contenedores ⏱️ Duración estimada: 55 min · Nivel: Avanzado
Aprender a ejecutar tu juego de Godot 4 como un servidor dedicado headless: sin ventana, sin render, sin audio, arrancando desde un argumento de línea de comandos y consumiendo el mínimo de recursos. Compararás el modelo servidor dedicado frente al listen server, detectarás el modo servidor por argumentos y por DisplayServer, y desplegarás el proceso localmente para probarlo con un cliente. Es la pieza que convierte un prototipo de escritorio en algo que corre 24/7 en un VPS o un contenedor.
Al finalizar, el alumno podrá:
godot --headless sin abrir ventana.OS.get_cmdline_args() y DisplayServer.get_name().| # | Tema | Por qué importa |
|---|---|---|
| 1 | Dedicado vs listen server | Decide quién tiene autoridad y quién paga el hosting |
| 2 | Modo headless de Godot | Corre sin GPU/ventana: barato y desplegable |
| 3 | Detección por argumentos | Un mismo binario sirve de cliente o servidor |
| 4 | Bucle de tick del servidor | El servidor no renderiza pero sí simula a ritmo fijo |
| 5 | Exportación del binario servidor | Un export template sin render reduce peso y superficie |
| 6 | Despliegue en VPS/contenedor | Lleva el proceso a producción reproducible |
| 7 | Recursos y límites | CPU, RAM y ancho de banda marcan cuántas partidas caben |
| 8 | Logs y observabilidad | Sin ventana, los logs son tus ojos |
--headless: bandera de arranque de Godot que activa el driver de display nulo. Clave: es la forma soportada de correr servidores.OS.get_cmdline_args(): devuelve los argumentos pasados al ejecutable. Clave: permite un flag propio como --server.physics_ticks_per_second, no los FPS de render.Necesitas Godot 4.x instalado y accesible como godot en tu PATH (en Windows puede ser Godot_v4.x-stable_win64.exe). El laboratorio no requiere nube: desplegarás "localmente" ejecutando el servidor como proceso de terminal. Para producción real, la referencia canónica es la guía de Godot sobre servidores dedicados (https://docs.godotengine.org/en/stable/tutorials/networking/dedicated_servers.html) y la de línea de comandos (https://docs.godotengine.org/en/stable/tutorials/editor/command_line_tutorial.html). Si más adelante externalizas la infraestructura, backends como Nakama (https://heroiclabs.com/docs/) o GodotSteam (https://godotsteam.com/) gestionan el ciclo de vida del servidor por ti. Prepara un main.gd como Autoload que decida el rol al arrancar.
Convertiremos el proyecto en un binario que arranca como servidor headless si recibe --server, y como cliente en caso contrario. Es observable: verás el servidor imprimir su tick y el cliente conectarse.
Paso 1 — Arranque que elige rol. Un Autoload lee los argumentos y detecta headless.
# arranque.gd — Autoload (Project Settings > Autoload)
extends Node
func _ready() -> void:
var args := OS.get_cmdline_args()
var es_headless := DisplayServer.get_name() == "headless"
if args.has("--server") or es_headless:
print("Iniciando en modo SERVIDOR (headless=%s)" % es_headless)
_iniciar_servidor()
else:
print("Iniciando en modo CLIENTE")
_iniciar_cliente()
func _iniciar_servidor() -> void:
# Ritmo de simulación del servidor, independiente de los FPS de render.
Engine.physics_ticks_per_second = 30
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(9000, 32)
if err != OK:
push_error("Fallo al crear servidor: %s" % err)
get_tree().quit(1)
return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(func(id): print("Peer conectado: ", id))
print("Servidor escuchando en :9000")
func _iniciar_cliente() -> void:
var peer := ENetMultiplayerPeer.new()
peer.create_client("127.0.0.1", 9000)
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(func(): print("Conectado al servidor"))
Paso 2 — Tick observable del servidor. Añade un contador que lata cada segundo para confirmar que el bucle vive sin render.
# En arranque.gd
var _acumulado := 0.0
var _tick := 0
func _physics_process(delta: float) -> void:
if not multiplayer.is_server():
return
_acumulado += delta
if _acumulado >= 1.0:
_acumulado -= 1.0
_tick += 1
print("[servidor] tick #%d, peers=%d" % [_tick, multiplayer.get_peers().size()])
Paso 3 — Ejecutar el servidor headless. Sin ventana, sin GPU.
# Servidor dedicado, sin render ni audio
godot --headless --path . --server
# En Windows con el ejecutable versionado:
# ./Godot_v4.4-stable_win64.exe --headless --path . --server
Verás en consola Servidor escuchando en :9000 y luego [servidor] tick #1, peers=0, #2, ... sin abrir ninguna ventana.
Paso 4 — Conectar un cliente y exportar. Arranca un cliente normal (con ventana) y luego exporta el binario del servidor.
# Cliente (misma máquina), abre ventana y conecta
godot --path .
# Exportar el binario del servidor por línea de comandos
# (define antes un preset "Linux/X11" o el de tu plataforma en el editor)
godot --headless --export-release "Linux/X11" ./build/servidor.x86_64
Observable: al conectar el cliente, el servidor imprime Peer conectado: <id> y el contador de peers sube a 1. El binario exportado en ./build/ se ejecuta igual: ./build/servidor.x86_64 --headless --server.
--port=NNNN parseando OS.get_cmdline_args() para elegir el puerto sin recompilar.Engine.get_physics_interpolation_fraction() cada 5 s.get_tree().quit().Dockerfile mínimo que copie el binario exportado y lo ejecute con --headless --server.Entrega un proyecto que, con un único código base, arranque como servidor dedicado headless al pasar --server (o al detectar display headless) y como cliente en cualquier otro caso, exportado a un binario ejecutable. El servidor debe simular a un tick fijo y registrar conexiones/desconexiones en el log.
Criterio de aceptación: godot --headless --path . --server corre sin abrir ventana e imprime ticks periódicos; una instancia cliente se conecta y el servidor registra el peer; el binario exportado reproduce el mismo comportamiento al ejecutarse con --headless --server.
| Síntoma | Causa y arreglo |
|---|---|
| Error "This application cannot run without a display" | Faltó --headless; añádelo o usa un export template server |
| El servidor consume 100 % de un núcleo | No limitaste el ritmo; fija Engine.physics_ticks_per_second y evita bucles busy-wait |
--server no detectado |
OS.get_cmdline_args() no incluye args del motor; pásalos tras -- o compara la cadena exacta |
| Cargas texturas/shaders en headless y crashea | El código de servidor no debe instanciar nodos de render; sepáralo por rol |
| El binario exportado no arranca en el VPS | Faltan permisos de ejecución (chmod +x) o librerías del sistema |
¿Un servidor headless renderiza algo? No. Con --headless el driver de display es nulo: no hay ventana ni GPU, solo simulación y red.
¿Puedo detectar el modo servidor sin flag propio? Sí, DisplayServer.get_name() == "headless" es fiable cuando arrancas con --headless. El flag --server da control explícito para casos con display.
¿A qué FPS corre un servidor? El render puede ser 0; lo que importa es physics_ticks_per_second, el ritmo de la simulación autoritativa (típico 20–60 Hz).
¿Necesito un export template especial? Godot ofrece plantillas server; reducen peso y dependencias gráficas, aunque un binario normal con --headless también funciona.
Clase 150 - Matchmaking, salas y relay (NAT traversal)
Continúa con 152 — Backends: Nakama, Steam y servicios gestionados, donde delegarás auth, salas y almacenamiento a un backend.