[-] Whappens@lemmy.world 3 points 2 weeks ago* (last edited 2 weeks ago)

Hi, I'm glad you like it! Currently I just started, so have only this. Possible to add something else fitting the game, but what's already decided: ships will be modular and have a crew with personalities, please keep watching.

15
submitted 2 weeks ago* (last edited 2 weeks ago) by Whappens@lemmy.world to c/godot@programming.dev

Project: Hadean chronicles.

Drunk yaw. Ships were flying like they’d had one too many at the cantin: pure proportional yaw meant no drag on angular velocity, so every heading correction sailed past the target and wobbled back. Dropped a D term on angular velocity. Now they settle like they mean it.

Docking death-spiral. The behavior planner checked emergency HP before docked status. A docked ship still patching its hull got reclassified as “emergency” every tick, which silently disabled both healing and hazard immunity. Reordered: docked wins. Always. No exceptions.

The overshoot plague. Ships barreled into docks and loot at max throttle, tunneling clean through the interaction radius on pure momentum. Same thing at hazards: they started the avoidance turn fine, but inertia carried them into the danger zone anyway. One shared cause, one shared fix — STEERING_ARRIVAL_RADIUS now clamps approach speed as targets get close. It’ll tune per capture radius later, as long as it stays under 65 so combat orbit hysteresis doesn’t get confused.

Combat strobe light. A single hard distance threshold meant ships at the boundary flickered between orbit and charge — two headings ~90° apart, flipping every frame. Replaced with hysteresis: separate enter/exit radii, orbit state remembered between ticks. Stable at all ranges.

“0 units from hazard.” Every planet was screaming about a collision that didn’t exist because Position was first assigned in _PhysicsProcess, and synchronous scene-load spawns beat it to the punch. Seeded position eagerly at setup with the same orbital formula and delta=0; physics ticks just update from there.

The invisible fleet. Everything rendered: ships, sun, background, all sharing the correct World3D. But there was no Camera3D — only a leftover Camera2D from the before-times. Zero errors, zero output, zero fleet on screen. Added a proper top-down Camera3D, evicted the dead legacy background scene that was still being instanced alongside the new one, and the armada reappeared.

Tiny win. Velocity-clamp check was doing a sqrt every tick for the 99% happy path where nothing needed clamping. Swapped to a squared-threshold compare. Minimal, but it adds up.

7
submitted 2 weeks ago* (last edited 2 weeks ago) by Whappens@lemmy.world to c/godot@programming.dev

Archived footage of star system view before the global refactoring.

How global singletons were killed, async race conditions resolved, and the door for custom player modifications opened

❌ The Problem

  • Development stagnation: Monolithic generation and logic controller bottlenecked project progress.
  • High risks: Minor changes threatened to break completely unrelated systems.
  • Cascading failures: Removing the legacy global singleton caused endless compilation errors.
  • Pointer errors: Instantiating subsystems before scene tree attachment broke configuration loading ($NullPointer$).

🔄 The Struggle

  • Dead-end refactoring: Trimming the singleton without changing architecture immediately broke builds.
  • Rigid hardcoding: Directory paths only functioned within one strictly defined file structure.
  • Modding unviability: Static paths completely prevented players from installing custom user mods.

The Solution

  • Separation of concerns: Monolith split into independent submodules via Single Responsibility Principle.
  • Dependency injection: Global singletons replaced with explicit, controlled dependency passing.
  • Asynchronous readiness: Added scene tree checks before generation, eliminating race conditions.
  • Dynamic discovery: Engine now automatically locates resources regardless of folder structure.
  • Language migration: Rewrote code from GDScript to C# for efficient modular development.
[-] Whappens@lemmy.world 3 points 2 weeks ago

Hi, thanks! Yes, this is my solo indie game going. Your planets look good, I like it. One sprite with one shader is good for performance. I use 2 shaders (terrain and clouds) with multiple textures mix currently.

31

The architectural evolution of the rendering pipeline in the space 4X/roguelite hybrid Project: Hadean recently shifted from an isolated multi-viewport structure to a single, high-performance 3D scene.

The Discarded Path: The Hybrid 2D/3D Viewport Setup

The initial rendering layer nested an isolated 3D SubViewport inside a 2D SubViewportContainer for every single planet. This approach aimed to let 2D orbit physics coexist with fully shaded 3D planetary surfaces. However, scaling this architecture introduced severe performance bottlenecks:

  • Lifecycle Race Conditions: Initialization logic inside _enter_tree() triggered frequent ERR_FILE_NOT_FOUND engine failures because global configuration files loaded before @onready node references were resolved. Moving setup to _ready() and caching node references resolved the issue, eliminating frame micro-stutters caused by repeated get_node_or_null() lookups.
  • VRAM and Resolution Spikes: Every planet rendered its own isolated World3D, duplicating render passes and light setups. Forcing runtime viewport resizing between 64×64 and 2048×2048 via UPDATE_WHEN_VISIBLE failed to keep VRAM overhead within stable bounds as the simulated solar systems expanded.

The Breakthrough: Migration to a Unified World3D

To ensure stable VRAM usage and fluid frame rates, the multi-viewport architecture was completely dismantled. All planetary bodies now inhabit a single, monolithic Node3D scene sharing a common World3D instance, viewed through a locked top-down orthographic Camera3D that handles panning and zoom. Real-time shadows are now efficiently handled via distance-culling rather than being baked per-object.

Shading and Orthographic Lighting Refactors

Planet shading completely overrides Godot’s default PBR lighting pass for maximum performance. Custom lighting logic is executed directly inside the light() shader function, utilizing the ATTENUATION parameter for pure occlusion mapping.

Fixing the sun lighting system to track correct orbital positions under top-down orthography required three major iterations:

  1. DirectionalLight3D: Produced parallel light rays from infinity, causing the day/night terminator line to twist 90–180° during planet orbits.
  2. Synced Global Vectors: Rebuilding shaders to use MODEL_MATRIX * vec4(TANGENT, 0.0) isolated normal mapping from camera transformations, preventing normals from collapsing into funnel-shaped artifacts at the poles. However, directional lighting still failed to track the real on-screen position of a nearby point-source sun.
  3. OmniLight3D Nodes: Spawning an OmniLight3D node at the exact spatial coordinates of the sun successfully resolved the polar vector collapse and fixed the terminator tracking.

Resolved Migration Traps

  • Tree Membership Trap: Querying GlobalPosition during synchronous node setup before integration into the active scene graph caused engine failures with !is_inside_tree() warnings. Implementing explicit is_inside_tree() validation guards resolved the hidden failures.
  • Unit Scaling Discrepancy: A legacy sprite-era pixel scale factor was accidentally mapped to the 3D mesh transformation, shrinking planets down to ~6 world units. Metrics are now strictly locked to canonical world-unit radius fields.

Whappens

0 post score
0 comment score
joined 2 weeks ago