Parte: 7 — Multijugador y networking · Fuente: Documentación de Godot 4 (High-level multiplayer, ENet DTLS) + prácticas de servidor autoritativo anti-cheat ⏱️ Duración estimada: 55 min · Nivel: Avanzado
Interiorizar el principio que sostiene todo el multijugador competitivo: nunca confíes en el cliente. Vas a repasar las amenazas reales —speed hack, teleport, aimbot, packet crafting, replay— y a construir un servidor autoritativo que valida cada acción, aplica límites de movimiento y hace rate limiting de acciones. En el laboratorio añadirás esas validaciones y probarás un "cliente tramposo" cuyos movimientos imposibles el servidor rechaza. También verás cómo cifrar el transporte con ENet DTLS. Al terminar sabrás cerrar las puertas que un jugador malicioso intentaría abrir.
Al finalizar, el alumno podrá:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Nunca confiar en el cliente | Es el axioma de todo anti-cheat serio |
| 2 | Speed hack y teleport | Se frenan validando movimiento en el servidor |
| 3 | Aimbot y validación de disparo | El servidor decide impactos, no el cliente |
| 4 | Packet crafting y rate limiting | Limita frecuencia y forma de las peticiones |
| 5 | Replay attacks | Reusar paquetes válidos exige nonces/secuencia |
| 6 | Servidor autoritativo | Centraliza la verdad del estado |
| 7 | Cifrado (ENet DTLS) | Protege el tráfico de sniffing y manipulación |
| 8 | No exponer datos sensibles | El cliente solo debe saber lo que puede ver |
Trabajarás sobre la API de alto nivel de Godot 4 (@rpc, multiplayer.is_server(), multiplayer.get_remote_sender_id()) con un servidor autoritativo. Para el cifrado, Godot expone DTLS en ENetMultiplayerPeer mediante set_dtls_enabled y certificados; revisa la documentación de ENetConnection y la guía SSL/TLS de Godot (https://docs.godotengine.org/en/stable/tutorials/networking/ssl_certificates.html). El principio de servidor autoritativo está descrito en la guía de multijugador (https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html). Si usas un backend, Nakama (https://heroiclabs.com/docs/) permite lógica autoritativa en el servidor y GodotSteam enruta por la red de Valve. Prepara un servidor con un jugador por peer y un cliente que puedas manipular para simular trampas.
Construiremos un servidor que recibe intención de movimiento, la valida contra límites físicos y aplica rate limiting a los disparos. Un cliente tramposo que pida un teleport será rechazado y registrado.
Paso 1 — El cliente envía intención, no posición. Manda un vector de dirección; el servidor decide el resultado.
# cliente_jugador.gd
extends CharacterBody2D
func _physics_process(_delta: float) -> void:
if not es_mio():
return
var dir := Input.get_vector("izq", "der", "arriba", "abajo")
# Enviamos intención al servidor, NUNCA la posición final.
pedir_mover.rpc_id(1, dir)
func es_mio() -> bool:
return get_multiplayer_authority() == multiplayer.get_unique_id()
@rpc("any_peer", "call_remote", "unreliable_ordered")
func pedir_mover(_dir: Vector2) -> void:
pass # vive en el servidor
Paso 2 — El servidor valida el movimiento. Límite de velocidad y de distancia por tick; posición fuera de rango se rechaza.
# servidor_juego.gd (nodo autoritativo por jugador)
extends Node
const VEL_MAX := 200.0 # px/seg permitidos
const MARGEN := 1.15 # tolerancia por jitter/latencia
var _posiciones: Dictionary = {} # peer_id -> Vector2
@rpc("any_peer", "call_remote", "unreliable_ordered")
func pedir_mover(dir: Vector2) -> void:
if not multiplayer.is_server():
return
var id := multiplayer.get_remote_sender_id()
var pos_actual: Vector2 = _posiciones.get(id, Vector2.ZERO)
var dt := 1.0 / Engine.physics_ticks_per_second
var desplazamiento := dir.normalized() * VEL_MAX * dt
var distancia := desplazamiento.length()
var max_permitido := VEL_MAX * dt * MARGEN
if distancia > max_permitido:
push_warning("Movimiento sospechoso de %d: %.1f > %.1f" % [id, distancia, max_permitido])
return # rechazado; no aplicamos el paso
var nueva := pos_actual + desplazamiento
_posiciones[id] = nueva
aplicar_posicion.rpc(id, nueva) # broadcast autoritativo
@rpc("authority", "call_remote", "unreliable_ordered")
func aplicar_posicion(_id: int, _pos: Vector2) -> void:
pass # los clientes actualizan el nodo correspondiente
Paso 3 — Rate limiting de disparos. El servidor descarta disparos que llegan demasiado seguidos.
# En servidor_juego.gd
const CADENCIA_MIN_MS := 200 # máximo 5 disparos/seg
var _ultimo_disparo: Dictionary = {} # peer_id -> ms
@rpc("any_peer", "call_remote", "reliable")
func pedir_disparo(objetivo: Vector2) -> void:
if not multiplayer.is_server():
return
var id := multiplayer.get_remote_sender_id()
var ahora := Time.get_ticks_msec()
var previo: int = _ultimo_disparo.get(id, -CADENCIA_MIN_MS)
if ahora - previo < CADENCIA_MIN_MS:
push_warning("Rate limit: disparo de %d descartado" % id)
return
_ultimo_disparo[id] = ahora
# Aquí el servidor valida línea de visión antes de aceptar el impacto.
print("Disparo válido de %d hacia %s" % [id, objetivo])
Paso 4 — Cliente tramposo y cifrado. Un cliente que intenta teleportarse es rechazado; y activamos DTLS.
# cliente_tramposo.gd — para PROBAR el rechazo (nunca en producción)
func intentar_teleport() -> void:
# Envía una dirección gigante: el servidor lo detecta como imposible.
pedir_mover.rpc_id(1, Vector2(9999, 9999))
# servidor con transporte cifrado (esquema)
func crear_servidor_seguro() -> void:
var peer := ENetMultiplayerPeer.new()
peer.create_server(9000, 32)
# Con DTLS, se asocian certificados y clave al host ENet subyacente.
# peer.host.dtls_server_setup(tls_options) # ver docs de ENetConnection/TLSOptions
multiplayer.multiplayer_peer = peer
Observable: al ejecutar intentar_teleport(), el servidor imprime Movimiento sospechoso de <id>: 9999.0 > ... y no actualiza la posición; los movimientos legítimos sí se aplican y difunden. Con rate limiting, disparos en ráfaga imprimen Rate limit: disparo ... descartado.
nueva fuera de los bounds del nivel.raycast en el servidor antes de aceptar el impacto.pedir_mover y descarta paquetes con secuencia repetida o antigua (anti-replay).Entrega un servidor autoritativo que valide el movimiento por velocidad máxima, aplique rate limiting a una acción y rechace explícitamente a un cliente tramposo, dejando traza en el log de cada rechazo. El cliente solo debe enviar intención (input), nunca estado final.
Criterio de aceptación: un cliente normal se mueve y dispara con normalidad; un cliente que envía un desplazamiento imposible es rechazado (posición no cambia) y queda registrado en el log del servidor; disparos por encima de la cadencia permitida se descartan; en ningún caso el cliente puede fijar directamente su posición o vida.
| Síntoma | Causa y arreglo |
|---|---|
| El cliente mueve su posición y todos lo ven | El cliente es autoritativo; mueve la lógica al servidor y envía solo input |
| Jugadores legítimos rechazados con lag | Umbral de distancia sin margen; añade tolerancia por latencia/jitter |
| Spam de acciones tumba el servidor | Falta rate limiting; limita por peer con marca de tiempo |
| Un paquete capturado repite una acción | No hay anti-replay; añade secuencia o nonce y descarta repetidos |
| Datos sensibles visibles en el cliente | Envías estado que el jugador no debería ver; filtra por relevancia y usa rpc_id |
¿El anti-cheat elimina todas las trampas? No existe blindaje perfecto, pero un servidor autoritativo con validación y rate limiting elimina la mayoría de exploits comunes y encarece el resto.
¿Validar todo no sobrecarga el servidor? Las comprobaciones de movimiento y cadencia son baratas (comparaciones y aritmética). El coste real está en cosas como raycasts de disparo, que se optimizan.
¿Necesito cifrar si ya valido en el servidor? La validación evita que el cliente mienta; el cifrado (DTLS) evita que un tercero espíe o manipule el tráfico. Son capas complementarias.
¿Dónde guardo el estado autoritativo? En el servidor, en estructuras que el cliente nunca escribe. El cliente recibe solo lo que puede percibir legítimamente.
Clase 153 - Testing de red: simular latencia y pérdida
Continúa con 155 — Capstone Parte 7: un juego en red mínimo cliente-servidor, donde integrarás todo lo aprendido.