Volver al Blog

Reveladas las nuevas características de Godot 4.7: Un vistazo a las actualizaciones de rendimiento de Dev3 y Dev4

Publicado el 14 de abril de 2026
Reveladas las nuevas características de Godot 4.7: Un vistazo a las actualizaciones de rendimiento de Dev3 y Dev4

Reveladas las nuevas características de Godot 4.7: Un vistazo a las actualizaciones de rendimiento de Dev3 y Dev4

Todo desarrollador independiente que gestiona un juego multijugador conoce el momento exacto en que su código de red empieza a producir desincronizaciones fantasma y tirones de físicas impredecibles. Pasas semanas construyendo un bucle de juego sólido, solo para darte cuenta de que sincronizar el estado a través de una conexión inestable requiere una mentalidad de ingeniería completamente diferente. Con el reciente lanzamiento de las versiones preliminares (snapshots) Dev3 y Dev4, los principales colaboradores del motor están superando los límites, y comprender estas nuevas características de godot 4.7 es fundamental para los estudios que planifican sus plazos de producción.

Godot 4 ha estado en una marcha implacable desde la reescritura masiva de su núcleo. Si bien las versiones estables son la base de la producción, las versiones preliminares de desarrollo —específicamente el salto de Dev2 a las recién lanzadas Dev3 y Dev4— ofrecen una ventana transparente hacia dónde se dirigen las prioridades arquitectónicas del motor. Para los desarrolladores técnicos, estas actualizaciones no son solo notas de parches; son advertencias tempranas para adaptar sus flujos de trabajo de red, renderizado y gestión de memoria.

En este análisis profundo, desglosaremos las realidades técnicas de actualizar tu proyecto, cómo aprovechar GDScript para el multijugador con autoridad de servidor, y por qué la continua evolución del motor exige un enfoque más inteligente para la infraestructura backend.

Decodificando el ciclo de lanzamiento de Godot: Lo que realmente significan las versiones Dev

Migrar un juego en producción a una versión de desarrollo es un riesgo calculado. Una versión "Dev" en la nomenclatura de Godot significa que aún no se ha producido el congelamiento de características (feature-freeze). La API podría cambiar, los comportamientos de los nodos podrían alterarse y las regresiones no documentadas están prácticamente garantizadas.

Sin embargo, ignorar estas versiones significa ignorar la trayectoria del motor. La transición hacia Godot 4.7 está fuertemente enfocada en estabilizar las adiciones masivas introducidas de la 4.3 a la 4.6. Estamos viendo un claro giro hacia el perfilado de rendimiento, comportamientos de físicas deterministas y una sincronización multijugador optimizada.

Para un desarrollador en solitario o un equipo pequeño, el principal punto de dolor no suele ser escribir la lógica del juego: es descubrir por qué una escena que funciona a 144 FPS en una máquina local de repente cae a 45 FPS cuando se instancia a través de una red, o por qué las pausas del recolector de basura están causando micro-tirones durante secuencias de combate intensas. Las actualizaciones que surgen en estas versiones de desarrollo apuntan directamente a los cuellos de botella en el recorrido del árbol de nodos y los asignadores de memoria internos.

El verdadero costo de las actualizaciones del motor

Actualizar la versión de un motor a mitad del desarrollo suele costar a un equipo entre dos y tres semanas de tiempo dedicado a la refactorización. Se deprecian nodos, se redefinen las capas de físicas y cambian los flujos de trabajo de compilación de shaders.

Al evaluar las nuevas características de godot 4.7, debes medir las mejoras de rendimiento prometidas frente a esta deuda de refactorización. Si tu proyecto actual depende en gran medida de módulos C++ personalizados (GDExtension), debes asegurarte de que tus cadenas de compilación estén preparadas para los encabezados actualizados. Si trabajas completamente en GDScript, los riesgos son menores, pero aún necesitas probar rigurosamente tus enlaces RPC (Llamada a Procedimiento Remoto).

Abordando la pesadilla de la desincronización multijugador

El desarrollo de juegos multijugador es fundamentalmente un ejercicio de ocultar la latencia. Cuando un jugador presiona un botón para saltar, el cliente local debe predecir ese salto instantáneamente, mientras pide permiso al servidor de forma simultánea. Si el servidor no está de acuerdo —quizás porque el jugador fue aturdido por un oponente una fracción de segundo antes—, el cliente debe reconciliar forzosamente la posición del jugador, lo que resulta en un discordante efecto visual de "banda elástica".

Godot 4 introdujo los nodos MultiplayerSynchronizer y MultiplayerSpawner, que abstrajeron gran parte del código repetitivo necesario para la replicación de estados. Sin embargo, la sincronización predeterminada rara vez es suficiente para juegos competitivos de ritmo rápido. Necesitas un control granular sobre qué datos se envían, con qué frecuencia se envían y si requieren canales de transporte confiables o no confiables.

Implementando movimiento con autoridad de servidor

Un error clásico que cometen los desarrolladores independientes es confiar en el cliente. Si tu cliente dicta su propia posición al servidor, los jugadores malintencionados simplemente modificarán su cliente para teletransportarse por el mapa. El servidor debe ser la máxima autoridad.

Aquí tienes un enfoque práctico y listo para producción para implementar movimiento con autoridad de servidor con predicción del lado del cliente en GDScript. Este patrón asegura que el movimiento se sienta receptivo mientras previene los hacks básicos de velocidad.

extends CharacterBody3D

# Configuración multijugador
@export var player_id := 1

# Constantes de movimiento
const SPEED := 5.0
const JUMP_VELOCITY := 4.5

# Seguimiento de estado para reconciliación
var unacknowledged_inputs := []
var latest_server_state := {}

func _ready() -> void:
    # Establecer la autoridad multijugador al ID del jugador
    set_multiplayer_authority(player_id)
    
    # Si somos el servidor, procesamos las físicas normalmente
    # Si somos el cliente, solo predecimos y esperamos las correcciones del servidor
    if not is_multiplayer_authority() and not multiplayer.is_server():
        set_physics_process(false)

func _physics_process(delta: float) -> void:
    if is_multiplayer_authority():
        # Capturar entrada
        var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
        var input_state := {
            "tick": Engine.get_physics_frames(),
            "dir": input_dir,
            "jump": Input.is_action_just_pressed("ui_accept")
        }
        
        # Aplicar localmente para retroalimentación inmediata (Predicción)
        _apply_movement(input_state, delta)
        
        # Almacenar entrada para posible reconciliación posterior
        unacknowledged_inputs.append(input_state)
        
        # Enviar al servidor para validación
        rpc_id(1, "_receive_client_input", input_state)

# Un RPC no confiable (unreliable) es crucial aquí para prevenir la congestión de la red.
# Las entradas perdidas serán corregidas por las actualizaciones de estado autoritativas del servidor.
@rpc("any_peer", "call_remote", "unreliable")
func _receive_client_input(input_state: Dictionary) -> void:
    # SOLO DEL LADO DEL SERVIDOR
    if not multiplayer.is_server():
        return
        
    var sender_id = multiplayer.get_remote_sender_id()
    if sender_id != player_id:
        # Rechazar suplantación de entrada no autorizada
        push_warning("El jugador %s intentó suplantar la entrada del jugador %s" % [sender_id, player_id])
        return
        
    # Aplicar la entrada en el servidor
    _apply_movement(input_state, get_physics_process_delta_time())
    
    # Transmitir el estado validado a todos los clientes
    var new_state = {
        "tick": input_state.tick,
        "pos": global_position,
        "vel": velocity
    }
    rpc("_receive_server_state", new_state)

@rpc("authority", "call_remote", "unreliable")
func _receive_server_state(server_state: Dictionary) -> void:
    # SOLO DEL LADO DEL CLIENTE
    if is_multiplayer_authority() or multiplayer.is_server():
        return
        
    # Ajustar a la posición del servidor (Reconciliación)
    # En un juego real, interpolarías esto para ocultar el salto brusco
    global_position = server_state.pos
    velocity = server_state.vel
    
    # Eliminar entradas reconocidas
    unacknowledged_inputs = unacknowledged_inputs.filter(func(input): return input.tick > server_state.tick)

func _apply_movement(state: Dictionary, delta: float) -> void:
    # Lógica estándar del controlador de personajes de Godot aplicada a una carga útil de estado específica
    if not is_on_floor():
        velocity.y -= 9.8 * delta

    if state.jump and is_on_floor():
        velocity.y = JUMP_VELOCITY

    var direction := (transform.basis * Vector3(state.dir.x, 0, state.dir.y)).normalized()
    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)

    move_and_slide()

Este script aborda el punto de dolor fundamental del movimiento autoritativo. Al utilizar RPCs unreliable (no confiables) para flujos de datos constantes como la posición y la entrada, evitamos que la cola de red subyacente se sature y cause retrasos catastróficos. Las nuevas actualizaciones del motor continúan refinando cómo se gestionan estas colas RPC internas, haciendo que los servidores con alta tasa de ticks (tickrate) sean significativamente más viables.

Perfilado de rendimiento: Escapando del cuello de botella de GDScript

GDScript es un lenguaje increíblemente productivo, pero su naturaleza dinámica conlleva un límite de rendimiento. Cuando procesas cientos de entidades en un bucle _physics_process, la sobrecarga de los tipos variantes y las búsquedas dinámicas de métodos pueden reducir tu tasa de fotogramas a la mitad.

Uno de los destructores de rendimiento más insidiosos en Godot es la asignación de memoria en tiempo de ejecución. Instanciar un nuevo nodo o crear un nuevo diccionario complejo en cada fotograma activa el asignador de memoria del motor. Con el tiempo, esto conduce a la fragmentación y a picos en la recolección de basura, manifestándose como tirones notables durante el juego.

Agrupación de objetos (Object Pooling): Una arquitectura obligatoria

Para eludir estos asignadores, debes implementar la agrupación de objetos (Object Pooling). En lugar de llamar a queue_free() e instantiate() durante el juego, preasignas una matriz masiva de objetos durante las pantallas de carga y simplemente alternas su visibilidad y estados de procesamiento.

Considera un juego de disparos tipo "bullet hell". Si un jefe dispara 500 proyectiles por segundo, instanciar 500 nodos Area2D dinámicamente aplastará tu CPU.

Así es como se construye un pool de objetos robusto en GDScript:

extends Node
class_name BulletPool

@export var bullet_scene: PackedScene
@export var pool_size: int = 1000

var _available_bullets: Array[Node] = []
var _active_bullets: Array[Node] = []

func _ready() -> void:
    # Preasignar todos los objetos antes de que comience el juego
    for i in range(pool_size):
        var bullet = bullet_scene.instantiate()
        
        # Deshabilitar la bala por completo
        bullet.process_mode = Node.PROCESS_MODE_DISABLED
        bullet.visible = false
        
        # Añadir al árbol de escenas pero mantenerlo inactivo
        add_child(bullet)
        _available_bullets.append(bullet)

func spawn_bullet(spawn_position: Vector2, direction: Vector2) -> Node:
    if _available_bullets.is_empty():
        push_error("¡Pool de balas agotado! Aumenta el tamaño del pool.")
        return null
        
    var bullet = _available_bullets.pop_back()
    
    # Reinicializar el estado de la bala
    bullet.global_position = spawn_position
    if bullet.has_method("set_direction"):
        bullet.set_direction(direction)
        
    # Despertar la bala
    bullet.visible = true
    bullet.process_mode = Node.PROCESS_MODE_INHERIT
    
    _active_bullets.append(bullet)
    return bullet

func return_bullet(bullet: Node) -> void:
    if not bullet in _active_bullets:
        return
        
    # Volver a poner la bala a dormir
    bullet.process_mode = Node.PROCESS_MODE_DISABLED
    bullet.visible = false
    
    _active_bullets.erase(bullet)
    _available_bullets.append(bullet)

Al trasladar la carga computacional del volátil bucle de juego a la fase estática de carga, garantizas un perfil de memoria plano y predecible. Al perfilar tu juego en el editor de Godot, deberías ver que el uso de memoria se estabiliza en lugar de subir y bajar constantemente. Esta técnica por sí sola puede reducir la variación del tiempo de fotograma de ~15 ms a unos sólidos ~2 ms en juegos con muchos proyectiles.

Flujos de trabajo de renderizado y optimización de escenas

Si bien el rendimiento del backend y la lógica son críticos, el renderizado sigue siendo el cuello de botella más evidente visualmente. El renderizador Vulkan de Godot 4 es potente, pero requiere una optimización deliberada. Un error común es confiar en que el motor descartará mágicamente la geometría invisible. Aunque Godot tiene un excelente descarte de frustum (frustum culling), enviar datos de vértices sin procesar a la GPU todavía requiere preparación por parte de la CPU (llamadas de dibujo o draw calls).

Para mitigar esto, los desarrolladores deben utilizar agresivamente MultiMeshInstance3D para geometría repetida como hierba, árboles o sistemas de multitudes. Un MeshInstance3D estándar requiere una llamada de dibujo única para cada objeto. Si tienes un bosque con 5000 árboles, eso son 5000 llamadas de dibujo: suficiente para paralizar una GPU de gama media.

Convertir esos 5000 nodos separados en un solo MultiMeshInstance3D reduce las llamadas de dibujo de 5000 a exactamente 1. La GPU es increíblemente eficiente dibujando la misma malla miles de veces; es la instrucción de la CPU para hacerlo lo que causa el cuello de botella. A medida que Godot evoluciona a lo largo de su ciclo de vida 4.x, el flujo de trabajo para gestionar estos lotes se vuelve cada vez más optimizado, pero la responsabilidad arquitectónica sigue siendo del desarrollador.

El dilema de la infraestructura backend

Abordemos el elefante en la habitación. Has optimizado tus pools de objetos, has escrito un GDScript limpio y con autoridad de servidor, y tu juego multijugador funciona sin problemas cuando lo pruebas en localhost.

Ahora quieres lanzarlo.

De repente, ya no eres un desarrollador de juegos; eres un ingeniero DevOps. Necesitas aprovisionar servidores Linux. Necesitas escribir un sistema de emparejamiento (matchmaker) que agrupe a los jugadores por ping y habilidad. Necesitas un sistema automatizado para activar instancias de servidores dedicados dinámicamente según la demanda de los jugadores, y desactivarlas para ahorrar dinero cuando el número de jugadores disminuya. Necesitas bases de datos seguras para los inventarios de los jugadores y las tablas de clasificación, todo protegido detrás de certificados SSL y capas de mitigación de ataques DDOS.

Construir esto tú mismo requiere configurar clústeres de Kubernetes, balanceadores de carga, fragmentación de bases de datos (sharding) y administradores de sockets en tiempo real: fácilmente de 4 a 6 meses de trabajo de infraestructura agotador que no tiene absolutamente nada que ver con hacer que tu juego sea divertido.

Esta es exactamente la razón por la que existe el Backend como Servicio (BaaS). Con horizOn, estos complejos servicios backend vienen preconfigurados específicamente para desarrolladores de juegos. En lugar de escribir lógica de emparejamiento personalizada y aprovisionar instancias AWS EC2, integras un SDK y dejas que la plataforma maneje la orquestación del servidor, la autenticación de jugadores y la persistencia de datos. Te permite lanzar tu juego real en lugar de tu pila de infraestructura.

Al delegar la gestión del servidor a una plataforma construida para juegos, recuperas los cientos de horas necesarias para pulir tu bucle de juego y corregir errores.

5 mejores prácticas para migrar a las versiones Dev de Godot 4.7

Actualizar a una versión preliminar de desarrollo es inherentemente peligroso. Si estás decidido a probar las nuevas características de godot 4.7 en tu proyecto actual, debes seguir una estricta higiene de implementación para evitar corromper los archivos de tu proyecto.

  1. Ramificación obligatoria: Nunca abras la carpeta principal de tu proyecto en una versión Dev. Usa Git para crear una rama dedicada específicamente para probar la actualización. Si el proyecto se rompe, simplemente puedes eliminar la rama y volver a la seguridad.
  2. Establecer líneas base de perfilado: Antes de actualizar, ejecuta tu juego en Godot 4.3/4.6 y registra el promedio de FPS, llamadas de dibujo y uso de memoria en tu escena más pesada. Compara estas métricas exactas en la nueva versión. Si el rendimiento cae, has encontrado una regresión que puedes reportar a los mantenedores del motor.
  3. Auditar tus configuraciones RPC: El código de red suele ser lo primero en romperse durante las actualizaciones del motor. Audita cada anotación @rpc. Asegúrate de que tus indicadores de confiabilidad (reliable) y no confiabilidad (unreliable) sigan comportándose como se espera bajo latencia de red simulada.
  4. Compilar plantillas de exportación personalizadas: Si estás construyendo un servidor dedicado, no confíes en las plantillas de exportación estándar. Compila plantillas sin interfaz gráfica (headless) personalizadas desde el código fuente de Godot para eliminar los módulos de audio y renderizado, reduciendo drásticamente el consumo de RAM de tu servidor.
  5. Implementar pruebas automatizadas: Usa un framework como GUT (Godot Unit Test) para escribir pruebas automatizadas para tu lógica matemática y de estado. Cuando actualices el motor, ejecutar estas pruebas señalará inmediatamente si un cálculo interno del motor ha cambiado.

Mirando hacia el futuro: El camino hacia la versión estable

El motor Godot está impulsado enteramente por la comunidad, lo que significa que su velocidad de desarrollo está directamente ligada a los desarrolladores que prueban estas versiones preliminares y reportan problemas. Si bien Dev3 y Dev4 son peldaños, representan la vanguardia del desarrollo de juegos de código abierto. Dan a los directores técnicos y a los desarrolladores en solitario la previsión necesaria para planificar su arquitectura meses antes de que se lance la versión estable.

Al dominar la arquitectura con autoridad de servidor, agrupar agresivamente tus objetos y comprender el flujo de renderizado, garantizas que tu juego escalará independientemente de la versión del motor. Y cuando estés listo para llevar ese juego multijugador altamente optimizado a una audiencia global, asegúrate de que tu backend sea tan robusto como el código de tu cliente.

¿Listo para escalar tu juego multijugador sin ahogarte en la gestión de servidores? Prueba horizOn gratis y concéntrate en lo que mejor sabes hacer: crear juegos increíbles.


Fuente: Godot 4.7 Dev3 and Dev4 Released