<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ziva</title>
    <description>The latest articles on DEV Community by Ziva (@ziva).</description>
    <link>https://dev.to/ziva</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3845272%2F6667c5de-b09a-4a44-b3a4-19819c554824.png</url>
      <title>DEV Community: Ziva</title>
      <link>https://dev.to/ziva</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ziva"/>
    <language>en</language>
    <item>
      <title>Multijugador en Godot 4: Guía práctica con MultiplayerSpawner</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Fri, 08 May 2026 18:38:43 +0000</pubDate>
      <link>https://dev.to/ziva/multijugador-en-godot-4-guia-practica-con-multiplayerspawner-4jem</link>
      <guid>https://dev.to/ziva/multijugador-en-godot-4-guia-practica-con-multiplayerspawner-4jem</guid>
      <description>&lt;p&gt;El sistema multijugador de Godot 4 cambió mucho desde Godot 3. Si vienes de la versión anterior o de Unity, vas a ver nodos nuevos como &lt;code&gt;MultiplayerSpawner&lt;/code&gt; y &lt;code&gt;MultiplayerSynchronizer&lt;/code&gt; que hacen casi todo el trabajo pesado por ti. Lo bueno: bien usados, ahorran cientos de líneas de código. Lo malo: la documentación oficial los explica mal, y la mayoría de los tutoriales en YouTube están desactualizados.&lt;/p&gt;

&lt;p&gt;Esta guía cubre lo que sí funciona en Godot 4.4+ para multijugador peer-to-peer simple, los errores más comunes y dónde la IA generativa todavía falla cuando la pides escribir código de red.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué viene incluido en Godot 4
&lt;/h2&gt;

&lt;p&gt;Godot 4 trae un stack de red completo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ENetMultiplayerPeer&lt;/code&gt;&lt;/strong&gt;: capa de transporte UDP confiable. Funciona en LAN, WAN y a través de NAT (con port forwarding).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WebSocketMultiplayerPeer&lt;/code&gt;&lt;/strong&gt;: alternativa para builds web/HTML5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WebRTCMultiplayerPeer&lt;/code&gt;&lt;/strong&gt;: peer-to-peer real con NAT traversal automático, pero requiere un signaling server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MultiplayerSpawner&lt;/code&gt;&lt;/strong&gt;: replica nodos automáticamente cuando se spawnean en el host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MultiplayerSynchronizer&lt;/code&gt;&lt;/strong&gt;: replica propiedades específicas (posición, salud, etc.) en cada frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RPCs (&lt;code&gt;@rpc&lt;/code&gt;)&lt;/strong&gt;: llamadas remotas a funciones, con tres modos de autoridad.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para la mayoría de los juegos indie, la combinación es: ENet + Spawner + Synchronizer + algunos RPCs. El resto (lobbies, matchmaking, autenticación) lo escribes tú o lo delegas a un servicio externo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando una sala simple
&lt;/h2&gt;

&lt;p&gt;Vamos a hacer una sala donde el primer jugador es el host y los demás se conectan a él. El código mínimo es sorprendentemente corto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7777&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_PLAYERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;host_game&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ENetMultiplayerPeer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_PLAYERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;push_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"No se pudo crear el servidor: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;multiplayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiplayer_peer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Servidor escuchando en el puerto "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;join_game&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ENetMultiplayerPeer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;push_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"No se pudo conectar: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;multiplayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiplayer_peer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Conectando a "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_on_peer_connected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Jugador conectado: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_on_peer_disconnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Jugador desconectado: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_ready&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;multiplayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peer_connected&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_on_peer_connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;multiplayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peer_disconnected&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_on_peer_disconnected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo. El host llama a &lt;code&gt;host_game()&lt;/code&gt;, los clientes llaman a &lt;code&gt;join_game("192.168.1.42")&lt;/code&gt;. Las señales &lt;code&gt;peer_connected&lt;/code&gt; y &lt;code&gt;peer_disconnected&lt;/code&gt; te avisan cuando alguien entra o sale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replicando jugadores con MultiplayerSpawner
&lt;/h2&gt;

&lt;p&gt;El nodo &lt;code&gt;MultiplayerSpawner&lt;/code&gt; es la forma idiomática en Godot 4 de spawnear personajes. Reemplaza el viejo patrón de Godot 3 donde tenías que llamar &lt;code&gt;rpc("spawn_player", id)&lt;/code&gt; y replicar manualmente.&lt;/p&gt;

&lt;p&gt;Configuración:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;En tu escena de juego, añade un nodo &lt;code&gt;MultiplayerSpawner&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;En el inspector, asigna su propiedad &lt;code&gt;spawn_path&lt;/code&gt; a un Node2D o Node3D donde van los jugadores (algo como &lt;code&gt;$Players&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Añade tu escena de jugador (&lt;code&gt;player.tscn&lt;/code&gt;) a la lista &lt;code&gt;auto_spawn_list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cuando el host instancie un jugador como hijo del nodo &lt;code&gt;Players&lt;/code&gt;, el Spawner replica automáticamente esa instancia en todos los clientes.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="c1"&gt;# En el host, despues de que un peer se conecta:&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_on_peer_connected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;multiplayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_server&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scenes/player.tscn"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instantiate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# importante: el name debe ser el id del peer&lt;/span&gt;
    &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;Players&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El detalle clave es que el nombre del nodo (&lt;code&gt;player.name&lt;/code&gt;) debe ser el ID del peer. Esto le dice a Godot a quién pertenece ese nodo. Si no lo haces, los inputs del jugador 1 controlarán a todos los personajes a la vez.&lt;/p&gt;

&lt;h2&gt;
  
  
  MultiplayerSynchronizer para movimiento
&lt;/h2&gt;

&lt;p&gt;El Synchronizer replica propiedades específicas de un nodo. Para un personaje que se mueve, querrás replicar &lt;code&gt;position&lt;/code&gt; y quizás &lt;code&gt;rotation&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dentro de tu escena de jugador, añade un &lt;code&gt;MultiplayerSynchronizer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;En el inspector, abre el editor de Replication.&lt;/li&gt;
&lt;li&gt;Añade &lt;code&gt;position&lt;/code&gt; (replicado en cada frame) y &lt;code&gt;rotation&lt;/code&gt; si aplica.&lt;/li&gt;
&lt;li&gt;Configura &lt;code&gt;replication_interval&lt;/code&gt; a 0 para movimiento crítico, o a 0.05 (20 Hz) para reducir tráfico de red.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El Synchronizer hace lo siguiente: en cada tick, el peer que tiene autoridad sobre el nodo envía las propiedades replicadas a todos los demás peers, que las aplican localmente. La autoridad por defecto es el host (peer ID 1), pero puedes cambiarla:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="c1"&gt;# En _ready() del nodo player:&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_ready&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Cada jugador es dueno de su propio personaje&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;peer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_int&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;set_multiplayer_authority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con esto, cada jugador controla su personaje y el host no tiene que procesar los inputs de todos.&lt;/p&gt;

&lt;h2&gt;
  
  
  RPCs para acciones puntuales
&lt;/h2&gt;

&lt;p&gt;Para cosas que pasan ocasionalmente (disparos, daño, chat) usa RPCs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"any_peer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"call_local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"reliable"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;chat_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"["&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_remote_sender_id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"]: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Llamar desde cualquier peer:&lt;/span&gt;
&lt;span class="n"&gt;chat_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hola a todos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las tres anotaciones importantes son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"any_peer"&lt;/code&gt; o &lt;code&gt;"authority"&lt;/code&gt;: quién puede llamar la función. &lt;code&gt;authority&lt;/code&gt; es solo el dueño del nodo; &lt;code&gt;any_peer&lt;/code&gt; permite que cualquiera la llame.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"call_local"&lt;/code&gt; o &lt;code&gt;"call_remote"&lt;/code&gt;: si el llamante también ejecuta la función localmente. Para chat sí, para movimiento no.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"reliable"&lt;/code&gt; o &lt;code&gt;"unreliable"&lt;/code&gt;: TCP-style o UDP-style. Acciones críticas usan reliable; movimiento puro usa unreliable_ordered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Donde la IA generativa todavía falla
&lt;/h2&gt;

&lt;p&gt;He pedido código multiplayer a varios LLMs y los errores se repiten:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Mezcla de APIs Godot 3 y Godot 4.&lt;/strong&gt; Los modelos generales fueron entrenados con muchos tutoriales viejos, así que te darán código con &lt;code&gt;rpc("function_name")&lt;/code&gt; en lugar de &lt;code&gt;function_name.rpc()&lt;/code&gt;, o &lt;code&gt;set_network_master()&lt;/code&gt; en vez de &lt;code&gt;set_multiplayer_authority()&lt;/code&gt;. El primero compila pero no hace lo correcto; el segundo ya ni existe en Godot 4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. RPC sin anotaciones.&lt;/strong&gt; Olvidan poner &lt;code&gt;@rpc("any_peer")&lt;/code&gt; y luego no entienden por qué el cliente no puede llamar la función. Por defecto, los RPCs son &lt;code&gt;authority&lt;/code&gt;-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Authority mal configurada.&lt;/strong&gt; Los clientes intentan modificar nodos de los que no son autoridad, las modificaciones se sobrescriben en el siguiente sync, y el código parece "casi funcionar".&lt;/p&gt;

&lt;p&gt;Herramientas como &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; que conocen específicamente la API de Godot 4 evitan estos tres problemas porque consultan la base de clases del editor en tiempo real, no patrones aprendidos de tutoriales viejos. Para multiplayer en particular, donde el modo authority + el tipo de RPC + el reliable/unreliable importan mucho, esto es la diferencia entre "compila" y "funciona en producción".&lt;/p&gt;

&lt;h2&gt;
  
  
  Errores comunes y cómo depurarlos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;El cliente conecta pero no se ve el otro jugador.&lt;/strong&gt; Casi siempre es porque el &lt;code&gt;MultiplayerSpawner&lt;/code&gt; no tiene la escena en su &lt;code&gt;auto_spawn_list&lt;/code&gt;, o porque el nombre del nodo no coincide con el peer ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Los inputs del jugador 1 controlan a todos.&lt;/strong&gt; Te falta &lt;code&gt;set_multiplayer_authority()&lt;/code&gt; en &lt;code&gt;_ready()&lt;/code&gt; del personaje.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cliente y host se desincronizan.&lt;/strong&gt; Probablemente estás modificando una propiedad replicada en ambos lados a la vez. Solo la autoridad debe modificar las propiedades sincronizadas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lag visible aún en LAN.&lt;/strong&gt; Sube &lt;code&gt;replication_interval&lt;/code&gt; a 0 (cada frame) o usa interpolación del lado del cliente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo usar otra cosa
&lt;/h2&gt;

&lt;p&gt;ENet con MultiplayerSpawner funciona para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Co-op de hasta ~8 jugadores&lt;/li&gt;
&lt;li&gt;Juegos PvP simples sin matchmaking complejo&lt;/li&gt;
&lt;li&gt;Juegos donde el host puede ser un jugador (sin servidor dedicado)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No es la opción correcta para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MMOs (necesitas servidores autoritarios escalables)&lt;/li&gt;
&lt;li&gt;Juegos competitivos con anti-cheat (necesitas servidor dedicado, no peer authority)&lt;/li&gt;
&lt;li&gt;Browser builds (usa WebRTC + signaling server)&lt;/li&gt;
&lt;li&gt;Cross-platform mobile (PlayFab, Photon o Nakama suelen ser mejores)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recursos para profundizar
&lt;/h2&gt;

&lt;p&gt;La &lt;a href="https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html" rel="noopener noreferrer"&gt;documentación oficial de Godot Multiplayer&lt;/a&gt; cubre todo lo anterior con más profundidad, aunque tiende a saltar entre Godot 3 y Godot 4 sin marcarlo. Lee dos veces antes de implementar.&lt;/p&gt;

&lt;p&gt;Si estás empezando con multiplayer en Godot, este código alcanza para un proyecto pequeño funcional en una tarde. Las complicaciones reales (matchmaking, anti-cheat, persistencia) vienen después y se resuelven con servicios externos, no con más código de Godot.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>spanish</category>
      <category>espanol</category>
    </item>
    <item>
      <title>Godot 4 für Web-Spiele: Export, WASM und Browser-Performance</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Fri, 08 May 2026 18:33:55 +0000</pubDate>
      <link>https://dev.to/ziva/godot-4-fur-web-spiele-export-wasm-und-browser-performance-4315</link>
      <guid>https://dev.to/ziva/godot-4-fur-web-spiele-export-wasm-und-browser-performance-4315</guid>
      <description>&lt;p&gt;Godot 4 kann seit Version 4.3 wieder zuverlässig in den Browser exportieren. Wer 2023 mit dem Web-Export gekämpft hat, sollte sich die aktuelle Version anschauen. Das Threading-Modell wurde überarbeitet, die Build-Größe ist gesunken, und auf modernen Browsern läuft die Performance brauchbar.&lt;/p&gt;

&lt;p&gt;Dieser Artikel ist eine praktische Übersicht: was funktioniert, was nicht, und wie du den Web-Export sauber einrichtest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status quo: was geht in Godot 4.4+ wirklich
&lt;/h2&gt;

&lt;p&gt;Der Web-Export von Godot kompiliert zu WebAssembly (WASM) und nutzt WebGL 2 als Renderer. In der aktuellen Stable (4.4) und Beta (4.7) sind die folgenden Punkte gelöst, die bis vor zwei Jahren noch Probleme gemacht haben:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Threading.&lt;/strong&gt; Godot nutzt jetzt SharedArrayBuffer mit den korrekten COOP/COEP-Headern. Das ist Pflicht, ohne entsprechend konfigurierten Server bricht der Export beim Start ab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-Größe.&lt;/strong&gt; Ein leeres Godot-4.4-Projekt liefert ungefähr 35 MB komprimierter WASM-Code aus. Mit Asset-Pack-Stripping kannst du das auf etwa 25 MB reduzieren. Vergleiche das mit Unity WebGL bei rund 12-15 MB für ein Mini-Projekt: Godot ist größer, aber nicht mehr inakzeptabel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio.&lt;/strong&gt; Web Audio funktioniert. Latenz liegt bei ~80-150 ms, was für die meisten Casual Games reicht. Für rhythmische Spiele bleibt es ein Kompromiss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Was 2026 immer noch nicht klappt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3D-Spiele mit komplexen Shadern. WebGL 2 ist nicht WebGPU. Wenn dein Spiel Compute Shader oder Forward+ Rendering nutzt, vergiss den Browser.&lt;/li&gt;
&lt;li&gt;Saves über Local Storage hinaus. Browser-Quotas sind unzuverlässig. Plan ein, dass Spielerdaten verloren gehen können.&lt;/li&gt;
&lt;li&gt;Mobile Browser auf iOS. Safari hat lange WASM-Probleme gehabt. Aktuell läuft es, aber Bug-Reports kommen häufiger als auf Desktop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Die Server-Konfiguration: hier scheitern die meisten
&lt;/h2&gt;

&lt;p&gt;Das größte Problem beim Web-Export ist nicht der Build, sondern das Hosting. Godot benötigt zwei spezifische HTTP-Header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ohne diese Header lädt der Browser keinen SharedArrayBuffer, und Godot bricht mit einem Threading-Fehler ab. Auf itch.io sind die Header standardmäßig gesetzt; auf einem eigenen Server musst du sie konfigurieren.&lt;/p&gt;

&lt;p&gt;Für Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/spiel/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cross-Origin-Embedder-Policy&lt;/span&gt; &lt;span class="s"&gt;require-corp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cross-Origin-Opener-Policy&lt;/span&gt; &lt;span class="s"&gt;same-origin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Für Apache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;Location&lt;/span&gt;&lt;span class="sr"&gt; "/spiel/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Cross-Origin-Embedder-Policy "require-corp"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Cross-Origin-Opener-Policy "same-origin"
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub Pages, Vercel und Netlify unterstützen diese Header über Konfigurationsdateien (&lt;code&gt;_headers&lt;/code&gt;-Datei bzw. &lt;code&gt;vercel.json&lt;/code&gt;). Cloudflare Pages braucht ein zusätzliches Worker-Skript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Den Web-Export einrichten
&lt;/h2&gt;

&lt;p&gt;Schritt 1: Im Godot-Editor unter &lt;code&gt;Project &amp;gt; Export&lt;/code&gt; den Web-Export hinzufügen. Falls die Export-Templates fehlen, lade sie über &lt;code&gt;Editor &amp;gt; Manage Export Templates&lt;/code&gt; herunter.&lt;/p&gt;

&lt;p&gt;Schritt 2: Im Export-Dialog die folgenden Optionen prüfen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Variant:&lt;/strong&gt; Threads (empfohlen). Singlethreaded läuft auch ohne COOP/COEP-Header, ist aber deutlich langsamer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VRAM Texture Compression:&lt;/strong&gt; ETC2 für mobile Browser, BPTC/S3TC für Desktop. Beide aktivieren, wenn du beides bedienst.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom HTML Shell:&lt;/strong&gt; leer lassen, falls du keine eigene Shell hast. Die Default-Shell macht alles, was du brauchst.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Schritt 3: &lt;code&gt;Export Project&lt;/code&gt; und einen Ordner wählen. Godot erzeugt fünf Dateien: &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;index.js&lt;/code&gt;, &lt;code&gt;index.wasm&lt;/code&gt;, &lt;code&gt;index.pck&lt;/code&gt; (das Asset-Pack) und &lt;code&gt;index.audio.worklet.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Schritt 4: Lokal testen mit einem CORS-fähigen Server. Der Python-Einzeiler reicht nicht, weil die Header fehlen. Stattdessen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;http.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimpleHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CrossOriginIsolatedHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cross-Origin-Embedder-Policy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;require-corp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cross-Origin-Opener-Policy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;same-origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;CrossOriginIsolatedHandler&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Speichern als &lt;code&gt;serve.py&lt;/code&gt;, im Export-Ordner ausführen, dann &lt;code&gt;http://localhost:8000&lt;/code&gt; im Browser öffnen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance-Tipps für den Browser
&lt;/h2&gt;

&lt;p&gt;Der Browser ist nicht der Desktop. Was auf dem Editor mit 60 FPS läuft, kann im Browser auf 25 FPS einbrechen, wenn du nicht aufpasst.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Texturen reduzieren.&lt;/strong&gt; WebGL 2 hat begrenzten VRAM auf mobilen Geräten. 4K-Texturen sind im Browser selten sinnvoll. Skaliere Assets auf 1024x1024 oder kleiner, wo möglich.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Polygon-Anzahl beobachten.&lt;/strong&gt; Browser-WebGL ist langsamer als nativer OpenGL-Treiber. Eine 100.000-Polygon-Szene, die nativ flüssig läuft, ruckelt im Browser. Halte die sichtbare Polygon-Zahl unter 50.000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Audio-Streams limitieren.&lt;/strong&gt; Jeder gleichzeitig spielende AudioStreamPlayer kostet im Browser mehr als auf Desktop. Maximal 8-10 simultane Streams für Casual Games, weniger für Mobile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;_process&lt;/code&gt; minimieren.&lt;/strong&gt; Jede Frame-Logik in &lt;code&gt;_process&lt;/code&gt; kostet im Browser mehr. Nutze Signale für ereignisgesteuerte Logik statt jeden Frame zu pollen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Pre-loading testen.&lt;/strong&gt; Der Browser lädt das &lt;code&gt;.pck&lt;/code&gt;-File komplett, bevor das Spiel startet. Bei 100 MB Assets sind das 30-60 Sekunden Wartezeit auf langsamen Verbindungen. Asset-Splitting via &lt;a href="https://docs.godotengine.org/en/stable/tutorials/export/exporting_pcks.html" rel="noopener noreferrer"&gt;PCK Embedding&lt;/a&gt; kann helfen, ist aber mit Aufwand verbunden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting-Optionen im Vergleich
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plattform&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Bandbreite&lt;/th&gt;
&lt;th&gt;Custom Domain&lt;/th&gt;
&lt;th&gt;Preis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;itch.io&lt;/td&gt;
&lt;td&gt;1 Klick&lt;/td&gt;
&lt;td&gt;unbegrenzt&lt;/td&gt;
&lt;td&gt;nein&lt;/td&gt;
&lt;td&gt;gratis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Pages&lt;/td&gt;
&lt;td&gt;mittel&lt;/td&gt;
&lt;td&gt;100 GB/Monat&lt;/td&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;gratis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;mittel&lt;/td&gt;
&lt;td&gt;100 GB/Monat&lt;/td&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;gratis Tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Pages&lt;/td&gt;
&lt;td&gt;hoch (Worker nötig)&lt;/td&gt;
&lt;td&gt;unbegrenzt&lt;/td&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;gratis Tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netlify&lt;/td&gt;
&lt;td&gt;mittel&lt;/td&gt;
&lt;td&gt;100 GB/Monat&lt;/td&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;gratis Tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eigener Server&lt;/td&gt;
&lt;td&gt;hoch&lt;/td&gt;
&lt;td&gt;je nach Provider&lt;/td&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;variabel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Für Game Jams und Demos: itch.io. Für eine eigene Marke mit Custom Domain und Analytics: Vercel oder Netlify. Für Produktion mit hohem Traffic: Cloudflare Pages, weil die Bandbreite nicht limitiert ist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fallstricke, die ich gesehen habe
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;iOS Safari Audio.&lt;/strong&gt; iOS verlangt eine Nutzeraktion (Klick), bevor Audio abspielt. Wenn dein Spiel Audio im &lt;code&gt;_ready()&lt;/code&gt; startet, hörst du auf iOS nichts. Workaround: einen "Start"-Button anzeigen, Audio erst nach dem Klick initialisieren.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gamepad-Inputs.&lt;/strong&gt; Der Browser-Gamepad-API ist standardisiert, aber Godot mappt nicht alle Buttons konsistent. Teste mit echten Controllern, nicht nur Tastatur-Emulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Leaks.&lt;/strong&gt; Lange Web-Sessions können Speicher leaken, weil die WASM-Heap nicht so aggressiv aufgeräumt wird wie der native Heap. Für Spiele unter 30 Minuten Spielzeit kein Problem; für Endless-Games solltest du den RAM-Verbrauch im DevTools-Performance-Tab überwachen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebGL-Kontext-Verlust.&lt;/strong&gt; Bei Tab-Wechsel oder Mobile-Hintergrund kann der WebGL-Kontext verloren gehen. Godot fängt das in den meisten Fällen ab, aber Texturen müssen neu geladen werden. Plan ein paar Sekunden Reload-Pause ein.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wann Web-Export sinnvoll ist und wann nicht
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sinnvoll:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2D-Casual-Games für Game Jams&lt;/li&gt;
&lt;li&gt;Demos und Trailer-Versionen, die direkt im Browser laufen&lt;/li&gt;
&lt;li&gt;Kleine Puzzle- und Strategiespiele&lt;/li&gt;
&lt;li&gt;Educational Games für den Schulkontext&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nicht sinnvoll:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3D-AAA-artige Projekte&lt;/li&gt;
&lt;li&gt;Spiele mit echten Multiplayer-Anforderungen (WebSockets gehen, aber Latenz ist höher)&lt;/li&gt;
&lt;li&gt;Große Open-World-Spiele&lt;/li&gt;
&lt;li&gt;Alles mit Compute-Shader-Bedarf&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fazit
&lt;/h2&gt;

&lt;p&gt;Der Web-Export von Godot 4 ist 2026 brauchbar geworden, wenn du die Einschränkungen kennst. Die Server-Konfiguration ist die häufigste Fehlerquelle. Wenn du einmal saubere COOP/COEP-Header eingerichtet hast, wirst du nicht mehr daran scheitern.&lt;/p&gt;

&lt;p&gt;Für Game Jams und Demos lohnt es sich. Für Vollpreis-Releases im Browser bist du wahrscheinlich besser bei Steam oder einem nativen Mobile-Build aufgehoben.&lt;/p&gt;

&lt;p&gt;Wer mehr zur &lt;a href="https://docs.godotengine.org/de/" rel="noopener noreferrer"&gt;offiziellen Godot-Dokumentation auf Deutsch&lt;/a&gt; sucht, findet dort die meisten Web-Export-Themen ausführlich behandelt.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>german</category>
      <category>deutsch</category>
    </item>
    <item>
      <title>Static Typing in GDScript: The 30 Minutes That Saved Me 30 Hours</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Fri, 08 May 2026 18:11:27 +0000</pubDate>
      <link>https://dev.to/ziva/static-typing-in-gdscript-the-30-minutes-that-saved-me-30-hours-1m8k</link>
      <guid>https://dev.to/ziva/static-typing-in-gdscript-the-30-minutes-that-saved-me-30-hours-1m8k</guid>
      <description>&lt;p&gt;Last month I spent an evening adding type hints to a 4,000-line GDScript codebase that had been running fine for a year. I expected nothing. By the time I finished, the editor had flagged 12 latent bugs I had never noticed: wrong return types, methods called with stale signatures, a &lt;code&gt;Vector2&lt;/code&gt; being passed where the function expected &lt;code&gt;Vector3&lt;/code&gt;. Every single one of those would have eventually crashed in production.&lt;/p&gt;

&lt;p&gt;Three of them already had: I just blamed them on something else when the bug reports came in.&lt;/p&gt;

&lt;p&gt;Static typing in GDScript is one of those features that sounds boring on the docs page and turns out to be the biggest quality-of-life upgrade you can make to a Godot 4 project. It is faster too. Independent benchmarks put the gain at &lt;a href="https://www.beep.blog/2024-02-14-gdscript-typing/" rel="noopener noreferrer"&gt;28-59% on hot paths&lt;/a&gt;, driven by the engine bypassing Variant dispatch when types are known at compile time.&lt;/p&gt;

&lt;p&gt;This post is the case for using type hints everywhere, including the gotchas I hit, and how to retrofit a typed style into a codebase that does not have it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What static typing actually catches
&lt;/h2&gt;

&lt;p&gt;Static typing in GDScript is opt-in per declaration. You can mix typed and untyped code freely in the same file. The type system catches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong type to a function&lt;/strong&gt;: &lt;code&gt;move_to(player.position_string)&lt;/code&gt; when &lt;code&gt;position_string&lt;/code&gt; does not exist on &lt;code&gt;player&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong return type&lt;/strong&gt;: a function declared &lt;code&gt;-&amp;gt; int&lt;/code&gt; that returns a &lt;code&gt;String&lt;/code&gt; somewhere down a branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misspelled property names&lt;/strong&gt;: &lt;code&gt;player.helath&lt;/code&gt; instead of &lt;code&gt;player.health&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale method signatures&lt;/strong&gt;: you renamed &lt;code&gt;attack(target)&lt;/code&gt; to &lt;code&gt;attack(target, damage)&lt;/code&gt; six months ago and the editor finds the one call site you missed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Null misuses&lt;/strong&gt;: assigning a &lt;code&gt;null&lt;/code&gt; to a non-nullable typed variable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first three I catch every time I run the project. The last two I never catch in dynamic GDScript because they only blow up under specific game state.&lt;/p&gt;

&lt;p&gt;I added types using the &lt;a href="https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/static_typing.html" rel="noopener noreferrer"&gt;official syntax&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (untyped, "I'll figure it out at runtime")&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;enemies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hp&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# After (typed)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;enemies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Enemy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;hp&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;Array[Enemy]&lt;/code&gt;. Typed arrays are a Godot 4 feature and they catch passing the wrong kind of array into a function that expects &lt;code&gt;Array[Player]&lt;/code&gt;. This single check found four bugs in my project where I had been silently mixing entity types.&lt;/p&gt;

&lt;h2&gt;
  
  
  The performance angle
&lt;/h2&gt;

&lt;p&gt;I would argue for typing on bug-catching grounds alone. The performance angle is a free bonus that turns out to be substantial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.beep.blog/2024-02-14-gdscript-typing/" rel="noopener noreferrer"&gt;The beep.blog benchmark&lt;/a&gt; ran 1 billion iterations across common GDScript operations on an M2 Max:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Untyped (ns)&lt;/th&gt;
&lt;th&gt;Typed (ns)&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integer addition&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;1.57x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector2 add&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;2.07x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method call&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;1.42x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engine API call&lt;/td&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;1.43x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The speedup comes from typed GDScript generating optimized opcodes that skip Variant unwrapping. In dynamic GDScript, every operation has to: check the type tag, dispatch to the right operator, perform the operation, wrap the result back in a Variant. Typed code knows the types ahead of time and just does the operation.&lt;/p&gt;

&lt;p&gt;The biggest wins are in vector math and engine API calls, which is exactly what game code does in &lt;code&gt;_process&lt;/code&gt; and &lt;code&gt;_physics_process&lt;/code&gt;. If your game has any per-frame work, typing the hot loops is a free 30%+ speedup with no algorithm changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch with AI-generated code
&lt;/h2&gt;

&lt;p&gt;I run a lot of code through LLMs while working in Godot. The pattern I have noticed: most cloud LLMs default to dynamic GDScript even when the surrounding file is typed. They generate this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;get_nearest_enemy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;closest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;closest_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;999999&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;enemies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;closest_dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;closest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
            &lt;span class="n"&gt;closest_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;closest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the rest of the file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;get_nearest_enemy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Enemy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Enemy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;closest_dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;INF&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Enemy&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;enemies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;closest_dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;closest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
            &lt;span class="n"&gt;closest_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;closest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dynamic version compiles. It even runs. But it skips every benefit you set up the typed style for: no IDE autocomplete on the return value, no type-mismatch checks, no opcode optimization. Worse, the next person editing the file has to mentally track which variables are which type.&lt;/p&gt;

&lt;p&gt;This is one reason game-dev-specific tools beat generic chat assistants. Tools like &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; (an AI agent built into the Godot editor) read your existing code style before generating new code, so a typed file gets typed completions. Generic assistants train on whatever GDScript samples were on the open web at scrape time, which skews dynamic.&lt;/p&gt;

&lt;p&gt;If you are using a generic assistant, the workaround is to put the typed signature in the prompt: "write &lt;code&gt;get_nearest_enemy(pos: Vector2) -&amp;gt; Enemy:&lt;/code&gt; that does X". The constraint is enough to flip the generation style.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to enforce typing in your project
&lt;/h2&gt;

&lt;p&gt;Godot has &lt;a href="https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/warning_system.html" rel="noopener noreferrer"&gt;a project setting for warnings on untyped declarations&lt;/a&gt;. Turn the relevant ones on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Project Settings -&amp;gt; Debug -&amp;gt; GDScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Untyped&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Declaration"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Warn&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Inferred&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Declaration"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Warn&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unsafe&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Method&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Access"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Error&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Discarded"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Warn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set "Treat Warnings as Errors" if you want hard enforcement. I treat untyped declarations as warnings only because mixing typed and untyped is occasionally pragmatic. You might want a generic helper that takes any value. But unsafe method access on untyped variables (the kind that crashes at runtime) is always an error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://allenwp.com/blog/2023/10/03/how-to-enforce-static-typing-in-gdscript/" rel="noopener noreferrer"&gt;Allen Pestaluky has a deeper guide&lt;/a&gt; on each warning and what to set it to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed in my workflow
&lt;/h2&gt;

&lt;p&gt;After the typing audit I made three habit changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every new function declaration starts with a return type. Even &lt;code&gt;-&amp;gt; void&lt;/code&gt;. The editor refuses to autocomplete properly without it.&lt;/li&gt;
&lt;li&gt;Every &lt;code&gt;var&lt;/code&gt; either gets a type annotation or uses &lt;code&gt;:=&lt;/code&gt; for inference. Bare &lt;code&gt;var x = ...&lt;/code&gt; is now a code smell I look for in PRs.&lt;/li&gt;
&lt;li&gt;Class members at the top of a script always have explicit types. Inference inside functions is fine; inference on class state means the next reader has to scroll to the assignment to figure out what the type is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cost of these habits is roughly one extra word per declaration. The benefit is a compiler that finds bugs before I do.&lt;/p&gt;

&lt;p&gt;If you maintain a Godot 4 project that has not had a typing pass, give it one evening. The bugs you find will pay for the time. The performance will be a nice surprise.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>gdscript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Profile GDScript Performance in Godot 4: A 2026 Guide</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Thu, 07 May 2026 14:52:28 +0000</pubDate>
      <link>https://dev.to/ziva/how-to-profile-gdscript-performance-in-godot-4-a-2026-guide-16jn</link>
      <guid>https://dev.to/ziva/how-to-profile-gdscript-performance-in-godot-4-a-2026-guide-16jn</guid>
      <description>&lt;p&gt;Profiling GDScript is one of those things every Godot developer eventually learns the hard way. The first time you ship a build that drops to 22 fps on a mid-tier laptop, you discover the profiler exists, you open it, and the numbers stare back at you with no obvious next step.&lt;/p&gt;

&lt;p&gt;This is the guide I wish I had when I started. It covers what changed in &lt;a href="https://www.gdquest.com/library/godot_4_6_workflow_changes/" rel="noopener noreferrer"&gt;Godot 4.6's unified docking&lt;/a&gt;, how to use the built-in profiler properly, when to reach for external tracing profilers like Tracy, and the four common GDScript bottlenecks that cause most of the frame-time damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I got wrong before I read the docs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Profiling in the editor.&lt;/strong&gt; The Godot editor adds overhead to every frame, so the numbers you see when you press F6 inside the editor are not the numbers your players see. &lt;a href="https://docs.godotengine.org/en/stable/tutorials/scripting/debug/the_profiler.html" rel="noopener noreferrer"&gt;Godot's own docs are explicit about this: "for accurate performance numbers, profile an exported build"&lt;/a&gt;. Half the optimizations I "made" in 2024 were undoing imaginary problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating frame time as one number.&lt;/strong&gt; Frame time has at least three layers: CPU work in your scripts, CPU work in the engine, and GPU work in the renderer. The Monitor tab gives you a single FPS number that mashes all three together. The Profiler tab is what splits it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not enabling typed code.&lt;/strong&gt; The single largest source of GDScript performance complaints I see on the forums is untyped code. From the &lt;a href="https://github.com/godotengine/godot-docs/blob/master/tutorials/performance/cpu_optimization.rst" rel="noopener noreferrer"&gt;Godot performance docs on CPU optimization&lt;/a&gt;: "Untyped variables require the runtime to determine the type and dispatch the correct operation on every operation, while typed variables skip this resolution." Same for arrays. If your hot loops use &lt;code&gt;var arr = []&lt;/code&gt; instead of &lt;code&gt;var arr: Array[int] = []&lt;/code&gt;, that is your low-hanging fruit before you ever touch the profiler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Open the right panel
&lt;/h2&gt;

&lt;p&gt;The profiling tools live in the Debugger panel at the bottom of the editor. With Godot 4.6, &lt;a href="https://godotengine.org/releases/4.6/" rel="noopener noreferrer"&gt;the Debugger is now a regular dock that can be moved or floated&lt;/a&gt;, which is useful if you have a vertical monitor and want the profiler in a side column.&lt;/p&gt;

&lt;p&gt;Three tabs matter for performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Profiler.&lt;/strong&gt; Per-script function call timings, sorted by self time or total time. This is where you find the slow function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitors.&lt;/strong&gt; Live graphs of FPS, memory, draw calls, physics steps, and more. Good for catching trends over a play session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Profiler.&lt;/strong&gt; Per-frame breakdown of the renderer cost. &lt;a href="https://www.linuxcompatible.org/story/godot-47-dev-4-released/" rel="noopener noreferrer"&gt;Godot 4.7 dev 4 added folding support to the Visual Profiler tree&lt;/a&gt;, which makes it usable on complex frames.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Profiler does not record by default. You have to click "Start" before you reproduce the slow scenario, and "Stop" when you are done. &lt;a href="https://docs.godotengine.org/en/stable/tutorials/scripting/debug/the_profiler.html" rel="noopener noreferrer"&gt;The docs note that profiling is performance-intensive because it instruments every frame&lt;/a&gt;, so leaving it on the whole session will distort the readings of the very thing you are measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Find the slow function
&lt;/h2&gt;

&lt;p&gt;Sort the Profiler results by &lt;strong&gt;Self Time&lt;/strong&gt; first, not Total Time. Self Time is what the function itself does, excluding everything it calls. Total Time can be misleading: a function that calls a slow library will show high Total Time but the fix is downstream.&lt;/p&gt;

&lt;p&gt;Common patterns I see in real Godot 4 projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;_process&lt;/code&gt; callback that runs &lt;code&gt;get_tree().get_nodes_in_group(...)&lt;/code&gt; every frame.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;_physics_process&lt;/code&gt; doing string concatenation for debug output that ships in release builds.&lt;/li&gt;
&lt;li&gt;A loop that calls &lt;code&gt;Vector2.distance_to&lt;/code&gt; instead of &lt;code&gt;distance_squared_to&lt;/code&gt; for a comparison check.&lt;/li&gt;
&lt;li&gt;A signal connected in &lt;code&gt;_ready&lt;/code&gt; that fires multiple times per frame because it was also connected in &lt;code&gt;_enter_tree&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The profiler tells you which function. Reading the function tells you which of these patterns it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Use custom monitors for things the built-in graphs miss
&lt;/h2&gt;

&lt;p&gt;The built-in Monitors tab has roughly 30 metrics. None of them know what your game does. If you want to track "active enemies on screen" or "outstanding network requests" or "items in the loot pool," you have to add a custom monitor.&lt;/p&gt;

&lt;p&gt;The API is straightforward. From &lt;a href="https://docs.godotengine.org/en/stable/tutorials/scripting/debug/custom_performance_monitors.html" rel="noopener noreferrer"&gt;the custom performance monitors docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_ready&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;Performance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_custom_monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"game/active_enemies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_count_enemies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Performance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_custom_monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"game/loot_drops_per_minute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_loot_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_count_enemies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_tree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_nodes_in_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"enemies"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_loot_rate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;loot_log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time_played_minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metric appears under your category in the Monitors tab next session. Custom monitors are the cheapest way to validate that what you think your game is doing matches what it actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Reach for external tracers when the built-in profiler isn't enough
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://godotengine.org/releases/4.6/" rel="noopener noreferrer"&gt;Godot 4.6 added support for tracing profilers like Tracy, Perfetto, and Apple Instruments&lt;/a&gt;. These give you per-frame, per-thread, microsecond-level visibility into the engine internals, useful when the bottleneck is in the renderer or physics step rather than your script.&lt;/p&gt;

&lt;p&gt;When to reach for external profilers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Try first&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Slow function in your code&lt;/td&gt;
&lt;td&gt;Built-in Profiler tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FPS drops on specific scenes&lt;/td&gt;
&lt;td&gt;Visual Profiler tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stutters that don't show up in script timings&lt;/td&gt;
&lt;td&gt;Tracy or Perfetto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac-specific performance problem&lt;/td&gt;
&lt;td&gt;Apple Instruments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frame pacing or VRR issues&lt;/td&gt;
&lt;td&gt;RenderDoc or PIX (per platform)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tracy is the one most commonly reported as worth the setup cost for indie projects. The build needs to be compiled with the Tracy hooks enabled, which is heavier than the built-in tools, but the resolution is in a different league.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Fix the four GDScript patterns that account for most of the damage
&lt;/h2&gt;

&lt;p&gt;After you have a profiler trace and you know which function is slow, the fix is usually one of four things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Untyped variables and arrays.&lt;/strong&gt; Add types. &lt;code&gt;var hp: int = 100&lt;/code&gt; is faster than &lt;code&gt;var hp = 100&lt;/code&gt;. &lt;code&gt;var enemies: Array[Enemy] = []&lt;/code&gt; is faster than &lt;code&gt;var enemies = []&lt;/code&gt;. The cost is one annotation per declaration. The benefit is the runtime skipping the type-resolve step on every access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;get_node&lt;/code&gt;, &lt;code&gt;get_tree&lt;/code&gt;, &lt;code&gt;get_nodes_in_group&lt;/code&gt; in hot loops.&lt;/strong&gt; These walk the scene tree on every call. Cache the result in &lt;code&gt;_ready&lt;/code&gt; and store it as &lt;code&gt;@onready var hud: HUD = $UI/HUD&lt;/code&gt;. Same for &lt;code&gt;get_tree().get_root()&lt;/code&gt; and group lookups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;String operations in &lt;code&gt;_process&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;str(x) + " " + str(y)&lt;/code&gt; allocates two strings per frame. Use &lt;code&gt;"%d %d" % [x, y]&lt;/code&gt; if you need formatting, or move the string work behind an &lt;code&gt;if Engine.is_editor_hint()&lt;/code&gt; so it only runs in debug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal duplication.&lt;/strong&gt; &lt;code&gt;connect()&lt;/code&gt; does not deduplicate. If &lt;code&gt;_ready&lt;/code&gt; and &lt;code&gt;_enter_tree&lt;/code&gt; both connect to the same signal, the slot fires twice per emit. The fix is to check &lt;code&gt;is_connected()&lt;/code&gt; first, or only connect in one lifecycle hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Read the profile, not the code, for confirmation
&lt;/h2&gt;

&lt;p&gt;The discipline that took me longest to internalize: do not optimize from intuition. Profile, change one thing, profile again, confirm the number moved in the direction you expected. Half the "optimizations" that look obvious in code do nothing for frame time, and a few make it worse.&lt;/p&gt;

&lt;p&gt;If you have an AI assistant in your editor, this is one of the few places where it can pull weight without making things worse. Ask it to read the profiler dump and the slow function side by side and propose a hypothesis. &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Tools like Ziva&lt;/a&gt; that run inside Godot can read the actual scene tree and the actual project structure, which matters here because most GDScript performance problems are about how a script interacts with the rest of the project, not about the script in isolation. Generic chat tools that only see the script text miss the cross-cutting issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to skip
&lt;/h2&gt;

&lt;p&gt;A few things are not worth your time at the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Premature &lt;code&gt;static&lt;/code&gt; typing of every single variable in your codebase. Type the hot loops first.&lt;/li&gt;
&lt;li&gt;Refactoring &lt;code&gt;Vector2&lt;/code&gt; math to use a custom struct. The engine version is fine; your bottleneck is elsewhere.&lt;/li&gt;
&lt;li&gt;Replacing GDScript with C# for "performance." &lt;a href="https://docs.godotengine.org/en/stable/tutorials/performance/index.html" rel="noopener noreferrer"&gt;GDScript and C# benchmarks are competitive in Godot 4 for most game logic&lt;/a&gt;. C# helps when you have CPU-bound math kernels. It does not help when your problem is &lt;code&gt;get_nodes_in_group&lt;/code&gt; in &lt;code&gt;_process&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two-line summary: profile exported builds, type your hot loops, cache scene tree lookups in &lt;code&gt;_ready&lt;/code&gt;, and use external tracers only after the built-in profiler tells you the bottleneck is not in your code. Most performance problems in shipped Godot projects are one of these four things, and the profiler will tell you which one if you let it.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>performance</category>
      <category>programming</category>
    </item>
    <item>
      <title>VirtualJoystick in Godot 4.7: Endlich ein nativer Touchscreen-Stick</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Fri, 01 May 2026 17:41:44 +0000</pubDate>
      <link>https://dev.to/ziva/virtualjoystick-in-godot-47-endlich-ein-nativer-touchscreen-stick-1enb</link>
      <guid>https://dev.to/ziva/virtualjoystick-in-godot-47-endlich-ein-nativer-touchscreen-stick-1enb</guid>
      <description>&lt;p&gt;Bis vor kurzem hattest du in Godot zwei Optionen, wenn du einen virtuellen Joystick auf dem Smartphone wolltest: Plugin installieren oder selbst bauen. Beides hatte den gleichen Effekt: Edge Cases mit Multitouch, Resolution-Scaling von Hand, und am Ende eine Klasse mit 200 Zeilen, die du bei jedem Engine-Update neu testen musstest.&lt;/p&gt;

&lt;p&gt;Mit &lt;a href="https://godotengine.org/article/dev-snapshot-godot-4-7-beta-1/" rel="noopener noreferrer"&gt;Godot 4.7 beta 1&lt;/a&gt; (24. April 2026) ist das vorbei. Der neue &lt;code&gt;VirtualJoystick&lt;/code&gt;-Knoten ist Teil der Engine, hat drei Modi und fügt sich sauber ins Action-Mapping-System ein.&lt;/p&gt;

&lt;h2&gt;
  
  
  Das Problem, das &lt;code&gt;TouchScreenButton&lt;/code&gt; nicht gelöst hat
&lt;/h2&gt;

&lt;p&gt;Godot hatte schon vorher einen Knoten namens &lt;code&gt;TouchScreenButton&lt;/code&gt; für mobile Steuerungen. Das Problem: Er erbt von &lt;code&gt;Node2D&lt;/code&gt;. Das klingt harmlos, ist es aber nicht. Es bedeutet, dass der Button keine Anchors verwenden kann, also nicht relativ zum Bildschirmrand positioniert werden kann.&lt;/p&gt;

&lt;p&gt;Der Reviewer Calinou hat es im Pull Request knapp formuliert: &lt;a href="https://github.com/godotengine/godot/pull/110933" rel="noopener noreferrer"&gt;"TouchScreenButton inherits from Node2D, which means it can't make use of anchors."&lt;/a&gt; Für ein Element, das auf jedem Display-Format ordentlich aussehen soll, ist das ein Showstopper.&lt;/p&gt;

&lt;p&gt;Im &lt;a href="https://github.com/godotengine/godot-proposals/issues/11192" rel="noopener noreferrer"&gt;ursprünglichen Proposal&lt;/a&gt; stand es so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When creating a mobile game, you often need a virtual joystick so the player can move around. However, this is nontrivial to implement correctly."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Das Resultat: Jedes mobile Godot-Projekt hatte entweder eine Asset-Library-Abhängigkeit oder eine selbst geschriebene Joystick-Klasse, die in vielen Fällen ein paar Edge Cases falsch handhabte. Multitouch-Tracking, Resolution-Scaling und das Verhalten beim Verlassen des Sticks sind die häufigsten Stolpersteine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was der neue &lt;code&gt;VirtualJoystick&lt;/code&gt; ist
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;VirtualJoystick&lt;/code&gt; erbt von &lt;code&gt;Control&lt;/code&gt;. Damit funktionieren Anchors, &lt;code&gt;size_flags&lt;/code&gt;, &lt;code&gt;theme_override&lt;/code&gt;, alles was Control-Nodes können. Das ist der wichtige Unterschied zum alten &lt;code&gt;TouchScreenButton&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Der Knoten zeichnet sich prozedural, also nicht als Bitmap. Das bedeutet: Egal ob 1080p oder 4K-Tablet, der Stick sieht identisch scharf aus. Der Look lässt sich über Theme-Properties anpassen (Hintergrund, Knopf, Farben).&lt;/p&gt;

&lt;p&gt;Hinzu kommen vier Signale, die du im Editor verbinden kannst:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tapped&lt;/code&gt;: losgelassen, ohne dass der Stick bewegt wurde&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;released&lt;/code&gt;: der Finger hat den Bildschirm verlassen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flicked&lt;/code&gt;: der Stick wurde aus der Deadzone heraus bewegt&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flick_canceled&lt;/code&gt;: ein Flick wurde initiiert, aber wieder zurückgezogen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Das ist mehr als die meisten Plugin-Implementierungen anbieten. Vor allem &lt;code&gt;tapped&lt;/code&gt; ist nützlich: Damit lässt sich der Joystick auch als Action-Button verwenden, wenn der Spieler nur kurz tippt statt zu schieben.&lt;/p&gt;

&lt;h2&gt;
  
  
  Die drei Modi: Fixed, Dynamic, Following
&lt;/h2&gt;

&lt;p&gt;Hier ist der Punkt, an dem die meisten Tutorials oberflächlich werden. Die drei Modi bestimmen, wie sich der Stick beim Berühren verhält.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;JOYSTICK_FIXED&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Der Stick bleibt da, wo du ihn platziert hast. Klassisch, simpel, vorhersehbar. Wenn der Spieler nicht direkt auf den Stick tippt, passiert nichts.&lt;/p&gt;

&lt;p&gt;Use Case: Action-Spiele mit fester UI, bei denen der Stick immer sichtbar ist. Spieler gewöhnen sich an die Position.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;JOYSTICK_DYNAMIC&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Tippt der Spieler in den Bereich des Sticks (also die &lt;code&gt;size&lt;/code&gt;-Region des Control-Nodes), springt der Knopf zur Berührungsposition. Der Stick selbst bewegt sich nicht weiter über den Berührungspunkt hinaus.&lt;/p&gt;

&lt;p&gt;Use Case: Mehr Komfort als &lt;code&gt;FIXED&lt;/code&gt;, weil man nicht millimetergenau treffen muss. Gut für Mobile-Ports von Joypad-Spielen.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;JOYSTICK_FOLLOWING&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Wie &lt;code&gt;DYNAMIC&lt;/code&gt;, aber der Stick folgt dem Finger auch über die ursprüngliche Bounding-Box hinaus. Beim Loslassen springt er zurück.&lt;/p&gt;

&lt;p&gt;Use Case: Twin-Stick-Shooter und alles, wo der Spieler den Daumen wandern lässt. Das fühlt sich auf großen Displays am natürlichsten an.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Modus&lt;/th&gt;
&lt;th&gt;Stick-Position&lt;/th&gt;
&lt;th&gt;Bewegung über Bounds&lt;/th&gt;
&lt;th&gt;Typischer Einsatz&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JOYSTICK_FIXED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;unverändert&lt;/td&gt;
&lt;td&gt;nein&lt;/td&gt;
&lt;td&gt;klassische UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JOYSTICK_DYNAMIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;springt zur Berührung&lt;/td&gt;
&lt;td&gt;nein&lt;/td&gt;
&lt;td&gt;Mobile-Ports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JOYSTICK_FOLLOWING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;springt zur Berührung&lt;/td&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;Twin-Stick&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Praktischer Setup-Code
&lt;/h2&gt;

&lt;p&gt;Im Editor legst du den &lt;code&gt;VirtualJoystick&lt;/code&gt; als Kind eines &lt;code&gt;CanvasLayer&lt;/code&gt; an, damit er nicht mit der Welt-Kamera scrollt. Dann mappst du die Richtungen auf Input-Actions und liest die Werte wie gewohnt aus.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;CharacterBody2D&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;move_speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_physics_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input_dir&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"move_left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"move_right"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"move_up"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"move_down"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_dir&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;move_speed&lt;/span&gt;
    &lt;span class="n"&gt;move_and_slide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Das ist alles. Im &lt;code&gt;VirtualJoystick&lt;/code&gt;-Inspector trägst du &lt;code&gt;move_left&lt;/code&gt;, &lt;code&gt;move_right&lt;/code&gt;, &lt;code&gt;move_up&lt;/code&gt; und &lt;code&gt;move_down&lt;/code&gt; als Action-Properties ein. Der Knoten triggert die Actions automatisch mit der entsprechenden Stärke (zwischen 0.0 und 1.0). Dein Spiel-Code muss nichts vom Joystick wissen, das ist plattform-agnostisch und funktioniert auch mit Gamepad oder Tastatur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was nicht drin ist (und warum)
&lt;/h2&gt;

&lt;p&gt;Bewusst weggelassen wurden zwei Features, die viele Plugins haben:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deadzone-Konfiguration im Knoten selbst.&lt;/strong&gt; Die Deadzone gehört zur Input-Action, nicht zum Joystick. Godot hat dafür schon ein Feld in den Project Settings unter "Input Map". Doppelte Konfiguration wäre nur eine Quelle für Bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clamp-Zone.&lt;/strong&gt; Eine Zone, in der der Stick "gefangen" bleibt. Im &lt;a href="https://github.com/godotengine/godot-proposals/issues/11192" rel="noopener noreferrer"&gt;Original-Proposal&lt;/a&gt; als optional markiert, in der finalen Version dann doch rausgeflogen.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Beides sind Design-Entscheidungen, keine Lücken, die später gefüllt werden. Wenn du das brauchst, kannst du den Knoten beerben und es selbst hinzufügen, aber im Standard-Workflow ist es nicht notwendig.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vor 4.7 vs. ab 4.7
&lt;/h2&gt;

&lt;p&gt;Wenn du schon Mobile-Spiele in Godot baust, hast du wahrscheinlich eine eigene &lt;code&gt;VirtualJoystick.gd&lt;/code&gt;-Klasse oder ein Asset aus der Library. Die meisten dieser Implementierungen liegen bei 100 bis 300 Zeilen Code, abhängig davon, wie viele Edge Cases sie abdecken.&lt;/p&gt;

&lt;p&gt;Die &lt;a href="https://godotengine.org/asset-library/asset?filter=joystick" rel="noopener noreferrer"&gt;Godot Asset Library&lt;/a&gt; listet aktuell 20 konkurrierende Joystick-Plugins. Genau das ist das Problem, das eine native Lösung beendet: Du musst nicht mehr entscheiden, welches Plugin am wenigsten Bugs hat oder welches noch zu Godot 4.6 kompatibel ist. Der Knoten ist da, gewartet vom Engine-Team, und verschwindet beim nächsten Update nicht.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lohnt sich der Umstieg für Mobile-Projekte?
&lt;/h2&gt;

&lt;p&gt;Wenn dein Spiel auf Touchscreen zielt, ja. &lt;a href="https://www.game.de/en/record-revenue-with-mobile-games-in-germany/" rel="noopener noreferrer"&gt;Mobile Games erzielten 2024 in Deutschland Rekord-Umsätze von 3 Milliarden Euro&lt;/a&gt;, 63 Prozent mehr als 2019, und das Smartphone ist seit Jahren die meistgenutzte Gaming-Plattform hierzulande. Viele Indie-Studios machen einen Hybrid-Ansatz: Desktop-Release zuerst, dann Mobile-Port. Genau in diesem Mobile-Port-Schritt war Touchscreen-Steuerung bisher ein Tag Handarbeit.&lt;/p&gt;

&lt;p&gt;4.7 ist gerade in der Beta-Phase, der stabile Release wird in etwa zwei Monaten erwartet. Wenn du an einem Mobile-Spiel arbeitest, lohnt sich der Sprung früh genug, um den Joystick vor dem Launch zu testen. Klein, aber konkret.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>german</category>
      <category>deutsch</category>
    </item>
    <item>
      <title>Generación procedural en Godot 4: guía práctica con GDScript</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Tue, 28 Apr 2026 23:46:37 +0000</pubDate>
      <link>https://dev.to/ziva/generacion-procedural-en-godot-4-guia-practica-con-gdscript-bdn</link>
      <guid>https://dev.to/ziva/generacion-procedural-en-godot-4-guia-practica-con-gdscript-bdn</guid>
      <description>&lt;p&gt;La generación procedural es una de las características que más diferencia un juego indie en Steam de un juego de portfolio. Roguelikes, sandboxes, dungeon crawlers, todos dependen de algún sistema procedural. En Godot 4 hay tres patrones que cubren el 80 por ciento de los casos de uso, y aún así hay sutilezas que la mayoría de tutoriales pasan por alto.&lt;/p&gt;

&lt;p&gt;Voy a recorrer los tres patrones, las trampas reales que aparecen al implementarlos, y por qué los asistentes de IA tienden a generar código procedural que parece correcto pero produce mundos rotos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patrón 1: Generación basada en ruido (Perlin/Simplex)
&lt;/h2&gt;

&lt;p&gt;Godot 4 incluye &lt;code&gt;FastNoiseLite&lt;/code&gt; con varios tipos de ruido (Perlin, Simplex, Value, Cellular). Es el camino más corto para generar terrenos, mapas de calor, distribuciones de recursos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;onready&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastNoiseLite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_ready&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noise_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastNoiseLite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TYPE_SIMPLEX_SMOOTH&lt;/span&gt;
    &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;randi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frequency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;get_height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_noise_2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La trampa principal: la frecuencia. Demasiado baja (0.001) y obtienes una superficie casi plana. Demasiado alta (0.5) y obtienes ruido aleatorio sin estructura. La mayoría de problemas de "mi terreno se ve raro" son problemas de frecuencia mal calibrada.&lt;/p&gt;

&lt;p&gt;Otra trampa común: usar &lt;code&gt;randi()&lt;/code&gt; como seed sin guardarla. Si quieres reproducibilidad (necesario para multiplayer y depuración), guarda la seed que usaste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patrón 2: Generación basada en celdas (cellular automata)
&lt;/h2&gt;

&lt;p&gt;Para cuevas, islas, distribuciones orgánicas, los autómatas celulares son superiores al ruido. El algoritmo clásico:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Llena una grilla aleatoriamente con piedra (1) y aire (0).&lt;/li&gt;
&lt;li&gt;Para N iteraciones: para cada celda, cuenta sus vecinos. Si tiene &amp;gt;= 4 vecinos sólidos, se vuelve sólida. Si no, se vuelve aire.&lt;/li&gt;
&lt;li&gt;Después de 4-5 iteraciones, los grupos pequeños desaparecen y emergen cuevas naturales.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;cellular_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;new_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;neighbors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count_neighbors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;new_grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;neighbors&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_grid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trampa: la primera iteración necesita cuidado en los bordes. Si tu &lt;code&gt;count_neighbors&lt;/code&gt; no maneja los bordes correctamente, las cuevas pegadas al borde del mapa se rompen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patrón 3: BSP (Binary Space Partitioning)
&lt;/h2&gt;

&lt;p&gt;Para mazmorras estructuradas con habitaciones y pasillos, BSP es el estándar de la industria. Divides el mapa recursivamente en dos, paras cuando los rectángulos son del tamaño de una habitación, colocas una habitación en cada hoja, y conectas habitaciones con pasillos siguiendo la jerarquía del árbol.&lt;/p&gt;

&lt;p&gt;Este es el patrón más complicado de los tres, porque combina recursión, geometría 2D, y un grafo de conectividad. Es también donde más fallan los asistentes de IA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué la IA genera código procedural roto
&lt;/h2&gt;

&lt;p&gt;Cuando le pides a ChatGPT, Claude o Cursor que genere un sistema procedural en GDScript, suele producir código que compila y ejecuta, pero genera mundos visiblemente rotos. Las razones recurrentes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frecuencias de ruido sin calibrar.&lt;/strong&gt; El modelo elige un valor "estándar" (0.1) que solo funciona para escalas específicas. Si tu mapa es de 1000x1000 tiles, no va a verse bien con esa frecuencia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bucles sin guardas.&lt;/strong&gt; El cellular automata necesita un número fijo de iteraciones. La IA a veces escribe &lt;code&gt;while not stable:&lt;/code&gt; sin definir bien &lt;code&gt;stable&lt;/code&gt;, y el bucle nunca termina o termina muy pronto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manejo de bordes inconsistente.&lt;/strong&gt; Wrap, clamp, o ignorar son tres opciones válidas según el tipo de mundo. La IA mezcla las tres en el mismo proyecto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seed no guardada.&lt;/strong&gt; El código genera un mundo bonito una vez y luego nunca puedes reproducirlo. La IA omite el almacenamiento de la seed por defecto.&lt;/p&gt;

&lt;p&gt;Esto encaja con el patrón general de fallos silenciosos en código generado por IA. El &lt;a href="https://www.sonarsource.com/state-of-code-developer-survey-report.pdf" rel="noopener noreferrer"&gt;reporte Sonarsource State of Code 2026&lt;/a&gt; reporta que el 60 por ciento de los fallos en código generado por IA son "fallos silenciosos": código que compila, parece correcto, y produce resultados incorrectos. La generación procedural es un caso particularmente afectado, porque el "resultado correcto" es subjetivo (un mundo visualmente plausible) y no hay test unitario que lo capture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo verificar generación procedural
&lt;/h2&gt;

&lt;p&gt;Tres reglas prácticas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visualiza siempre.&lt;/strong&gt; No confíes en que un mundo "se ve bien" porque el código corre. Genera 10 mundos, mira los 10. Las trampas de calibración aparecen como "mundos demasiado parecidos" o "mundos demasiado caóticos."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guarda la seed que produjo el bug.&lt;/strong&gt; Cuando un mundo procedural se vea raro, copia la seed que lo generó al portapapeles. Sin la seed, no puedes reproducir el bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usa herramientas que ejecuten el código en Godot.&lt;/strong&gt; Una clase emergente de herramientas (proyectos como &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; específicamente para Godot) ejecuta el código generado dentro del editor, observa el resultado, y reacciona cuando el mundo se ve roto. Eso cierra el bucle entre "el código compila" y "el código produce un mundo jugable."&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Los tres patrones (ruido, celular, BSP) cubren la mayoría de necesidades procedurales en juegos indie. Cada uno tiene trampas que la IA tiende a pasar por alto. La defensa más fuerte es siempre la misma: ejecuta el código en Godot, observa el resultado, no confíes en que "el código compila" significa "el mundo está bien."&lt;/p&gt;

&lt;p&gt;Para tu próximo proyecto procedural en Godot, empieza con &lt;code&gt;FastNoiseLite&lt;/code&gt; y calibra la frecuencia visualmente. Cuando necesites estructura (habitaciones, conectividad), pasa a BSP. Para orgánicos, autómatas celulares. Y guarda esa seed.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>gdscript</category>
      <category>programming</category>
    </item>
    <item>
      <title>KI-Tools in Godot 2026: Was funktioniert, was scheitert</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Tue, 28 Apr 2026 23:44:04 +0000</pubDate>
      <link>https://dev.to/ziva/ki-tools-in-godot-2026-was-funktioniert-was-scheitert-2o2b</link>
      <guid>https://dev.to/ziva/ki-tools-in-godot-2026-was-funktioniert-was-scheitert-2o2b</guid>
      <description>&lt;p&gt;Wer 2026 mit Godot anfängt und nach KI-Hilfe sucht, stößt auf ein Problem, das in Tutorials selten erwähnt wird: ChatGPT und Claude können GDScript zwar gut schreiben, aber sie wissen nicht, wie das Godot-Projekt strukturiert ist, das du gerade aufbaust. Sie raten. Manchmal richtig, oft daneben.&lt;/p&gt;

&lt;p&gt;Ich nutze KI-Tools seit Jahren beim Webentwicklung und seit etwa 8 Monaten in Godot. Die Erfahrung ist deutlich anders. Hier eine ehrliche Übersicht, was 2026 in Godot mit KI funktioniert und was nicht.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was funktioniert
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Boilerplate für gängige Patterns.&lt;/strong&gt; Ein Player-Controller mit &lt;code&gt;CharacterBody2D&lt;/code&gt;, Eingabebehandlung über &lt;code&gt;Input.get_axis()&lt;/code&gt;, Sprung-Logik mit Coyote-Time, all das schreiben generische KI-Tools korrekt. Wenn du ein Standard-Pattern willst, ist das schneller als selbst tippen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Erklärung von Engine-Features.&lt;/strong&gt; Wenn du nicht verstehst, was &lt;code&gt;_physics_process&lt;/code&gt; vs &lt;code&gt;_process&lt;/code&gt; macht, oder wann du &lt;code&gt;queue_free()&lt;/code&gt; statt &lt;code&gt;free()&lt;/code&gt; verwendest, sind LLMs eine sinnvolle Lernhilfe. Die Grundkonzepte sitzen ausreichend gut im Trainingsmaterial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GDScript-Syntax und Typen.&lt;/strong&gt; Godot 4.x Typing (&lt;code&gt;var hp: int = 100&lt;/code&gt;) wird korrekt gehandhabt. &lt;code&gt;@export&lt;/code&gt;-Annotationen, &lt;code&gt;@onready&lt;/code&gt;, das funktioniert in 95 Prozent der Fälle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactoring von eigenen Skripten.&lt;/strong&gt; Ein vorhandenes Skript in mehrere Klassen aufteilen, Magic Numbers extrahieren, Funktionen umbenennen, das macht generische KI gut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was nicht funktioniert
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Signale, die zur Laufzeit gebunden werden.&lt;/strong&gt; Die KI sieht nur den Quellcode. Sie kann nicht überprüfen, ob ein &lt;code&gt;connect()&lt;/code&gt;-Aufruf zum Zeitpunkt der Emission noch gültig ist, ob der Receiver freigegeben wurde, ob die Verbindung doppelt existiert. Ein Großteil der "fast richtigen" Godot-Bugs sind Signal-Bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AnimationTree-Verkabelung.&lt;/strong&gt; Der &lt;code&gt;parameters/playback&lt;/code&gt;-Pfad ist ein String. Generische KI tippt diese Strings souverän falsch ab und merkt es nicht. Die Animation läuft einfach nicht. Kein Fehler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Szenenpfade in &lt;code&gt;load()&lt;/code&gt;.&lt;/strong&gt; Die KI nimmt an, dein Projekt sei strukturiert wie das erste Tutorial, das sie gelernt hat. Wenn deine &lt;code&gt;Player.tscn&lt;/code&gt; woanders liegt, gibt &lt;code&gt;load()&lt;/code&gt; &lt;code&gt;null&lt;/code&gt; zurück, und die KI merkt es nicht.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autoload-Referenzen.&lt;/strong&gt; &lt;code&gt;AudioManager.play("hit")&lt;/code&gt; setzt voraus, dass &lt;code&gt;AudioManager&lt;/code&gt; als Autoload registriert ist. KI-generierte Code überspringt diesen Check standardmäßig. Du bekommst NIL-Errors zur Laufzeit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plattform-spezifischer Export-Code.&lt;/strong&gt; Web-Export, Mobile-Export, Konsolen-Build-Konfigurationen sind in den Trainingsdaten unterrepräsentiert. Hier rät die KI sehr.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warum das passiert
&lt;/h2&gt;

&lt;p&gt;Der zugrundeliegende Grund ist einfach: 60 Prozent der Fehler in KI-generiertem Code sind laut &lt;a href="https://www.sonarsource.com/state-of-code-developer-survey-report.pdf" rel="noopener noreferrer"&gt;Sonarsource State of Code Report 2026&lt;/a&gt; "stille Fehler". Code, der kompiliert, plausibel aussieht und in der Produktion das Falsche tut. In Godot ist "Produktion" der erste Druck auf F5.&lt;/p&gt;

&lt;p&gt;Die &lt;a href="https://stackoverflow.blog/2025/12/29/developers-remain-willing-but-reluctant-to-use-ai-the-2025-developer-survey-results-are-here/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey 2025&lt;/a&gt; zeigt zudem: 84 Prozent der Entwickler nutzen KI, nur 29 Prozent vertrauen den Ergebnissen. Bei Godot-Devs ist das Vertrauensniveau noch niedriger, weil die Fehler stiller sind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was 2026 hilft
&lt;/h2&gt;

&lt;p&gt;Drei praktische Tipps, die wirklich Zeit sparen:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run-then-paste statt paste-then-run.&lt;/strong&gt; Lass die KI Code generieren, paste ihn ein, drücke F5 BEVOR du committest. Das Output-Panel von Godot zeigt die meisten stillen Fehler innerhalb von Sekunden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;is_connected()&lt;/code&gt;-Guards bei &lt;code&gt;disconnect()&lt;/code&gt;.&lt;/strong&gt; Das ist die billigste Defensiv-Praxis, die du KI-Code hinzufügen kannst. Sie verhindert eine ganze Klasse von "signal not connected"-Fehlern beim Szenenwechsel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engine-bewusste KI-Tools, wo möglich.&lt;/strong&gt; Eine kleine, aber wachsende Klasse von Tools (Projekte wie &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; für Godot speziell) integriert sich direkt in den Godot-Editor. Der Agent kann die Szene laden, F5 drücken, die Ausgabe lesen, und reagieren, wenn das Signal nicht gefeuert hat. Das schließt die Lücke zwischen "Code sieht richtig aus" und "Code funktioniert."&lt;/p&gt;

&lt;h2&gt;
  
  
  Fazit für deutsche Godot-Entwickler
&lt;/h2&gt;

&lt;p&gt;Generische KI-Tools sind 2026 nicht überflüssig, aber sie sind nicht das Endgame. Die nächste Generation von Godot-Tools ist engine-aware, kennt die Szenenhierarchie, kann F5 drücken. Wenn du gerade mit KI-gestützter Godot-Entwicklung anfängst, würde ich empfehlen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generische LLMs für Konzepte und GDScript-Syntax verwenden.&lt;/li&gt;
&lt;li&gt;Kritische Engine-Pfade (Signale, AnimationTree, Autoloads) IMMER manuell testen.&lt;/li&gt;
&lt;li&gt;Ein engine-aware Tool ausprobieren, wenn du regelmäßig Godot-Code schreibst.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Die Stunden, die du beim Debuggen "fast richtigen" KI-Codes sparst, summieren sich schnell. Was ich als Faustregel gelernt habe: KI-Code, der nicht in der Engine ausgeführt wurde, ist KI-Code, dem du nicht trauen solltest.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>ai</category>
      <category>gdscript</category>
    </item>
    <item>
      <title>Testing Godot Code Is Harder Than Testing a Webapp. Here's What Helps.</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Tue, 28 Apr 2026 23:23:26 +0000</pubDate>
      <link>https://dev.to/ziva/testing-godot-code-is-harder-than-testing-a-webapp-heres-what-helps-5gb1</link>
      <guid>https://dev.to/ziva/testing-godot-code-is-harder-than-testing-a-webapp-heres-what-helps-5gb1</guid>
      <description>&lt;p&gt;If you've come from web development, you have muscle memory for testing. Spin up Jest, mock the database, hit the route, assert. Or use Playwright to drive a headless browser through a flow. CI runs the suite on every PR. You know the loop.&lt;/p&gt;

&lt;p&gt;Then you open a Godot project. The web testing playbook breaks in five places before you finish your morning coffee.&lt;/p&gt;

&lt;p&gt;I'm writing this because the gap between "AI wrote my Godot code" and "the code actually works" is mostly testing, and most of the testing tooling for Godot is one or two layers behind what web devs are used to. Here's what's different and what currently helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Godot doesn't have a default headless test mode that just works
&lt;/h2&gt;

&lt;p&gt;A web project ships with a test framework on day one. Jest is built into Create React App. Vitest is the Vite default. You inherit a working test runner before you've written a line of business logic.&lt;/p&gt;

&lt;p&gt;A Godot project ships with the editor and &lt;code&gt;_ready()&lt;/code&gt;. There is no default test framework. The community standard, &lt;a href="https://github.com/bitwes/Gut" rel="noopener noreferrer"&gt;GUT (Godot Unit Testing)&lt;/a&gt;, is a third-party plugin you install yourself. It's good. It's also one of those "obvious in hindsight, surprising on day one" gaps when you arrive from a webdev background.&lt;/p&gt;

&lt;p&gt;There's also &lt;a href="https://github.com/chickensoft-games/GodotTestDriver" rel="noopener noreferrer"&gt;GodotTestDriver&lt;/a&gt; for integration tests, and Godot does support a &lt;code&gt;--headless&lt;/code&gt; mode for running scenes without a window. But none of this is wired up out of the box, and the headless mode has quirks: rendering-dependent code (anything that uses &lt;code&gt;Viewport.get_texture()&lt;/code&gt;, for example) silently produces empty results.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state of your scene tree is invisible to most test runners
&lt;/h2&gt;

&lt;p&gt;Web tests deal with three kinds of state: the DOM, your store, and the database. All three are introspectable. You can &lt;code&gt;screen.getByRole('button')&lt;/code&gt;, you can read Redux state, you can &lt;code&gt;SELECT *&lt;/code&gt; from the test DB.&lt;/p&gt;

&lt;p&gt;Godot has a fourth kind: the scene tree. Nodes have parents. Signals connect nodes to other nodes. The active StateMachine state is buried inside an AnimationTree's &lt;code&gt;parameters/playback&lt;/code&gt; property. None of this surfaces in a stack trace. None of it is in a test database. You can write a unit test that verifies your signal-emitting function fires the signal, and still have a broken game because the receiving node was freed two frames earlier.&lt;/p&gt;

&lt;p&gt;This is the failure mode that bit me hardest moving from web to Godot: tests that pass in isolation, gameplay that breaks at runtime because the test runner doesn't know what the scene tree looked like when the bug happened.&lt;/p&gt;

&lt;p&gt;GodotTestDriver helps here by providing a &lt;code&gt;Fixture&lt;/code&gt; class that owns scene nodes during a test and tears them down cleanly. But you have to write integration tests that exercise actual scene behavior, not just unit tests that exercise pure functions. Most game logic is not pure functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most AI-generated Godot code never gets run before it ships
&lt;/h2&gt;

&lt;p&gt;The 2026 &lt;a href="https://www.sonarsource.com/state-of-code-developer-survey-report.pdf" rel="noopener noreferrer"&gt;Sonarsource State of Code report&lt;/a&gt; found that 60% of faults in AI-generated code are "silent failures." Code that compiles, looks right, and produces wrong results in production. The 2025 &lt;a href="https://stackoverflow.blog/2025/12/29/developers-remain-willing-but-reluctant-to-use-ai-the-2025-developer-survey-results-are-here/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey&lt;/a&gt; shows trust in AI output dropped from 40% to 29%, with 66% of devs citing "almost-right" code as their top frustration.&lt;/p&gt;

&lt;p&gt;For a webdev, this hurts a little. Type-check catches some of it. Failing test catches more. The user-visible failure mode is a 500 error and a Sentry alert.&lt;/p&gt;

&lt;p&gt;For a Godot dev, the same code can ship without anything obvious going wrong. The build succeeds. The editor doesn't complain. You press Play, the scene loads, your character moves around. Then you realize the death animation isn't playing because the signal was connected to a node that gets freed before the signal fires. There's no exception. There's no log line. The gameplay is just slightly wrong.&lt;/p&gt;

&lt;p&gt;Pasting AI-generated code into Godot without running it in the engine first is the equivalent of merging an untested PR straight to production. Web devs would never do that. Godot devs do it constantly, because the tooling makes it the path of least resistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What helps
&lt;/h2&gt;

&lt;p&gt;A few things narrow the gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run a smoke test scene before pasting AI output.&lt;/strong&gt; Open the project, open the scene, press F5. If the AI's change broke node references or signal wiring, you'll see it in the output panel within seconds. This sounds obvious. It's also the thing most people skip because the dev/AI/dev/AI loop has too many context switches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add GUT and write integration tests for systems that touch the scene tree.&lt;/strong&gt; Pure-function tests are not enough for Godot. You need tests that load a scene, fire input, advance the frame, and assert on the resulting state. GUT supports this with &lt;code&gt;add_child_autofree()&lt;/code&gt; and the &lt;code&gt;await&lt;/code&gt; keyword for waiting on signals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use AI tools that integrate with the engine, not just the editor.&lt;/strong&gt; Most AI coding tools edit text files and stop. They have no view into the scene tree, no way to press Play, no read access to the Godot output panel. A small but growing class of tools (projects like &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; for Godot specifically) wire the AI agent to the engine itself, so the same model that writes the code can run the scene, watch the output, and react when a signal didn't fire. That's the part of the loop that closes the gap between "code looks right" and "code works."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat headless CI as a goal, not a starting point.&lt;/strong&gt; You will not have headless CI on day one of a Godot project. That's fine. Get to the point where you can press Play and watch a smoke test scene first. Build up to running tests on a CI runner with &lt;code&gt;--headless&lt;/code&gt; later. Web devs are used to that order being reversed; in Godot it's almost always smoke-test-first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;Game dev testing is harder than webapp testing in 2026. The frameworks are younger. The state model is more complex. The default failure mode is silent. AI-generated code raises the floor for syntax correctness and lowers the floor for runtime correctness in ways that are particularly bad for game projects, because game projects already had a runtime correctness problem.&lt;/p&gt;

&lt;p&gt;If you're a web dev experimenting with Godot and you're confused why your AI-assisted prototype keeps almost-but-not-quite working, this is most of the answer. The fix is upstream of the AI: better integration between the model writing the code and the engine running the code. The community is figuring this out in real time, and the tooling is improving fast.&lt;/p&gt;

&lt;p&gt;In the meantime, press Play before you commit.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>Domain-Specific AI Beats General AI on Niche Code. Here is Why.</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Fri, 24 Apr 2026 21:34:02 +0000</pubDate>
      <link>https://dev.to/ziva/domain-specific-ai-beats-general-ai-on-niche-code-here-is-why-305f</link>
      <guid>https://dev.to/ziva/domain-specific-ai-beats-general-ai-on-niche-code-here-is-why-305f</guid>
      <description>&lt;p&gt;If your AI coding assistant is great at React but keeps hallucinating on your Django ORM, or fine with Python but useless on your Rust lifetimes, or solid with webdev but writes broken Godot scripts, this post is for you. The pattern is consistent across domains: general-purpose AI coding tools have a frontier they do well inside, and everything outside that frontier gets progressively worse.&lt;/p&gt;

&lt;p&gt;This post is about why, what to do about it, and when staying with a general tool is still the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;You ask a general AI assistant to write code for a framework or engine that is not in the top 20 most-discussed on Stack Overflow. The code looks plausible. It compiles sometimes. When it runs, it breaks in ways that make you question your sanity because the error message points somewhere unrelated to the actual bug.&lt;/p&gt;

&lt;p&gt;You paste the error back. The AI apologizes and writes a different wrong version. Forty minutes in, you give up and do it yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/best-practices" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, GitHub Copilot, and &lt;a href="https://cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; all hit this wall. It is not about which model is better. It is about the distribution of training data and the lack of runtime context for the specific project you are working on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Three overlapping causes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training data frequency.&lt;/strong&gt; LLMs get better at code patterns that appear often in their training corpus. A framework with 100K GitHub repos gets way more attention than one with 1K. This is why React code is almost always right and Godot GDScript code is often half wrong. &lt;a href="https://dev.to/t/gamedev"&gt;DEV.to's community analysis of gamedev engagement&lt;/a&gt; shows the gap: gamedev posts get 10 to 20 reactions while webdev posts regularly clear 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context window limits plus "lost in the middle".&lt;/strong&gt; Context windows grew to &lt;a href="https://claude5.com/news/context-window-race-2026-how-200k-to-1m-tokens-transform-ai" rel="noopener noreferrer"&gt;200K, 1M, and beyond over 2024-2026&lt;/a&gt;, which sounded like it would fix everything. It did not. Studies of long-context retrieval found that information buried in the middle of a large context window is retrieved worse than information at the start or end. Paste your whole codebase, and the model will happily ignore the autoload registration on line 11,000 that breaks everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No runtime state.&lt;/strong&gt; A pasted codebase is static. The actual project has scene trees, registered globals, input maps, environment variables, database schemas, and build outputs that the AI cannot see through pasting alone. For niche frameworks where the runtime state matters a lot, the AI is guessing at half of the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "domain-specific" actually means
&lt;/h2&gt;

&lt;p&gt;There are three tiers of AI coding tool, and the names get confusing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;What it knows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;General-purpose&lt;/td&gt;
&lt;td&gt;ChatGPT, Claude, basic Copilot&lt;/td&gt;
&lt;td&gt;Syntax, patterns, public docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDE-integrated&lt;/td&gt;
&lt;td&gt;Cursor, &lt;a href="https://code.claude.com" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, full Copilot&lt;/td&gt;
&lt;td&gt;Syntax plus your codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain-specific&lt;/td&gt;
&lt;td&gt;Tools like &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; for Godot, framework-specific helpers&lt;/td&gt;
&lt;td&gt;Syntax, codebase, and runtime state for one ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The third tier is what this post is about. A domain-specific AI is one whose training, retrieval, and context gathering are built around a single ecosystem. It reads the framework's current release notes, the runtime state of your specific project, and the niche APIs that general AI glosses over.&lt;/p&gt;

&lt;p&gt;The narrow bet is: on the domain it covers, it outperforms general AI. Off-domain, it does nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  When general AI is still the right call
&lt;/h2&gt;

&lt;p&gt;Domain-specific tools are a tradeoff. If your work spans 5 different stacks on a given week, you do not want 5 different AI assistants. The context switching and subscription overhead kills the win.&lt;/p&gt;

&lt;p&gt;General AI is the right call when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your code is in a high-training-frequency ecosystem (React, Django, Rails, Spring, Express).&lt;/li&gt;
&lt;li&gt;You work across many stacks and need one tool that handles all of them.&lt;/li&gt;
&lt;li&gt;The niche parts of your work are small and you can fix them yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Domain-specific AI starts earning its keep when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You spend the majority of your week in one ecosystem.&lt;/li&gt;
&lt;li&gt;That ecosystem has framework-specific patterns the AI keeps getting wrong.&lt;/li&gt;
&lt;li&gt;You have hit the "AI wrote 40 lines of confident-looking garbage" wall more than once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The practical test
&lt;/h2&gt;

&lt;p&gt;Before committing to a domain-specific tool, run this test. Take three tasks you did this week in your niche framework. Hand them to a general AI with the full relevant file pasted in. Measure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did the generated code compile or run on the first attempt?&lt;/li&gt;
&lt;li&gt;Did it use APIs that actually exist in your framework version?&lt;/li&gt;
&lt;li&gt;Did it reference existing project structures (globals, singletons, shared resources) correctly?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you get two out of three, you are fine. General AI is working for you.&lt;/p&gt;

&lt;p&gt;If you are below that, the cost of a specialized tool is probably worth it. At that point, you are paying in compounding time loss, not just single-task friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;For Godot specifically, &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;tools like Ziva&lt;/a&gt; that run inside the editor and read project state directly get the autoload, scene-tree, and signal context that a chat-based AI cannot. For Rust, tools that understand borrow-check state across your project do better than generic completion. For Django, ORM-aware tools outperform generic ones on query optimization.&lt;/p&gt;

&lt;p&gt;The pattern holds across ecosystems. The bet is the same: narrow your AI's surface area so the context it has is actually useful, and accept that it does not work outside that surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would not do
&lt;/h2&gt;

&lt;p&gt;Do not buy a specialized AI for every framework you touch. The tradeoff only makes sense when the time you save exceeds the time you lose to juggling tools.&lt;/p&gt;

&lt;p&gt;Do not expect a domain-specific AI to fix a problem that is not domain-specific. If your issue is bad code architecture or unclear requirements, a better AI will not save you.&lt;/p&gt;

&lt;p&gt;Do not assume "domain-specific" means better. It means narrow. On the ecosystem it covers, it should outperform general AI. Off the ecosystem, it does nothing. Measure before you switch, and measure after.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;General AI is great at code patterns that show up a lot in its training data. It gets progressively worse on niche frameworks, version-specific features, and anything that requires runtime project state. Bigger context windows do not fix this. Domain-specific tools exist for the cases where the gap is costing you time.&lt;/p&gt;

&lt;p&gt;Whether you need one depends on how much of your week is in the niche. Run the three-task test before you decide.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>godot</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cómo llegar a 1000 wishlists en Steam con tu juego de Godot</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Tue, 21 Apr 2026 00:14:58 +0000</pubDate>
      <link>https://dev.to/ziva/como-llegar-a-1000-wishlists-en-steam-con-tu-juego-de-godot-4dn6</link>
      <guid>https://dev.to/ziva/como-llegar-a-1000-wishlists-en-steam-con-tu-juego-de-godot-4dn6</guid>
      <description>&lt;p&gt;Si estás desarrollando tu primer juego en Godot y apuntas a Steam, el número que más importa no es cuánto dinero vas a hacer. Es cuántas wishlists acumulas antes del lanzamiento. La data de 2026 es clara: las wishlists pre-lanzamiento correlacionan con las ventas de la primera semana a r = 0.825, según &lt;a href="https://howtomarketagame.com/2026/04/13/making-sense-of-the-february-2026-steam-next-fest/" rel="noopener noreferrer"&gt;el survey de Chris Zukowski&lt;/a&gt; de 182 desarrolladores que participaron en Steam Next Fest de febrero 2026.&lt;/p&gt;

&lt;p&gt;El problema es que la mediana de un Next Fest suma 806 wishlists. Si entras con cero seguidores, el resultado más probable es que termines con 322 wishlists sumadas en toda la semana. Eso no alcanza.&lt;/p&gt;

&lt;p&gt;Te voy a contar lo que funciona para llegar a 1000 wishlists antes del lanzamiento, basado en lo que los devs que shipearon en Godot en 2025-2026 realmente hicieron.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subí la página de Steam lo antes posible
&lt;/h2&gt;

&lt;p&gt;El algoritmo de Steam premia velocidad, no acumulación. Un juego que lleva 3 meses con la página viva acumulando 10 wishlists por día (900 wishlists al final) termina con mejor señal algorítmica que uno que acumuló 900 wishlists en una sola semana viral.&lt;/p&gt;

&lt;p&gt;Esto significa subir la &lt;a href="https://partner.steamgames.com/doc/store/coming_soon" rel="noopener noreferrer"&gt;Coming Soon page&lt;/a&gt; antes de tener el trailer final. Antes de tener la demo. El costo de equivocarse (actualizar capsules después) es mucho menor que el costo de empezar tarde.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apuntá a 50 reviews, no a 10,000 wishlists
&lt;/h2&gt;

&lt;p&gt;Una observación incómoda de los datos de 2026: Steam empieza a mostrar tu juego en superficies orgánicas (Popular Upcoming, Trending, More Like This) recién cuando cruzás las &lt;a href="https://metricusapp.com/blog/indie-game-distribution-user-acquisition-painpoints-2025-2026/" rel="noopener noreferrer"&gt;50 reviews&lt;/a&gt;. Antes de eso sos invisible, sin importar si tenés 5,000 o 10,000 wishlists.&lt;/p&gt;

&lt;p&gt;Esto cambia la estrategia. No necesitás 10,000 wishlists para lanzar. Necesitás suficiente audiencia para generar 50 reviews la primera semana. La aritmética: si tu conversion rate wishlist-to-buyer es 20% (normal para indie) y el 10% de compradores deja review, necesitás 2,500 wishlists para 50 reviews garantizadas. Por debajo de 2,500 estás apostando.&lt;/p&gt;

&lt;h2&gt;
  
  
  La demo temprano supera a la demo de último momento
&lt;/h2&gt;

&lt;p&gt;Los juegos que lanzaron demo meses antes de Next Fest sumaron 2.5x más wishlists que los que lanzaron la demo durante el fest, según &lt;a href="https://howtomarketagame.com/2026/04/13/making-sense-of-the-february-2026-steam-next-fest/" rel="noopener noreferrer"&gt;el mismo survey&lt;/a&gt;. La correlación no es gigante (r = -0.205) pero la dirección es consistente en tres ediciones de Next Fest seguidas.&lt;/p&gt;

&lt;p&gt;Cairn, de &lt;a href="https://howtomarketagame.com/2025/10/20/steam-next-fest-october-2025-checking-in-on-the-games-that-broke-through/" rel="noopener noreferrer"&gt;The Game Bakers&lt;/a&gt;, lanzó su demo en diciembre 2024 y entró a Next Fest en octubre 2025. 16 meses de runway. Al momento del fest, la demo ya tenía 200,000+ downloads y el juego acumulaba 32,000 wishlists.&lt;/p&gt;

&lt;p&gt;Para un dev solo en LatAm, la lección: no guardes la demo para "cuando esté perfecta". Lanzá a los 6 meses de desarrollo, aunque tengas solo 20 minutos jugables. Lo que buscás es tiempo en el algoritmo, no perfección en la demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Godot es el engine correcto para iterar rápido
&lt;/h2&gt;

&lt;p&gt;Acá es donde la elección del engine importa. &lt;a href="https://godotengine.org/releases/4.5/" rel="noopener noreferrer"&gt;Godot 4.5&lt;/a&gt; compila cambios en menos de 5 segundos en hardware mediano. Los archivos de escena son texto plano, así que podés usar git real sin los problemas del YAML de Unity. El shader baker de 4.5 te da 20x mejor load time en Metal y D3D12.&lt;/p&gt;

&lt;p&gt;Para iteración semanal (que es lo que importa cuando estás respondiendo a feedback de tu Discord de 100 personas), Godot te saca como 15 minutos de cada ciclo comparado con Unity. Eso en 6 meses son ~30 horas de trabajo recuperadas.&lt;/p&gt;

&lt;p&gt;Si estás usando una IA para generar código GDScript, herramientas como &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; corren el código que generan contra el editor de Godot en vivo, lo que te evita el problema de que la IA alucine funciones de Godot 3 que ya no existen en 4.5. No es que sea mejor que escribir a mano, es que reduce el costo de "lo probé y no compila" a cero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marketing concentrado: 5 YouTubers, no 50
&lt;/h2&gt;

&lt;p&gt;La distribución en español de Godot está concentrada. Hay 4-5 creators en YouTube y Twitch que cubren prácticamente toda la comunidad hispanohablante. Si tu demo es buena, no necesitás mandarles email a 200 influencers. Necesitás que 3 de esos 5 la mencionen. Eso te llega al 80% de tu audiencia hispanohablante potencial. La distribución concentrada en un engine chico es, desde el punto de vista de marketing indie, mejor que la distribución fragmentada de Unity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist de 6 meses
&lt;/h2&gt;

&lt;p&gt;Si empezás hoy y apuntás a lanzar en 6 meses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mes 1-2:&lt;/strong&gt; Subí la Coming Soon page. Capsules básicas, trailer de 30 segundos. Empezá a hacer devlog semanal en YouTube o TikTok.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mes 3:&lt;/strong&gt; Lanzá una demo de 15-20 minutos. No esperes a tener todo perfecto. La demo va a iterar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mes 4-5:&lt;/strong&gt; Revisá las reviews de la demo semanalmente. Subí patches. Construí una comunidad de Discord de 50+ personas activas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mes 6:&lt;/strong&gt; Entrá a Steam Next Fest de junio u octubre con una demo mejorada y ~3,000 wishlists acumuladas. El Next Fest te va a sumar 1,500-3,000 más si hiciste bien los meses anteriores.&lt;/p&gt;

&lt;p&gt;Lanzá al mes 7 con ~5,000-7,000 wishlists. Primera semana: ~500 ventas, ~50 reviews. A partir de ahí, Steam empieza a trabajar a favor tuyo.&lt;/p&gt;

&lt;p&gt;Ninguna de estas cifras garantiza éxito. El 91.5% de los juegos indie en Steam &lt;a href="https://www.steampageanalyzer.com/blog/indie-game-revenue-data" rel="noopener noreferrer"&gt;no pasan los $100K en ingresos brutos&lt;/a&gt;. Pero apuntar a 1,000 wishlists antes de lanzar con un plan claro es la diferencia entre tener una chance real y no tenerla.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>godot</category>
      <category>gamedev</category>
      <category>ai</category>
    </item>
    <item>
      <title>7 Godot 4 API Calls Your AI Assistant Still Gets Wrong</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Mon, 20 Apr 2026 23:57:36 +0000</pubDate>
      <link>https://dev.to/ziva/7-godot-4-api-calls-your-ai-assistant-still-gets-wrong-3ep6</link>
      <guid>https://dev.to/ziva/7-godot-4-api-calls-your-ai-assistant-still-gets-wrong-3ep6</guid>
      <description>&lt;p&gt;If you have asked Copilot, Claude, or ChatGPT to write Godot code in the last six months, you have hit this: the generated code looks reasonable, compiles clean, and then fails at runtime because the function name does not exist. Godot 4 shipped in &lt;a href="https://godotengine.org/article/godot-4-0-sets-sail/" rel="noopener noreferrer"&gt;March 2023&lt;/a&gt; with a completely rewritten GDScript and dozens of renamed APIs. Training data for most LLMs still overrepresents Godot 3 tutorials. The result is an asymmetric failure mode: the code looks right, even to a reviewer who has not touched Godot 3, and you only find out it is broken when you run the game.&lt;/p&gt;

&lt;p&gt;Here are seven of the most common API calls where AI assistants hallucinate the Godot 3 version, with the Godot 4 replacement and a short note on why each one specifically keeps tripping up the models.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;deg2rad&lt;/code&gt; → &lt;code&gt;deg_to_rad&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The old name, &lt;code&gt;deg2rad(angle)&lt;/code&gt;, still appears all over Reddit and StackOverflow posts from 2020 to 2022. In Godot 4 it is &lt;a href="https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#class-globalscope-method-deg-to-rad" rel="noopener noreferrer"&gt;&lt;code&gt;deg_to_rad&lt;/code&gt;&lt;/a&gt; with underscores. Same for &lt;code&gt;rad2deg&lt;/code&gt; → &lt;code&gt;rad_to_deg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the single most-hallucinated function in my experience. The numeric "2" in the old name is legacy from the C++ style used in older Godot versions, and the rename to a readable verb was one of the first things the Godot 4 style guide pushed. LLMs still reach for the short form because it is shorter and because the training data is denser.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;KinematicBody2D&lt;/code&gt; → &lt;code&gt;CharacterBody2D&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Godot 3 had &lt;code&gt;KinematicBody2D&lt;/code&gt; and &lt;code&gt;KinematicBody&lt;/code&gt; (for 3D). Godot 4 &lt;a href="https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html" rel="noopener noreferrer"&gt;renamed both&lt;/a&gt; to &lt;code&gt;CharacterBody2D&lt;/code&gt; and &lt;code&gt;CharacterBody3D&lt;/code&gt;. The internal semantics also changed: &lt;code&gt;move_and_slide()&lt;/code&gt; no longer takes a velocity argument; you set &lt;code&gt;velocity&lt;/code&gt; as a property first, then call the method.&lt;/p&gt;

&lt;p&gt;AI assistants get the class rename right about half the time. Where they fail consistently is the call signature. You will see generated code like &lt;code&gt;move_and_slide(velocity, Vector2.UP)&lt;/code&gt; which is valid Godot 3 and a runtime error in Godot 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;set_shader_param&lt;/code&gt; → &lt;code&gt;set_shader_parameter&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ShaderMaterial.set_shader_param(name, value)&lt;/code&gt; became &lt;a href="https://docs.godotengine.org/en/stable/classes/class_shadermaterial.html" rel="noopener noreferrer"&gt;&lt;code&gt;set_shader_parameter(name, value)&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;get_shader_param&lt;/code&gt; pair also renamed.&lt;/p&gt;

&lt;p&gt;This one is tricky because shader workflow tutorials from the Godot 3 era are high-quality and heavily SEO-optimized. When an AI reaches for shader code, it reaches for the tutorial it was trained on, and that tutorial says &lt;code&gt;set_shader_param&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;BUTTON_LEFT&lt;/code&gt; → &lt;code&gt;MOUSE_BUTTON_LEFT&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Mouse button constants got a prefix. &lt;code&gt;BUTTON_LEFT&lt;/code&gt;, &lt;code&gt;BUTTON_RIGHT&lt;/code&gt;, &lt;code&gt;BUTTON_MIDDLE&lt;/code&gt;, and the wheel constants are now &lt;a href="https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-mousebutton" rel="noopener noreferrer"&gt;&lt;code&gt;MOUSE_BUTTON_*&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Why it matters: this is a runtime-only failure. If your input handler checks &lt;code&gt;event.button_index == BUTTON_LEFT&lt;/code&gt;, the comparison never matches because &lt;code&gt;BUTTON_LEFT&lt;/code&gt; resolves to a different numeric value (or doesn't exist at all, depending on how the constant is scoped).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;rand_range&lt;/code&gt; → &lt;code&gt;randf_range&lt;/code&gt; / &lt;code&gt;randi_range&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Godot 3's &lt;code&gt;rand_range(min, max)&lt;/code&gt; returned a float. Godot 4 splits this into &lt;a href="https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#class-globalscope-method-randf-range" rel="noopener noreferrer"&gt;&lt;code&gt;randf_range&lt;/code&gt;&lt;/a&gt; (float) and &lt;a href="https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#class-globalscope-method-randi-range" rel="noopener noreferrer"&gt;&lt;code&gt;randi_range&lt;/code&gt;&lt;/a&gt; (int). The type-explicit naming is good. The fact that every AI assistant still generates &lt;code&gt;rand_range&lt;/code&gt; is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. &lt;code&gt;yield()&lt;/code&gt; → &lt;code&gt;await&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Godot 3 used a custom &lt;code&gt;yield()&lt;/code&gt; function for coroutines: &lt;code&gt;yield(get_tree().create_timer(1.0), "timeout")&lt;/code&gt;. Godot 4 &lt;a href="https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#awaiting-signals-or-coroutines" rel="noopener noreferrer"&gt;replaced this with standard &lt;code&gt;await&lt;/code&gt;&lt;/a&gt;: &lt;code&gt;await get_tree().create_timer(1.0).timeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the largest syntactic shift in the list, and the one where AI code breaks most visibly. &lt;code&gt;yield()&lt;/code&gt; in Godot 4 is a Python-style function that does not exist. You get an immediate parse error, which at least makes it easy to catch. Compare that to the mouse button case, which silently fails forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. &lt;code&gt;Directory&lt;/code&gt; → &lt;code&gt;DirAccess&lt;/code&gt; / &lt;code&gt;FileAccess&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The global &lt;code&gt;Directory&lt;/code&gt; class, and its pair &lt;code&gt;File&lt;/code&gt;, were both removed in Godot 4. The replacement is &lt;a href="https://docs.godotengine.org/en/stable/classes/class_diraccess.html" rel="noopener noreferrer"&gt;&lt;code&gt;DirAccess&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.godotengine.org/en/stable/classes/class_fileaccess.html" rel="noopener noreferrer"&gt;&lt;code&gt;FileAccess&lt;/code&gt;&lt;/a&gt;, both with static factory methods: &lt;code&gt;DirAccess.open(path)&lt;/code&gt; and &lt;code&gt;FileAccess.open(path, FileAccess.READ)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The old class-based API used &lt;code&gt;var f = File.new()&lt;/code&gt; followed by &lt;code&gt;f.open(path, File.READ)&lt;/code&gt;. AI assistants still reach for this because File I/O is one of the most commonly documented patterns and the old one was in wide circulation for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps happening
&lt;/h2&gt;

&lt;p&gt;Two forces make Godot particularly prone to this failure mode:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training data density favors Godot 3.&lt;/strong&gt; Godot 4 launched in March 2023. The bulk of high-quality community tutorials, Reddit answers, and StackOverflow posts were written before that. When an LLM computes "what is the most likely next token after &lt;code&gt;shader_&lt;/code&gt;," the frequency of &lt;code&gt;param&lt;/code&gt; in the training corpus still dominates &lt;code&gt;parameter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Godot 4 minor versions keep changing signatures.&lt;/strong&gt; The &lt;a href="https://godotengine.org/releases/4.5/" rel="noopener noreferrer"&gt;Godot 4.5 release notes&lt;/a&gt; mention variadic function signature changes that broke compatibility with 4.3 and 4.4. Even a model with training data that ends in late 2025 has not seen enough 4.5 examples to correctly weight them.&lt;/p&gt;

&lt;p&gt;The result: the newer your Godot version, the worse any general-purpose coding assistant performs. This is not a problem that gets solved by a bigger context window. It gets solved by giving the model the current API, either through retrieval-augmented generation against Godot's docs or through a purpose-built plugin that can run the code and test the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to work around it
&lt;/h2&gt;

&lt;p&gt;Three things help:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Paste the &lt;a href="https://gist.github.com/raulsntos/06ac5dd10ebccc3a4f1e7e3ad30dc876" rel="noopener noreferrer"&gt;Godot 4.x breaking changes list&lt;/a&gt; into your system prompt.&lt;/strong&gt; It is a short file maintained by a Godot contributor and it catches most of the renames. A 2-page addendum to the system prompt is worth more than any clever reasoning you can add.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use an agent that runs the code it generates.&lt;/strong&gt; A parse error catches &lt;code&gt;yield()&lt;/code&gt;. A runtime test catches &lt;code&gt;BUTTON_LEFT&lt;/code&gt;. The mouse button case specifically cannot be caught by any static linter because the comparison is syntactically valid. Tools like &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; solve this by running the generated code against a live Godot editor and surfacing runtime errors back to the model, but any setup that executes the code will catch the silent-failure cases that matter most.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prefer static typing.&lt;/strong&gt; Godot 4 supports &lt;code&gt;var velocity: Vector2&lt;/code&gt;. Typed variables turn some of these hallucinations into editor errors. It does not help with constants or function names, but it catches a surprising number of incompatible signature cases.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are shipping a Godot game in 2026 and letting AI generate scripts, the practical move is to pair any generation step with an execution step. "Does it compile" is not the right test. The right test is "does the node still behave correctly when you run the scene." Everything else is theater.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>ai</category>
      <category>gamedev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Godot 4.4 Added .uid Files Everywhere. Here's What They Actually Do.</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Mon, 20 Apr 2026 00:38:57 +0000</pubDate>
      <link>https://dev.to/ziva/godot-44-added-uid-files-everywhere-heres-what-they-actually-do-4e56</link>
      <guid>https://dev.to/ziva/godot-44-added-uid-files-everywhere-heres-what-they-actually-do-4e56</guid>
      <description>&lt;p&gt;If you upgraded a Godot project from 4.3 to 4.4 and suddenly saw a wave of new &lt;code&gt;.uid&lt;/code&gt; files next to every &lt;code&gt;.gd&lt;/code&gt; and &lt;code&gt;.gdshader&lt;/code&gt;, you were not alone. Every GitHub thread I found from early 2025 had someone asking whether to delete them, gitignore them, or commit them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://godotengine.org/article/uid-changes-coming-to-godot-4-4/" rel="noopener noreferrer"&gt;The answer is: commit them.&lt;/a&gt; They are not noise. They fix a real problem Godot has had since day one, and understanding them is the difference between a clean refactor and a broken scene tree the next morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem the .uid files solve
&lt;/h2&gt;

&lt;p&gt;Before 4.4, Godot referenced every resource by its file path. If you moved &lt;code&gt;res://scripts/player.gd&lt;/code&gt; to &lt;code&gt;res://entities/player/player.gd&lt;/code&gt; without doing it through the editor, every scene that referenced that script would break. The typical workflow for a larger team looked like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Refactor the folder structure outside the editor (in VS Code or the terminal)&lt;/li&gt;
&lt;li&gt;Reopen Godot&lt;/li&gt;
&lt;li&gt;Watch half your scenes fail to load&lt;/li&gt;
&lt;li&gt;Manually re-link every broken reference in the inspector&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Godot already used UIDs for scenes and most imported resources. But &lt;a href="https://godotengine.org/article/uid-changes-coming-to-godot-4-4/" rel="noopener noreferrer"&gt;scripts and shaders were excluded&lt;/a&gt; because they are plain text files with no place to store engine metadata. You moved &lt;code&gt;player.gd&lt;/code&gt; and Godot had no way to know it was still the same script.&lt;/p&gt;

&lt;p&gt;The 4.4 release solves that with sidecar files. &lt;code&gt;player.gd&lt;/code&gt; now ships with &lt;code&gt;player.gd.uid&lt;/code&gt; alongside it. The &lt;code&gt;.uid&lt;/code&gt; file contains a single line: a UID like &lt;code&gt;uid://c82j4l3r4k4n2&lt;/code&gt;. Every scene that references that script stores the UID, not the path. Move the file to another folder, and Godot finds it through the UID.&lt;/p&gt;

&lt;h2&gt;
  
  
  What file types got .uid files
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://godotengine.org/article/uid-changes-coming-to-godot-4-4/" rel="noopener noreferrer"&gt;Godot blog post&lt;/a&gt; is explicit about which types are affected:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source file&lt;/th&gt;
&lt;th&gt;Sidecar file&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.gd&lt;/code&gt; (GDScript)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gd.uid&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.cs&lt;/code&gt; (C#)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cs.uid&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.gdshader&lt;/code&gt; (shader)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gdshader.uid&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.gdshaderinc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gdshaderinc.uid&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Scenes (&lt;code&gt;.tscn&lt;/code&gt;) and most imported resources already had UIDs stored in their own headers since earlier versions, so they did not need sidecars. This change is specifically for the plain-text formats that previously had no UID hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  The .gitignore mistake
&lt;/h2&gt;

&lt;p&gt;The single biggest footgun with the new system is treating &lt;code&gt;.uid&lt;/code&gt; files like build artifacts and adding them to &lt;code&gt;.gitignore&lt;/code&gt;. I have seen this in multiple public Godot repos since the 4.4 release. If you gitignore &lt;code&gt;.uid&lt;/code&gt;, every clone of your repo generates fresh UIDs, and every scene that references your scripts points at UIDs that exist only on the original author's machine. The project opens, the scripts technically load, but the scene-to-script links are now broken for every collaborator.&lt;/p&gt;

&lt;p&gt;The correct answer, per the Godot team: &lt;a href="https://godotengine.org/article/uid-changes-coming-to-godot-4-4/" rel="noopener noreferrer"&gt;commit the &lt;code&gt;.uid&lt;/code&gt; files to version control&lt;/a&gt;. They are part of your project state, not generated output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for external refactoring
&lt;/h2&gt;

&lt;p&gt;The part I like most is that moving files outside the editor is now officially supported. Before 4.4, the unwritten rule was "always do refactors inside Godot." Now you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Close Godot&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git mv&lt;/code&gt; or &lt;code&gt;mv&lt;/code&gt; on any script or shader, together with its &lt;code&gt;.uid&lt;/code&gt; sidecar&lt;/li&gt;
&lt;li&gt;Reopen Godot&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The editor looks up the UID, finds the file in its new location, and updates the path in any referencing scene when you save. Nothing breaks. This is the kind of affordance that only matters when your project is large enough that "refactor inside the editor" becomes tedious, but at that point it matters a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  One gotcha: the UID cache can lag
&lt;/h2&gt;

&lt;p&gt;There is &lt;a href="https://github.com/godotengine/godot/issues/112509" rel="noopener noreferrer"&gt;a known bug&lt;/a&gt; where renaming a scene file externally does not immediately update the UID cache, which can cause temporary stale references. The fix is to reopen the project so Godot rescans the filesystem. The bug is open as of writing and affects the 4.4 line; the 4.6 release did not mark it as fixed. If you hit it, reopening the project clears it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this has to do with AI coding tools
&lt;/h2&gt;

&lt;p&gt;Every AI assistant trained on pre-4.4 Godot does not know what a &lt;code&gt;.uid&lt;/code&gt; file is. Ask ChatGPT or a generic coding assistant to refactor your Godot project's folder structure, and it will happily suggest moving scripts without moving their &lt;code&gt;.uid&lt;/code&gt; sidecars. You run the suggested commands, reopen the editor, and watch your scenes break in the exact way the UID system was designed to prevent.&lt;/p&gt;

&lt;p&gt;Tools that run natively inside the Godot editor (there are a handful now, &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; being one of them) have a structural advantage here: they can read the current UID index directly, know which sidecar belongs to which source file, and keep them together on any file operation. The generic chatbot approach cannot do this. It can only suggest; it cannot see.&lt;/p&gt;

&lt;p&gt;If you are evaluating AI tooling for Godot work, this is the kind of thing to test. Ask your candidate tool to move &lt;code&gt;player.gd&lt;/code&gt; from one folder to another and see whether it touches the &lt;code&gt;.uid&lt;/code&gt; file. If not, expect broken scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical migration checklist
&lt;/h2&gt;

&lt;p&gt;If you are upgrading a Godot 4.3 project to 4.4 or later today, three things make the transition painless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open the project in 4.4 and re-save every scene.&lt;/strong&gt; The editor regenerates UID references in each scene file, which is what makes the sidecars useful. This is a one-time cleanup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove any &lt;code&gt;*.uid&lt;/code&gt; entry from your &lt;code&gt;.gitignore&lt;/code&gt; if it exists.&lt;/strong&gt; Commit the files. They are ~30 bytes each and they are part of the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When moving files externally, always move the &lt;code&gt;.uid&lt;/code&gt; alongside.&lt;/strong&gt; &lt;code&gt;git mv player.gd entities/player/player.gd &amp;amp;&amp;amp; git mv player.gd.uid entities/player/player.gd.uid&lt;/code&gt; is the pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UID system is one of the Godot 4.x changes that feels invisible until it saves you an afternoon of re-linking references. Treat the sidecar files the way you treat &lt;code&gt;package-lock.json&lt;/code&gt; or &lt;code&gt;Cargo.lock&lt;/code&gt;: commit them and forget about them.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
