<?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: CGT Rocket X Racer</title>
    <description>The latest articles on DEV Community by CGT Rocket X Racer (@cgtrocketx_dev).</description>
    <link>https://dev.to/cgtrocketx_dev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4199459%2Febf519b7-873f-41bf-b865-777d03388ab5.png</url>
      <title>DEV Community: CGT Rocket X Racer</title>
      <link>https://dev.to/cgtrocketx_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cgtrocketx_dev"/>
    <language>en</language>
    <item>
      <title>Shipping a 3D Godot 4 game to the desktop browser: what we changed for performance</title>
      <dc:creator>CGT Rocket X Racer</dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:31:36 +0000</pubDate>
      <link>https://dev.to/cgtrocketx_dev/shipping-a-3d-godot-4-game-to-the-desktop-browser-what-we-changed-for-performance-30ol</link>
      <guid>https://dev.to/cgtrocketx_dev/shipping-a-3d-godot-4-game-to-the-desktop-browser-what-we-changed-for-performance-30ol</guid>
      <description>&lt;p&gt;CGT Rocket X Racer is a fan-made 3D runner we build in Godot 4 and play in a desktop browser. You fly a hovering rocket car through a large factory made of many separate halls, then out over the rooftops and back in. That is a lot of geometry for a web build. This post collects the performance decisions that mattered most for us. I'm describing techniques, not quoting benchmark numbers, because results depend heavily on the hardware.&lt;/p&gt;

&lt;p&gt;Fan-made, not affiliated with Tesla. Desktop-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pick the renderer once, and use it everywhere
&lt;/h2&gt;

&lt;p&gt;Godot's web export only supports the Compatibility renderer (OpenGL ES 3 / WebGL 2). We made the same choice for every platform. The game looks identical everywhere, it runs on older GPUs, and we only have one set of materials and lighting to tune.&lt;/p&gt;

&lt;p&gt;The trade-off is real: the Mobile and Forward+ renderers give better lighting and post-processing. Switching native builds later is a one-line project setting, but every material and light would need checking again. For us, "looks the same in the browser as in testing" was worth more than the extra effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The no-threads web template
&lt;/h2&gt;

&lt;p&gt;We export with the no-threads web template. It doesn't need SharedArrayBuffer, so it also doesn't need the cross-origin isolation headers (COOP/COEP) that threaded builds require. In practice that means the build runs on any static host and is easier to embed or deploy.&lt;/p&gt;

&lt;p&gt;The cost is that you can't rely on threads in the browser the way you can on native. Anything that loads a lot of data has to be designed so it doesn't stall the main loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Render 3D below native resolution, keep the UI sharp
&lt;/h2&gt;

&lt;p&gt;Fill rate is the first wall you hit in a browser on a high-DPI screen. Our approach on web and mobile is to render only the 3D viewport at a reduced scale, while the UI canvas stays at native resolution. The scale comes from the device pixel ratio and the window's pixel count, clamped to a sensible range, with bilinear filtering. There's also a project-level fallback scale in case the script never runs.&lt;/p&gt;

&lt;p&gt;The result: the world is a little softer, but text and HUD stay crisp, and crisp text is what players notice. On a normal desktop window the scale stays at full resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Near and far versions of every hall
&lt;/h2&gt;

&lt;p&gt;The factory is made of many hall models. Each hall exists twice: a detailed "near" file and a cheap "far" shell that keeps the silhouette and landmarks. A site streamer swaps between them by distance.&lt;/p&gt;

&lt;p&gt;Obstacles use the same idea with Godot visibility ranges. The detailed LOD0 mesh is only drawn close to the camera, and LOD1 takes over beyond that. Both levels share one animated hierarchy: the LOD1 meshes are grafted onto the LOD0 nodes. Animation runs once, and collision reads the same transforms.&lt;/p&gt;

&lt;p&gt;This matters most when the route climbs out above the roofs, where you can suddenly see the whole site.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Fewer surfaces, fewer draw calls
&lt;/h2&gt;

&lt;p&gt;Draw calls are expensive in WebGL. A few habits helped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge by material at import.&lt;/strong&gt; A post-import script merges the car, coins and parts into one mesh with one surface per material and regenerates LODs. It keeps the named marker nodes we need, like nozzle positions, as Marker3D.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MultiMesh for pickups.&lt;/strong&gt; Coins and parts are drawn with MultiMesh instead of one node per pickup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bake debuffs per pivot.&lt;/strong&gt; The debuff obstacles are baked into a few merged meshes per moving pivot, to stay inside our draw budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bake instanced site geometry.&lt;/strong&gt; The site was originally authored elsewhere. Our export script bakes each instanced mesh into one merged mesh per material with vertex colours before it becomes a GLB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Keep textures in check
&lt;/h2&gt;

&lt;p&gt;Obstacle textures come in as KTX2. Godot imports them and re-compresses to VRAM formats for the web build (S3TC and ETC2 in the pack). We also cap texture sizes per map type: base colour gets the most resolution, while ORM and emissive maps get far less. When two LOD levels had byte-identical textures, we imported the far one without images and let its meshes reuse the near materials. That avoids loading the same texture twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Cheap effects
&lt;/h2&gt;

&lt;p&gt;Mist from the car's nozzles and pickup sparkles use CPUParticles3D, and the emitters are pooled rather than created per event. Pickup sparkles are parented to the car so they keep up at high speed without extra work. The crash effect is a short screen flash and a decaying camera shake. Both are cheap, and both do a lot for feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Audio that respects the browser
&lt;/h2&gt;

&lt;p&gt;Browsers block audio until the user interacts with the page. Instead of fighting that, we don't create any audio players or play anything until the first real tap, click or key press. That avoids autoplay warnings and wasted work at startup. Our placeholder sound effects are also generated procedurally by a small script, so there are no downloaded audio files to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Measure at fixed points, not just "it feels fine"
&lt;/h2&gt;

&lt;p&gt;The project has a bench mode. It runs the game under a virtual framebuffer at a fixed resolution and frame rate, and records performance monitors at specific route points: a heavy press, the sign approaches, the garage and a worst-case combination of hazards. There's a separate mode for measuring the in-race HUD alone. Fixed checkpoints mean "this change made the press area slower" is something you can actually see, instead of a vibe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting a 3D Godot web game
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Decide on Compatibility and no-threads early, and design around them.&lt;/li&gt;
&lt;li&gt;Split 3D resolution from UI resolution.&lt;/li&gt;
&lt;li&gt;Build near/far versions of big environment chunks before you need them.&lt;/li&gt;
&lt;li&gt;Merge meshes by material and use MultiMesh for anything repeated.&lt;/li&gt;
&lt;li&gt;Make performance measurable at named points on your level.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to see how it holds up in your own browser, the game is playable on desktop at &lt;a href="https://cgt.monster/x/devto2" rel="noopener noreferrer"&gt;https://cgt.monster/x/devto2&lt;/a&gt;. Feedback on frame pacing is very welcome.&lt;/p&gt;

&lt;p&gt;Fan-made, not affiliated with Tesla. Desktop-only.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Making a hover car feel like it flies: notes from a Godot 4 runner</title>
      <dc:creator>CGT Rocket X Racer</dc:creator>
      <pubDate>Thu, 22 Oct 2026 03:48:07 +0000</pubDate>
      <link>https://dev.to/cgtrocketx_dev/making-a-hover-car-feel-like-it-flies-notes-from-a-godot-4-runner-55j3</link>
      <guid>https://dev.to/cgtrocketx_dev/making-a-hover-car-feel-like-it-flies-notes-from-a-godot-4-runner-55j3</guid>
      <description>&lt;p&gt;CGT Rocket X Racer is a fan-made, desktop-only rocket-car runner we have been building in Godot 4. It is not affiliated with any car maker. You steer a hovering car through a giant factory: lane changes, jumps, rolls and a boost, with presses and robot arms trying to ruin your run. This post is about three problems that turned out to matter far more than we expected: making the car feel airborne, switching between indoor and outdoor sections without breaking the camera, and proving that every obstacle is actually fair.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Flight feel is mostly about thrust you can see
&lt;/h2&gt;

&lt;p&gt;Our first builds had a car that simply floated at a fixed height above the track. It worked, but it looked like a sticker sliding on glass. The fix was not physics, it was feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous underbody jets.&lt;/strong&gt; The car always has mist jets running underneath it. They never turn off, so the player's eye always has something tying the car to the ground below. That alone made the hover height believable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nozzles that sit where they should.&lt;/strong&gt; The car model has named marker nodes for every nozzle. At import time a post-import script merges the meshes by material but keeps those markers, and the car script reads the nozzle positions from the imported model at runtime. We moved the side nozzles back behind the front wheel arch so side mist reads as steering rather than exhaust. Then we wrote a small check tool that renders the car and confirms every mist emitter sits exactly on its marker. It sounds pedantic, but when skins swap the car model, the nozzles still line up, because both the base car and the skins are read the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A boost with no cooldown.&lt;/strong&gt; This is the rule in the current build, and it went through several iterations before we were happy with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A tap boosts for 1.5 seconds, ramping linearly up to 370.&lt;/li&gt;
&lt;li&gt;Holding the button keeps boosting at about 370, and it holds there.&lt;/li&gt;
&lt;li&gt;When you release, speed eases back down to natural speed over 1 second.&lt;/li&gt;
&lt;li&gt;It behaves the same indoors and outdoors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We tried cooldown-based boosts earlier. They made players wait instead of play. Removing the cooldown and putting the skill into &lt;em&gt;when&lt;/em&gt; you release turned boost into a steering tool: you feather it to line up a gap, then let it ease off before a hazard. The linear ramp matters too. An instant jump to top speed felt like a teleport; a short ramp feels like thrust building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small, cheap juice.&lt;/strong&gt; Pickups get a short sparkle burst parented to the car so it keeps up at high speed, and the coin itself pops in scale for a fraction of a second. A crash is a single screen flash (no strobe) plus a short decaying camera shake. None of this is expensive, and all of it adds to the sense of speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Indoor and outdoor switching
&lt;/h2&gt;

&lt;p&gt;The route is one long closed loop through a factory site made of many separate hall models. The run keeps alternating: inside a hall, burst out above the roofs, fly through a big SUPER FACTORY sign tower, dive back in, and eventually fly into an end garage where the car brakes, hovers and the camera orbits before the results screen.&lt;/p&gt;

&lt;p&gt;Three things made this workable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming and LOD.&lt;/strong&gt; Each hall exists as a detailed "near" model and a cheap "far" shell. A site streamer loads halls on a background thread and swaps LOD by distance. Without this, flying above the roofs, where you can suddenly see everything, would tank the frame rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A rail-locked camera, with its own test.&lt;/strong&gt; The camera follows the route frame rather than free physics. Transitions between tight halls and open sky are exactly where cameras clip into walls, so we added a dedicated camera test mode that flies the whole loop in all three lanes and reports clipping. Running it after every route change is far cheaper than finding clipping by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outdoors changes the look, not the rules.&lt;/strong&gt; Outside, the sky is brighter and there is much less fog, so leaving a hall feels like a release. We deliberately do not change speed outdoors, and boost behaves exactly the same. Players should never have to relearn the controls because the ceiling disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Proving obstacles are fair
&lt;/h2&gt;

&lt;p&gt;Runners live or die on fairness. If a player crashes and thinks "that was impossible", they quit. So we stopped trusting our eyes and started auditing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collision that matches the visuals.&lt;/strong&gt; The car uses three oriented boxes fitted to its body, including during banking and barrel rolls. Obstacles use boxes attached to their animated nodes: the press head, posts, top beam and die plate; the arm's upper arm, forearm, wrist and gripper. Because collision reads the same transforms as the visuals, there is no invisible hitbox. On a hit, the car and the obstacle clock are bisected back to the moment of contact and frozen, so nothing is ever drawn inside the car on the crash screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic obstacle timing.&lt;/strong&gt; The robot arm animation is sampled by the car's distance along the track, not by wall-clock time. That means every arm pattern has fixed safe lanes and jump windows at any speed, which makes them auditable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An audit mode.&lt;/strong&gt; A headless audit run tests every press pattern at every phase of its cycle at several speeds, and checks that the free lane is never hit. It also measures arm jump windows and rejects patterns where the window is too short to be fair. Indoor arm patterns always leave a stay-safe lane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pickups never sit inside danger.&lt;/strong&gt; Each candidate coin or part is tested against the obstacle's boxes over its whole motion, and dropped if it touches. Nothing is worse than a coin that baits you into a press.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debuffs with their own colors.&lt;/strong&gt; Besides presses and arms there are debuffs such as red tape, a roadside media wave, an approval gate and a charging bear. Each has a distinct warning color family, so players can read what is coming before they reach it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few smaller notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Everything runs on Godot's Compatibility renderer, and the web build uses the no-threads template, so it runs from a plain static host with no special headers.&lt;/li&gt;
&lt;li&gt;Along the way you collect five part types: body, chip, motor, thrust and energy.&lt;/li&gt;
&lt;li&gt;There are ten skins in the current build; confirming a skin saves it and applies it to the car.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to feel the boost rule for yourself, CGT Rocket X Racer is playable in a desktop browser (desktop-only for now):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.cgt.monster/x/devto" rel="noopener noreferrer"&gt;https://www.cgt.monster/x/devto&lt;/a&gt;&lt;/strong&gt; · invite code &lt;strong&gt;48WYTRMMDW&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fan-made · Not affiliated · Desktop-only. Feedback on flight feel and obstacle readability is the most useful thing you can send us.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>gamedev</category>
      <category>indiedev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
