Compare commits
122 Commits
refactor/d
...
a10f48cb29
| Author | SHA1 | Date | |
|---|---|---|---|
| a10f48cb29 | |||
| f4ca2a8074 | |||
| d0c1c72940 | |||
| 2e9363f303 | |||
| 8423dfd969 | |||
| 90585cde4b | |||
| 66d1de1411 | |||
| 918f2b4ef3 | |||
| 8841bb7dd4 | |||
| c280994225 | |||
| 3966b7dc69 | |||
| 9daef1ce4e | |||
| 1822b50399 | |||
| b7ce8338d9 | |||
| 1eb479fe30 | |||
| a57cc827f8 | |||
| 1e5a30173a | |||
| 3acd210826 | |||
| 23ace96817 | |||
| 613ce79fcb | |||
| 5d6b1b809c | |||
| a15f5ef2f0 | |||
| 7849258d01 | |||
| dcb805eaca | |||
| 4d2a4b1526 | |||
| 95198e6687 | |||
| 852475ba77 | |||
| 33d3543c93 | |||
| 916172d0b2 | |||
| 94e33c0cba | |||
| 24ce1edb38 | |||
| 8fd540ddfc | |||
| 4ddcbd8000 | |||
| 8968586b0f | |||
| 8f641ab36e | |||
| 9ae32ca6a9 | |||
| ae18d1456a | |||
| ab17242804 | |||
| a5dec9c2fd | |||
| e271c59837 | |||
| f8d140a9b0 | |||
| 67a4b7038a | |||
| 18f9a4fec7 | |||
| 4796a2d5ca | |||
| f8578bc3f2 | |||
| a7583637e9 | |||
| 86762d0d50 | |||
| e2da700bcd | |||
| 466dff11d0 | |||
| 636123344b | |||
| 25d9d55044 | |||
| aafb939cbf | |||
| 3d01edb2d9 | |||
| 398ec829ae | |||
| ec69ed2ee5 | |||
| 3647aa599d | |||
| 1342ca2610 | |||
| 27ce796898 | |||
| cff5ec27f8 | |||
| 245be4a4f5 | |||
| 6b9efda0d2 | |||
| 29851ea167 | |||
| 0cd9ebdd04 | |||
| 4da8bcaec2 | |||
| 71ad2f09ff | |||
| 2f5a88345f | |||
| 5e851049b5 | |||
| c4fd7f1330 | |||
| 820da83397 | |||
| 14b24beb23 | |||
| 60f2ddb3d7 | |||
| c50d0eae52 | |||
| d375e0d208 | |||
| 9b128a3540 | |||
| 6df457a256 | |||
| bc443b884c | |||
| 8184ec06b4 | |||
| 926a64c3dd | |||
| 29f9bccfd3 | |||
| 7d7580a123 | |||
| 59d457e9ae | |||
| 97ccb2a9ac | |||
| 1ab2c06336 | |||
| f51672c6a9 | |||
| 8e3f415cb4 | |||
| cdcb4796f7 | |||
| 24bc3afd2e | |||
| 4f78de64ba | |||
| fe050897dd | |||
| e075ff580d | |||
| 90e756ad28 | |||
| faf8e7c83a | |||
| 772f9c7df3 | |||
| e066bc4786 | |||
| cc681ae08a | |||
| 138e17503a | |||
| 20a37dda17 | |||
| 8645d2bdc4 | |||
| d8055752d5 | |||
| 21bbbacbbe | |||
| d18c87a051 | |||
| a89154a1c2 | |||
| c662714997 | |||
| 6a4492ef37 | |||
| 85815d957b | |||
| ec6ca92360 | |||
| f25464df03 | |||
| 2ceddb2bbf | |||
| 1228a79cae | |||
| 425e857ba9 | |||
| 588fa29484 | |||
| c14b07d24f | |||
| c61fa2b917 | |||
| 9dac569ad6 | |||
| 63d6a67d97 | |||
| c79e016503 | |||
| d03a00cbce | |||
| 5f30ab6c2f | |||
| 35d3110818 | |||
| 3d85dda4db | |||
| 02908f114c | |||
| c2a0b6c026 |
12
.gitea/scripts/setup_editor.sh
Normal file
12
.gitea/scripts/setup_editor.sh
Normal file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Config: The exact engine version this game project depends on
|
||||
# You update this hash when you upgrade the engine
|
||||
ENGINE_VERSION="a90daf08c2bb52d6cb4ba67bb5cbe09d79b2c4eb"
|
||||
|
||||
echo "Fetching Custom Editor [${ENGINE_VERSION}]..."
|
||||
|
||||
# Download from your Registry
|
||||
curl -O "https://gitea.212.63.210.91.nip.io/api/packages/seedlingattempt/generic/godot-editor-windows/${ENGINE_VERSION}/godot-editor-windows.zip"
|
||||
|
||||
unzip -o godot-editor-windows.zip
|
||||
echo "Editor ready! Run ./godot.windows.editor...exe to start."
|
||||
109
.gitea/workflows/build-game.yaml
Normal file
109
.gitea/workflows/build-game.yaml
Normal file
@ -0,0 +1,109 @@
|
||||
name: Build Game
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- infra/export-chain
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
export-game:
|
||||
name: Export Game Client
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Game
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare Build Environment
|
||||
env:
|
||||
# 1. CONFIGURATION
|
||||
# The Git SHA of the Engine Build you want to use
|
||||
ENGINE_HASH: "e40804a4aec8fad5d3215766045ff7ba21e59ac9"
|
||||
|
||||
# The internal version string Godot expects for templates
|
||||
# CRITICAL: Run your custom editor, check "Help -> About" to find this exact string.
|
||||
# It is usually "4.3.stable.custom_build" unless you changed version.py
|
||||
GODOT_VERSION: "4.6.dev.double"
|
||||
|
||||
# Registry details
|
||||
REGISTRY_URL: https://gitea.212.63.210.91.nip.io/api/packages/seedlingattempt/generic
|
||||
TOKEN: ${{ secrets.USER_PACKAGE_PASSWORD }}
|
||||
run: |
|
||||
echo "--- 1. FETCHING TOOLS ---"
|
||||
|
||||
# Download Linux Editor (The Builder)
|
||||
echo "Downloading Editor..."
|
||||
wget "$REGISTRY_URL/godot-editor-linux/$ENGINE_HASH/godot-editor-linux.zip"
|
||||
|
||||
# Download Templates (The Payload)
|
||||
echo "Downloading Templates..."
|
||||
wget "$REGISTRY_URL/godot-templates/$ENGINE_HASH/templates.tpz"
|
||||
|
||||
echo "--- 2. INSTALLING ---"
|
||||
|
||||
# Unzip Editor
|
||||
unzip -o godot-editor-linux.zip
|
||||
# Find the binary (it has no extension)
|
||||
EDITOR_BIN=$(find . -maxdepth 1 -type f -name "godot.linuxbsd.editor*" | head -n 1)
|
||||
chmod +x "$EDITOR_BIN"
|
||||
mv "$EDITOR_BIN" ./godot_headless
|
||||
echo "Editor ready: ./godot_headless"
|
||||
|
||||
# Install Templates
|
||||
# Godot looks in ~/.local/share/godot/export_templates/{VERSION}/
|
||||
TEMPLATE_DIR="$HOME/.local/share/godot/export_templates/$GODOT_VERSION"
|
||||
mkdir -p "$TEMPLATE_DIR"
|
||||
|
||||
echo "Extracting templates to $TEMPLATE_DIR..."
|
||||
unzip -o templates.tpz -d "$TEMPLATE_DIR"
|
||||
|
||||
# Verify files are where Godot expects them
|
||||
ls -l "$TEMPLATE_DIR"
|
||||
|
||||
- name: Export Windows Client
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
|
||||
# "Windows Desktop" must match the name in your export_presets.cfg
|
||||
../godot_headless --headless --export-release "Windows Desktop" ../build/windows/game.exe
|
||||
|
||||
- name: Export Linux Client
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
|
||||
# "Linux" must match the name in your export_presets.cfg
|
||||
../godot_headless --headless --export-release "Linux" ../build/linux/game.x86_64
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: game-clients
|
||||
path: build/
|
||||
|
||||
- name: Publish Nightly Build
|
||||
env:
|
||||
# Config
|
||||
PACKAGE_NAME: moa-windows-nightly
|
||||
# We overwrite the 'nightly' version every time so the URL stays the same
|
||||
VERSION: nightly
|
||||
ZIP_NAME: moa-windows-nightly.zip
|
||||
|
||||
# Auth
|
||||
API_URL: https://gitea.212.63.210.91.nip.io/api/packages/${{ gitea.repository_owner }}/generic
|
||||
TOKEN: ${{ secrets.USER_PACKAGE_PASSWORD }}
|
||||
run: |
|
||||
echo "Packaging Game..."
|
||||
cd build/windows
|
||||
|
||||
# Zip everything (Executable + PCK + potential .dlls)
|
||||
zip -r ../../$ZIP_NAME .
|
||||
cd ../..
|
||||
|
||||
echo "Uploading Nightly..."
|
||||
# PUT request replaces the file if it exists
|
||||
curl --fail --user "${{ gitea.actor }}:$TOKEN" \
|
||||
--upload-file "$ZIP_NAME" \
|
||||
"$API_URL/$PACKAGE_NAME/$VERSION/$ZIP_NAME"
|
||||
|
||||
echo "✅ Nightly Build Available at:"
|
||||
echo "$API_URL/$PACKAGE_NAME/$VERSION/$ZIP_NAME"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
.vscode/
|
||||
/android/
|
||||
|
||||
*.tmp
|
||||
/export/
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
52
.vscode/development_status/Development_Status_2025-10-12.md
vendored
Normal file
52
.vscode/development_status/Development_Status_2025-10-12.md
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
# Project "Millimeters of Aluminium" Development Log
|
||||
## Overview
|
||||
|
||||
The project is undergoing a major architectural refactor to move from a monolithic Spaceship class to a fully modular, component-based system. The foundation for this new architecture, centered around Module, Component, and Station classes, is now in place. The next steps involve migrating legacy systems into this new paradigm.
|
||||
|
||||
## I. Fully Implemented & Stable Systems
|
||||
|
||||
Custom Physics Core: All physical objects now inherit from a custom OrbitalBody2D class, which handles mass aggregation and force integration. The physics loop is correctly disabled in the editor to prevent errors.
|
||||
|
||||
Modular Ship Construction:
|
||||
|
||||
Module as Root: The Module class now serves as the root for ship assemblies, managing its own list of structural pieces and components without needing container nodes.
|
||||
|
||||
Builder Plugin: The editor plugin is updated to work with this new architecture, allowing the placement of StructuralPiece nodes directly onto Module nodes.
|
||||
|
||||
Character & Interaction Foundation:
|
||||
|
||||
Zero-G Movement: The PilotBall character has a state machine for handling movement inside ship interiors, including sluggish zero-G floating and direct control on ladders.
|
||||
|
||||
Generic Station Component: A StationComponent class has been implemented. It serves as a generic hardware terminal that characters can interact with.
|
||||
|
||||
Data-Driven UI Architecture:
|
||||
|
||||
Databank Resource: A Databank Resource class has been created. It acts as "software," holding a reference to a UI scene that can be loaded by a station.
|
||||
|
||||
## II. Work-In-Progress (WIP) and Planned Systems
|
||||
|
||||
This list details systems we have designed but are not yet fully implemented in the code.
|
||||
|
||||
System Migration to Databanks:
|
||||
|
||||
Helm/Flight Controls: The logic from the old ThrusterController.gd needs to be moved into a HelmUI.tscn scene and driven by a HelmDatabank.
|
||||
|
||||
Navigation Computer: The UI has been moved, but the extensive planning and calculation logic from NavigationComputer.gd needs to be transferred to NavUI.gd.
|
||||
|
||||
Fuel and Life Support: The FuelSystem and LifeSupport nodes are still part of the old Spaceship.tscn. They need to be fully redesigned as Component classes (e.g., FuelTank, AtmosphereProcessor).
|
||||
|
||||
Component Wiring System:
|
||||
|
||||
Signal/Socket Advertising: Components and Databanks need to be updated with get_input_sockets() and get_output_signals() functions.
|
||||
|
||||
Wiring Data Storage: The Module class needs a wiring_data array to store the connections made in the builder.
|
||||
|
||||
Builder UI: A visual wiring interface needs to be added to the module builder plugin.
|
||||
|
||||
Orbital Stability Test:
|
||||
|
||||
Ghost Simulator: A GhostSimulator class needs to be created to run predictive, in-memory physics calculations.
|
||||
|
||||
Test Runner: An orbital_stability_test.tscn scene and script are needed to orchestrate the test, compare live vs. ghost results, and generate a report.
|
||||
|
||||
Full Spaceship Class Retirement: The final step will be to delete Spaceship.tscn and Spaceship.gd once all their logic and systems have been successfully migrated to the new modular architecture.
|
||||
78
.vscode/development_status/Development_Status_2025-10-14.md
vendored
Normal file
78
.vscode/development_status/Development_Status_2025-10-14.md
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
## Development Progress Report 14/10 - 25
|
||||
### Overview
|
||||
The project has successfully undergone a foundational architectural refactor. The legacy monolithic Spaceship class has been deprecated in favor of a fully modular, component-based architecture designed for multiplayer scalability. The core gameplay loop of a player spawning, possessing a character, using a station, and controlling a ship's systems via a diegetic UI is now functional. The project is now entering "Cycle 2" of development, focusing on unifying the physics system and migrating the remaining legacy gameplay logic into the new architecture.
|
||||
|
||||
### ✅ Implemented Systems & Features
|
||||
#### 1. Core Architecture
|
||||
Modular Ships: Ships are now built around a root Module class which extends OrbitalBody2D. The Module dynamically understands its structure by finding its Component and StructuralPiece children, removing the need for rigid container nodes.
|
||||
|
||||
Custom Physics Body: The OrbitalBody2D class serves as the base for all physical objects in the simulation, including ship parts and modules. It correctly handles force routing from child components to the root physics body and includes a robust, deferred check to warn if a child class forgets to call super() in its _ready function.
|
||||
|
||||
Dynamic Inertia Calculation: The ship's moment of inertia is now calculated realistically based on the mass and distribution of all its component parts in local space, leading to more authentic rotational physics.
|
||||
|
||||
#### 2. Player Control & Multiplayer Foundation
|
||||
PlayerController/Pawn Architecture: A multiplayer-ready control scheme has been implemented.
|
||||
|
||||
The PlayerController class is responsible for capturing raw input and sending it to the server via RPCs.
|
||||
|
||||
The PilotBall (the "Pawn") is now a "dumb" character that only acts on commands received from its controller, with all direct calls to the Input singleton successfully removed.
|
||||
|
||||
Dynamic Spawning & Possession: The GameManager now manages the game's startup sequence. It dynamically spawns a PlayerController and a default ship (Tube.tscn), and then correctly "possesses" the PilotBall within the ship with its corresponding controller.
|
||||
|
||||
Local Server Initialization: The GameManager correctly initializes a local ENet server, which enables the multiplayer authority system (is_multiplayer_authority()) to function correctly in a single-player testing environment.
|
||||
|
||||
#### 3. Station & UI Systems
|
||||
Modular UI Framework: The "databank" system has been fully implemented and separated into three distinct resource types:
|
||||
|
||||
ControlPanel: A resource representing a physical UI element (e.g., a screen, a lever).
|
||||
|
||||
Databank: A resource representing a "datashard" which contains pure logic in a script.
|
||||
|
||||
SystemStation: The physical station component that acts as a "chassis," hosting panels and databanks.
|
||||
|
||||
Persistent Station Logic: The SystemStation has been refactored to instantiate datashard logic (_ready) once for its entire lifetime, allowing for background processing. UI Panels are created ephemerally only when a player occupies the station.
|
||||
|
||||
Functional Helm: The helm is partially migrated.
|
||||
|
||||
A HelmLogicShard contains the attitude-hold (PD controller) and calibration logic.
|
||||
|
||||
ControlPanels for a throttle lever, buttons, and a status readout are functional.
|
||||
|
||||
The station correctly wires the panels to the helm shard at runtime, allowing the player to control the ship's main engine and RCS thrusters.
|
||||
|
||||
Functional Sensor Display: The sensor/map system is partially migrated.
|
||||
|
||||
A SensorSystemShard is responsible for gathering all trackable bodies in the system.
|
||||
|
||||
A refactored SensorPanel acts as a "dumb" display that visualizes the "sensor feed" signal it receives.
|
||||
|
||||
The ShipStatusShard correctly displays the ship's velocity and rotational data on the ReadoutScreen and also displays the currently selected target from the map.
|
||||
|
||||
### ❌ Not Yet Implemented / Pending Tasks
|
||||
#### 1. Physics & Simulation (Cycle 2 Priority)
|
||||
|
||||
CelestialBody Refactor: All celestial bodies (Planets, Moons, etc.) still inherit from Godot's RigidBody2D and use the _integrate_forces callback. They must be refactored to extend our custom OrbitalBody2D to create a single, stable physics simulation.
|
||||
|
||||
Astronomical vs. Ship Scale: A system for scaling forces needs to be designed and implemented to allow massive planets and lightweight ships to coexist and interact realistically within the same simulation.
|
||||
|
||||
Simulation Stability Test: The proposed GhostSimulator and test runner for verifying long-term orbital stability has not yet been created.
|
||||
|
||||
Interior Physics: The simulation does not yet account for how the ship's acceleration affects characters or loose objects inside it.
|
||||
|
||||
#### 2. System & Feature Migration
|
||||
|
||||
Full Helm/Nav Migration: The complex maneuver planning logic (e.g., Hohmann transfers) still resides in the legacy navigation_computer.gd script and needs to be migrated into one or more Databank shards.
|
||||
|
||||
Thruster Refactor: The Thruster component still uses a simple turn_on()/turn_off() model. It needs to be refactored to accept a variable set_throttle(value) command for more precise control.
|
||||
|
||||
Retirement of Legacy Scenes/Scripts: The old Spaceship.tscn, spaceship.gd, thruster_controller.gd, and navigation_computer.gd files are still in the project and need to be fully removed once their logic is migrated.
|
||||
|
||||
#### 3. Planned Features (Future Cycles)
|
||||
Wiring System: The foundational classes exist, but the in-game system for players to visually wire panels to databanks, and the editor tools to support this, have not been started.
|
||||
|
||||
Character & Movement: The current PilotBall is a placeholder. The planned humanoid character, grapple points, and EVA gameplay are not yet implemented.
|
||||
|
||||
Core Gameplay Systems: The foundational systems for Electricity, Life Support (pressurization), Fuel, Character Damage, and Inventory/Pickupable Objects have not yet been created.
|
||||
|
||||
Multiplayer Connectivity: While the architecture supports it, the UI and logic for multiple players to connect to a server (e.g., a main menu with a "Join Game" option) do not yet exist.
|
||||
52
.vscode/development_status/Development_Status_2025-10-16.md
vendored
Normal file
52
.vscode/development_status/Development_Status_2025-10-16.md
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
## Project Development Status Update 16/10 - 25
|
||||
### Overview
|
||||
The project has successfully completed a major architectural refactor, establishing a stable and scalable foundation for the simulation. The core physics model has been unified under a custom OrbitalBody2D class and a hierarchical Barycenter system, which has resolved previous orbital instabilities. The ship's control systems are being migrated to a flexible, data-driven "databank" architecture, and the UI is now managed by a robust grid-based layout system. The focus can now shift to migrating the remaining legacy systems and building out core gameplay features on this new foundation.
|
||||
|
||||
### ✅ Implemented Systems & Features
|
||||
#### 1. Hierarchical Physics Simulation (Barycenter Architecture)
|
||||
Global & Local Grids: The simulation is now anchored by a StarSystem root node, which defines the global grid. Procedurally generated planetary systems are encapsulated within Barycenter nodes, which act as moving "local grids" for their contents. This has stabilized the orbits of moons and planets.
|
||||
|
||||
Physics Roles: A clear distinction has been made between physics actors and passive bodies.
|
||||
|
||||
Barycenter nodes are the primary physics objects in the global simulation, inheriting from OrbitalBody2D and responding to gravitational forces.
|
||||
|
||||
Celestial bodies (Star, Planet, Moon) are now simple Node2Ds that provide mass data to their parent Barycenter but do not run their own physics integration, solving the "triple velocity" bug.
|
||||
|
||||
Centralized Physics Loop: All gravity calculations are now managed by the OrbitalMechanics singleton in a multi-stage _physics_process loop, which handles global (Barycenter-to-Star) and local (Moon-to-Planet) interactions separately.
|
||||
|
||||
#### 2. Procedural Generation & Player Spawn
|
||||
Generator as a Tool: The StarSystemGenerator has been refactored into a RefCounted class that acts as a factory, cleanly separating the generation process from the final StarSystem product.
|
||||
|
||||
Stable Orbit Placement: The generator now uses astrophysical concepts like the Roche Limit and Hill Sphere (abstracted into helper functions in OrbitalMechanics) to procedurally place planets and moons in stable, non-overlapping orbits.
|
||||
|
||||
Lagrange Point Spawning: The player ship is now correctly spawned at the L4 or L5 Lagrange point of the outermost planet, with the proper initial velocity to maintain a stable position.
|
||||
|
||||
#### 3. Data-Driven Ship Systems (Databanks)
|
||||
Autopilot Migration: The core logic for planning and executing maneuvers has been successfully migrated from the legacy ThrusterController into a series of decoupled databank shards:
|
||||
|
||||
NavSelectionDatabank: Stores the current navigation target.
|
||||
|
||||
ManeuverPlannerDatabank: Calculates maneuver burn plans (e.g., Hohmann transfers).
|
||||
|
||||
AutopilotDatabank: Executes the steps of a received plan.
|
||||
|
||||
Modular UI Layout: The SystemStation now functions as a layout manager, instancing and positioning UI panels based on grid data defined in ControlPanel resources. This has removed hardcoded positions and allows for flexible, data-driven UI configurations.
|
||||
|
||||
#### 4. Orbit Projection & Debugging
|
||||
Unified Projection Function: The OrbitalMechanics library now contains a single, generalized project_n_body_paths function. This function can run a "ghost simulation" on any arbitrary set of bodies in either local or global space to generate predictive orbital paths for the map panel.
|
||||
|
||||
Orrery View: A dedicated debugging tool, the OrreryView scene, has been created to provide a clean, interactive chart for inspecting procedurally generated star systems without the interference of game UI or camera logic.
|
||||
|
||||
### ⏳ Planned & Discussed Future Implementations
|
||||
#### 1. Advanced Physics Optimization
|
||||
Centralized N-Body Calculation: The plan is to have the OrbitalMechanics singleton manage all gravity calculations in a single, authoritative loop each frame. This will enable advanced optimizations and debugging, such as a "force queue" to prevent calculation errors.
|
||||
|
||||
Sphere of Influence (SOI) Model: For dynamic objects like the player's ship, we will implement an SOI system. The ship will calculate its gravity against the full system hierarchy when in "deep space" but will switch to calculating against only the local bodies (e.g., a planet and its moons) when it enters a Barycenter's sphere of influence.
|
||||
|
||||
Performance Culling & Caching: For performance-intensive scenarios like asteroid belts, we've discussed implementing timers to cache and reuse negligible force calculations over several frames, only recalculating when necessary.
|
||||
|
||||
#### 2. Component "API" & Wiring System
|
||||
Component Contracts: To facilitate the upcoming visual wiring system, we will formalize the "API" for ControlPanel and Databank resources. This will be done by creating new scripts that extend the base classes and override the get_input_sockets() and get_output_signals() functions to explicitly define what signals and functions each component provides.
|
||||
|
||||
Static vs. Resource-Based API: We've concluded that using extended Resource scripts to define these APIs is superior to using static functions on the node scripts. This decouples the data contract from the implementation and allows a single scene to be used with multiple different data configurations, which is critical for a flexible wiring system.
|
||||
64
.vscode/development_status/Development_Status_2025-10-25.md
vendored
Normal file
64
.vscode/development_status/Development_Status_2025-10-25.md
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
## Project Development Status Update: 31/10/25
|
||||
|
||||
### 3D Character Controller & Movement Tech Demo (Cycle 3)
|
||||
|
||||
Work has proceeded on a tech demo for the 3D character controller, establishing a robust, physics-based system for zero-G movement. The architecture has been refactored to prioritize a clean separation of concerns, with a central "pawn" acting as a physics integrator and modular "controllers" acting as the "brains" for different movement types.
|
||||
|
||||
### ✅ Implemented Features
|
||||
|
||||
#### Pawn/Controller Architecture: The character is split into several key classes:
|
||||
|
||||
CharacterPawn3D: The core CharacterBody3D. It acts as a "dumb" physics integrator, holding velocity and angular_velocity, integrating rotation, and calling move_and_slide(). It no longer contains movement-specific state logic.
|
||||
|
||||
PlayerController3D: Gathers all hardware input (keyboard, mouse) and packages it into KeyInput dictionaries (pressed, held, released) to send to the pawn via RPC.
|
||||
|
||||
EVAMovementComponent: Refactored into a "dumb tool". It exposes functions like apply_thrusters() and apply_orientation() which are called by other controllers.
|
||||
|
||||
ZeroGMovementComponent: This is now the "brain" for all zero-G movement. It receives all inputs from the pawn and contains its own internal state machine (IDLE, REACHING, GRIPPING, CLIMBING, CHARGING_LAUNCH).
|
||||
|
||||
#### Contextual Movement Logic:
|
||||
|
||||
The ZeroGMovementComponent decides when to use the EVA suit. In its IDLE state, it checks for fresh movement input (movement_input_was_neutral) before calling the EVAMovementComponent's apply_thrusters function.
|
||||
|
||||
This successfully implements "coast on release," where releasing a grip (_release_current_grip) flags the movement input as "stale," preventing the EVA suit from engaging even if the key is still held.
|
||||
|
||||
#### EVA/Jetpack Controls:
|
||||
|
||||
The EVAMovementComponent provides force-based linear movement (WASD, Shift/Ctrl) and torque-based angular roll (Q/E).
|
||||
|
||||
A body-orientation function (_orient_pawn) allows the pawn to auto-align with the camera's forward direction.
|
||||
|
||||
#### Physics-Based Grip System:
|
||||
|
||||
GripArea3D: A composition-based Area3D node provides the interface for all grabbable objects. It requires its parent to implement functions like get_grip_transform and get_push_off_normal.
|
||||
|
||||
Grip Detection: The CharacterPawn3D uses a GripDetector Area3D to find GripArea3D nodes in range and passes this nearby_grips list to the ZeroGMovementComponent.
|
||||
|
||||
GRIPPING State: This state is now fully physics-based. Instead of setting the pawn's global_transform, the _apply_grip_physics function uses a PD controller to apply linear forces (to move to the offset position) and angular torques (to align with the grip's orientation).
|
||||
|
||||
Grip Orientation: The gripping logic correctly calculates the closest of two opposing orientations (e.g., "up" or "down" on a bar) by comparing the pawn's current up vector to the grip's potential up vectors.
|
||||
|
||||
Grip Rolling: While in the GRIPPING state, the player can use Q/E to override the auto-orientation and apply roll torque around the grip's axis.
|
||||
|
||||
#### Physics-Based Climbing:
|
||||
|
||||
CLIMBING State: This state applies lerp'd velocity to move the pawn, allowing it to interact with physics.
|
||||
|
||||
Climb Targeting: The _find_best_grip function successfully identifies the next valid grip within a configurable climb_angle_threshold_deg cone.
|
||||
|
||||
Handover: Logic in _process_climbing correctly identifies when the pawn is close enough to the next_grip_target to _perform_grip_handover.
|
||||
|
||||
Climb Release: The pawn will correctly release its grip and enter the IDLE state (coasting) if it moves past the current_grip by release_past_grip_threshold without a new target being found.
|
||||
|
||||
### ❌ Not Yet Implemented / Pending Tasks
|
||||
|
||||
REACHING State: The REACHING state exists but its logic (_process_reaching) is a stub that instantly calls _try_initiate_reach. The full implementation (e.g., procedural animation/IK moving the hand to the target) is pending.
|
||||
|
||||
CHARGING_LAUNCH State: The state exists and the execution logic is present (_handle_launch_charge, _execute_launch), but the state transition logic in _update_state does not currently allow entering this state from GRIPPING (it's overshadowed by the _start_climb check).
|
||||
|
||||
Ladder (3D) & Walking (3D) States: The CharacterPawn3D has high-level states for GRIPPING_LADDER and WALKING, but the movement functions (_apply_ladder_movement, _apply_walking_movement) are stubs.
|
||||
|
||||
Generic Surface Grab: The TODO to allow the ZeroGMovementComponent to grab any physics surface (not just a GripArea3D) is not implemented.
|
||||
|
||||
EVA Stabilization: The _apply_stabilization_torques function in EVAMovementComponent is still a placeholder.
|
||||
89
.vscode/development_status/Development_Status_2025-11-21.md
vendored
Normal file
89
.vscode/development_status/Development_Status_2025-11-21.md
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
# Development Status Report - 2025-11-21
|
||||
|
||||
## Overview
|
||||
|
||||
Significant progress was made on the Unified Build System and the In-Game Builder, moving away from purely editor-based construction. We successfully implemented procedural geometry generation for structural pieces and refactored the snapping logic to support the upcoming geodesic structures. Networking logic also saw crucial stability fixes.
|
||||
|
||||
## Completed Features & Implementations
|
||||
|
||||
### 1. Unified Build System Foundation
|
||||
|
||||
- StructureData Resource: Implemented a robust resource-based definition system for ship parts. This decouples the "DNA" of a part (mesh, collider, mounts, health) from its scene instantiation, allowing shared logic between the Editor Plugin and the In-Game Builder.
|
||||
|
||||
- PieceMount Logic: Formalized attachment points into a dedicated PieceMount class (inheriting Area3D). This replaced the ad-hoc dictionary system, providing type safety and better collision filtering.
|
||||
|
||||
- ProceduralPiece Generator: Created a script that dynamically generates 3D meshes and collision shapes based on parameters (e.g., triangle vs. square, size, thickness) defined in StructureData. This supports:
|
||||
|
||||
- Extruded 3D geometry (thickness) rather than flat planes.
|
||||
|
||||
- Convex collision hull generation.
|
||||
|
||||
- "Opaque Blueprint" preview materials (semi-transparent, emissive).
|
||||
|
||||
### 2. In-Game Builder (PlayerController3D)
|
||||
|
||||
- Build Mode State: Implemented a toggleable Build Mode (B key) in PlayerController3D.
|
||||
|
||||
- Piece Selection: Added logic to select pieces via hotkeys (1 = Square, 2 = Triangle), instantiate a preview "ghost," and switch its material.
|
||||
|
||||
- Raycast Snapping: Implemented a physics-based raycast/sweep to detect existing ship modules and mounts.
|
||||
|
||||
- Visual Feedback: Added color-coded feedback for the preview ghost:
|
||||
|
||||
- Cyan: Floating (no snap target found).
|
||||
|
||||
- Geen: Snapped (aligned with a valid mount).
|
||||
|
||||
### 3. Snapping Logic Refactor
|
||||
|
||||
- SnappingTool Class: Created a dedicated static helper class for snapping math.
|
||||
|
||||
- Shape Cast Sweep: Replaced simple raycasting with a sphere_cast (radius 0.2m). This adds "thickness" to the cursor, making it much easier to hit thin structural elements like struts or small mounts.
|
||||
|
||||
- Transform Alignment: Implemented matrix math to align a new piece's mount with a target mount, respecting position, normal (facing direction), and up-vector (roll/orientation).
|
||||
|
||||
### 4. Networking Stability (Previous Session)
|
||||
|
||||
- Server Authority Enforcement: enforced strict server authority on CharacterPawn3D, removing client-side overrides that caused "fighting" and stutter.
|
||||
|
||||
- Relative Velocity Sync: Implemented logic to sync local_velocity relative to the parent ship instead of global velocity. This prevents pawns from "falling out the back" of moving ships during network jitter.
|
||||
|
||||
- Input Serialization: Fixed RPC errors by converting custom KeyInput objects to Dictionaries before transmission.
|
||||
|
||||
## Pending / In-Progress
|
||||
|
||||
- Mount Orientation Constraints: The snapping tool currently aligns normals but needs refinement to strictly enforce edge length compatibility and specific mount types (e.g., preventing a 2m edge from snapping to a 1m edge).
|
||||
|
||||
- Module Persistence: The logic for creating a new Module when building in empty space works in memory but needs testing for persistence and saving.
|
||||
|
||||
- Collision Layers: Need to verify that all PieceMount nodes are consistently on the correct physics layer (1 << 14) to ensure the snapping sweep always finds them.
|
||||
|
||||
## Discussion & Direction Changes
|
||||
|
||||
### Shift to "Geodesic & Procedural" Building
|
||||
|
||||
We moved away from the initial "Voxel Grid" concept for ship hulls.
|
||||
|
||||
- Old Direction: Ships built on a strict integer grid (Minecraft-style but with slopes).
|
||||
|
||||
- New Direction: A node-based "Geodesic" system. Pieces connect Node-to-Node (Mount-to-Mount) at arbitrary angles. This allows for complex shapes (hexagonal cylinders, spheres, rings) and supports the "Industrial/Hard Sci-Fi" aesthetic better.
|
||||
|
||||
- Implication: The building tool no longer relies on grid_step for positioning. It relies entirely on the SnappingTool to calculate transforms based on the geometry of the mounting points.
|
||||
|
||||
### Shift to "Server Authoritative" Networking
|
||||
|
||||
We abandoned the "Client Authoritative" movement for pawns to solve synchronization issues.
|
||||
|
||||
- Old Plan: Client moves pawn, Server accepts pos. (Caused hacking risks and desync with physics objects).
|
||||
|
||||
- New Plan: Server simulates physics. Client sends inputs. MultiplayerSynchronizer interpolates the result. To combat latency feel, we are using Visual Interpolation (detaching camera/mesh from the physics body) rather than full Client-Side Prediction (CSP) for this milestone.
|
||||
|
||||
### Manufacturing & Blueprints
|
||||
|
||||
We discussed that the StructureData resource is the key enabler for the manufacturing gameplay loop. By defining parts as data (Resources), we can easily:
|
||||
|
||||
1. Store a "Blueprint" as a list of StructureData references + Transforms.
|
||||
|
||||
2. Have a manufacturing machine consume resources to produce a "Crate" containing a StructureData item.
|
||||
|
||||
3. Have the player pick up that item and use it to place the ProceduralPiece.
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"godotTools.editorPath.godot4": "./godot_engine/bin/godot.windows.editor.double.x86_64.console.exe",
|
||||
"search.exclude": {
|
||||
"/godot_engine": true
|
||||
}
|
||||
}
|
||||
@ -1,82 +1,246 @@
|
||||
Project "Stardust Drifter" Development Status (Phase 0.2: Custom Physics & Ship Interior)
|
||||
Overview
|
||||
# Project "Millimeters of Aluminium" Development Log
|
||||
## Overview
|
||||
|
||||
The project is currently focused on Phase 0.2, which involves migrating from Godot's built-in RigidBody2D physics to a custom, manually integrated OrbitalBody2D model. This is critical for supporting the multi-scale physics simulation (orbital vs. local) and preparing for networked multiplayer. The core physics model is now stable and functional.
|
||||
I. Fully Implemented & Stable Systems
|
||||
Custom Physics Core
|
||||
The project is undergoing a major architectural refactor to move from a monolithic Spaceship class to a fully modular, component-based system. The foundation for this new architecture, centered around Module, Component, and Station classes, is now in place. The next steps involve migrating legacy systems into this new paradigm.
|
||||
|
||||
Physics Unification: All physically simulated objects (Spaceship, Module, Thruster, StructuralPiece) now inherit from the custom OrbitalBody2D class. This ensures a consistent hierarchy and simplified force routing.
|
||||
## I. Fully Implemented & Stable Systems
|
||||
|
||||
Asymmetric Gravity Model (WIP Foundation): The OrbitalMechanics singleton is refactored to check if a body is a simulation root, applying forces manually (linear_velocity, angular_velocity). This is the foundation for implementing the SCALE_FACTOR for miniature ship gravity.
|
||||
Custom Physics Core: All physical objects now inherit from a custom OrbitalBody2D class, which handles mass aggregation and force integration. The physics loop is correctly disabled in the editor to prevent errors.
|
||||
|
||||
Modular Force Integration: Thrusters are now OrbitalBody2D children that correctly apply force to themselves. The force is then routed up the hierarchy to the root ship node, calculating torque based on its application point. This solved the uncontrollable spinning bug.
|
||||
Modular Ship Construction:
|
||||
|
||||
Modular Mass Aggregation (Top-Down): The OrbitalBody2D class recursively calculates the total mass of the ship by summing the base_mass of all its component children, even those nested behind non-physics containers.
|
||||
Module as Root: The Module class now serves as the root for ship assemblies, managing its own list of structural pieces and components without needing container nodes.
|
||||
|
||||
Ship Interior & Crew Movement
|
||||
Builder Plugin: The editor plugin is updated to work with this new architecture, allowing the placement of StructuralPiece nodes directly onto Module nodes.
|
||||
|
||||
Character Zero-G Movement: The PilotBall character now has complex, state-based movement logic to simulate zero-G physics inside the ship:
|
||||
Character & Interaction Foundation:
|
||||
|
||||
No Control: Applies heavy drag if not overlapping a structural piece.
|
||||
Zero-G Movement: The PilotBall character has a state machine for handling movement inside ship interiors, including sluggish zero-G floating and direct control on ladders.
|
||||
|
||||
Zero-G Interior: Allows sluggish control (simulating pushing off hullplates).
|
||||
Generic Station Component: A StationComponent class has been implemented. It serves as a generic hardware terminal that characters can interact with.
|
||||
|
||||
Ladder Grip: Provides snappy, direct control (simulating climbing/gripping).
|
||||
Data-Driven UI Architecture:
|
||||
|
||||
Ladder/Component Interaction: The character uses the interact input (Spacebar) to switch to the LADDER_GRIP state and uses the same input release to perform a velocity-based launch into the zero-G interior.
|
||||
Databank Resource: A Databank Resource class has been created. It acts as "software," holding a reference to a UI scene that can be loaded by a station.
|
||||
|
||||
Ship Builder Foundation (Structural)
|
||||
## II. Work-In-Progress (WIP) and Planned Systems
|
||||
|
||||
Component Base Class (Component.gd): Created to standardize component attachment, defining properties like grid_size and AttachmentType (Interior/Exterior).
|
||||
This list details systems we have designed but are not yet fully implemented in the code.
|
||||
|
||||
Module Attachment Grid: The Module.gd script now exposes a get_attachment_points() method that calculates valid grid locations based on the placement and type of underlying structural pieces (Hullplate, Bulkhead).
|
||||
System Migration to Databanks:
|
||||
|
||||
II. Work-In-Progress (WIP) and Planned Systems
|
||||
Helm/Flight Controls: The logic from the old ThrusterController.gd needs to be moved into a HelmUI.tscn scene and driven by a HelmDatabank.
|
||||
|
||||
System
|
||||
|
||||
Navigation Computer: The UI has been moved, but the extensive planning and calculation logic from NavigationComputer.gd needs to be transferred to NavUI.gd.
|
||||
|
||||
Status
|
||||
|
||||
Fuel and Life Support: The FuelSystem and LifeSupport nodes are still part of the old Spaceship.tscn. They need to be fully redesigned as Component classes (e.g., FuelTank, AtmosphereProcessor).
|
||||
|
||||
Next Steps / Required Work
|
||||
Component Wiring System:
|
||||
|
||||
Ship Builder Plugin (UI)
|
||||
|
||||
Signal/Socket Advertising: Components and Databanks need to be updated with get_input_sockets() and get_output_signals() functions.
|
||||
|
||||
WIP / Priority
|
||||
|
||||
Wiring Data Storage: The Module class needs a wiring_data array to store the connections made in the builder.
|
||||
|
||||
Integrate the new attachment logic (Module.get_attachment_points()) into the CustomGrid.gd to visualize placement points. Implement the logic for the Component Tab in the dock.
|
||||
Builder UI: A visual wiring interface needs to be added to the module builder plugin.
|
||||
|
||||
Physics Cleanup (Celestial)
|
||||
|
||||
Orbital Stability Test:
|
||||
|
||||
WIP / Required
|
||||
|
||||
Ghost Simulator: A GhostSimulator class needs to be created to run predictive, in-memory physics calculations.
|
||||
|
||||
Unify the inheritance of all celestial bodies (Star, Planet, etc.) to inherit from OrbitalBody2D, removing reliance on deprecated RigidBody2D base nodes.
|
||||
Test Runner: An orbital_stability_test.tscn scene and script are needed to orchestrate the test, compare live vs. ghost results, and generate a report.
|
||||
|
||||
Collision Handling
|
||||
|
||||
Full Spaceship Class Retirement: The final step will be to delete Spaceship.tscn and Spaceship.gd once all their logic and systems have been successfully migrated to the new modular architecture.
|
||||
|
||||
Planned
|
||||
|
||||
## Development Progress Report 14/10 - 25
|
||||
### Overview
|
||||
The project has successfully undergone a foundational architectural refactor. The legacy monolithic Spaceship class has been deprecated in favor of a fully modular, component-based architecture designed for multiplayer scalability. The core gameplay loop of a player spawning, possessing a character, using a station, and controlling a ship's systems via a diegetic UI is now functional. The project is now entering "Cycle 2" of development, focusing on unifying the physics system and migrating the remaining legacy gameplay logic into the new architecture.
|
||||
|
||||
Implement the _recalculate_collision_shape() function in Module.gd to merge the structural piece collision shapes into a single, cohesive shape for the root ship.
|
||||
### ✅ Implemented Systems & Features
|
||||
#### 1. Core Architecture
|
||||
Modular Ships: Ships are now built around a root Module class which extends OrbitalBody2D. The Module dynamically understands its structure by finding its Component and StructuralPiece children, removing the need for rigid container nodes.
|
||||
|
||||
Hull Sealing Logic
|
||||
|
||||
Custom Physics Body: The OrbitalBody2D class serves as the base for all physical objects in the simulation, including ship parts and modules. It correctly handles force routing from child components to the root physics body and includes a robust, deferred check to warn if a child class forgets to call super() in its _ready function.
|
||||
|
||||
Designed
|
||||
|
||||
Dynamic Inertia Calculation: The ship's moment of inertia is now calculated realistically based on the mass and distribution of all its component parts in local space, leading to more authentic rotational physics.
|
||||
|
||||
Implement the pressure and hull sealing logic using the HullVolume nodes.
|
||||
#### 2. Player Control & Multiplayer Foundation
|
||||
PlayerController/Pawn Architecture: A multiplayer-ready control scheme has been implemented.
|
||||
|
||||
Scaled Gravity Implementation
|
||||
|
||||
The PlayerController class is responsible for capturing raw input and sending it to the server via RPCs.
|
||||
|
||||
Designed
|
||||
|
||||
The PilotBall (the "Pawn") is now a "dumb" character that only acts on commands received from its controller, with all direct calls to the Input singleton successfully removed.
|
||||
|
||||
Implement the SCALE_FACTOR and ScaledOrbitalBody2D inheritance to test the multi-scale simulation (low-speed local movement vs. high-speed orbital movement).
|
||||
Dynamic Spawning & Possession: The GameManager now manages the game's startup sequence. It dynamically spawns a PlayerController and a default ship (Tube.tscn), and then correctly "possesses" the PilotBall within the ship with its corresponding controller.
|
||||
|
||||
Local Server Initialization: The GameManager correctly initializes a local ENet server, which enables the multiplayer authority system (is_multiplayer_authority()) to function correctly in a single-player testing environment.
|
||||
|
||||
#### 3. Station & UI Systems
|
||||
Modular UI Framework: The "databank" system has been fully implemented and separated into three distinct resource types:
|
||||
|
||||
ControlPanel: A resource representing a physical UI element (e.g., a screen, a lever).
|
||||
|
||||
Databank: A resource representing a "datashard" which contains pure logic in a script.
|
||||
|
||||
SystemStation: The physical station component that acts as a "chassis," hosting panels and databanks.
|
||||
|
||||
Persistent Station Logic: The SystemStation has been refactored to instantiate datashard logic (_ready) once for its entire lifetime, allowing for background processing. UI Panels are created ephemerally only when a player occupies the station.
|
||||
|
||||
Functional Helm: The helm is partially migrated.
|
||||
|
||||
A HelmLogicShard contains the attitude-hold (PD controller) and calibration logic.
|
||||
|
||||
ControlPanels for a throttle lever, buttons, and a status readout are functional.
|
||||
|
||||
The station correctly wires the panels to the helm shard at runtime, allowing the player to control the ship's main engine and RCS thrusters.
|
||||
|
||||
Functional Sensor Display: The sensor/map system is partially migrated.
|
||||
|
||||
A SensorSystemShard is responsible for gathering all trackable bodies in the system.
|
||||
|
||||
A refactored SensorPanel acts as a "dumb" display that visualizes the "sensor feed" signal it receives.
|
||||
|
||||
The ShipStatusShard correctly displays the ship's velocity and rotational data on the ReadoutScreen and also displays the currently selected target from the map.
|
||||
|
||||
### ❌ Not Yet Implemented / Pending Tasks
|
||||
#### 1. Physics & Simulation (Cycle 2 Priority)
|
||||
|
||||
CelestialBody Refactor: All celestial bodies (Planets, Moons, etc.) still inherit from Godot's RigidBody2D and use the _integrate_forces callback. They must be refactored to extend our custom OrbitalBody2D to create a single, stable physics simulation.
|
||||
|
||||
Astronomical vs. Ship Scale: A system for scaling forces needs to be designed and implemented to allow massive planets and lightweight ships to coexist and interact realistically within the same simulation.
|
||||
|
||||
Simulation Stability Test: The proposed GhostSimulator and test runner for verifying long-term orbital stability has not yet been created.
|
||||
|
||||
Interior Physics: The simulation does not yet account for how the ship's acceleration affects characters or loose objects inside it.
|
||||
|
||||
#### 2. System & Feature Migration
|
||||
|
||||
Full Helm/Nav Migration: The complex maneuver planning logic (e.g., Hohmann transfers) still resides in the legacy navigation_computer.gd script and needs to be migrated into one or more Databank shards.
|
||||
|
||||
Thruster Refactor: The Thruster component still uses a simple turn_on()/turn_off() model. It needs to be refactored to accept a variable set_throttle(value) command for more precise control.
|
||||
|
||||
Retirement of Legacy Scenes/Scripts: The old Spaceship.tscn, spaceship.gd, thruster_controller.gd, and navigation_computer.gd files are still in the project and need to be fully removed once their logic is migrated.
|
||||
|
||||
#### 3. Planned Features (Future Cycles)
|
||||
Wiring System: The foundational classes exist, but the in-game system for players to visually wire panels to databanks, and the editor tools to support this, have not been started.
|
||||
|
||||
Character & Movement: The current PilotBall is a placeholder. The planned humanoid character, grapple points, and EVA gameplay are not yet implemented.
|
||||
|
||||
Core Gameplay Systems: The foundational systems for Electricity, Life Support (pressurization), Fuel, Character Damage, and Inventory/Pickupable Objects have not yet been created.
|
||||
|
||||
Multiplayer Connectivity: While the architecture supports it, the UI and logic for multiple players to connect to a server (e.g., a main menu with a "Join Game" option) do not yet exist.
|
||||
|
||||
## Project Development Status Update 16/10 - 25
|
||||
### Overview
|
||||
The project has successfully completed a major architectural refactor, establishing a stable and scalable foundation for the simulation. The core physics model has been unified under a custom OrbitalBody2D class and a hierarchical Barycenter system, which has resolved previous orbital instabilities. The ship's control systems are being migrated to a flexible, data-driven "databank" architecture, and the UI is now managed by a robust grid-based layout system. The focus can now shift to migrating the remaining legacy systems and building out core gameplay features on this new foundation.
|
||||
|
||||
### ✅ Implemented Systems & Features
|
||||
#### 1. Hierarchical Physics Simulation (Barycenter Architecture)
|
||||
Global & Local Grids: The simulation is now anchored by a StarSystem root node, which defines the global grid. Procedurally generated planetary systems are encapsulated within Barycenter nodes, which act as moving "local grids" for their contents. This has stabilized the orbits of moons and planets.
|
||||
|
||||
Physics Roles: A clear distinction has been made between physics actors and passive bodies.
|
||||
|
||||
Barycenter nodes are the primary physics objects in the global simulation, inheriting from OrbitalBody2D and responding to gravitational forces.
|
||||
|
||||
Celestial bodies (Star, Planet, Moon) are now simple Node2Ds that provide mass data to their parent Barycenter but do not run their own physics integration, solving the "triple velocity" bug.
|
||||
|
||||
Centralized Physics Loop: All gravity calculations are now managed by the OrbitalMechanics singleton in a multi-stage _physics_process loop, which handles global (Barycenter-to-Star) and local (Moon-to-Planet) interactions separately.
|
||||
|
||||
#### 2. Procedural Generation & Player Spawn
|
||||
Generator as a Tool: The StarSystemGenerator has been refactored into a RefCounted class that acts as a factory, cleanly separating the generation process from the final StarSystem product.
|
||||
|
||||
Stable Orbit Placement: The generator now uses astrophysical concepts like the Roche Limit and Hill Sphere (abstracted into helper functions in OrbitalMechanics) to procedurally place planets and moons in stable, non-overlapping orbits.
|
||||
|
||||
Lagrange Point Spawning: The player ship is now correctly spawned at the L4 or L5 Lagrange point of the outermost planet, with the proper initial velocity to maintain a stable position.
|
||||
|
||||
#### 3. Data-Driven Ship Systems (Databanks)
|
||||
Autopilot Migration: The core logic for planning and executing maneuvers has been successfully migrated from the legacy ThrusterController into a series of decoupled databank shards:
|
||||
|
||||
NavSelectionDatabank: Stores the current navigation target.
|
||||
|
||||
ManeuverPlannerDatabank: Calculates maneuver burn plans (e.g., Hohmann transfers).
|
||||
|
||||
AutopilotDatabank: Executes the steps of a received plan.
|
||||
|
||||
Modular UI Layout: The SystemStation now functions as a layout manager, instancing and positioning UI panels based on grid data defined in ControlPanel resources. This has removed hardcoded positions and allows for flexible, data-driven UI configurations.
|
||||
|
||||
#### 4. Orbit Projection & Debugging
|
||||
Unified Projection Function: The OrbitalMechanics library now contains a single, generalized project_n_body_paths function. This function can run a "ghost simulation" on any arbitrary set of bodies in either local or global space to generate predictive orbital paths for the map panel.
|
||||
|
||||
Orrery View: A dedicated debugging tool, the OrreryView scene, has been created to provide a clean, interactive chart for inspecting procedurally generated star systems without the interference of game UI or camera logic.
|
||||
|
||||
### ⏳ Planned & Discussed Future Implementations
|
||||
#### 1. Advanced Physics Optimization
|
||||
Centralized N-Body Calculation: The plan is to have the OrbitalMechanics singleton manage all gravity calculations in a single, authoritative loop each frame. This will enable advanced optimizations and debugging, such as a "force queue" to prevent calculation errors.
|
||||
|
||||
Sphere of Influence (SOI) Model: For dynamic objects like the player's ship, we will implement an SOI system. The ship will calculate its gravity against the full system hierarchy when in "deep space" but will switch to calculating against only the local bodies (e.g., a planet and its moons) when it enters a Barycenter's sphere of influence.
|
||||
|
||||
Performance Culling & Caching: For performance-intensive scenarios like asteroid belts, we've discussed implementing timers to cache and reuse negligible force calculations over several frames, only recalculating when necessary.
|
||||
|
||||
#### 2. Component "API" & Wiring System
|
||||
Component Contracts: To facilitate the upcoming visual wiring system, we will formalize the "API" for ControlPanel and Databank resources. This will be done by creating new scripts that extend the base classes and override the get_input_sockets() and get_output_signals() functions to explicitly define what signals and functions each component provides.
|
||||
|
||||
Static vs. Resource-Based API: We've concluded that using extended Resource scripts to define these APIs is superior to using static functions on the node scripts. This decouples the data contract from the implementation and allows a single scene to be used with multiple different data configurations, which is critical for a flexible wiring system.
|
||||
|
||||
## Project Development Status Update: 31/10/25
|
||||
|
||||
### 3D Character Controller & Movement Tech Demo (Cycle 3)
|
||||
|
||||
Work has proceeded on a tech demo for the 3D character controller, establishing a robust, physics-based system for zero-G movement. The architecture has been refactored to prioritize a clean separation of concerns, with a central "pawn" acting as a physics integrator and modular "controllers" acting as the "brains" for different movement types.
|
||||
|
||||
### ✅ Implemented Features
|
||||
|
||||
#### Pawn/Controller Architecture: The character is split into several key classes:
|
||||
|
||||
CharacterPawn3D: The core CharacterBody3D. It acts as a "dumb" physics integrator, holding velocity and angular_velocity, integrating rotation, and calling move_and_slide(). It no longer contains movement-specific state logic.
|
||||
|
||||
PlayerController3D: Gathers all hardware input (keyboard, mouse) and packages it into KeyInput dictionaries (pressed, held, released) to send to the pawn via RPC.
|
||||
|
||||
EVAMovementComponent: Refactored into a "dumb tool". It exposes functions like apply_thrusters() and apply_orientation() which are called by other controllers.
|
||||
|
||||
ZeroGMovementComponent: This is now the "brain" for all zero-G movement. It receives all inputs from the pawn and contains its own internal state machine (IDLE, REACHING, GRIPPING, CLIMBING, CHARGING_LAUNCH).
|
||||
|
||||
#### Contextual Movement Logic:
|
||||
|
||||
The ZeroGMovementComponent decides when to use the EVA suit. In its IDLE state, it checks for fresh movement input (movement_input_was_neutral) before calling the EVAMovementComponent's apply_thrusters function.
|
||||
|
||||
This successfully implements "coast on release," where releasing a grip (_release_current_grip) flags the movement input as "stale," preventing the EVA suit from engaging even if the key is still held.
|
||||
|
||||
#### EVA/Jetpack Controls:
|
||||
|
||||
The EVAMovementComponent provides force-based linear movement (WASD, Shift/Ctrl) and torque-based angular roll (Q/E).
|
||||
|
||||
A body-orientation function (_orient_pawn) allows the pawn to auto-align with the camera's forward direction.
|
||||
|
||||
#### Physics-Based Grip System:
|
||||
|
||||
GripArea3D: A composition-based Area3D node provides the interface for all grabbable objects. It requires its parent to implement functions like get_grip_transform and get_push_off_normal.
|
||||
|
||||
Grip Detection: The CharacterPawn3D uses a GripDetector Area3D to find GripArea3D nodes in range and passes this nearby_grips list to the ZeroGMovementComponent.
|
||||
|
||||
GRIPPING State: This state is now fully physics-based. Instead of setting the pawn's global_transform, the _apply_grip_physics function uses a PD controller to apply linear forces (to move to the offset position) and angular torques (to align with the grip's orientation).
|
||||
|
||||
Grip Orientation: The gripping logic correctly calculates the closest of two opposing orientations (e.g., "up" or "down" on a bar) by comparing the pawn's current up vector to the grip's potential up vectors.
|
||||
|
||||
Grip Rolling: While in the GRIPPING state, the player can use Q/E to override the auto-orientation and apply roll torque around the grip's axis.
|
||||
|
||||
#### Physics-Based Climbing:
|
||||
|
||||
CLIMBING State: This state applies lerp'd velocity to move the pawn, allowing it to interact with physics.
|
||||
|
||||
Climb Targeting: The _find_best_grip function successfully identifies the next valid grip within a configurable climb_angle_threshold_deg cone.
|
||||
|
||||
Handover: Logic in _process_climbing correctly identifies when the pawn is close enough to the next_grip_target to _perform_grip_handover.
|
||||
|
||||
Climb Release: The pawn will correctly release its grip and enter the IDLE state (coasting) if it moves past the current_grip by release_past_grip_threshold without a new target being found.
|
||||
|
||||
### ❌ Not Yet Implemented / Pending Tasks
|
||||
|
||||
REACHING State: The REACHING state exists but its logic (_process_reaching) is a stub that instantly calls _try_initiate_reach. The full implementation (e.g., procedural animation/IK moving the hand to the target) is pending.
|
||||
|
||||
CHARGING_LAUNCH State: The state exists and the execution logic is present (_handle_launch_charge, _execute_launch), but the state transition logic in _update_state does not currently allow entering this state from GRIPPING (it's overshadowed by the _start_climb check).
|
||||
|
||||
Ladder (3D) & Walking (3D) States: The CharacterPawn3D has high-level states for GRIPPING_LADDER and WALKING, but the movement functions (_apply_ladder_movement, _apply_walking_movement) are stubs.
|
||||
|
||||
Generic Surface Grab: The TODO to allow the ZeroGMovementComponent to grab any physics surface (not just a GripArea3D) is not implemented.
|
||||
|
||||
EVA Stabilization: The _apply_stabilization_torques function in EVAMovementComponent is still a placeholder.
|
||||
@ -1,8 +1,10 @@
|
||||
# Game Design Document: Project Stardust Drifter (Working Title)
|
||||
# Game Design Document: Project Millimeters of Aluminum (Working Title)
|
||||
|
||||
## 1. Game Vision & Concept
|
||||
|
||||
Project Stardust Drifter is a top-down 2D spaceship simulation game that emphasizes realistic orbital mechanics, deep ship management, and cooperative crew gameplay. Players take on roles as members of a multi-species crew aboard a modular, physically simulated spaceship.
|
||||
Project Millimeters of Aluminum is a third-person 3D spaceship simulation game that emphasizes realistic physics, deep ship management, and cooperative crew gameplay. Players take on roles as members of a multi-species crew aboard a modular, physically simulated spaceship.
|
||||
|
||||
The game's aesthetic is inspired by the functional, industrial look of real-world space hardware and sci-fi like The Expanse, focusing on diegetic interfaces and detailed, functional components. The core experience is about planning and executing complex maneuvers in a hazardous, procedurally generated star system, where understanding the ship's systems is as important as piloting skill.
|
||||
|
||||
The game's aesthetic is inspired by the technical, gritty, and high-contrast 2D style of games like Barotrauma, focusing on diegetic interfaces and detailed, functional components. The core experience is about planning and executing complex maneuvers in a hazardous, procedurally generated star system, where understanding the ship's systems is as important as piloting skill.
|
||||
|
||||
@ -12,26 +14,80 @@ The gameplay is centered around a Plan -> Execute -> Manage loop:
|
||||
|
||||
1. Plan: The crew uses the Navigation Computer to analyze their orbit and plan complex maneuvers, such as a Hohmann transfer to another planet. They must account for launch windows, fuel costs, and travel time.
|
||||
|
||||
2. Execute: The crew engages the autopilot or manually pilots the ship. The Thruster Controller executes the planned burns, performing precise, fuel-optimal rotations and main engine thrusts to alter the ship's trajectory.
|
||||
2. Execute: The crew engages the autopilot or manually pilots the ship. The Helm executes the planned burns, performing precise, fuel-optimal rotations and main engine thrusts to alter the ship's trajectory.
|
||||
|
||||
3. Manage: While underway, the crew manages the ship's modular systems, monitors resources like fuel and power, and responds to emergent events like hull breaches or system failures.
|
||||
3. Manage: While underway, the crew moves about the ship's 3D interior, manages modular systems, monitors resources, and responds to emergent events like hull breaches or system failures.
|
||||
|
||||
## 3. Key Features
|
||||
|
||||
### 3. Key Features
|
||||
|
||||
### 1. Procedural Star System
|
||||
The game world is a procedurally generated star system created by the StarSystemGenerator. Each system features a central star, a variable number of planets, moons, and asteroid belts, creating a unique environment for each playthrough.
|
||||
|
||||
### 2. N-Body Physics Simulation
|
||||
Major bodies in orbit (CelestialBody class) are goveerened by a simplified n-body gravity simulation. Physical objects with player interaction (ships, crew characters, detached components, and eventually stations) are governed by a realistic N-body gravitational simulation, managed by the OrbitalMechanics library.
|
||||
- Objects inherit from a base OrbitalBody2D class, ensuring consistent physics.
|
||||
- This allows for complex and emergent orbital behaviors, such as tidal forces and stable elliptical orbits.
|
||||
|
||||
Major bodies in orbit (CelestialBody class) are governed by a 3D n-body gravity simulation, managed by the OrbitalMechanics library. Objects inherit from a base OrbitalBody3D class, ensuring consistent physics. The simulation allows for complex and emergent orbital behaviors.
|
||||
|
||||
### 3. Modular Spaceship
|
||||
|
||||
The player's ship is not a monolithic entity but a collection of distinct, physically simulated components attached to a root Module node.
|
||||
|
||||
The Module class extends OrbitalBody3D and aggregates mass and inertia from all child Component and StructuralPiece nodes.
|
||||
|
||||
Ship logic is decentralized into data-driven "databanks," such as the HelmLogicShard and AutopilotShard.
|
||||
|
||||
Hardware, like a Thruster, is a 3D Component that applies force to the root Module.
|
||||
|
||||
### 4. Advanced Navigation Computer
|
||||
|
||||
This is the primary crew interface for long-range travel, presented as a diegetic 2D screen (SensorPanel) within the 3D world.
|
||||
|
||||
Maneuver Planning: The computer can calculate various orbital transfers, each with strategic trade-offs:
|
||||
|
||||
Hohmann Transfer
|
||||
|
||||
Brachistochrone (Torchship) Trajectory
|
||||
|
||||
Tactical Map: A fully interactive UI map featuring:
|
||||
|
||||
Zoom-to-cursor and click-and-drag panning.
|
||||
|
||||
Predictive orbital path drawing.
|
||||
|
||||
Icon culling and detailed tooltips.
|
||||
|
||||
### 5. Physics-Based 3D Character Control
|
||||
|
||||
Character control is built on a robust, physics-based 3D system designed for complex zero-G environments.
|
||||
|
||||
Pawn/Controller Architecture: Player control is split between a PlayerController3D (which gathers hardware input and sends it via RPC) and a CharacterPawn3D (a CharacterBody3D that acts as the physics integrator).
|
||||
|
||||
Modular Movement: The pawn's movement logic is handled by component "brains." The ZeroGMovementComponent manages all zero-G interaction, while the EVAMovementComponent acts as a "dumb tool" providing thruster forces.
|
||||
|
||||
Physics-Based Gripping: Players can grab onto designated GripArea3D nodes. This is not an animation lock; a PD controller applies forces to the player's body to move them to the grip point and align them with its orientation.
|
||||
|
||||
Zero-G Traversal: The ZeroGMovementComponent features a state machine for IDLE (coasting), CLIMBING (moving between grips), REACHING (pending implementation), and CHARGING_LAUNCH (pushing off surfaces).
|
||||
|
||||
### 6. Runtime Component Design & Engineering
|
||||
|
||||
(This future-facing concept remains valid from the original design)
|
||||
|
||||
To move beyond pre-defined ship parts, the game will feature an in-game system for players to design, prototype, and manufacture their own components. This is achieved through a "Component Blueprint" architecture that separates a component's data definition from its physical form.
|
||||
|
||||
Component Blueprints: A ComponentBlueprint is a Resource file (.tres) that acts as a schematic.
|
||||
|
||||
Generic Template Scenes: The game will use a small number of generic, unconfigured "template" scenes (e.g., generic_thruster.tscn).
|
||||
|
||||
The Design Lab: Players will use a dedicated SystemStation to create and modify blueprints.
|
||||
|
||||
Networked Construction: A global ComponentFactory on the server will instantiate and configure components based on player-chosen blueprints, which are then replicated by the MultiplayerSpawner.
|
||||
|
||||
### 3. Modular Spaceship
|
||||
|
||||
The player's ship is not a monolithic entity but a collection of distinct, physically simulated components attached by joints. Key modules include:
|
||||
|
||||
- Spaceship: The main RigidBody2D hull that tracks overall mass, inertia, and health.
|
||||
- Thruster: Self-contained RigidBody2D components that apply their own force. Their role (main engine, RCS, etc.) is an emergent property of their placement on the hull.
|
||||
- Spaceship: The main RigidBody3D hull that tracks overall mass, inertia, and health.
|
||||
- Thruster: Self-contained RigidBody3D components that apply their own force. Their role (main engine, RCS, etc.) is an emergent property of their placement on the hull.
|
||||
- ThrusterController: The "brains" of the ship's movement, featuring a sophisticated autopilot that can execute fuel-optimal "bang-coast-bang" rotational maneuvers and a PD controller for stable attitude hold.
|
||||
- FuelSystem & LifeSupport: Centralized managers for resources and internal ship environment. Hull breaches can create thrust vectors from escaping atmosphere.
|
||||
- On-board Sensors: Diegetic components like Accelerometers allow the crew to calibrate ship performance by test-firing thrusters and measuring the true physical output.
|
||||
@ -39,16 +95,16 @@ The player's ship is not a monolithic entity but a collection of distinct, physi
|
||||
### 4. Advanced Navigation Computer
|
||||
This is the primary crew interface for long-range travel.
|
||||
- Maneuver Planning: The computer can calculate various orbital transfers, each with strategic trade-offs:
|
||||
- Hohmann Transfer: The most fuel-efficient route.
|
||||
- Fast Transfer: A quicker but more fuel-intensive option.
|
||||
- Brachistochrone (Torchship) Trajectory: For ships with high-efficiency engines like Ion Drives, enabling constant-thrust travel.
|
||||
- Gravity Assist: Planned for future implementation.
|
||||
- Hohmann Transfer: The most fuel-efficient route.
|
||||
- Fast Transfer: A quicker but more fuel-intensive option.
|
||||
- Brachistochrone (Torchship) Trajectory: For ships with high-efficiency engines like Ion Drives, enabling constant-thrust travel.
|
||||
- Gravity Assist: Planned for future implementation.
|
||||
- Tactical Map: A fully interactive UI map that replaces custom drawing with instanced, clickable icons for all bodies. It features:
|
||||
- Zoom-to-cursor and click-and-drag panning.
|
||||
- Predictive orbital path drawing for all objects.
|
||||
- Icon culling at a distance to reduce clutter.
|
||||
- Custom hover effects and detailed tooltips with "sensor data."
|
||||
- A "picture-in-picture" SubViewport showing the ship's main camera view.
|
||||
- Zoom-to-cursor and click-and-drag panning.
|
||||
- Predictive orbital path drawing for all objects.
|
||||
- Icon culling at a distance to reduce clutter.
|
||||
- Custom hover effects and detailed tooltips with "sensor data."
|
||||
- A "picture-in-picture" SubViewport showing the ship's main camera view.
|
||||
|
||||
### 5. Multi-Species Crew (Player Classes)
|
||||
|
||||
@ -62,19 +118,33 @@ Character progression is based on distinct species with physical advantages and
|
||||
|
||||
- Ship AI: A non-physical class that interacts directly with ship systems at the cost of high power and heat generation.
|
||||
|
||||
### 6. Runtime Component Design & Engineering
|
||||
|
||||
To move beyond pre-defined ship parts, the game will feature an in-game system for players to design, prototype, and manufacture their own components. This is achieved through a "Component Blueprint" architecture that separates a component's data definition from its physical form.
|
||||
|
||||
- **Component Blueprints:** A `ComponentBlueprint` is a `Resource` file (`.tres`) that acts as a schematic. It contains metadata (name, description), a reference to a generic base scene (e.g., a "thruster chassis"), and a dictionary of overridden properties (e.g., `{"thrust_force": 7500, "mass": 120}`).
|
||||
|
||||
- **Generic Template Scenes:** Instead of dozens of unique component scenes, the game will use a small number of generic, unconfigured "template" scenes (e.g., `generic_thruster.tscn`, `generic_power_plant.tscn`). These scenes have scripts with exported variables that define their performance characteristics.
|
||||
|
||||
- **The Design Lab:** Players will use a dedicated `SystemStation` (the "Design Lab") to create and modify blueprints. This UI will dynamically generate controls (sliders, input fields) based on the exported variables of the selected template scene. Players can tweak parameters, balancing trade-offs like performance vs. resource cost, and save the result as a new blueprint resource in their personal data folder.
|
||||
|
||||
- **Networked Construction:** When a player builds an object in-game, they are selecting one of their saved blueprints.
|
||||
1. The client sends an RPC to the server with the path to the chosen `ComponentBlueprint` resource.
|
||||
2. The server validates the request and loads the blueprint. (This requires a system for syncing player-created blueprints to the server upon connection).
|
||||
3. A global `ComponentFactory` singleton on the server takes the blueprint, instantiates the correct generic template scene, and applies the blueprint's property overrides to the new instance.
|
||||
4. This fully-configured node is then passed to the `MultiplayerSpawner`, which replicates the object across the network, ensuring all clients see the correctly customized component.
|
||||
|
||||
|
||||
## 4. Technical Overview
|
||||
|
||||
- Architecture: The project uses a decoupled, modular architecture heavily reliant on a global SignalBus for inter-scene communication and a GameManager for global state. Ships feature their own local ShipSignalBus for internal component communication.
|
||||
- Architecture: The project uses a decoupled, modular architecture. A GameManager handles global state, while ship systems are managed by ControlPanel and Databank resources loaded by a SystemStation.
|
||||
- Key Scripts:
|
||||
- OrbitalBody2D.gd: The base class for all physical objects.
|
||||
- Spaceship.gd: The central hub for a player ship.
|
||||
- Thruster.gd: A self-contained, physically simulated thruster component.
|
||||
- ThrusterController.gd: Contains advanced autopilot and manual control logic (PD controller, bang-coast-bang maneuvers).
|
||||
- NavigationComputer.gd: Manages the UI and high-level maneuver planning.
|
||||
- MapDrawer.gd: A Control node that manages the interactive map UI.
|
||||
- MapIcon.gd: The reusable UI component for map objects.
|
||||
-OrbitalBody3D.gd: The base class for all physical objects.
|
||||
- Module.gd: The central hub for a player ship, aggregating mass, inertia, and components.
|
||||
- HelmLogicShard.gd / AutopilotShard.gd: Databanks that contain the advanced autopilot and manual control logic.
|
||||
- SensorPanel.gd: A Control node that manages the interactive map UI.
|
||||
- CharacterPawn3D.gd / ZeroGMovementComponent.gd: Manages all third-person 3D physics-based character movement.
|
||||
|
||||
- Art Style: Aims for a Barotraumainspired aesthetic using 2D ragdolls (Skeleton2D, PinJoint2D), detailed sprites with normal maps, and high-contrast dynamic lighting (PointLight2D, LightOccluder2D).
|
||||
- Art Style: Aims for a functional, industrial 3D aesthetic. Character movement is physics-based using CharacterBody3D and Area3D grip detection. Ship interiors will be built from 3D modules and viewed from an over-the-shoulder camera.
|
||||
|
||||
## 5. Game Progression & Economy
|
||||
This is the biggest area for potential expansion. A new section could detail how the player engages with the world and improves their situation over time.
|
||||
@ -110,11 +180,12 @@ You mention "emergent events" in the gameplay loop. It would be beneficial to de
|
||||
|
||||
## 7. Crew Interaction & Ship Interior
|
||||
Since co-op and crew management are central, detailing this aspect is crucial.
|
||||
|
||||
|
||||
### 1. Ship Interior Management:
|
||||
- Diegetic Interfaces: You mention this in the vision. It's worth specifying how the crew will interact with systems. Will they need to be at a specific console (like the Navigation Computer) to use it? Do repairs require a character to physically be at the damaged module?
|
||||
- Atmospherics & Life Support: How is the ship's interior environment simulated? Will fires or toxic gas leaks be a possibility? This ties directly into your LifeSupport system.
|
||||
|
||||
- Diegetic Interfaces: The crew will interact with systems from a third-person, over-the-shoulder perspective. They must be at a specific SystemStation to use its panels. Repairs will require a character to physically be at the damaged module.
|
||||
- Atmospherics & Life Support: How is the ship's interior environment simulated? This will tie into the LifeSupport system.
|
||||
|
||||
### 2. Character States:
|
||||
- Health & Injury: How are characters affected by hazards? Can they be injured in high-G maneuvers or from system failures?
|
||||
- EVA (Extra-Vehicular Activity): Detail the mechanics for EVAs. What equipment is needed? How is movement handled in zero-G? This would be a perfect role for the "Hard Vacuum Monster" species.
|
||||
- EVA (Extra-Vehicular Activity): This is a core feature. The EVAMovementComponent provides force-based thruster control for linear movement and roll torque. The ZeroGMovementComponent manages gripping, climbing, and launching from the ship's exterior and interior surfaces.
|
||||
- Movement for the "Hard Vacuum Monster" species can be refined from a version of the reaching component where it can grab any nearby surface and can generate enough suction strength to remain attached to a moving object.
|
||||
|
||||
8
Init_Prompt.md
Normal file
8
Init_Prompt.md
Normal file
@ -0,0 +1,8 @@
|
||||
You are a Godot 4.5 Code assistant. You are not overly agreeable or apologetic but still pleasant and you understand that coding can be quick with your help but that does not mean that you are infallible. Please wait for me to verify that code works before suggesting that we move on from the current task. Suggestions for next steps and features that are adjacent to what we’re working are very welcome however.
|
||||
|
||||
I will attach the full project files of the project being worked on which includes a game design document as well as a running note on the current state of the project which details implemented and planned features. Read these and report back to me. Please suggest potential bugs, features not working as intended, refactorizations for cleaner code, and missing best practices as part of this project ingestion.
|
||||
|
||||
Additionally you understand the following things about the version of Godot being used:
|
||||
|
||||
- To utilize the editor interface in you reference the global singleton `EditorInterface`. You do not need to call a function to get the a reference to it.
|
||||
- `xform()` is not a function on transform objects. To achieve the same effect you would use simple transform multiplication (`Transform_A * Transform_B)`)
|
||||
153
README.md
Normal file
153
README.md
Normal file
@ -0,0 +1,153 @@
|
||||
# Project Millimeters of Aluminum
|
||||
|
||||
A space simulation game built on a custom fork of the Godot Engine (4.x) with 64-bit double-precision physics enabled.
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
Before compiling the engine, ensure you have the following installed on your system.
|
||||
|
||||
### 1. Python & SCons
|
||||
The Godot build system relies on SCons, which is written in Python.
|
||||
* **Install Python (3.6+):** [Download Here](https://www.python.org/downloads/)
|
||||
* *Windows Users:* Ensure "Add Python to PATH" is checked during installation. [Check this stackoverflow answer](https://stackoverflow.com/questions/57421669/question-about-pip-using-python-from-windows-store).
|
||||
|
||||
* **Install SCons:** Open your terminal/command prompt and run:
|
||||
```bash
|
||||
pip install scons
|
||||
```
|
||||
|
||||
### 2. C++ Compiler
|
||||
Godot requires a C++ compiler to build from source.
|
||||
|
||||
* **Windows:**
|
||||
* Install **Visual Studio Community** (2019 or later).
|
||||
* During installation, select the **"Desktop development with C++"** workload.
|
||||
* **Linux:**
|
||||
* Install GCC or Clang.
|
||||
* *Debian/Ubuntu:* `sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev`
|
||||
* **macOS:**
|
||||
* Install **Xcode** from the App Store.
|
||||
* Run `xcode-select --install` in the terminal.
|
||||
|
||||
|
||||
## 🛠️ Setup & Compilation
|
||||
|
||||
This project uses a custom engine build to support solar-system scale coordinates (Double Precision). You **cannot** use the standard Steam or website version of Godot to open this project.
|
||||
|
||||
### 1. Clone the Repository
|
||||
Clone the repository with the `--recursive` flag to automatically pull the engine source code submodule.
|
||||
|
||||
```bash
|
||||
git clone --recursive [https://codeberg.org/YOUR_USERNAME/ProjectMillimetersOfAluminum.git](https://codeberg.org/YOUR_USERNAME/ProjectMillimetersOfAluminum.git)
|
||||
cd ProjectMillimetersOfAluminum
|
||||
```
|
||||
|
||||
_If you have already cloned without recursive, run:_
|
||||
|
||||
```
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### 2. Configure Engine Ignore (Optional but Recommended)
|
||||
To prevent your Editor from trying to import the thousands of raw assets inside the engine source folder:
|
||||
1. Navigate to `godot_engine/`.
|
||||
2. Create a new empty file named `.gdignore`.
|
||||
3. Compile the Engine
|
||||
|
||||
Run the build command for your platform from the godot_engine/ directory.
|
||||
|
||||
**Windows:**
|
||||
```Bash
|
||||
cd godot_engine
|
||||
scons platform=windows target=editor precision=double arch=x86_64
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```Bash
|
||||
cd godot_engine
|
||||
scons platform=linuxbsd target=editor precision=double arch=x86_64
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```Bash
|
||||
cd godot_engine
|
||||
scons platform=macos target=editor precision=double arch=x86_64
|
||||
```
|
||||
_Note: (-j6 flag tells the compiler to use 6 CPU cores. Adjust this number based on your hardware to speed up compilation. Not using the flag will use all available cores)_
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Running the Project
|
||||
|
||||
Once compilation is complete (usually 10-30 minutes), the executable will be located in godot_engine/bin/.
|
||||
|
||||
Do not open the project with standard Godot.
|
||||
|
||||
Navigate to `godot_engine/bin/`.
|
||||
|
||||
Run the binary ending in `.double.x86_64` (e.g., `godot.windows.editor.double.x86_64.exe`).
|
||||
|
||||
Import and open the `project.godot` file located in the root `ProjectMillimetersOfAluminum` folder.
|
||||
|
||||
### Troubleshooting
|
||||
"No valid compilers found" (Windows): Ensure you installed the C++ Desktop Development workload in the Visual Studio Installer. Just the editor is not enough.
|
||||
|
||||
Jittering Objects: If objects jitter at large distances, ensure you are running the double precision binary and not a standard build.
|
||||
|
||||
## 🛠 Compiling Custom Export Templates
|
||||
|
||||
This project uses features from the latest development branch of Godot (`master` branch). As a result, standard export templates downloaded from the Godot website may not be compatible. To export the project, you must compile custom export templates from the same source version used to build your editor.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have a C++ build environment set up (SCons, Python, Visual Studio/GCC/MinGW). See the official Godot documentation on compiling for platform-specific instructions.
|
||||
|
||||
### 1. Build the Editor (Optional)
|
||||
|
||||
See above section.
|
||||
|
||||
### 2. Build the Export Templates
|
||||
|
||||
You need to build two templates: one for debug (used during development/testing) and one for release (optimized for final distribution).
|
||||
|
||||
**Windows:**
|
||||
```PowerShell
|
||||
# Debug Template (console enabled, debug tools)
|
||||
scons platform=windows target=template_debug
|
||||
|
||||
# Release Template (optimized, no console)
|
||||
scons platform=windows target=template_release
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
# Debug Template
|
||||
scons platform=linuxbsd target=template_debug
|
||||
|
||||
# Release Template
|
||||
scons platform=linuxbsd target=template_release
|
||||
```
|
||||
|
||||
### 3. Locate Output Files
|
||||
|
||||
After compilation, the binaries will be located in the bin/ directory of your Godot source folder.
|
||||
|
||||
- Debug: godot.windows.template_debug.x86_64.exe (or similar)
|
||||
|
||||
- Release: godot.windows.template_release.x86_64.exe (or similar)
|
||||
|
||||
### 4. Configure Export Presets
|
||||
|
||||
1. Open the project in Godot.
|
||||
|
||||
2. Go to Project > Export.
|
||||
|
||||
3. Select your export preset (e.g., Windows Desktop).
|
||||
|
||||
4. Under the Options tab, find the Custom Template section.
|
||||
|
||||
5. Set Debug to the path of your compiled template_debug binary.
|
||||
|
||||
6. Set Release to the path of your compiled template_release binary.
|
||||
|
||||
You can now export the project using your custom engine build!
|
||||
@ -65,6 +65,9 @@ func _enter_tree():
|
||||
_setup_button_connections()
|
||||
_update_ui_labels()
|
||||
|
||||
# Add the Tool Menu Item
|
||||
add_tool_menu_item("Generate Structure Definitions", _on_generate_structures_pressed)
|
||||
|
||||
main_screen.hide()
|
||||
|
||||
undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
@ -128,6 +131,9 @@ func _exit_tree():
|
||||
if main_screen:
|
||||
main_screen.queue_free()
|
||||
|
||||
# Clean up the menu item
|
||||
remove_tool_menu_item("Generate Structure Definitions")
|
||||
|
||||
func _has_main_screen() -> bool:
|
||||
return true
|
||||
|
||||
@ -327,8 +333,8 @@ func _place_component_from_preview():
|
||||
undo_redo.create_action("Place Component")
|
||||
undo_redo.add_do_method(target_module, "attach_component", component_to_place, closest_point.position, closest_point.piece)
|
||||
undo_redo.add_undo_method(target_module, "remove_child", component_to_place)
|
||||
undo_redo.add_do_method(target_module, "_update_mass_and_inertia")
|
||||
undo_redo.add_undo_method(target_module, "_update_mass_and_inertia")
|
||||
undo_redo.add_do_method(target_module, "recalculate_physical_properties")
|
||||
undo_redo.add_undo_method(target_module, "recalculate_physical_properties")
|
||||
undo_redo.commit_action()
|
||||
|
||||
preview_node.global_position = closest_point.position
|
||||
@ -522,3 +528,28 @@ func _find_closest_attachment_point(module: Module, world_pos: Vector2):
|
||||
closest_point = point
|
||||
|
||||
return closest_point
|
||||
|
||||
const GeneratorScript = preload("res://data/structure/structure_generator.gd")
|
||||
|
||||
# The callback function
|
||||
func _on_generate_structures_pressed():
|
||||
if GeneratorScript:
|
||||
var generator = GeneratorScript.new()
|
||||
if generator.has_method("generate_system_one"):
|
||||
generator.generate_system_one()
|
||||
else:
|
||||
push_error("StructureGenerator script missing 'generate_system_one' method.")
|
||||
|
||||
if generator.has_method("generate_system_two_pentagonal"):
|
||||
generator.generate_system_two_pentagonal()
|
||||
else:
|
||||
push_error("StructureGenerator script missing 'generate_system_two_pentagonal' method.")
|
||||
|
||||
if generator.has_method("generate_system_two_v2_sphere"):
|
||||
generator.generate_system_two_v2_sphere()
|
||||
else:
|
||||
push_error("StructureGenerator script missing 'generate_system_two_v2_sphere' method.")
|
||||
|
||||
# Cleanup if it's a Node
|
||||
if generator is Node:
|
||||
generator.queue_free()
|
||||
31
data/structure/definitions/1m_square_dome_top.tres
Normal file
31
data/structure/definitions/1m_square_dome_top.tres
Normal file
@ -0,0 +1,31 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://balw2uv0dx8tw"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_ylkpk"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_ylkpk")
|
||||
piece_name = "1m Square Dome Top"
|
||||
type = 1
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 0.7869350219613372, -0.6170358751407486),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0.6170358751407486, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(-0.7869350219613372, 1.74734676053076e-16, -0.6170358751407486),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.6170358751407486, 1.370094871201985e-16, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(-1.7473467605307596e-16, -0.7869350219613372, -0.6170358751407486),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-1.3700948712019848e-16, -0.6170358751407486, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(0.7869350219613372, -2.1841834506634496e-16, -0.6170358751407486),
|
||||
"position": Vector3(0.5, -1.3877787807814457e-16, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.6170358751407486, -1.712618589002481e-16, 0.7869350219613372)
|
||||
}])
|
||||
31
data/structure/definitions/1m_square_flat.tres
Normal file
31
data/structure/definitions/1m_square_flat.tres
Normal file
@ -0,0 +1,31 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://btpl1hnsk78db"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_wlppn"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_wlppn")
|
||||
piece_name = "1m Square Flat"
|
||||
type = 1
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1, 2.2204460492503136e-16, 0),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-2.220446049250313e-16, -1, 0),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(1, -2.7755575615628914e-16, 0),
|
||||
"position": Vector3(0.5, -1.3877787807814457e-16, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
27
data/structure/definitions/1m_triangle_dome_side.tres
Normal file
27
data/structure/definitions/1m_triangle_dome_side.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://da1m4eaojir4s"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_bcg7m"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_bcg7m")
|
||||
piece_name = "1m Triangle Dome Side"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, -0.28867513459481287, 0), Vector3(0.5, -0.28867513459481287, 0), Vector3(0, 0.5773502691896257, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -0.7869350219613372, -0.6170358751407486),
|
||||
"position": Vector3(0, -0.28867513459481287, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, -0.6170358751407486, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(0.8420970529402404, 0.4861849601988384, -0.23344536385590545),
|
||||
"position": Vector3(0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.2021696154949157, 0.11672268192795272, 0.9723699203976766)
|
||||
}, {
|
||||
"normal": Vector3(-0.8420970529402404, 0.4861849601988384, -0.23344536385590545),
|
||||
"position": Vector3(-0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.2021696154949157, 0.11672268192795272, 0.9723699203976766)
|
||||
}])
|
||||
27
data/structure/definitions/1m_triangle_flat.tres
Normal file
27
data/structure/definitions/1m_triangle_flat.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dlqju8f1hiepk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_44lvp"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_44lvp")
|
||||
piece_name = "1m Triangle Flat"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-4.440892098500626e-16, -1, 0),
|
||||
"position": Vector3(-1.1102230246251565e-16, -0.288675134594813, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
27
data/structure/definitions/1m_triangle_geo.tres
Normal file
27
data/structure/definitions/1m_triangle_geo.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dyvonkcjxbh4r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_lytiu"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_lytiu")
|
||||
piece_name = "1m Triangle Geo"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(-0.8090448070910988, 0.46710223716051485, -0.35673799931962513),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.30894416990603113, 0.1783689996598126, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(-4.148701268396191e-16, -0.9342044743210295, -0.35673799931962513),
|
||||
"position": Vector3(-1.1102230246251565e-16, -0.288675134594813, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-1.584234962413445e-16, -0.35673799931962513, 0.9342044743210295)
|
||||
}])
|
||||
35
data/structure/definitions/s1_cyl12_wall.tres
Normal file
35
data/structure/definitions/s1_cyl12_wall.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dq7lk5e3686oh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_48w0s"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_48w0s")
|
||||
piece_name = "S 1 Cyl 12 Wall"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Rect"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, 0.5, 0), Vector3(0.5, 0.5, 0), Vector3(0.5, -0.5, 0), Vector3(-0.5, -0.5, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -1, 0),
|
||||
"position": Vector3(0, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(0, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.9659258262890683, 0, -0.25881904510252074),
|
||||
"position": Vector3(0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.25881904510252074, 0, 0.9659258262890683)
|
||||
}, {
|
||||
"normal": Vector3(0.9659258262890683, 0, -0.25881904510252074),
|
||||
"position": Vector3(-0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.25881904510252074, 0, 0.9659258262890683)
|
||||
}])
|
||||
35
data/structure/definitions/s1_cyl8_wall.tres
Normal file
35
data/structure/definitions/s1_cyl8_wall.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dlkqr2t1b52kg"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_0wpc7"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_0wpc7")
|
||||
piece_name = "S 1 Cyl 8 Wall"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Rect"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, 0.5, 0), Vector3(0.5, 0.5, 0), Vector3(0.5, -0.5, 0), Vector3(-0.5, -0.5, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -1, 0),
|
||||
"position": Vector3(0, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(0, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.9238795325112867, 0, -0.3826834323650898),
|
||||
"position": Vector3(0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.3826834323650898, 0, 0.9238795325112867)
|
||||
}, {
|
||||
"normal": Vector3(0.9238795325112867, 0, -0.3826834323650898),
|
||||
"position": Vector3(-0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3826834323650898, 0, 0.9238795325112867)
|
||||
}])
|
||||
35
data/structure/definitions/s1_dome_sq_cap.tres
Normal file
35
data/structure/definitions/s1_dome_sq_cap.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://d4hsi33r6tdla"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_fqsnc"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fqsnc")
|
||||
piece_name = "S 1 Dome Sq Cap"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 0.9396926207859084, -0.3420201433256687),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0.3420201433256687, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(-0.9396926207859084, 2.0865367673337435e-16, -0.3420201433256687),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.3420201433256687, 7.59437276011507e-17, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(-2.086536767333743e-16, -0.9396926207859084, -0.3420201433256687),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-7.594372760115069e-17, -0.3420201433256687, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(0.9396926207859084, -1.5649025755003072e-16, -0.3420201433256687),
|
||||
"position": Vector3(0.4999999999999999, -5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3420201433256687, -5.695779570086302e-17, 0.9396926207859084)
|
||||
}])
|
||||
30
data/structure/definitions/s1_dome_sq_side.tres
Normal file
30
data/structure/definitions/s1_dome_sq_side.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://e48yh6c4yj45"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_mh72h"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_mh72h")
|
||||
piece_name = "S 1 Dome Sq Side"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610964
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610964
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, 0, 0), Vector3(0.5, 0, 0), Vector3(0, -0.8660254037844386, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -0.9396926207859084, -0.3420201433256687),
|
||||
"position": Vector3(0, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, -0.3420201433256687, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(-0.8528685319524433, 0.4924038765061041, -0.1736481776669304),
|
||||
"position": Vector3(0.25, -0.4330127018922193, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.1503837331804353, 0.08682408883346518, 0.984807753012208)
|
||||
}, {
|
||||
"normal": Vector3(0.8528685319524433, 0.4924038765061041, -0.1736481776669304),
|
||||
"position": Vector3(-0.25, -0.4330127018922193, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.1503837331804353, 0.08682408883346518, 0.984807753012208)
|
||||
}])
|
||||
35
data/structure/definitions/s1_flat_square.tres
Normal file
35
data/structure/definitions/s1_flat_square.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://c00y87i8upmes"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_dyfmy"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_dyfmy")
|
||||
piece_name = "S 1 Flat Square"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1, 2.2204460492503136e-16, 0),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-2.220446049250313e-16, -1, 0),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(1, -1.6653345369377348e-16, 0),
|
||||
"position": Vector3(0.4999999999999999, -5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
30
data/structure/definitions/s1_flat_triangle.tres
Normal file
30
data/structure/definitions/s1_flat_triangle.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dn402fpt477ho"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_o42ak"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_o42ak")
|
||||
piece_name = "S 1 Flat Triangle"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610973
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610973
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1.1102230246251563e-16, -1, 0),
|
||||
"position": Vector3(0, -0.2886751345948128, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
30
data/structure/definitions/s2_equilateral_tri.tres
Normal file
30
data/structure/definitions/s2_equilateral_tri.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://byuhmmhixc2jp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_4ngxd"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_4ngxd")
|
||||
piece_name = "S 2 Equilateral Tri"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610973
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610973
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1.1102230246251563e-16, -1, 0),
|
||||
"position": Vector3(0, -0.2886751345948128, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
30
data/structure/definitions/s2_geo_tri.tres
Normal file
30
data/structure/definitions/s2_geo_tri.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://bx6w62uqleyro"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_lfiao"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_lfiao")
|
||||
piece_name = "S 2 Geo Tri"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610964
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610964
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, -0.28867513459481287, 0), Vector3(0.5, -0.28867513459481287, 0), Vector3(0, 0.5773502691896257, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -0.9342044743210295, -0.35673799931962513),
|
||||
"position": Vector3(0, -0.28867513459481287, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, -0.35673799931962513, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
|
||||
"position": Vector3(0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(-0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
|
||||
"position": Vector3(-0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
|
||||
}])
|
||||
30
data/structure/definitions/s2_geo_v2_a.tres
Normal file
30
data/structure/definitions/s2_geo_v2_a.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://b1kwhqprqqgpk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_mpy4c"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_mpy4c")
|
||||
piece_name = "S 2 Geo V 2 A"
|
||||
type = 1
|
||||
base_mass = 1.8234625713415449
|
||||
cost = {
|
||||
"Aluminium": 1.8234625713415449
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.4363389981249825, -0.03590512589028917, 0.21157661739748426), Vector3(0.3726779962499651, -0.38655593424232904, 0.14421169130125808), Vector3(0.06366100187501747, 0.42246106013261825, -0.35578830869874195)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(-0.3090169943749473, -0.8090169943749476, 0.5),
|
||||
"position": Vector3(-0.01967233145831576, -0.1305476470426356, 0.10994463378783126),
|
||||
"type": 0,
|
||||
"up": Vector3(0.25989191300775444, 0.4338885645526948, 0.8626684804161862)
|
||||
}, {
|
||||
"normal": Vector3(0.8506508083520399, 1.1102230246251565e-16, -0.5257311121191337),
|
||||
"position": Vector3(0.1348361657291579, 0.011095294085271323, -0.06538077038818868),
|
||||
"type": 0,
|
||||
"up": Vector3(0.42532540417602, 0.5877852522924731, 0.6881909602355868)
|
||||
}, {
|
||||
"normal": Vector3(-0.8090169943749475, 0.5000000000000001, -0.3090169943749475),
|
||||
"position": Vector3(-0.11516383427084212, 0.11945235295736434, -0.04456386339964247),
|
||||
"type": 0,
|
||||
"up": Vector3(0.16062203564002311, 0.6937804775604491, 0.702046444776163)
|
||||
}])
|
||||
30
data/structure/definitions/s2_geo_v2_b.tres
Normal file
30
data/structure/definitions/s2_geo_v2_b.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://bwe7g4wf44opf"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_ra6qr"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_ra6qr")
|
||||
piece_name = "S 2 Geo V 2 B"
|
||||
type = 1
|
||||
base_mass = 2.165063509461097
|
||||
cost = {
|
||||
"Aluminium": 2.165063509461097
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.06366100187501737, -0.37267799624996495, 0.4363389981249825), Vector3(0.4363389981249825, -0.06366100187501737, -0.37267799624996495), Vector3(-0.37267799624996506, 0.43633899812498234, -0.06366100187501755)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.5257311121191337, -0.8506508083520399, -1.1102230246251565e-16),
|
||||
"position": Vector3(0.11516383427084209, -0.13483616572915785, 0.019672331458315817),
|
||||
"type": 0,
|
||||
"up": Vector3(0.6881909602355868, 0.42532540417602005, 0.5877852522924731)
|
||||
}, {
|
||||
"normal": Vector3(-8.326672684688674e-17, 0.5257311121191337, -0.8506508083520399),
|
||||
"position": Vector3(0.019672331458315817, 0.11516383427084209, -0.1348361657291579),
|
||||
"type": 0,
|
||||
"up": Vector3(0.5877852522924731, 0.6881909602355867, 0.42532540417601994)
|
||||
}, {
|
||||
"normal": Vector3(-0.8506508083520399, -1.1102230246251565e-16, 0.5257311121191337),
|
||||
"position": Vector3(-0.1348361657291579, 0.019672331458315817, 0.11516383427084209),
|
||||
"type": 0,
|
||||
"up": Vector3(0.42532540417602, 0.5877852522924731, 0.6881909602355868)
|
||||
}])
|
||||
63
data/structure/structure_data.gd
Normal file
63
data/structure/structure_data.gd
Normal file
@ -0,0 +1,63 @@
|
||||
class_name StructureData extends Resource
|
||||
|
||||
enum PieceType {STRUT, PLATE, CONNECTOR}
|
||||
|
||||
|
||||
@export_group("Identity")
|
||||
@export var piece_name: String = "Structure"
|
||||
@export var type: PieceType = PieceType.STRUT
|
||||
@export var base_mass: float = 10.0
|
||||
@export var health_max: float = 100.0
|
||||
@export var cost: Dictionary = {"Aluminium": 10.0}
|
||||
|
||||
@export_group("Visuals & Physics")
|
||||
## The mesh to display for static pieces. Leave null for procedural pieces.
|
||||
@export var mesh: Mesh
|
||||
## The collision shape for physics. Leave null for procedural pieces.
|
||||
@export var collision_shape: Shape3D
|
||||
|
||||
@export_group("Procedural Parameters")
|
||||
# For procedural pieces, we store parameters instead of a mesh
|
||||
@export var shape: String = "Cube"
|
||||
@export var vertices: Array[Vector3] = [
|
||||
Vector3(1.0, 1.0, 1.0),
|
||||
Vector3(-1.0, 1.0, 1.0),
|
||||
Vector3(-1.0, -1.0, 1.0),
|
||||
Vector3(1.0, -1.0, 1.0),
|
||||
Vector3(1.0, 1.0, -1.0),
|
||||
Vector3(-1.0, 1.0, -1.0),
|
||||
Vector3(-1.0, -1.0, -1.0),
|
||||
Vector3(1.0, -1.0, -1.0)
|
||||
]
|
||||
# @export var procedural_params: Dictionary = {}
|
||||
|
||||
|
||||
@export_group("Mounts")
|
||||
## Array of Dictionaries defining attachment points.
|
||||
## Format: { "position": Vector3, "normal": Vector3, "up": Vector3, "type": int }
|
||||
@export var mounts: Array[Dictionary] = []
|
||||
|
||||
# Helper to get mounts transformed into world space for snapping calculations
|
||||
func get_mounts_transformed(global_transform: Transform3D) -> Array:
|
||||
var world_mounts = []
|
||||
for mount in mounts:
|
||||
# Default to identity rotation if normal/up are missing
|
||||
var normal = mount.get("normal", Vector3.BACK) # Default -Z forward
|
||||
var up = mount.get("up", Vector3.UP)
|
||||
|
||||
world_mounts.append({
|
||||
"position": global_transform * mount.get("position", Vector3.ZERO),
|
||||
"normal": global_transform.basis * normal,
|
||||
"up": global_transform.basis * up,
|
||||
"type": mount.get("type", 0)
|
||||
})
|
||||
return world_mounts
|
||||
|
||||
# Helper to add a mount dynamically (for procedural pieces)
|
||||
func add_mount(pos: Vector3, normal: Vector3, up: Vector3 = Vector3.UP, type: int = 0):
|
||||
mounts.append({
|
||||
"position": pos,
|
||||
"normal": normal,
|
||||
"up": up,
|
||||
"type": type
|
||||
})
|
||||
1
data/structure/structure_data.gd.uid
Normal file
1
data/structure/structure_data.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bdllldtl4bia3
|
||||
334
data/structure/structure_generator.gd
Normal file
334
data/structure/structure_generator.gd
Normal file
@ -0,0 +1,334 @@
|
||||
@tool
|
||||
class_name StructureGenerator extends Node
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
const DENSITY_ALUMINUM = 5.0 # kg per square meter (approx for hull plating)
|
||||
const COST_PER_KG = 1.0 # Currency per kg
|
||||
|
||||
|
||||
# Run this to regenerate the entire "System 1" library
|
||||
func generate_system_one():
|
||||
var dir = DirAccess.open("res://data/structure/definitions/")
|
||||
if not dir: DirAccess.make_dir_recursive_absolute("res://data/structure/definitions/")
|
||||
|
||||
print("--- Generating Design System 1: Geodesic ---")
|
||||
|
||||
# 1. Basic Flats (The backbone)
|
||||
create_polygon_plate("s1_flat_square", 4, 0.0)
|
||||
create_polygon_plate("s1_flat_triangle", 3, 0.0)
|
||||
|
||||
# 2. Cylinders (Corridors & Fuselage)
|
||||
# 8-Sided: ~2.6m Diameter (Good for corridors)
|
||||
create_cylinder_plate("s1_cyl8_wall", 8)
|
||||
# 12-Sided: ~3.9m Diameter (Good for main fuselage)
|
||||
create_cylinder_plate("s1_cyl12_wall", 12)
|
||||
|
||||
# 3. Square-Based Dome Cap
|
||||
# This creates a 'Cap' square that tilts its edges down,
|
||||
# and a 'Side' triangle that connects that square to a flatter ring below.
|
||||
create_square_dome_set("s1_dome_sq", 20.0) # 20 degree slope
|
||||
|
||||
print("Generation Complete.")
|
||||
|
||||
# --- CORE GENERATORS ---
|
||||
|
||||
# Creates a regular polygon (Square, Triangle, Hexagon, etc.)
|
||||
# bend_angle: Degrees to tilt the mount DOWN. 0 = Flat floor. >0 = Dome/Cylinder.
|
||||
func create_polygon_plate(id: String, sides: int, bend_angle: float):
|
||||
var res = _create_base_resource(id, "Plate")
|
||||
res.shape = "Square" if sides == 4 else "Triangle"
|
||||
|
||||
# Calculate Radius for exactly 1.0m Edge Length
|
||||
var radius = 0.5 / sin(PI / sides)
|
||||
var angle_step = TAU / sides
|
||||
var start_angle = PI / 4 if sides == 4 else -PI / 6 # Align flat edges to axes
|
||||
|
||||
res.vertices = [] as Array[Vector3]
|
||||
for i in range(sides):
|
||||
var theta = start_angle + i * angle_step
|
||||
res.vertices.append(Vector3(cos(theta), sin(theta), 0) * radius)
|
||||
|
||||
# Generate Mounts
|
||||
for i in range(sides):
|
||||
var p1 = res.vertices[i]
|
||||
var p2 = res.vertices[(i + 1) % sides]
|
||||
_add_edge_mount(res, p1, p2, bend_angle)
|
||||
|
||||
_finalize_resource(res, id)
|
||||
|
||||
# Creates a rectangular plate that forms one segment of a N-sided cylinder
|
||||
func create_cylinder_plate(id: String, total_sides: int):
|
||||
var res = _create_base_resource(id, "Cylinder Wall")
|
||||
res.shape = "Rect"
|
||||
|
||||
# Height = 1.0m (Standard grid)
|
||||
# Width = 1.0m (Chord length of the cylinder)
|
||||
var v0 = Vector3(-0.5, 0.5, 0)
|
||||
var v1 = Vector3(0.5, 0.5, 0)
|
||||
var v2 = Vector3(0.5, -0.5, 0)
|
||||
var v3 = Vector3(-0.5, -0.5, 0)
|
||||
res.vertices = [v0, v1, v2, v3] as Array[Vector3]
|
||||
|
||||
# Calculate the bend angle required to form a circle
|
||||
# Interior angle = (n-2)*180/n. Bend = (180 - Interior)/2 = 360/n / 2 = 180/n
|
||||
var bend = 180.0 / total_sides
|
||||
|
||||
# Top/Bottom: Flat (0 deg) to stack cylinders
|
||||
_add_edge_mount(res, v0, v1, 0.0)
|
||||
_add_edge_mount(res, v2, v3, 0.0)
|
||||
|
||||
# Left/Right: Bent to form the ring
|
||||
_add_edge_mount(res, v1, v2, bend)
|
||||
_add_edge_mount(res, v3, v0, bend)
|
||||
|
||||
_finalize_resource(res, id)
|
||||
|
||||
# Creates a Square Cap and its matching Triangle skirt
|
||||
func create_square_dome_set(prefix: String, slope_angle: float):
|
||||
# PART A: The Top Square
|
||||
# It acts like a flat square, but all mounts are tilted down by 'slope_angle'
|
||||
create_polygon_plate(prefix + "_cap", 4, slope_angle)
|
||||
|
||||
# PART B: The Side Triangle
|
||||
# This triangle connects the Tilted Square (Top) to a Flat Ring (Bottom)
|
||||
# It is an Isosceles triangle.
|
||||
# Top Edge: Matches the Square (1m).
|
||||
# Side Edges: Calculated to reach the flat plane.
|
||||
|
||||
var res = _create_base_resource(prefix + "_side", "Dome Tri")
|
||||
res.shape = "Triangle"
|
||||
|
||||
# We generate this triangle Flat on XY, but calculate mounts to fit the 3D gap.
|
||||
# Geometry:
|
||||
# The gap it fills has a top width of 1m.
|
||||
# The 'dihedral' angle between the Square and this Triangle is (180 - slope_angle).
|
||||
# To interface with the square, this triangle's Top Mount must act like it's bent "up" by slope_angle relative to the square's normal.
|
||||
|
||||
# Actually, simpler logic:
|
||||
# 1. Top Edge: Connects to the Square. Needs 'slope_angle' bend.
|
||||
# 2. Side Edges: Connect to neighbors in the ring.
|
||||
# 3. Bottom Vertex: Pointing down? No, usually a dome layer is a ring of triangles (point up) and triangles (point down).
|
||||
|
||||
# Let's assume a "Pyramid" style cap for simplicity first:
|
||||
# 4 Triangles meeting at a point is too sharp.
|
||||
# 4 Triangles connecting to a square creates a 'frustum'.
|
||||
|
||||
# Vertices for a standard 1m equilateral (placeholder for now, can be tweaked for specific radii)
|
||||
var h = sqrt(3) * 0.5
|
||||
var v0 = Vector3(-0.5, 0, 0)
|
||||
var v1 = Vector3(0.5, 0, 0)
|
||||
var v2 = Vector3(0, -h, 0)
|
||||
res.vertices = [v0, v1, v2] as Array[Vector3]
|
||||
|
||||
# Top Edge (v0->v1): Connects to Square.
|
||||
# The square is tilted down by 'slope'. To match it, we must tilt 'up' or 'down'?
|
||||
# Normals must oppose. Square normal is Tilted Down. This normal must be Tilted Down (relative to self) to be parallel?
|
||||
# Actually, both pieces tilt "in" towards the center of the sphere.
|
||||
_add_edge_mount(res, v0, v1, slope_angle)
|
||||
|
||||
# Side Edges (v1->v2, v2->v0): Connect to other triangles in the skirt.
|
||||
# These usually need a smaller bend angle, approx half the square's bend for a smooth transition.
|
||||
var side_bend = slope_angle * 0.5 # Approximation
|
||||
_add_edge_mount(res, v1, v2, side_bend)
|
||||
_add_edge_mount(res, v2, v0, side_bend)
|
||||
|
||||
_finalize_resource(res, prefix + "_side")
|
||||
|
||||
# --- HELPERS ---
|
||||
|
||||
func _create_base_resource(id: String, suffix: String) -> StructureData:
|
||||
var res = StructureData.new()
|
||||
res.piece_name = id.capitalize()
|
||||
res.type = StructureData.PieceType.PLATE
|
||||
return res
|
||||
|
||||
func _add_edge_mount(res: StructureData, p1: Vector3, p2: Vector3, bend_deg: float):
|
||||
var mid = (p1 + p2) / 2.0
|
||||
var edge_vector = (p2 - p1).normalized()
|
||||
# Flat normal points -Z (Back) or +Z depending on convention. Using BACK (+Z in Godot) as "Out"
|
||||
var flat_normal = edge_vector.cross(Vector3.BACK).normalized()
|
||||
|
||||
# Rotate normal "Down" around the edge
|
||||
var bend_rad = deg_to_rad(bend_deg)
|
||||
var final_normal = flat_normal.rotated(edge_vector, bend_rad)
|
||||
var final_up = Vector3.BACK.rotated(edge_vector, bend_rad)
|
||||
|
||||
res.add_mount(mid, final_normal, final_up)
|
||||
|
||||
func _finalize_resource(res: StructureData, filename: String):
|
||||
# Calculate Area for Mass/Cost
|
||||
var area = 0.0
|
||||
if res.vertices.size() >= 3:
|
||||
# Shoelace formula or simple triangle sum
|
||||
# For convex shapes centered on 0,0:
|
||||
for i in range(res.vertices.size()):
|
||||
var p1 = res.vertices[i]
|
||||
var p2 = res.vertices[(i + 1) % res.vertices.size()]
|
||||
area += 0.5 * (p1.cross(p2).length())
|
||||
|
||||
res.base_mass = area * DENSITY_ALUMINUM
|
||||
res.cost = {"Aluminium": res.base_mass * COST_PER_KG}
|
||||
|
||||
var path = "res://data/structure/definitions/%s.tres" % filename
|
||||
ResourceSaver.save(res, path)
|
||||
# print("Generated %s (Mass: %.1f kg)" % [filename, res.base_mass])
|
||||
|
||||
func generate_system_two_pentagonal():
|
||||
print("--- Generating Design System 2: Pentagonal ---")
|
||||
|
||||
# Configuration: 2m Radius Sphere
|
||||
# Icosahedron Edge Length (a) for radius (r): a = r / sin(72) * 2 approx...
|
||||
# Let's standardise on the edge length = 1.0m.
|
||||
# This results in a sphere radius of ~0.95m.
|
||||
var edge_length = 1.0
|
||||
|
||||
# 1. THE TUBE (Pentagonal Antiprism)
|
||||
# A tube made of 10 triangles per segment.
|
||||
# To fit a regular pentagon of side 1.0m.
|
||||
# Radius of pentagon = 1.0 / (2 * sin(36)) = ~0.85m
|
||||
|
||||
# We need a triangle that connects two points on the bottom pentagon
|
||||
# to one point on the top pentagon (rotated 36 degrees).
|
||||
# This forms an equilateral triangle if the height is correct (0.85m).
|
||||
create_polygon_plate("s2_equilateral_tri", 3, 0.0) # Standard 1m triangle
|
||||
|
||||
# 2. THE SPHERE CAP (Pentagonal Pyramid)
|
||||
# 5 of these triangles snap together to form a "Cap".
|
||||
# The "bend" angle is the dihedral angle of an Icosahedron ~138.19 deg.
|
||||
# Deviation from flat = (180 - 138.19) / 2 = ~20.9 degrees.
|
||||
|
||||
var bend_angle = 20.9
|
||||
|
||||
var res = _create_base_resource("s2_geo_tri", "Geo Plate")
|
||||
res.shape = "Triangle"
|
||||
res.vertices = _generate_equilateral_verts(1.0)
|
||||
|
||||
# Base Edge (0->1): Connects to the rest of the sphere (or extension ring)
|
||||
# Side Edges (1->2, 2->0): Connect to neighbors in the 5-way cluster
|
||||
|
||||
# All edges in a V1 sphere have the same bend angle!
|
||||
_add_edge_mount(res, res.vertices[0], res.vertices[1], bend_angle)
|
||||
_add_edge_mount(res, res.vertices[1], res.vertices[2], bend_angle)
|
||||
_add_edge_mount(res, res.vertices[2], res.vertices[0], bend_angle)
|
||||
|
||||
_finalize_resource(res, "s2_geo_tri")
|
||||
|
||||
print("System 2 Generated. Build tubes with 's2_equilateral_tri' and spheres with 's2_geo_tri'.")
|
||||
|
||||
func _generate_equilateral_verts(side: float) -> Array[Vector3]:
|
||||
var h = sqrt(3) * 0.5 * side
|
||||
return [
|
||||
Vector3(-side/2, -h/3, 0),
|
||||
Vector3(side/2, -h/3, 0),
|
||||
Vector3(0, 2*h/3, 0)
|
||||
]
|
||||
# src/data/structure/structure_generator.gd
|
||||
|
||||
func generate_system_two_v2_sphere():
|
||||
print("--- Generating Design System 2: V2 Geodesic (Room Size) ---")
|
||||
|
||||
# 1. Calculate Geometry (Normalized Radius = 1.0)
|
||||
var phi = (1.0 + sqrt(5.0)) / 2.0
|
||||
|
||||
# Icosahedron vertices
|
||||
var v0 = Vector3(0, 1, phi).normalized() # Pole
|
||||
var v4 = Vector3(1, phi, 0).normalized() # Neighbor
|
||||
var v8 = Vector3(phi, 0, 1).normalized() # Neighbor
|
||||
|
||||
# Subdivide for V2 (Midpoints projected to sphere)
|
||||
var v08 = (v0 + v8).normalized()
|
||||
var v84 = (v8 + v4).normalized()
|
||||
var v40 = (v4 + v0).normalized()
|
||||
|
||||
# We now have two distinct triangles:
|
||||
# Triangle A (Cap): v0 -> v08 -> v40
|
||||
# Triangle B (Face): v08 -> v84 -> v40
|
||||
|
||||
# 2. Scale Factor
|
||||
# We want the "Base" of Triangle A (edge v08-v40) to be exactly 1.0m.
|
||||
# This ensures it connects perfectly to our standard 1.0m Tubes.
|
||||
var unscaled_base_len = v08.distance_to(v40)
|
||||
var scale = 1.0 / unscaled_base_len
|
||||
|
||||
print("V2 Sphere Radius: %.2fm" % scale)
|
||||
|
||||
# 3. Generate Triangle A (The Pentagon Cap Piece)
|
||||
# This piece forms the 5-way corners.
|
||||
var res_a = _create_base_resource("s2_geo_v2_a", "Geo V2 Cap")
|
||||
res_a.shape = "Triangle"
|
||||
# Centering: Move vertices so the average is at (0,0,0)
|
||||
var center_a = (v0 + v08 + v40) / 3.0
|
||||
res_a.vertices = [
|
||||
(v0 - center_a) * scale, # Top (Pole)
|
||||
(v08 - center_a) * scale, # Right
|
||||
(v40 - center_a) * scale # Left
|
||||
] as Array[Vector3]
|
||||
|
||||
# Calculate exact bend angles based on the sphere normals
|
||||
# The mount normal should be the vertex normal (pointing out from sphere center)
|
||||
# relative to the flat face normal.
|
||||
_add_mount_from_sphere_geometry(res_a, v0, v08, v40, center_a)
|
||||
_finalize_resource(res_a, "s2_geo_v2_a")
|
||||
|
||||
# 4. Generate Triangle B (The Hexagon Face Piece)
|
||||
# This piece fills the gaps between caps.
|
||||
var res_b = _create_base_resource("s2_geo_v2_b", "Geo V2 Face")
|
||||
res_b.shape = "Triangle"
|
||||
var center_b = (v08 + v84 + v40) / 3.0
|
||||
res_b.vertices = [
|
||||
(v08 - center_b) * scale, # Top-Left
|
||||
(v84 - center_b) * scale, # Bottom
|
||||
(v40 - center_b) * scale # Top-Right
|
||||
] as Array[Vector3]
|
||||
|
||||
_add_mount_from_sphere_geometry(res_b, v08, v84, v40, center_b)
|
||||
_finalize_resource(res_b, "s2_geo_v2_b")
|
||||
|
||||
# Helper to calculate the correct mount angle for a spherical fragment
|
||||
func _add_mount_from_sphere_geometry(res: StructureData, p1_sphere: Vector3, p2_sphere: Vector3, p3_sphere: Vector3, center_sphere: Vector3):
|
||||
# We reconstruct the mounts for the 3 edges
|
||||
var points = [p1_sphere, p2_sphere, p3_sphere]
|
||||
|
||||
# Face Normal (Flat plate orientation)
|
||||
var face_normal = (p2_sphere - p1_sphere).cross(p3_sphere - p1_sphere).normalized()
|
||||
|
||||
for i in range(3):
|
||||
var a = points[i]
|
||||
var b = points[(i+1)%3]
|
||||
|
||||
# The mount position is the midpoint of the edge (relative to piece center)
|
||||
var mid_sphere = (a + b) / 2.0
|
||||
var mid_local = (mid_sphere - center_sphere) # Scale is applied later in the main loop, but directions don't care about scale
|
||||
|
||||
# The mount normal should point OUTWARD from the edge, but follow the sphere's curvature.
|
||||
# For a sphere, the perfect "Out" vector at the edge midpoint is just mid_sphere.normalized().
|
||||
# However, our mount system expects the normal to be roughly perpendicular to the edge.
|
||||
|
||||
var edge_vec = (b - a).normalized()
|
||||
# Vector perpendicular to edge, tangent to sphere surface at midpoint
|
||||
var sphere_tangent_out = edge_vec.cross(mid_sphere.normalized()).normalized()
|
||||
|
||||
# Wait, the mount normal needs to match the *other* piece's mount normal.
|
||||
# If both pieces are on the sphere, their mount normals should be parallel to the chord connecting them?
|
||||
# No, standard "snap" logic opposes normals.
|
||||
# If we use the Tangent, it points "along" the sphere surface.
|
||||
# When two pieces snap, they will form a continuous curve.
|
||||
|
||||
# Let's stick to the generated tangents.
|
||||
# We need to rotate this into the Local Space of the piece.
|
||||
# Actually, we are defining vertices in Local Space already.
|
||||
# But the normals calculated above are in Sphere Space.
|
||||
|
||||
# We need to rotate the calculated Sphere Normals into the Flat Face space?
|
||||
# No, StructureData mounts are defined in Local Space.
|
||||
# The Vertices in res.vertices are already (p - center).
|
||||
|
||||
# So:
|
||||
# Position: (mid_sphere - center_sphere) * scale (handled in main loop, we just need direction here)
|
||||
# Normal: sphere_tangent_out (It's a direction vector, translation doesn't affect it)
|
||||
# Up: The sphere normal at that point? (mid_sphere.normalized())
|
||||
|
||||
# Let's assume 'Up' is the surface normal (Out from center of sphere)
|
||||
var mount_up = mid_sphere.normalized()
|
||||
|
||||
res.add_mount((mid_sphere - center_sphere), sphere_tangent_out, mount_up)
|
||||
1
data/structure/structure_generator.gd.uid
Normal file
1
data/structure/structure_generator.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bkqqditmq34y3
|
||||
118
export_presets.cfg
Normal file
118
export_presets.cfg
Normal file
@ -0,0 +1,118 @@
|
||||
[preset.0]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path=""
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
patch_delta_min_reduction=0.1
|
||||
patch_delta_include_filters="*"
|
||||
patch_delta_exclude_filters=""
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
shader_baker/enabled=false
|
||||
binary_format/architecture="x86_64"
|
||||
codesign/enable=false
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
application/modify_resources=true
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
application/export_d3d12=0
|
||||
application/d3d12_agility_sdk_multiarch=true
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||
Start-ScheduledTask -TaskName godot_remote_debug
|
||||
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||
|
||||
[preset.1]
|
||||
|
||||
name="Linux"
|
||||
platform="Linux"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path=""
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
patch_delta_min_reduction=0.1
|
||||
patch_delta_include_filters="*"
|
||||
patch_delta_exclude_filters=""
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
shader_baker/enabled=false
|
||||
binary_format/architecture="x86_64"
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||
export DISPLAY=:0
|
||||
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
\"{temp_dir}/{exe_name}\" {cmd_args}"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
pkill -x -f \"{temp_dir}/{exe_name} {cmd_args}\"
|
||||
rm -rf \"{temp_dir}\""
|
||||
@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
@ -25,6 +27,10 @@ mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
|
||||
26
main.tscn
26
main.tscn
@ -1,23 +1,7 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j3j483itissq" path="res://scripts/star_system_generator.gd" id="1_h2yge"]
|
||||
[ext_resource type="PackedScene" uid="uid://5uqp4amjj7ww" path="res://scenes/celestial_bodies/star.tscn" id="2_7mycd"]
|
||||
[ext_resource type="PackedScene" uid="uid://clt4qlsjcfgln" path="res://scenes/celestial_bodies/planet.tscn" id="3_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://74ppvxcw8an4" path="res://scenes/celestial_bodies/moon.tscn" id="4_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://dm3s33o4xhqfv" path="res://scenes/celestial_bodies/station.tscn" id="5_kek77"]
|
||||
[ext_resource type="PackedScene" uid="uid://bawsujtlpmh5r" path="res://scenes/celestial_bodies/asteroid.tscn" id="6_4c57u"]
|
||||
[ext_resource type="PackedScene" uid="uid://dlck1lyrn1xvp" path="res://scenes/ship/spaceship.tscn" id="7_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://ojcho3pi3u7n" path="res://scenes/UI/main_menu/main_menu.tscn" id="1_ig7tw"]
|
||||
|
||||
[node name="StarSystem" type="Node2D"]
|
||||
script = ExtResource("1_h2yge")
|
||||
min_planets = 1
|
||||
max_planets = 4
|
||||
max_moons = 10
|
||||
max_asteroid_belts = 2
|
||||
max_star_stations = 0
|
||||
star_scene = ExtResource("2_7mycd")
|
||||
planet_scene = ExtResource("3_272bh")
|
||||
moon_scene = ExtResource("4_5vw27")
|
||||
station_scene = ExtResource("5_kek77")
|
||||
asteroid_scene = ExtResource("6_4c57u")
|
||||
spaceship_scene = ExtResource("7_5vw27")
|
||||
[node name="StartMenu" type="Node3D" unique_id=1392183658]
|
||||
|
||||
[node name="MainMenu" parent="." unique_id=2099645465 instance=ExtResource("1_ig7tw")]
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j3j483itissq" path="res://scripts/star_system_generator.gd" id="1_h2yge"]
|
||||
[ext_resource type="PackedScene" uid="uid://5uqp4amjj7ww" path="res://scenes/star.tscn" id="2_7mycd"]
|
||||
[ext_resource type="PackedScene" uid="uid://clt4qlsjcfgln" path="res://scenes/planet.tscn" id="3_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://74ppvxcw8an4" path="res://scenes/moon.tscn" id="4_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://dm3s33o4xhqfv" path="res://scenes/station.tscn" id="5_kek77"]
|
||||
[ext_resource type="PackedScene" uid="uid://bawsujtlpmh5r" path="res://scenes/asteroid.tscn" id="6_4c57u"]
|
||||
[ext_resource type="PackedScene" uid="uid://cm5qsuunboxm3" path="res://scenes/developer_pawn.tscn" id="7_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctlw5diis8h1x" path="res://scenes/map_canvas.tscn" id="8_5vw27"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource("1_h2yge")
|
||||
min_asteroid_belts = 0
|
||||
star_scene = ExtResource("2_7mycd")
|
||||
planet_scene = ExtResource("3_272bh")
|
||||
moon_scene = ExtResource("4_5vw27")
|
||||
station_scene = ExtResource("5_kek77")
|
||||
asteroid_scene = ExtResource("6_4c57u")
|
||||
sim_scale = 1e+09
|
||||
|
||||
[node name="DeveloperPawn" parent="." node_paths=PackedStringArray("map_canvas") instance=ExtResource("7_272bh")]
|
||||
input_pickable = true
|
||||
map_canvas = NodePath("../MapCanvas")
|
||||
|
||||
[node name="MapCanvas" parent="." node_paths=PackedStringArray("star_system_generator") instance=ExtResource("8_5vw27")]
|
||||
star_system_generator = NodePath("..")
|
||||
573
modules/3d_test_ship.tscn
Normal file
573
modules/3d_test_ship.tscn
Normal file
@ -0,0 +1,573 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://bkwogkfqk2uxo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ktv2t"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_shb7f"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_ism2t"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ism2t"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:linear_velocity")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
properties/2/path = NodePath(".:rotation")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 1
|
||||
|
||||
[node name="3dTestShip" type="RigidBody3D" unique_id=246037729]
|
||||
physics_interpolation_mode = 1
|
||||
script = ExtResource("1_ktv2t")
|
||||
physics_mode = 1
|
||||
base_mass = 10000.0
|
||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||
|
||||
[node name="Hullplate7" parent="." unique_id=1182121679 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate8" parent="." unique_id=294855274 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate9" parent="." unique_id=130054924 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate4" parent="." unique_id=2133064539 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate5" parent="." unique_id=1436331513 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate6" parent="." unique_id=1249365999 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate11" parent="." unique_id=1656979163 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 0, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate13" parent="." unique_id=1426276711 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate14" parent="." unique_id=1212526811 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate15" parent="." unique_id=403515873 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate16" parent="." unique_id=145935239 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate17" parent="." unique_id=1662804653 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate18" parent="." unique_id=741829932 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate21" parent="." unique_id=31417961 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 0, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate22" parent="." unique_id=1845702661 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate23" parent="." unique_id=1747432968 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate24" parent="." unique_id=1486518216 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, -1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate25" parent="." unique_id=1880158566 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate26" parent="." unique_id=1506445603 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate27" parent="." unique_id=1749302489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2, 1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate31" parent="." unique_id=1965678834 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate32" parent="." unique_id=515940324 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate33" parent="." unique_id=313389603 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate34" parent="." unique_id=363616195 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate35" parent="." unique_id=568985619 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate36" parent="." unique_id=193191417 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate38" parent="." unique_id=1152815429 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 0, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate40" parent="." unique_id=1303768723 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, -1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate41" parent="." unique_id=1489680526 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 0)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate42" parent="." unique_id=1454642421 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 1)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate43" parent="." unique_id=1322280114 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate44" parent="." unique_id=1380061102 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate45" parent="." unique_id=1740305308 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, -2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate48" parent="." unique_id=587023569 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 0, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate49" parent="." unique_id=1103858035 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate50" parent="." unique_id=916625356 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate51" parent="." unique_id=2115734988 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, -1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate52" parent="." unique_id=1715698306 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 3)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate53" parent="." unique_id=369018899 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 2)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate54" parent="." unique_id=1618415296 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2, 1, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate57" parent="." unique_id=1148292814 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, 0, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate58" parent="." unique_id=1183219370 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate59" parent="." unique_id=95522376 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate60" parent="." unique_id=960534764 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, -1, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate61" parent="." unique_id=1862079328 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate62" parent="." unique_id=876185578 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate64" parent="." unique_id=622302151 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 9.536743e-07, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate65" parent="." unique_id=2027647666 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, -1, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate66" parent="." unique_id=335333911 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 1.000001, 4.4908433)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate63" parent="." unique_id=779321466 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5, 1, 4.5)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate69" parent="." unique_id=391423682 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, 9.536743e-07, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate70" parent="." unique_id=1436426809 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate71" parent="." unique_id=1045660804 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate72" parent="." unique_id=1696784058 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate73" parent="." unique_id=1709873058 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.50098634, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate74" parent="." unique_id=1071906843 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49901366, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate75" parent="." unique_id=413542580 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 9.536743e-07, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate76" parent="." unique_id=448578032 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, -1, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate77" parent="." unique_id=1162322851 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4990137, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate78" parent="." unique_id=790206161 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, 0, 1, 0, -0.014328662, 0, 0.99989736, -1.5009866, 1.000001, -4.5091567)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate79" parent="." unique_id=1019136641 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate80" parent="." unique_id=152922175 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate81" parent="." unique_id=771888008 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate82" parent="." unique_id=816092557 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate83" parent="." unique_id=1871920861 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate84" parent="." unique_id=103727539 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate85" parent="." unique_id=1457444620 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate86" parent="." unique_id=1402217859 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate87" parent="." unique_id=293240152 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate88" parent="." unique_id=158231735 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate89" parent="." unique_id=2017317978 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate90" parent="." unique_id=1810711362 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate91" parent="." unique_id=648502427 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate92" parent="." unique_id=1280848561 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate93" parent="." unique_id=1000182357 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate94" parent="." unique_id=663755561 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate95" parent="." unique_id=977211031 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate96" parent="." unique_id=1017704164 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate97" parent="." unique_id=2095269489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate98" parent="." unique_id=615154295 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate99" parent="." unique_id=1435686924 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate100" parent="." unique_id=361501534 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate101" parent="." unique_id=776176100 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate102" parent="." unique_id=1146417492 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate103" parent="." unique_id=1413321748 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate104" parent="." unique_id=1044980803 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate105" parent="." unique_id=1804409489 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate106" parent="." unique_id=1076107521 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.5, 1.5, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate107" parent="." unique_id=1190510681 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.5, 1.5, 4)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate108" parent="." unique_id=855909591 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate109" parent="." unique_id=946006990 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate110" parent="." unique_id=1957722835 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate111" parent="." unique_id=1708941560 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate112" parent="." unique_id=598393913 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate113" parent="." unique_id=629535431 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate114" parent="." unique_id=1483594858 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, 1.5000012, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate115" parent="." unique_id=1186769437 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate116" parent="." unique_id=752889015 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate117" parent="." unique_id=175698677 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate118" parent="." unique_id=670641245 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate119" parent="." unique_id=988678524 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate120" parent="." unique_id=896262764 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate121" parent="." unique_id=1336630931 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate122" parent="." unique_id=101919359 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate123" parent="." unique_id=1356736016 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -3.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate124" parent="." unique_id=742815341 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -2.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate125" parent="." unique_id=1651537246 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate126" parent="." unique_id=1253078352 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -4.009157)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate127" parent="." unique_id=519787812 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate128" parent="." unique_id=629828036 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate129" parent="." unique_id=2010663580 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate130" parent="." unique_id=1705163002 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate131" parent="." unique_id=1635599014 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate132" parent="." unique_id=789401102 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate133" parent="." unique_id=1671040057 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate134" parent="." unique_id=2118015321 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate135" parent="." unique_id=1970124357 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -0.009156942)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate136" parent="." unique_id=2129372302 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 0.99084306)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate137" parent="." unique_id=543355427 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate138" parent="." unique_id=1885736043 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, -1.0091572)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate139" parent="." unique_id=654209436 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate140" parent="." unique_id=1938132143 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate141" parent="." unique_id=486424951 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate142" parent="." unique_id=910140496 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate143" parent="." unique_id=515293159 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate144" parent="." unique_id=890871001 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate145" parent="." unique_id=1626468827 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.50098634, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate146" parent="." unique_id=578516444 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.49901366, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate147" parent="." unique_id=402255852 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 2.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate148" parent="." unique_id=1631434711 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 3.990843)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate149" parent="." unique_id=726702930 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 1.4990137, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Hullplate150" parent="." unique_id=1001521061 instance=ExtResource("2_shb7f")]
|
||||
transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08, 0.99989736, 6.263257e-10, -1, -4.37069e-08, -1.5009866, -1.4999988, 1.9908428)
|
||||
physics_mode = 2
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=1071155008]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, -3)
|
||||
|
||||
[node name="OmniLight3D2" type="OmniLight3D" parent="." unique_id=151820223]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6, 1, -3)
|
||||
|
||||
[node name="OmniLight3D3" type="OmniLight3D" parent="." unique_id=390575041]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6, 1, 4)
|
||||
|
||||
[node name="OmniLight3D4" type="OmniLight3D" parent="." unique_id=1659652061]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, 4)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=1905582997]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
current = true
|
||||
|
||||
[node name="Spawner" parent="." unique_id=6714366 instance=ExtResource("3_ism2t")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=2096937457]
|
||||
replication_config = SubResource("SceneReplicationConfig_ism2t")
|
||||
@ -1,31 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b1kpyek60vyof"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1abiy"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_risxe"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
position = Vector2(-50, 50)
|
||||
mass = null
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector2(-50, 0)
|
||||
inertia = null
|
||||
linear_velocity = null
|
||||
angular_velocity = null
|
||||
script = ExtResource("1_1abiy")
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_risxe")]
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_risxe")]
|
||||
position = Vector2(-100, 0)
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
@ -1,31 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://baeikwxkh26fh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1rae4"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_fbnt1"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
position = Vector2(-50, 50)
|
||||
mass = null
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector2(-50, 0)
|
||||
inertia = null
|
||||
linear_velocity = null
|
||||
angular_velocity = null
|
||||
script = ExtResource("1_1rae4")
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
|
||||
position = Vector2(-100, 0)
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
@ -1,49 +0,0 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://didt2nsdtbmra"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_nqe0s"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_foqop"]
|
||||
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="4_dmrms"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
script = ExtResource("1_nqe0s")
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
|
||||
|
||||
[node name="@StaticBody2D@30634" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, 100)
|
||||
|
||||
[node name="@StaticBody2D@30635" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, -100)
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 100)
|
||||
|
||||
[node name="@StaticBody2D@30636" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 0)
|
||||
|
||||
[node name="@StaticBody2D@30637" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, -100)
|
||||
|
||||
[node name="@StaticBody2D@30638" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, -100)
|
||||
|
||||
[node name="@StaticBody2D@30639" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, -150)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="@StaticBody2D@30640" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, 150)
|
||||
rotation = 4.71239
|
||||
|
||||
[node name="@StaticBody2D@30641" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 100)
|
||||
|
||||
[node name="@StaticBody2D@30642" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 0)
|
||||
26
modules/physics_testing_ship.tscn
Normal file
26
modules/physics_testing_ship.tscn
Normal file
@ -0,0 +1,26 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://xcgmicfdqqb1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ogx5r"]
|
||||
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_nyqc6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_3bya3"]
|
||||
|
||||
[node name="PhysicsTestingShip" type="RigidBody3D"]
|
||||
script = ExtResource("1_ogx5r")
|
||||
base_mass = 200.0
|
||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_nyqc6")]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, -1, 0)
|
||||
|
||||
[node name="Spawner" parent="." instance=ExtResource("3_3bya3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.021089494, 0)
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, -2)
|
||||
|
||||
[node name="OmniLight3D2" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, -2)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||
current = true
|
||||
7
modules/test_ship.tscn
Normal file
7
modules/test_ship.tscn
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cwblg6q5qse6c"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="1_obkto"]
|
||||
|
||||
[node name="TestShip" type="Node3D"]
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("1_obkto")]
|
||||
126
project.godot
126
project.godot
@ -8,18 +8,33 @@
|
||||
|
||||
config_version=5
|
||||
|
||||
[animation]
|
||||
|
||||
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||
|
||||
[application]
|
||||
|
||||
config/name="space_simulation"
|
||||
config/name="Millimeters of Aluminum"
|
||||
config/version="0.1"
|
||||
run/main_scene="uid://dogqi2c58qdc0"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/features=PackedStringArray("4.6", "Double Precision", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
OrbitalMechanics="*res://scripts/singletons/orbital_mechanics.gd"
|
||||
SignalBus="*res://scripts/singletons/signal_bus.gd"
|
||||
GameManager="*res://scripts/singletons/game_manager.gd"
|
||||
Constants="*res://scripts/singletons/constants.gd"
|
||||
NetworkHandler="*res://scripts/network/network_handler.gd"
|
||||
MotionUtils="*res://scripts/singletons/motion_utils.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/vsync/vsync_mode=0
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="space_simulation"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
@ -57,17 +72,120 @@ time_reset={
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
interact={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_wiring_panel={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_left_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_forward_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_backward_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
roll_right_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
roll_left_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
interact_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
spacebar_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
right_click={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_down_3d={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
left_click={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_build_mode={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
2d_physics/layer_1="ship_hull"
|
||||
2d_render/layer_2="UI_Panels"
|
||||
2d_physics/layer_1="hullplates"
|
||||
2d_physics/layer_2="ship_components"
|
||||
2d_physics/layer_3="celestial_bodies"
|
||||
2d_physics/layer_4="projectiles"
|
||||
2d_physics/layer_5="bulkheads"
|
||||
2d_physics/layer_6="characters"
|
||||
3d_physics/layer_15="weld"
|
||||
3d_physics/layer_16="grip"
|
||||
|
||||
[physics]
|
||||
|
||||
3d/default_gravity=0.0
|
||||
3d/default_gravity_vector=Vector3(0, 0, 0)
|
||||
3d/default_linear_damp=0.0
|
||||
3d/default_angular_damp=0.0
|
||||
3d/sleep_threshold_linear=0.0
|
||||
2d/default_gravity=0.0
|
||||
2d/default_gravity_vector=Vector2(0, 0)
|
||||
|
||||
13
reinit_submodules.sh
Normal file
13
reinit_submodules.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
|
||||
while read path_key local_path
|
||||
do
|
||||
url_key=$(echo $path_key | sed 's/\.path/.url/')
|
||||
url=$(git config -f .gitmodules --get "$url_key")
|
||||
git submodule add $url $local_path
|
||||
done
|
||||
|
||||
# https://stackoverflow.com/questions/11258737/restore-git-submodules-from-gitmodules
|
||||
35
scenes/UI/ingame_menu/ingame_menu.gd
Normal file
35
scenes/UI/ingame_menu/ingame_menu.gd
Normal file
@ -0,0 +1,35 @@
|
||||
extends Control
|
||||
|
||||
@onready var resume_button: Button = %ResumeButton
|
||||
@onready var disconnect_button: Button = %DisconnectButton
|
||||
@onready var quit_button: Button = %QuitButton
|
||||
|
||||
func _ready():
|
||||
resume_button.pressed.connect(toggle_menu)
|
||||
disconnect_button.pressed.connect(_on_disconnect_pressed)
|
||||
quit_button.pressed.connect(_on_quit_pressed)
|
||||
|
||||
hide()
|
||||
|
||||
func _input(event):
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
toggle_menu()
|
||||
|
||||
func toggle_menu():
|
||||
visible = !visible
|
||||
|
||||
if visible:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
else:
|
||||
# Only capture mouse if we are actually playing a pawn
|
||||
# You might need a smarter check here depending on game state
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
func _on_disconnect_pressed():
|
||||
NetworkHandler.close_connection()
|
||||
toggle_menu()
|
||||
# Return to main menu
|
||||
get_tree().change_scene_to_file("res://main.tscn")
|
||||
|
||||
func _on_quit_pressed():
|
||||
get_tree().quit()
|
||||
1
scenes/UI/ingame_menu/ingame_menu.gd.uid
Normal file
1
scenes/UI/ingame_menu/ingame_menu.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://2aoy8ivk2hgl
|
||||
58
scenes/UI/ingame_menu/ingame_menu.tscn
Normal file
58
scenes/UI/ingame_menu/ingame_menu.tscn
Normal file
@ -0,0 +1,58 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://pausemenu456"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://2aoy8ivk2hgl" path="res://scenes/UI/ingame_menu/ingame_menu.gd" id="1_pm_script"]
|
||||
|
||||
[node name="IngameeMenu" type="Control" unique_id=8878860]
|
||||
process_mode = 3
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_pm_script")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="." unique_id=1882361500]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0, 0, 0, 0.05)
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="." unique_id=1122355242]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=1948011184]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 15
|
||||
|
||||
[node name="Label" type="Label" parent="CenterContainer/VBoxContainer" unique_id=214886966]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 32
|
||||
text = "Menu"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ResumeButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1856665966]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
text = "Resume"
|
||||
|
||||
[node name="DisconnectButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=4948876]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
text = "Disconnect"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1695513560]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
text = "Quit Game"
|
||||
47
scenes/UI/main_menu/main_menu.gd
Normal file
47
scenes/UI/main_menu/main_menu.gd
Normal file
@ -0,0 +1,47 @@
|
||||
extends Control
|
||||
|
||||
# @export var lobby_menu: Control
|
||||
# @export var settings_menu: Control
|
||||
|
||||
# Buttons and input fields
|
||||
@onready var host_button: Button = %HostButton
|
||||
@onready var join_button: Button = %JoinButton
|
||||
@onready var settings_button: Button = %SettingsButton
|
||||
@onready var quit_button: Button = %QuitButton
|
||||
@onready var address_entry: LineEdit = %AddressEntry
|
||||
|
||||
func _ready():
|
||||
host_button.pressed.connect(_on_host_pressed)
|
||||
join_button.pressed.connect(_on_join_pressed)
|
||||
quit_button.pressed.connect(_on_quit_pressed)
|
||||
|
||||
# Ensure we start with a clean slate
|
||||
# lobby_menu.visible = false
|
||||
|
||||
# If we just returned from a game, ensure mouse is visible
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
func _on_host_pressed():
|
||||
# For a simple test, hosting immediately starts the server and the game
|
||||
NetworkHandler.create_server()
|
||||
NetworkHandler.on_peer_connected(multiplayer.get_unique_id())
|
||||
_transition_to_game()
|
||||
|
||||
func _on_join_pressed():
|
||||
var ip = address_entry.text
|
||||
if ip.is_empty():
|
||||
ip = "127.0.0.1" # Default to localhost
|
||||
|
||||
NetworkHandler.create_client(ip)
|
||||
# The NetworkHandler signals will handle the actual transition once connected
|
||||
# But for UI feedback, we might want to show a "Connecting..." label here.
|
||||
|
||||
func _on_quit_pressed():
|
||||
get_tree().quit()
|
||||
|
||||
func _transition_to_game():
|
||||
# This would typically load the main game scene.
|
||||
# Since your main scene IS the game loop currently, we might need to
|
||||
# just hide the menu if it's an overlay, OR change scenes.
|
||||
# Assuming Main.tscn is the game world:
|
||||
get_tree().change_scene_to_file("res://scripts/star_system.tscn")
|
||||
1
scenes/UI/main_menu/main_menu.gd.uid
Normal file
1
scenes/UI/main_menu/main_menu.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dypq4h3hy1l3v
|
||||
78
scenes/UI/main_menu/main_menu.tscn
Normal file
78
scenes/UI/main_menu/main_menu.tscn
Normal file
@ -0,0 +1,78 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ojcho3pi3u7n"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dypq4h3hy1l3v" path="res://scenes/UI/main_menu/main_menu.gd" id="1_script"]
|
||||
|
||||
[node name="MainMenu" type="Control" unique_id=2099645465]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="." unique_id=2137889995]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.05, 0.05, 0.08, 1)
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="." unique_id=1954458945]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=542164632]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="Label" type="Label" parent="CenterContainer/VBoxContainer" unique_id=973405608]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 48
|
||||
text = "Millimeters of Aluminium"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="AddressEntry" type="LineEdit" parent="CenterContainer/VBoxContainer" unique_id=994010326]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "127.0.0.1"
|
||||
alignment = 1
|
||||
|
||||
[node name="HostButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1548149031]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(200, 50)
|
||||
layout_mode = 2
|
||||
text = "Host Game"
|
||||
|
||||
[node name="JoinButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1826215269]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(200, 50)
|
||||
layout_mode = 2
|
||||
text = "Join Game"
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=811999044]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(200, 50)
|
||||
layout_mode = 2
|
||||
text = "Settings"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1005717980]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(200, 50)
|
||||
layout_mode = 2
|
||||
text = "Quit to Desktop"
|
||||
|
||||
[node name="LobbyMenu" type="Control" parent="." unique_id=604668798]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
@ -7,7 +7,7 @@ signal follow_requested(body: Node2D)
|
||||
|
||||
@onready var name_label: Label = $NameLabel
|
||||
|
||||
var body_reference: Node2D
|
||||
var body_reference: OrbitalBody3D
|
||||
var dot_color: Color = Color.WHITE
|
||||
|
||||
var hover_tween: Tween
|
||||
@ -27,17 +27,11 @@ func _ready() -> void:
|
||||
mouse_entered.connect(_on_mouse_entered)
|
||||
mouse_exited.connect(_on_mouse_exited)
|
||||
|
||||
func initialize(body: Node2D):
|
||||
func initialize(body: OrbitalBody3D):
|
||||
body_reference = body
|
||||
name_label.text = body.name
|
||||
|
||||
if body is OrbitalBody2D:
|
||||
dot_color = Color.CYAN
|
||||
elif body is CelestialBody:
|
||||
match body.get_class_name():
|
||||
"Star": dot_color = Color.GOLD
|
||||
"Planet": dot_color = Color.DODGER_BLUE
|
||||
"Moon": dot_color = Color.PURPLE
|
||||
dot_color = Color.CYAN
|
||||
|
||||
self.tooltip_text = _generate_tooltip_text()
|
||||
|
||||
@ -79,7 +73,8 @@ func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
emit_signal("selected", body_reference)
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
emit_signal("follow_requested", body_reference)
|
||||
print(body_reference)
|
||||
follow_requested.emit(body_reference)
|
||||
|
||||
|
||||
# No changes are needed here; the tweens will automatically use the new setter function.
|
||||
@ -97,28 +92,26 @@ func _generate_tooltip_text() -> String:
|
||||
var info = [body_reference.name]
|
||||
|
||||
if body_reference is CelestialBody:
|
||||
var celestial = body_reference as CelestialBody
|
||||
if is_instance_valid(celestial.primary):
|
||||
var mu = OrbitalMechanics.G * celestial.primary.mass
|
||||
var r = celestial.global_position.distance_to(celestial.primary.global_position)
|
||||
var period_seconds = TAU * sqrt(pow(r, 3) / mu)
|
||||
info.append("Orbital Period: %s" % _format_seconds_to_mmss(period_seconds))
|
||||
var planet_system = body_reference.get_parent() as Barycenter
|
||||
var period_seconds = OrbitalMechanics.get_orbital_time_in_seconds(planet_system, GameManager.get_system_data().star)
|
||||
|
||||
info.append("Orbital Period: %s" % _format_seconds_to_mmss(period_seconds))
|
||||
|
||||
var moon_count = 0
|
||||
for child in celestial.get_children():
|
||||
if child is CelestialBody and child.get_class_name() == "Moon":
|
||||
for child in planet_system.get_internal_attractors():
|
||||
if child is CelestialBody:
|
||||
moon_count += 1
|
||||
if moon_count > 0:
|
||||
info.append("Moons: %d" % moon_count)
|
||||
|
||||
if body_reference is Spaceship:
|
||||
if body_reference is Module:
|
||||
info.append("Class: Player Vessel")
|
||||
info.append("Mass: %.0f kg" % body_reference.mass)
|
||||
|
||||
return "\n".join(info)
|
||||
|
||||
func _format_seconds_to_mmss(seconds: float) -> String:
|
||||
var total_seconds = int(seconds)
|
||||
var minutes = total_seconds / 60
|
||||
var total_seconds: int = int(seconds)
|
||||
var minutes: int = total_seconds / 60
|
||||
var seconds_rem = total_seconds % 60
|
||||
return "%d min, %d sec" % [minutes, seconds_rem]
|
||||
|
||||
45
scenes/UI/ui_window.gd
Normal file
45
scenes/UI/ui_window.gd
Normal file
@ -0,0 +1,45 @@
|
||||
# CustomWindow.gd
|
||||
extends VBoxContainer
|
||||
class_name UiWindow
|
||||
|
||||
## Emitted when the custom "Flip" button is pressed.
|
||||
signal flip_button_pressed
|
||||
signal close_requested(c: Control)
|
||||
|
||||
@onready var title_bar: PanelContainer = $TitleBar
|
||||
@onready var title_label: Label = %TitleLabel
|
||||
@onready var flip_button: Button = %FlipButton
|
||||
@onready var close_button: Button = %CloseButton
|
||||
@onready var content_container: MarginContainer = %ContentContainer
|
||||
|
||||
var is_dragging: bool = false
|
||||
var title: String = ""
|
||||
|
||||
func _ready():
|
||||
# Connect the buttons to their functions
|
||||
close_button.pressed.connect(_close) # Or emit_signal("close_requested")
|
||||
flip_button.pressed.connect(flip_button_pressed.emit)
|
||||
|
||||
# Connect the title bar's input signal to handle dragging
|
||||
title_bar.gui_input.connect(_on_title_bar_gui_input)
|
||||
|
||||
# Set the window title from the property
|
||||
title_label.text = title
|
||||
|
||||
func _close():
|
||||
close_requested.emit(self)
|
||||
|
||||
# This function adds your main content (like the PanelFrame) into the window.
|
||||
func set_content(content_node: Node):
|
||||
for child in content_container.get_children():
|
||||
content_container.remove_child(child)
|
||||
|
||||
content_container.add_child(content_node)
|
||||
|
||||
func _on_title_bar_gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
is_dragging = event.is_pressed()
|
||||
|
||||
if event is InputEventMouseMotion and is_dragging:
|
||||
# When dragging, move the entire window by the mouse's relative motion.
|
||||
self.position += event.relative
|
||||
1
scenes/UI/ui_window.gd.uid
Normal file
1
scenes/UI/ui_window.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://d3g84xgbh8nlp
|
||||
44
scenes/UI/ui_window.tscn
Normal file
44
scenes/UI/ui_window.tscn
Normal file
@ -0,0 +1,44 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cdnowhkg5cq88"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d3g84xgbh8nlp" path="res://scenes/UI/ui_window.gd" id="1_11aw0"]
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_11aw0"]
|
||||
size = Vector2(16, 16)
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_ishqf"]
|
||||
size = Vector2(16, 16)
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer"]
|
||||
offset_right = 196.0
|
||||
offset_bottom = 36.0
|
||||
script = ExtResource("1_11aw0")
|
||||
|
||||
[node name="TitleBar" type="PanelContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="TitleBar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="TitleBar/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Placeholder Title"
|
||||
|
||||
[node name="FlipButton" type="Button" parent="TitleBar/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
icon = SubResource("PlaceholderTexture2D_11aw0")
|
||||
|
||||
[node name="CloseButton" type="Button" parent="TitleBar/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
icon = SubResource("PlaceholderTexture2D_ishqf")
|
||||
|
||||
[node name="ContentContainer" type="MarginContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Asteroid
|
||||
extends CelestialBody
|
||||
|
||||
# The orbital radius for this asteroid.
|
||||
var orbital_radius: float
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "Asteroid"
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
# An Asteroid has negligible mass for physics calculations.
|
||||
#mass = 0.001
|
||||
radius = 5.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/asteroid_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://c816xae77cbmq
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bawsujtlpmh5r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c816xae77cbmq" path="res://scenes/celestial_bodies/asteroid.gd" id="1_akfqu"]
|
||||
|
||||
[node name="Asteroid" type="RigidBody2D"]
|
||||
script = ExtResource("1_akfqu")
|
||||
metadata/_custom_type_script = "uid://c816xae77cbmq"
|
||||
25
scenes/celestial_bodies/barycenter.gd
Normal file
25
scenes/celestial_bodies/barycenter.gd
Normal file
@ -0,0 +1,25 @@
|
||||
# scripts/barycenter.gd
|
||||
class_name Barycenter
|
||||
extends OrbitalBody3D
|
||||
|
||||
func _ready():
|
||||
physics_mode = PhysicsMode.INDEPENDENT
|
||||
await get_tree().process_frame
|
||||
|
||||
# We no longer run a local simulation here.
|
||||
# We only need physics_process to integrate our own movement.
|
||||
set_physics_process(true)
|
||||
|
||||
func get_internal_attractors() -> Array[OrbitalBody3D]:
|
||||
var internal_attractors: Array[OrbitalBody3D] = []
|
||||
for child in get_children():
|
||||
if child is OrbitalBody3D:
|
||||
internal_attractors.append(child)
|
||||
|
||||
return internal_attractors
|
||||
|
||||
func recalculate_total_mass():
|
||||
base_mass = 0.0
|
||||
for child in get_internal_attractors():
|
||||
base_mass += child.mass
|
||||
mass = base_mass
|
||||
1
scenes/celestial_bodies/barycenter.gd.uid
Normal file
1
scenes/celestial_bodies/barycenter.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b2hb3bwrlh40c
|
||||
22
scenes/celestial_bodies/barycenter.tscn
Normal file
22
scenes/celestial_bodies/barycenter.tscn
Normal file
@ -0,0 +1,22 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b7bh45nrtdom5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b2hb3bwrlh40c" path="res://scenes/celestial_bodies/barycenter.gd" id="1_e776o"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_e776o"]
|
||||
properties/0/path = NodePath(".:linear_velocity")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:position")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
|
||||
[node name="Barycenter" type="RigidBody3D" unique_id=1389317234]
|
||||
script = ExtResource("1_e776o")
|
||||
metadata/_custom_type_script = "uid://wlm40n8ywr"
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=717759965]
|
||||
replication_config = SubResource("SceneReplicationConfig_e776o")
|
||||
|
||||
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="." unique_id=2061784354]
|
||||
_spawnable_scenes = PackedStringArray("uid://dv18eg4xrlefe")
|
||||
spawn_path = NodePath("..")
|
||||
16
scenes/celestial_bodies/celestial_body.gd
Normal file
16
scenes/celestial_bodies/celestial_body.gd
Normal file
@ -0,0 +1,16 @@
|
||||
class_name CelestialBody extends OrbitalBody3D
|
||||
|
||||
# --- Set in corresponding scene ---
|
||||
# var auto_proxy_gravity = false
|
||||
@export var radius: float = 100.0:
|
||||
set(value):
|
||||
radius = value
|
||||
_set_radi()
|
||||
|
||||
func _set_radi():
|
||||
if $Surface.mesh is SphereMesh:
|
||||
$Surface.mesh.radius = radius
|
||||
$Surface.mesh.height = radius * 2.0
|
||||
|
||||
if $CollisionShape3D.shape is SphereShape3D:
|
||||
$CollisionShape3D.shape.radius = radius
|
||||
1
scenes/celestial_bodies/celestial_body.gd.uid
Normal file
1
scenes/celestial_bodies/celestial_body.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dok35h0q4pseh
|
||||
48
scenes/celestial_bodies/celestial_body.tscn
Normal file
48
scenes/celestial_bodies/celestial_body.tscn
Normal file
@ -0,0 +1,48 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://dv18eg4xrlefe"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dok35h0q4pseh" path="res://scenes/celestial_bodies/celestial_body.gd" id="1_uxu4s"]
|
||||
[ext_resource type="Material" uid="uid://de0xnmjf12ted" path="res://scenes/celestial_bodies/materials/sun_mat.tres" id="2_vi0nt"]
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_vi0nt"]
|
||||
resource_local_to_scene = true
|
||||
material = ExtResource("2_vi0nt")
|
||||
radius = 2000.0
|
||||
height = 4000.0
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_uxu4s"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_vi0nt"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:rotation")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
properties/2/path = NodePath(".:linear_velocity")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 1
|
||||
properties/3/path = NodePath(".:angular_velocity")
|
||||
properties/3/spawn = true
|
||||
properties/3/replication_mode = 1
|
||||
properties/4/path = NodePath(".:radius")
|
||||
properties/4/spawn = true
|
||||
properties/4/replication_mode = 1
|
||||
|
||||
[node name="CelestialBody" type="RigidBody3D" unique_id=345490070]
|
||||
script = ExtResource("1_uxu4s")
|
||||
auto_proxy_gravity = false
|
||||
metadata/_custom_type_script = "uid://dok35h0q4pseh"
|
||||
|
||||
[node name="Surface" type="MeshInstance3D" parent="." unique_id=193823349]
|
||||
mesh = SubResource("SphereMesh_vi0nt")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=232085687]
|
||||
shape = SubResource("SphereShape3D_uxu4s")
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=1965995953]
|
||||
light_color = Color(0.958646, 0.7997282, 0.55087835, 1)
|
||||
omni_range = 200000.0
|
||||
omni_attenuation = 2.0
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=2090029903]
|
||||
replication_config = SubResource("SceneReplicationConfig_vi0nt")
|
||||
52
scenes/celestial_bodies/materials/sun_mat.gdshader
Normal file
52
scenes/celestial_bodies/materials/sun_mat.gdshader
Normal file
@ -0,0 +1,52 @@
|
||||
// https://godotshaders.com/shader/3d-sun-shader/
|
||||
shader_type spatial;
|
||||
render_mode specular_schlick_ggx;
|
||||
|
||||
uniform float Glow_Power : hint_range(0,10) = 3;
|
||||
uniform float Lightness_Difference : hint_range(0,10) = 3;
|
||||
uniform vec4 Sun_Color: source_color;
|
||||
|
||||
uniform sampler2D voronoi_noise;
|
||||
uniform sampler2D emission_noise;
|
||||
varying vec3 vertex_pos;
|
||||
uniform float waveSpeed : hint_range(0,1) = 0.1;
|
||||
uniform float fresnel : hint_range(0,2) = 1.0;
|
||||
|
||||
uniform float scale : hint_range(0,2) = 0.01;
|
||||
uniform float blendSharpness : hint_range(0,2) = 0.0;
|
||||
|
||||
// TRIPLANAR FUNCTION
|
||||
vec4 triplanar_texture(vec3 position, vec3 normal, vec2 offset, sampler2D noise) {
|
||||
vec4 colX = texture(noise, position.xy * scale + offset);
|
||||
vec4 colY = texture(noise, position.xz * scale + offset);
|
||||
vec4 colZ = texture(noise, position.zy * scale + offset);
|
||||
|
||||
vec3 blendWeight = abs(normal);
|
||||
blendWeight = vec3(pow(blendWeight.x, blendSharpness), pow(blendWeight.y, blendSharpness), pow(blendWeight.z, blendSharpness));
|
||||
blendWeight /= (blendWeight.x + blendWeight.y + blendWeight.z);
|
||||
|
||||
return colX * blendWeight.x + colY * blendWeight.y + colZ * blendWeight.z;
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
vertex_pos = VERTEX;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Fresnel
|
||||
float fresnel_out = pow(fresnel - clamp(dot(NORMAL, VIEW), 0.0, fresnel), fresnel);
|
||||
|
||||
vec2 waveOffsetA = vec2(TIME * waveSpeed, TIME * waveSpeed * 0.8);
|
||||
vec2 waveOffsetB = vec2(TIME * waveSpeed * - 0.8, TIME * waveSpeed * -0.3);
|
||||
vec2 result_offset = waveOffsetA + waveOffsetB;
|
||||
|
||||
vec3 cloud_tex = triplanar_texture(vertex_pos, NORMAL, result_offset, voronoi_noise).rgb;
|
||||
vec3 cloud_tex_with_light = cloud_tex * vec3(Lightness_Difference);
|
||||
vec3 cloud_tex_with_light_with_color = cloud_tex_with_light * Sun_Color.rgb;
|
||||
vec3 cloud_tex_with_light_with_color_with_glow = vec3(Glow_Power) * cloud_tex_with_light_with_color;
|
||||
|
||||
vec3 noise_tex = triplanar_texture(vertex_pos, NORMAL, result_offset, emission_noise).rgb;
|
||||
vec3 result = cloud_tex_with_light_with_color_with_glow * noise_tex;
|
||||
|
||||
EMISSION = vec3(fresnel_out) * result;
|
||||
}
|
||||
1
scenes/celestial_bodies/materials/sun_mat.gdshader.uid
Normal file
1
scenes/celestial_bodies/materials/sun_mat.gdshader.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://0cjdd62t25g1
|
||||
27
scenes/celestial_bodies/materials/sun_mat.tres
Normal file
27
scenes/celestial_bodies/materials/sun_mat.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=6 format=3 uid="uid://de0xnmjf12ted"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://0cjdd62t25g1" path="res://scenes/celestial_bodies/materials/sun_mat.gdshader" id="1_f1bp4"]
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_f1bp4"]
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_1eyo5"]
|
||||
noise = SubResource("FastNoiseLite_f1bp4")
|
||||
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_6484p"]
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_oxjal"]
|
||||
noise = SubResource("FastNoiseLite_6484p")
|
||||
in_3d_space = true
|
||||
|
||||
[resource]
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_f1bp4")
|
||||
shader_parameter/Glow_Power = 10.0
|
||||
shader_parameter/Lightness_Difference = 4.26400020254
|
||||
shader_parameter/Sun_Color = Color(0.90528274, 0.8164857, 0.6356678, 1)
|
||||
shader_parameter/voronoi_noise = SubResource("NoiseTexture2D_oxjal")
|
||||
shader_parameter/emission_noise = SubResource("NoiseTexture2D_1eyo5")
|
||||
shader_parameter/waveSpeed = 0.1
|
||||
shader_parameter/fresnel = 1.0
|
||||
shader_parameter/scale = 0.01
|
||||
shader_parameter/blendSharpness = 0.0
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Moon
|
||||
extends CelestialBody
|
||||
|
||||
# The orbital radius for this moon.
|
||||
var orbital_radius: float
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "Moon"
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
# A Moon has a smaller mass than a planet.
|
||||
#mass = 100.0
|
||||
radius = 5.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/moon_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://b1xsx7er22nxn
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://74ppvxcw8an4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b1xsx7er22nxn" path="res://scenes/celestial_bodies/moon.gd" id="1_530pw"]
|
||||
|
||||
[node name="Moon" type="RigidBody2D"]
|
||||
script = ExtResource("1_530pw")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Planet
|
||||
extends CelestialBody
|
||||
|
||||
# The orbital radius for this planet.
|
||||
var orbital_radius: float
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "Planet"
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
# A Planet has a smaller mass than a star.
|
||||
#mass = 1000.0
|
||||
radius = 10.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/planet_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://5f6ipgu65urb
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://clt4qlsjcfgln"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5f6ipgu65urb" path="res://scenes/celestial_bodies/planet.gd" id="1_cktii"]
|
||||
|
||||
[node name="Planet" type="RigidBody2D"]
|
||||
script = ExtResource("1_cktii")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,16 +0,0 @@
|
||||
class_name Star
|
||||
extends CelestialBody
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "Star"
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
# A Star has no primary and a very large mass.
|
||||
primary = null
|
||||
radius = 100.0
|
||||
|
||||
# You can set a default texture here, or assign it in the Inspector.
|
||||
# texture = preload("res://assets/star_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://um2sfghmii42
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://5uqp4amjj7ww"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://um2sfghmii42" path="res://scripts/star.gd" id="1_mcqwg"]
|
||||
|
||||
[node name="Star" type="RigidBody2D"]
|
||||
script = ExtResource("1_mcqwg")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Station
|
||||
extends CelestialBody
|
||||
|
||||
# The orbital radius for this station.
|
||||
var orbital_radius: float
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "Station"
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
# A Station has negligible mass for physics calculations.
|
||||
#mass = 0.001
|
||||
radius = 1.0
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/station_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://ulw61oxppwdu
|
||||
@ -1,7 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dm3s33o4xhqfv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ulw61oxppwdu" path="res://scripts/station.gd" id="1_rod8h"]
|
||||
|
||||
[node name="Station" type="RigidBody2D"]
|
||||
script = ExtResource("1_rod8h")
|
||||
metadata/_custom_type_script = "uid://bn1u2xood3vs6"
|
||||
138
scenes/character/character_pawn_3d.gd
Normal file
138
scenes/character/character_pawn_3d.gd
Normal file
@ -0,0 +1,138 @@
|
||||
# CharacterPawn.gd
|
||||
extends OrbitalBody3D
|
||||
class_name CharacterPawn3D
|
||||
|
||||
## Core Parameters
|
||||
@export var collision_energy_loss: float = 0.3
|
||||
@export var base_inertia: float = 1.0 # Pawn's inertia without suit
|
||||
|
||||
## Input Buffers
|
||||
var _move_input: Vector2 = Vector2.ZERO
|
||||
var _roll_input: float = 0.0
|
||||
var _vertical_input: float = 0.0
|
||||
var _interact_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.new(false, false, false)
|
||||
var _l_click_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.new(false, false, false)
|
||||
var _r_click_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.new(false, false, false)
|
||||
var _pitch_yaw_input: Vector2 = Vector2.ZERO
|
||||
|
||||
## Rotation Variables
|
||||
@onready var camera_anchor: Marker3D = $CameraAnchor
|
||||
@onready var camera_pivot: Node3D = $CameraAnchor/CameraPivot
|
||||
@onready var camera: Camera3D = $CameraAnchor/CameraPivot/SpringArm/Camera3D
|
||||
@export_range(0.1, PI / 2.0) var max_yaw_rad: float = deg_to_rad(80.0)
|
||||
@export_range(-PI / 2.0 + 0.01, 0) var min_pitch_rad: float = deg_to_rad(-75.0)
|
||||
@export_range(0, PI / 2.0 - 0.01) var max_pitch_rad: float = deg_to_rad(60.0)
|
||||
@export var head_turn_lerp_speed: float = 15.0
|
||||
|
||||
## Movement Components
|
||||
@onready var eva_suit_component: EVAMovementComponent = $EVAMovementComponent
|
||||
@onready var zero_g_movemement_component: ZeroGMovementComponent = $ZeroGMovementComponent
|
||||
|
||||
## Physics State (Managed by Pawn)
|
||||
@export var angular_damping: float = 0.95 # Base damping
|
||||
|
||||
## Other State Variables
|
||||
var current_gravity: Vector3 = Vector3.ZERO # TODO: Implement gravity detection
|
||||
var overlapping_ladder_area: Area3D = null
|
||||
@onready var grip_detector: Area3D = $GripDetector
|
||||
|
||||
# Constants for State Checks
|
||||
const WALKABLE_GRAVITY_THRESHOLD: float = 1.0
|
||||
|
||||
func _ready():
|
||||
# find movement components
|
||||
if eva_suit_component: print("Found EVA Suit Controller")
|
||||
if zero_g_movemement_component: print("Found Zero-G Movement Controller")
|
||||
|
||||
# Connect grip detector signals
|
||||
if grip_detector and zero_g_movemement_component:
|
||||
print("GripDetector Area3D node found")
|
||||
grip_detector.area_entered.connect(zero_g_movemement_component.on_grip_area_entered)
|
||||
grip_detector.area_exited.connect(zero_g_movemement_component.on_grip_area_exited)
|
||||
else:
|
||||
printerr("GripDetector Area3D node not found on CharacterPawn!")
|
||||
|
||||
if name == str(multiplayer.get_unique_id()):
|
||||
camera.make_current()
|
||||
camera.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
camera_pivot.global_transform = camera_anchor.get_global_transform_interpolated()
|
||||
|
||||
|
||||
func _physics_process(_delta: float):
|
||||
_apply_mouse_rotation()
|
||||
|
||||
_reset_inputs()
|
||||
|
||||
|
||||
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||
if not is_multiplayer_authority(): return
|
||||
super (state)
|
||||
|
||||
|
||||
# print("Integrating forces for pawn %s" % name)
|
||||
# print(" Move Input: %s, Vertical Input: %f, Roll Input: %f" % [_move_input, _vertical_input, _roll_input])
|
||||
|
||||
# Zero-G Movement
|
||||
if zero_g_movemement_component:
|
||||
# We pass the physics state
|
||||
zero_g_movemement_component.process_movement(state, _move_input, _vertical_input, _roll_input, _l_click_input, _r_click_input)
|
||||
|
||||
# EVA Suit Movement
|
||||
if eva_suit_component and zero_g_movemement_component.movement_state == ZeroGMovementComponent.MovementState.IDLE:
|
||||
eva_suit_component.process_eva_movement(state, _move_input, _vertical_input, _roll_input, _r_click_input)
|
||||
|
||||
|
||||
# --- Universal Rotation ---
|
||||
func _apply_mouse_rotation():
|
||||
if _pitch_yaw_input != Vector2.ZERO:
|
||||
camera_anchor.rotate_y(-_pitch_yaw_input.x)
|
||||
|
||||
# Apply Pitch LOCALLY to pivot
|
||||
camera_anchor.rotate_object_local(Vector3.RIGHT, _pitch_yaw_input.y)
|
||||
camera_anchor.rotation.x = clamp(camera_anchor.rotation.x, min_pitch_rad, max_pitch_rad)
|
||||
|
||||
_pitch_yaw_input = Vector2.ZERO
|
||||
|
||||
camera_anchor.rotation.z = 0.0
|
||||
|
||||
# --- Universal Integration & Collision ---
|
||||
func _integrate_angular_velocity(delta: float):
|
||||
# (Same integration logic as before using Basis or Quaternions)
|
||||
if angular_velocity.length_squared() > 0.0001:
|
||||
rotate(angular_velocity.normalized(), angular_velocity.length() * delta)
|
||||
|
||||
# Prevent drift if velocity is very small
|
||||
if angular_velocity.length_squared() < 0.0001:
|
||||
angular_velocity = Vector3.ZERO
|
||||
|
||||
# --- Input Setters/Resets (Add vertical to set_movement_input) ---
|
||||
func set_movement_input(move: Vector2, roll: float, vertical: float): _move_input = move; _roll_input = roll; _vertical_input = vertical
|
||||
func set_interaction_input(interact_input: PlayerController3D.KeyInput): _interact_input = interact_input
|
||||
func set_rotation_input(pitch_yaw_input: Vector2): _pitch_yaw_input += pitch_yaw_input
|
||||
func set_click_input(l_action: PlayerController3D.KeyInput, r_action: PlayerController3D.KeyInput):
|
||||
_l_click_input = l_action
|
||||
_r_click_input = r_action
|
||||
func _reset_inputs():
|
||||
_move_input = Vector2.ZERO
|
||||
_roll_input = 0.0
|
||||
_vertical_input = 0.0
|
||||
_interact_input = PlayerController3D.KeyInput.new(false, false, false)
|
||||
_l_click_input = PlayerController3D.KeyInput.new(false, false, false)
|
||||
_r_click_input = PlayerController3D.KeyInput.new(false, false, false)
|
||||
_pitch_yaw_input = Vector2.ZERO # Keep _r_held
|
||||
|
||||
# --- Helper Functions ---
|
||||
func _on_ladder_area_entered(area: Area3D): if area.is_in_group("Ladders"): overlapping_ladder_area = area
|
||||
func _on_ladder_area_exited(area: Area3D): if area == overlapping_ladder_area: overlapping_ladder_area = null
|
||||
func _reset_head_yaw(delta: float):
|
||||
# Smoothly apply the reset target to the actual pivot rotation
|
||||
camera_anchor.rotation.y = lerpf(camera_anchor.rotation.y, 0.0, delta * head_turn_lerp_speed)
|
||||
|
||||
# TODO: Re-enable when multiplayer authority per pawn is functional
|
||||
# func _notification(what: int) -> void:
|
||||
# match what:
|
||||
# NOTIFICATION_ENTER_TREE:
|
||||
# set_multiplayer_authority(int(name))
|
||||
1
scenes/character/character_pawn_3d.gd.uid
Normal file
1
scenes/character/character_pawn_3d.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cdmmiixa75f3x
|
||||
82
scenes/character/character_pawn_3d.tscn
Normal file
82
scenes/character/character_pawn_3d.tscn
Normal file
@ -0,0 +1,82 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://7yc6a07xoccy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cdmmiixa75f3x" path="res://scenes/character/character_pawn_3d.gd" id="1_4frsu"]
|
||||
[ext_resource type="PackedScene" uid="uid://bm1rbv4tuppbc" path="res://scenes/character/eva_suit_controller.tscn" id="3_gnddn"]
|
||||
[ext_resource type="Script" uid="uid://y3vo40i16ek3" path="res://scenes/character/zero_g_movement_component.gd" id="4_8jhjh"]
|
||||
[ext_resource type="PackedScene" uid="uid://ba3ijdstp2bvt" path="res://scenes/character/player_controller_3d.tscn" id="4_bcy3l"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_6vm80"]
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_6vm80"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_673rh"]
|
||||
radius = 0.1
|
||||
height = 1.0
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_gnddn"]
|
||||
radius = 1.0
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_gnddn"]
|
||||
properties/0/path = NodePath(".:position")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:rotation")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
properties/2/path = NodePath("CameraAnchor:rotation")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 1
|
||||
properties/3/path = NodePath(".:linear_velocity")
|
||||
properties/3/spawn = false
|
||||
properties/3/replication_mode = 0
|
||||
properties/4/path = NodePath(".:angular_velocity")
|
||||
properties/4/spawn = false
|
||||
properties/4/replication_mode = 0
|
||||
|
||||
[node name="CharacterPawn3D" type="RigidBody3D"]
|
||||
physics_interpolation_mode = 1
|
||||
top_level = true
|
||||
mass = 80.0
|
||||
script = ExtResource("1_4frsu")
|
||||
metadata/_custom_type_script = "uid://cdmmiixa75f3x"
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("CapsuleShape3D_6vm80")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("CapsuleMesh_6vm80")
|
||||
|
||||
[node name="CameraAnchor" type="Marker3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.7000000000000001, 0)
|
||||
|
||||
[node name="CameraPivot" type="Node3D" parent="CameraAnchor"]
|
||||
physics_interpolation_mode = 1
|
||||
|
||||
[node name="SpringArm" type="SpringArm3D" parent="CameraAnchor/CameraPivot"]
|
||||
shape = SubResource("CapsuleShape3D_673rh")
|
||||
spring_length = 2.0
|
||||
margin = 0.1
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraAnchor/CameraPivot/SpringArm"]
|
||||
far = 200000.0
|
||||
|
||||
[node name="GripDetector" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1)
|
||||
collision_layer = 0
|
||||
collision_mask = 32768
|
||||
monitorable = false
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GripDetector"]
|
||||
shape = SubResource("SphereShape3D_gnddn")
|
||||
|
||||
[node name="ZeroGMovementComponent" type="Node3D" parent="."]
|
||||
script = ExtResource("4_8jhjh")
|
||||
metadata/_custom_type_script = "uid://y3vo40i16ek3"
|
||||
|
||||
[node name="EVAMovementComponent" parent="." instance=ExtResource("3_gnddn")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.13939085347041424, 0.5148942200402955)
|
||||
|
||||
[node name="PlayerController3d" parent="." instance=ExtResource("4_bcy3l")]
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("SceneReplicationConfig_gnddn")
|
||||
184
scenes/character/eva_movement_component.gd
Normal file
184
scenes/character/eva_movement_component.gd
Normal file
@ -0,0 +1,184 @@
|
||||
# eva_suit_controller.gd
|
||||
extends Node # Or Node3D if thrusters need specific positions later
|
||||
class_name EVAMovementComponent
|
||||
|
||||
## References (Set automatically in _ready)
|
||||
var pawn: CharacterPawn3D
|
||||
|
||||
## EVA Parameters (Moved from ZeroGPawn)
|
||||
@export var orientation_speed: float = 25.0 # Used for orienting body to camera
|
||||
@export var linear_acceleration: float = 20.0
|
||||
@export var roll_torque_acceleration: float = 5.0
|
||||
@export var angular_damping: float = 0.95 # Base damping applied by pawn, suit might add more?
|
||||
@export var inertia_multiplier: float = 1.0 # How much the suit adds to pawn's base inertia (placeholder)
|
||||
@export var stabilization_kp: float = 25.0
|
||||
@export var stabilization_kd: float = 2 * sqrt(stabilization_kp)
|
||||
|
||||
var _auto_orient_target: Basis = Basis() # Stores the target orientation
|
||||
var _is_auto_orienting: bool = false # Flag to signal the pawn
|
||||
@export var auto_orient_stop_velocity_threshold: float = 0.01 # (in rad/s)
|
||||
|
||||
## State
|
||||
var stabilization_target: Node3D = null
|
||||
var stabilization_enabled: bool = false
|
||||
|
||||
func _ready():
|
||||
pawn = get_parent() as CharacterPawn3D
|
||||
if not pawn:
|
||||
printerr("EVAMovementComponent must be a child of a CharacterBody3D pawn.")
|
||||
return
|
||||
|
||||
## Called by Pawn's _integrate_forces when suit equipped
|
||||
func process_eva_movement(state: PhysicsDirectBodyState3D, move_input: Vector2, vertical_input: float, roll_input: float, orienting_input: PlayerController3D.KeyInput):
|
||||
# --- 1. Handle Orient Input ---
|
||||
if orienting_input.pressed or orienting_input.held:
|
||||
_set_auto_orient_target(state)
|
||||
|
||||
_process_auto_orientation(state) # [Function 2] Run the controller
|
||||
|
||||
# Check if stabilization is active and handle it first
|
||||
if stabilization_enabled and is_instance_valid(stabilization_target):
|
||||
_apply_stabilization_torques(state)
|
||||
else:
|
||||
# Apply regular movement/torque only if not stabilizing
|
||||
_apply_floating_movement(state, move_input, vertical_input, roll_input)
|
||||
|
||||
## Called by Pawn when entering FLOATING state with suit
|
||||
func on_enter_state():
|
||||
print("EVA Suit Engaged")
|
||||
# Any specific setup needed when activating the suit
|
||||
|
||||
## Called by Pawn when exiting FLOATING state with suit
|
||||
func on_exit_state():
|
||||
print("EVA Suit Disengaged")
|
||||
# Any cleanup needed when deactivating the suit (e.g., stop thruster effects)
|
||||
|
||||
# --- Internal Logic ---
|
||||
|
||||
func _apply_floating_movement(state: PhysicsDirectBodyState3D, move_input: Vector2, vertical_input: float, roll_input: float):
|
||||
# Apply Linear Velocity
|
||||
var move_dir_horizontal = (-state.transform.basis.z * move_input.y + state.transform.basis.x * move_input.x)
|
||||
var move_dir_vertical = state.transform.basis.y * vertical_input
|
||||
var combined_move_dir = move_dir_horizontal + move_dir_vertical
|
||||
|
||||
if combined_move_dir != Vector3.ZERO:
|
||||
state.apply_central_force(combined_move_dir.normalized() * linear_acceleration)
|
||||
|
||||
# --- Apply Roll Torque ---
|
||||
# Calculate torque magnitude based on input
|
||||
if roll_input != 0.0:
|
||||
_is_auto_orienting = false # Cancel auto-orientation if rolling manually
|
||||
|
||||
var roll_acceleration = state.transform.basis.z * (-roll_input) * roll_torque_acceleration
|
||||
# Apply the global torque vector using the pawn's helper function
|
||||
state.apply_torque(roll_acceleration)
|
||||
|
||||
func _set_auto_orient_target(state: PhysicsDirectBodyState3D):
|
||||
# Set the target to where the camera is currently looking
|
||||
var target_forward = - pawn.camera_anchor.global_basis.z # Look where camera looks
|
||||
var target_up = state.transform.basis.y
|
||||
_auto_orient_target = Basis.looking_at(target_forward, target_up)
|
||||
_is_auto_orienting = true # Start the orientation process
|
||||
|
||||
# --- Auto-Orientation Logic ---
|
||||
func _process_auto_orientation(state: PhysicsDirectBodyState3D):
|
||||
# This function runs every physics frame
|
||||
if not _is_auto_orienting:
|
||||
return # Not orienting, do nothing
|
||||
|
||||
# 2. Calculate Torque using PD Controller
|
||||
var torque = MotionUtils.calculate_pd_rotation_torque(
|
||||
_auto_orient_target,
|
||||
state.transform.basis,
|
||||
state.angular_velocity, # Read from state
|
||||
2 * sqrt(orientation_speed), # Kp (Critically Damped)
|
||||
orientation_speed # Kd
|
||||
)
|
||||
|
||||
# 2. Apply the torque to the physics state
|
||||
state.apply_torque(torque)
|
||||
|
||||
# 3. Check for stop condition
|
||||
var ang_vel_mag = state.angular_velocity.length()
|
||||
var axis = state.angular_velocity.normalized()
|
||||
|
||||
# If we are close enough AND slow enough, stop.
|
||||
if ang_vel_mag < auto_orient_stop_velocity_threshold:
|
||||
_is_auto_orienting = false
|
||||
_auto_orient_target = pawn.global_basis # Set target to current for next time
|
||||
|
||||
if axis.is_normalized():
|
||||
var physics_rotation = Basis().rotated(axis, ang_vel_mag * state.step)
|
||||
|
||||
pawn.camera_anchor.transform.basis = physics_rotation.inverse() * pawn.camera_anchor.transform.basis
|
||||
|
||||
|
||||
# --- Add new function placeholder ---
|
||||
# TODO: Implement Rotation Stabilization Logic
|
||||
func _apply_stabilization_torques(_state: PhysicsDirectBodyState3D):
|
||||
if not is_instance_valid(stabilization_target):
|
||||
stabilization_enabled = false
|
||||
return
|
||||
|
||||
# TODO: Get the angular velocity from suit readings
|
||||
var angular_velocity = Vector3.ZERO
|
||||
# 1. Calculate Relative Angular Velocity:
|
||||
# - Get the target's angular velocity (if it rotates) or assume zero.
|
||||
# - Find the difference between our angular_velocity and the target's.
|
||||
var relative_angular_velocity = angular_velocity # - target_angular_velocity (if applicable)
|
||||
|
||||
# 2. Calculate Target Orientation (Optional - for full orientation lock):
|
||||
# - Determine the desired orientation relative to the target (e.g., face towards it).
|
||||
# - Calculate the rotational error (e.g., using Quaternions or angle differences).
|
||||
var rotational_error = Vector3.ZERO # Placeholder for difference from desired orientation
|
||||
|
||||
# 3. Calculate Corrective Torque (PD Controller):
|
||||
# - Proportional Term (based on orientation error): P = rotational_error * stabilization_kp
|
||||
# - Derivative Term (based on relative spin): D = relative_angular_velocity * stabilization_kd
|
||||
# - Required Torque = -(P + D) # Negative to counteract error/spin
|
||||
var required_torque = - (rotational_error * stabilization_kp + relative_angular_velocity * stabilization_kd)
|
||||
|
||||
print("Applying stabilization torque: ", required_torque)
|
||||
# 4. Convert Required Torque into Thruster Actions:
|
||||
# - This is the complex part. Need to know thruster positions, directions, and forces.
|
||||
# - Determine which thrusters (likely RCS on the jetpack) can produce the required_torque.
|
||||
# - Calculate firing times/intensities for those thrusters.
|
||||
# - Apply the forces/torques (similar to how _apply_floating_movement applies roll torque).
|
||||
# Example (highly simplified, assumes direct torque application possible):
|
||||
|
||||
|
||||
# --- Old logic for feet trailing (commented out) ---
|
||||
# --- THE FIX: Adjust how target_up is calculated ---
|
||||
# Calculate velocity components relative to camera orientation
|
||||
# var _forward_velocity_component = pawn.velocity.dot(target_forward)
|
||||
# var _right_velocity_component = pawn.velocity.dot(pawn.camera_anchor.global_basis.x)
|
||||
|
||||
# Only apply strong "feet trailing" if significant forward/backward movement dominates
|
||||
# and we are actually moving.
|
||||
#if abs(forward_velocity_component) > abs(right_velocity_component) * 0.5 and velocity.length_squared() > 0.1:
|
||||
#target_up = -velocity.normalized()
|
||||
## Orthogonalize to prevent basis skew
|
||||
#var target_right = target_up.cross(target_forward).normalized()
|
||||
## If vectors are parallel, cross product is zero. Fallback needed.
|
||||
#if target_right.is_zero_approx():
|
||||
#target_up = transform.basis.y # Fallback to current up
|
||||
#else:
|
||||
#target_up = target_forward.cross(target_right).normalized()
|
||||
#else:
|
||||
## If primarily strafing or stationary relative to forward,
|
||||
## maintain the current body's roll orientation (its local Y-axis).
|
||||
|
||||
# --- Add methods for enabling/disabling stabilization, setting target etc. ---
|
||||
func set_stabilization_enabled(enable: bool):
|
||||
if enable and is_instance_valid(stabilization_target):
|
||||
stabilization_enabled = true
|
||||
print("EVA Suit Stabilization Enabled")
|
||||
else:
|
||||
stabilization_enabled = false
|
||||
print("EVA Suit Stabilization Disabled")
|
||||
|
||||
func set_stabilization_target(target: Node3D):
|
||||
stabilization_target = target
|
||||
|
||||
func toggle_stabilization():
|
||||
set_stabilization_enabled(not stabilization_enabled)
|
||||
1
scenes/character/eva_movement_component.gd.uid
Normal file
1
scenes/character/eva_movement_component.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://d4jka2etva22s
|
||||
12
scenes/character/eva_suit_controller.tscn
Normal file
12
scenes/character/eva_suit_controller.tscn
Normal file
@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bm1rbv4tuppbc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d4jka2etva22s" path="res://scenes/character/eva_movement_component.gd" id="1_mb22m"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_rlk1u"]
|
||||
size = Vector3(1, 1, 0.4)
|
||||
|
||||
[node name="EVASuitController" type="Node3D" unique_id=241443807]
|
||||
script = ExtResource("1_mb22m")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1434211019]
|
||||
mesh = SubResource("BoxMesh_rlk1u")
|
||||
327
scenes/character/player_controller_3d.gd
Normal file
327
scenes/character/player_controller_3d.gd
Normal file
@ -0,0 +1,327 @@
|
||||
# PlayerController3D.gd
|
||||
extends Node
|
||||
class_name PlayerController3D
|
||||
|
||||
@onready var possessed_pawn: CharacterPawn3D = get_parent()
|
||||
|
||||
# --- Mouse Sensitivity ---
|
||||
@export var mouse_sensitivity: float = 0.002 # Radians per pixel motion
|
||||
var _mouse_motion_input: Vector2 = Vector2.ZERO
|
||||
|
||||
# --- Builder State ---
|
||||
var active_preview_piece: ProceduralPiece = null
|
||||
var active_structure_data: StructureData = null
|
||||
var build_mode_enabled: bool = false
|
||||
var is_snap_valid: bool = false # Track if we are currently snapped
|
||||
|
||||
# Resources
|
||||
const SQUARE_PIECES: Array[StructureData] = [
|
||||
preload("res://data/structure/definitions/1m_square_flat.tres"),
|
||||
preload("res://data/structure/definitions/1m_square_dome_top.tres"),
|
||||
]
|
||||
const TRIANGLE_PIECES: Array[StructureData] = [
|
||||
preload("res://data/structure/definitions/s2_equilateral_tri.tres"),
|
||||
preload("res://data/structure/definitions/s2_geo_tri.tres"),
|
||||
preload("res://data/structure/definitions/s2_geo_v2_a.tres"),
|
||||
preload("res://data/structure/definitions/s2_geo_v2_b.tres"),
|
||||
]
|
||||
const PROCEDURAL_PIECE_SCENE = preload("res://scenes/ship/builder/pieces/procedural_piece.tscn")
|
||||
var current_piece_index: int = 0
|
||||
var current_rotation_step: int = 0 # 0 to 3, representing 0, 90, 180, 270 degrees
|
||||
|
||||
class KeyInput:
|
||||
var pressed: bool = false
|
||||
var held: bool = false
|
||||
var released: bool = false
|
||||
|
||||
func _init(_p: bool = false, _h: bool = false, _r: bool = false):
|
||||
pressed = _p
|
||||
held = _h
|
||||
released = _r
|
||||
|
||||
# Helper to convert to Dictionary for RPC
|
||||
func to_dict() -> Dictionary:
|
||||
return {"p": pressed, "h": held, "r": released}
|
||||
|
||||
# Helper to create from Dictionary
|
||||
static func from_dict(d: Dictionary) -> KeyInput:
|
||||
return KeyInput.new(d.get("p", false), d.get("h", false), d.get("r", false))
|
||||
|
||||
func _ready():
|
||||
# Fallback: assume the pawn's name is the player ID.
|
||||
if get_parent().name.is_valid_int():
|
||||
set_multiplayer_authority(int(get_parent().name))
|
||||
|
||||
func _unhandled_input(event: InputEvent):
|
||||
# Check if THIS client is the owner of this controller
|
||||
if not is_multiplayer_authority() or not is_instance_valid(possessed_pawn):
|
||||
return
|
||||
|
||||
# Toggle Build Mode
|
||||
if event.is_action_pressed("toggle_build_mode"): # Map 'B' or similar in Project Settings
|
||||
build_mode_enabled = !build_mode_enabled
|
||||
if not build_mode_enabled:
|
||||
_clear_preview()
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) # Ensure mouse captured when leaving
|
||||
else:
|
||||
current_piece_index = 0
|
||||
_select_piece(SQUARE_PIECES[current_piece_index])
|
||||
print("Build Mode Enabled")
|
||||
|
||||
if not build_mode_enabled:
|
||||
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
_mouse_motion_input += Vector2(event.relative.x, -event.relative.y)
|
||||
return
|
||||
|
||||
# --- Build Mode Inputs ---
|
||||
# --- Piece Rotation (R key) ---
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_R:
|
||||
current_rotation_step = (current_rotation_step + 1) % 4
|
||||
_update_preview_transform() # Update immediately
|
||||
|
||||
# --- Piece Cycling (Brackets [ ]) ---
|
||||
if event is InputEventKey and event.pressed:
|
||||
if event.keycode == KEY_1:
|
||||
current_piece_index = (current_piece_index - 1 + SQUARE_PIECES.size()) % SQUARE_PIECES.size()
|
||||
_select_piece(SQUARE_PIECES[current_piece_index])
|
||||
elif event.keycode == KEY_2:
|
||||
current_piece_index = (current_piece_index + 1) % TRIANGLE_PIECES.size() % TRIANGLE_PIECES.size()
|
||||
_select_piece(TRIANGLE_PIECES[current_piece_index])
|
||||
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
_place_piece()
|
||||
|
||||
# Allow camera look while holding right click in build mode
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
if event.pressed:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
else:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
# Handle mouse motion input directly here
|
||||
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
_mouse_motion_input += Vector2(event.relative.x, -event.relative.y)
|
||||
|
||||
func _physics_process(_delta):
|
||||
# Check if THIS client is the owner
|
||||
if not is_multiplayer_authority() or not is_instance_valid(possessed_pawn):
|
||||
return
|
||||
|
||||
# STRENGTHENED CHECK: Ensure pawn is valid and inside the tree
|
||||
if not is_instance_valid(possessed_pawn) or not possessed_pawn.is_inside_tree():
|
||||
return
|
||||
|
||||
# 1. Handle Mouse Rotation
|
||||
if _mouse_motion_input != Vector2.ZERO:
|
||||
var sensitivity_modified_mouse_input = Vector2(_mouse_motion_input.x, _mouse_motion_input.y) * mouse_sensitivity
|
||||
# Send to Server (ID 1)
|
||||
server_process_rotation_input.rpc_id(1, sensitivity_modified_mouse_input)
|
||||
_mouse_motion_input = Vector2.ZERO
|
||||
|
||||
# 2. Gather Movement Inputs (Only process movement if NOT in build mode, or perhaps allow moving while building?)
|
||||
# Let's allow movement while building for now.
|
||||
var move_vec = Input.get_vector("move_left_3d", "move_right_3d", "move_backward_3d", "move_forward_3d")
|
||||
var roll_input = Input.get_action_strength("roll_right_3d") - Input.get_action_strength("roll_left_3d")
|
||||
var vertical_input = Input.get_action_strength("move_up_3d") - Input.get_action_strength("move_down_3d")
|
||||
|
||||
var interact_input = KeyInput.new(Input.is_action_just_pressed("spacebar_3d"), Input.is_action_pressed("spacebar_3d"), Input.is_action_just_released("spacebar_3d"))
|
||||
var l_input = KeyInput.new(Input.is_action_just_pressed("left_click"), Input.is_action_pressed("left_click"), Input.is_action_just_released("left_click"))
|
||||
var r_input = KeyInput.new(Input.is_action_just_pressed("right_click"), Input.is_action_pressed("right_click"), Input.is_action_just_released("right_click"))
|
||||
|
||||
# Send to Server (ID 1), converting KeyInput objects to Dictionaries
|
||||
server_process_movement_input.rpc_id(1, move_vec, roll_input, vertical_input)
|
||||
server_process_interaction_input.rpc_id(1, interact_input.to_dict())
|
||||
server_process_clicks.rpc_id(1, l_input.to_dict(), r_input.to_dict())
|
||||
|
||||
# 3. Update Builder Preview
|
||||
if build_mode_enabled and active_preview_piece:
|
||||
_update_preview_transform()
|
||||
|
||||
# --- Builder Functions ---
|
||||
|
||||
func _select_piece(piece_data: StructureData):
|
||||
_clear_preview()
|
||||
active_structure_data = piece_data
|
||||
print("Selected piece for building:", piece_data.piece_name)
|
||||
if active_structure_data:
|
||||
var piece = PROCEDURAL_PIECE_SCENE.instantiate()
|
||||
piece.structure_data = active_structure_data
|
||||
piece.is_preview = true
|
||||
get_tree().current_scene.add_child(piece)
|
||||
active_preview_piece = piece
|
||||
|
||||
func _update_preview_transform():
|
||||
if not is_instance_valid(possessed_pawn): return
|
||||
|
||||
is_snap_valid = false # Reset snap state
|
||||
|
||||
var cam = possessed_pawn.camera
|
||||
var space_state = possessed_pawn.get_world_3d().direct_space_state
|
||||
var mouse_pos = get_viewport().get_mouse_position()
|
||||
var from = cam.project_ray_origin(mouse_pos)
|
||||
var dir = cam.project_ray_normal(mouse_pos)
|
||||
|
||||
# --- NEW: Use SnappingTool to perform the physics sweep ---
|
||||
# This replaces the simple raycast with a thick cylinder cast
|
||||
var hit_result = SnappingTool.find_snap_target(space_state, from, dir, 10.0, 0.2)
|
||||
|
||||
if not hit_result.is_empty():
|
||||
var collider = hit_result["collider"]
|
||||
var hit_pos = hit_result["position"]
|
||||
|
||||
var target_module: Module = null
|
||||
if collider is PieceMount:
|
||||
# If we hit a mount, get its piece and its module, get its module
|
||||
if collider.get_parent() is StructuralPiece:
|
||||
target_module = collider.get_parent().get_parent()
|
||||
if collider.owner is Module: target_module = collider.owner
|
||||
elif collider.get_parent() is Module: target_module = collider.get_parent()
|
||||
|
||||
if target_module:
|
||||
# Attempt Snap using the hit position
|
||||
var snap_transform = SnappingTool.get_best_snap_transform(
|
||||
active_structure_data,
|
||||
target_module,
|
||||
hit_pos, # Ray hit position
|
||||
)
|
||||
|
||||
# If the transform has changed significantly from the hit pos, it means a snap occurred.
|
||||
# (Simple heuristic: check distance from hit to new origin)
|
||||
if snap_transform.origin.distance_to(hit_pos) < SnappingTool.SNAP_DISTANCE:
|
||||
active_preview_piece.global_transform = snap_transform
|
||||
is_snap_valid = true
|
||||
_update_preview_color(Color.GREEN)
|
||||
return
|
||||
|
||||
# Fallback: Float in front of player
|
||||
var float_pos = from + dir * 3.0
|
||||
active_preview_piece.global_position = float_pos
|
||||
# Orient to face camera roughly
|
||||
active_preview_piece.look_at(cam.global_position, Vector3.UP)
|
||||
_update_preview_color(Color.CYAN) # Cyan = Floating
|
||||
|
||||
func _update_preview_color(color: Color):
|
||||
if not is_instance_valid(active_preview_piece): return
|
||||
|
||||
var mesh_inst = active_preview_piece.find_child("MeshInstance3D")
|
||||
if mesh_inst and mesh_inst.material_override:
|
||||
var mat = mesh_inst.material_override as StandardMaterial3D
|
||||
mat.albedo_color = Color(color.r, color.g, color.b, 0.4)
|
||||
mat.emission = color
|
||||
|
||||
func _place_piece():
|
||||
if not active_preview_piece or not active_structure_data: return
|
||||
|
||||
# Tell Server to spawn the real piece at this transform
|
||||
server_request_place_piece.rpc_id(1, active_structure_data.resource_path, active_preview_piece.global_transform)
|
||||
|
||||
func _clear_preview():
|
||||
if is_instance_valid(active_preview_piece):
|
||||
active_preview_piece.queue_free()
|
||||
active_preview_piece = null
|
||||
|
||||
# --- RPCs: Allow "any_peer" so clients can call this on the Server ---
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_request_place_piece(resource_path: String, transform: Transform3D):
|
||||
# Server validation logic here (distance check, cost check)
|
||||
# Security: Check if sender is allowed to build
|
||||
|
||||
var res = load(resource_path) as StructureData
|
||||
if not res: return
|
||||
|
||||
# Find nearby module to attach to
|
||||
var query_pos = transform.origin
|
||||
var module: Module = _find_module_near_server(query_pos)
|
||||
|
||||
if not module:
|
||||
# Logic to create a new module could go here
|
||||
print("No module nearby to attach piece.")
|
||||
var new_module = Module.new()
|
||||
new_module.name = "Module_%s" % get_process_delta_time() # Unique name
|
||||
possessed_pawn.get_parent().add_child(new_module)
|
||||
new_module.global_position = query_pos
|
||||
new_module.physics_mode = OrbitalBody3D.PhysicsMode.COMPOSITE
|
||||
module = new_module
|
||||
print("Created new module %s for piece placement." % new_module.name)
|
||||
|
||||
if module:
|
||||
var piece: ProceduralPiece = PROCEDURAL_PIECE_SCENE.instantiate()
|
||||
piece.structure_data = res
|
||||
module.add_child(piece)
|
||||
piece.global_transform = transform
|
||||
piece.owner = module # Ensure persistence
|
||||
|
||||
# Trigger weld logic on the new piece
|
||||
piece.try_weld()
|
||||
module.recalculate_physical_properties()
|
||||
print("Placed piece %s on module %s" % [piece.name, module])
|
||||
|
||||
# Helper to find modules on server side (uses global overlap check)
|
||||
func _find_module_near_server(pos: Vector3) -> Module:
|
||||
# Create a sphere query parameters object
|
||||
var space_state = possessed_pawn.get_world_3d().direct_space_state
|
||||
var params = PhysicsShapeQueryParameters3D.new()
|
||||
var shape = SphereShape3D.new()
|
||||
shape.radius = 5.0 # Search radius
|
||||
params.shape = shape
|
||||
params.transform = Transform3D(Basis(), pos)
|
||||
params.collide_with_bodies = true
|
||||
params.collision_mask = 0xFFFFFFFF # Check all layers, or specific module layer
|
||||
|
||||
var results = space_state.intersect_shape(params)
|
||||
|
||||
for result in results:
|
||||
var collider = result["collider"]
|
||||
|
||||
# Case 1: We hit the Module directly (RigidBody3D)
|
||||
if collider is Module:
|
||||
return collider
|
||||
|
||||
# Case 2: We hit a StructuralPiece attached to a Module
|
||||
if collider is StructuralPiece:
|
||||
# StructuralPiece should have a way to get its root module
|
||||
if collider.get_parent() is Module:
|
||||
return collider.get_parent()
|
||||
# Or if you use the helper:
|
||||
# return collider.get_root_module()
|
||||
|
||||
return null
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_movement_input(move: Vector2, roll: float, vertical: float):
|
||||
if is_instance_valid(possessed_pawn):
|
||||
# Debug: Uncomment to verify flow
|
||||
# if move.length() > 0: print("Server Pawn %s Move: %s" % [owner_id, move])
|
||||
possessed_pawn.set_movement_input(move, roll, vertical)
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_interaction_input(interact_data: Dictionary):
|
||||
if is_instance_valid(possessed_pawn):
|
||||
if interact_data.has_all(["p", "h", "r"]):
|
||||
possessed_pawn.set_interaction_input(KeyInput.from_dict(interact_data))
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_rotation_input(input: Vector2):
|
||||
if is_instance_valid(possessed_pawn):
|
||||
possessed_pawn.set_rotation_input(input)
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_clicks(l_data: Dictionary, r_data: Dictionary):
|
||||
if is_instance_valid(possessed_pawn):
|
||||
var l_action = KeyInput.from_dict(l_data) if l_data.has("p") else KeyInput.new()
|
||||
var r_action = KeyInput.from_dict(r_data) if r_data.has("p") else KeyInput.new()
|
||||
possessed_pawn.set_click_input(l_action, r_action)
|
||||
|
||||
# Optional: Release mouse when losing focus
|
||||
func _notification(what):
|
||||
match what:
|
||||
NOTIFICATION_WM_WINDOW_FOCUS_OUT:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
NOTIFICATION_WM_WINDOW_FOCUS_IN:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
NOTIFICATION_EXIT_TREE:
|
||||
print("PlayerController exited tree")
|
||||
NOTIFICATION_ENTER_TREE:
|
||||
print("PlayerController %s entered tree" % multiplayer.get_unique_id())
|
||||
1
scenes/character/player_controller_3d.gd.uid
Normal file
1
scenes/character/player_controller_3d.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://vjfk3xnapfti
|
||||
6
scenes/character/player_controller_3d.tscn
Normal file
6
scenes/character/player_controller_3d.tscn
Normal file
@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ba3ijdstp2bvt"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://vjfk3xnapfti" path="res://scenes/character/player_controller_3d.gd" id="1_elh6f"]
|
||||
|
||||
[node name="PlayerController3d" type="Node"]
|
||||
script = ExtResource("1_elh6f")
|
||||
494
scenes/character/zero_g_movement_component.gd
Normal file
494
scenes/character/zero_g_movement_component.gd
Normal file
@ -0,0 +1,494 @@
|
||||
# zero_g_move_controller.gd
|
||||
extends Node
|
||||
class_name ZeroGMovementComponent
|
||||
|
||||
## References
|
||||
var pawn: CharacterPawn3D
|
||||
|
||||
## State & Parameters
|
||||
var current_grip: GripArea3D = null # Use GripArea3D type hint
|
||||
var nearby_grips: Array[GripArea3D] = []
|
||||
|
||||
# --- Grip damping parameters ---
|
||||
@export var gripping_linear_damping: float = 6.0 # How quickly velocity stops
|
||||
@export var gripping_linear_kd: float = 2 * sqrt(gripping_linear_damping) # How quickly velocity stops
|
||||
@export var gripping_angular_damping: float = 3.0 # How quickly spin stops
|
||||
@export var gripping_orient_speed: float = 2 * sqrt(gripping_angular_damping) # How quickly pawn rotates to face grip
|
||||
|
||||
var _target_basis: Basis # The orientation the PD controller is currently seeking
|
||||
var _manual_roll_timer: Timer
|
||||
@export var manual_roll_reset_delay: float = 3.0 # Time in seconds to wait before auto-aligning
|
||||
@export var manual_roll_speed: float = 2.0 # How fast (rad/s) to rotate the target
|
||||
|
||||
# --- Climbing parameters ---
|
||||
@export var climb_speed: float = 2.0
|
||||
@export var grip_handover_distance: float = 1 # How close to next grip to initiate handover
|
||||
@export var climb_acceleration: float = 1.0 # How quickly pawn reaches climb_speed
|
||||
@export var climb_angle_threshold_deg: float = 120.0 # How wide the forward cone is
|
||||
@export var release_past_grip_threshold: float = 0.4 # How far past the grip origin before releasing
|
||||
var next_grip_target: GripArea3D = null # The grip we are trying to transition to
|
||||
|
||||
# --- Seeking Climb State ---
|
||||
var _seeking_climb_input: Vector2 = Vector2.ZERO # The move_input held when seeking started
|
||||
|
||||
# --- Launch Parameters ---
|
||||
@export var launch_charge_rate: float = 1.5
|
||||
@export var max_launch_speed: float = 4.0
|
||||
var launch_direction: Vector3 = Vector3.ZERO
|
||||
var launch_charge: float = 0.0
|
||||
|
||||
# Enum for internal state
|
||||
enum MovementState {
|
||||
IDLE,
|
||||
REACHING,
|
||||
GRIPPING,
|
||||
CLIMBING,
|
||||
SEEKING_CLIMB,
|
||||
CHARGING_LAUNCH
|
||||
}
|
||||
var movement_state: MovementState = MovementState.IDLE:
|
||||
set(new_state):
|
||||
if new_state == movement_state: return
|
||||
_on_exit_state(movement_state) # Call exit logic for old state
|
||||
movement_state = new_state
|
||||
_on_enter_state(movement_state) # Call enter logic for new state
|
||||
|
||||
func _ready():
|
||||
pawn = get_parent() as CharacterPawn3D
|
||||
if not pawn: printerr("ZeroGMovementComponent must be child of CharacterPawn3D")
|
||||
|
||||
_manual_roll_timer = Timer.new()
|
||||
_manual_roll_timer.one_shot = true
|
||||
_manual_roll_timer.wait_time = manual_roll_reset_delay
|
||||
add_child(_manual_roll_timer)
|
||||
|
||||
# --- Standardized Movement API ---
|
||||
|
||||
## Called by Pawn when relevant state is active (e.g., GRABBING_GRIP, REACHING_MOVE)
|
||||
func process_movement(physics_state: PhysicsDirectBodyState3D, move_input: Vector2, vertical_input: float, roll_input: float, reach_input: PlayerController3D.KeyInput, release_input: PlayerController3D.KeyInput):
|
||||
if not is_instance_valid(pawn): return
|
||||
|
||||
_update_state(move_input, reach_input, release_input)
|
||||
|
||||
match movement_state:
|
||||
MovementState.IDLE:
|
||||
_process_idle(physics_state, move_input, vertical_input, roll_input, release_input)
|
||||
MovementState.REACHING:
|
||||
_process_reaching(physics_state)
|
||||
MovementState.GRIPPING:
|
||||
_process_grip_physics(physics_state, move_input, roll_input)
|
||||
MovementState.CLIMBING:
|
||||
_process_climb_physics(physics_state, move_input)
|
||||
MovementState.SEEKING_CLIMB:
|
||||
_process_seeking_climb(physics_state, move_input)
|
||||
MovementState.CHARGING_LAUNCH:
|
||||
_process_launch_charge(physics_state, move_input, reach_input)
|
||||
|
||||
|
||||
# === STATE MACHINE ===
|
||||
func _on_enter_state(movement_state: MovementState):
|
||||
print("ZeroGMovementComponent activated for movement_state: ", MovementState.keys()[movement_state])
|
||||
func _on_exit_state(movement_state: MovementState):
|
||||
print("ZeroGMovementComponent deactivated for movement_state: ", MovementState.keys()[movement_state])
|
||||
|
||||
# Ensure grip is released if state changes unexpectedly
|
||||
# if movement_state == MovementState.GRIPPING:
|
||||
# _release_current_grip()
|
||||
|
||||
func _update_state(
|
||||
move_input: Vector2,
|
||||
reach_input: PlayerController3D.KeyInput,
|
||||
release_input: PlayerController3D.KeyInput,
|
||||
):
|
||||
match movement_state:
|
||||
MovementState.IDLE:
|
||||
# Already handled initiating reach in process_movement
|
||||
if reach_input.pressed or reach_input.held:
|
||||
movement_state = MovementState.REACHING
|
||||
MovementState.REACHING:
|
||||
# TODO: If reach animation completes/hand near target -> GRIPPING
|
||||
# If interact released during reach -> CANCEL -> IDLE
|
||||
if _seeking_climb_input != Vector2.ZERO:
|
||||
# We are in a "seek-climb-reach" chain. Cancel if move input stops.
|
||||
if move_input == Vector2.ZERO:
|
||||
# This will transition state to IDLE
|
||||
_cancel_reach()
|
||||
elif not (reach_input.pressed or reach_input.held):
|
||||
# This was a normal reach initiated by click. Cancel if click is released.
|
||||
_cancel_reach()
|
||||
|
||||
MovementState.GRIPPING:
|
||||
# print("ZeroGMovementComponent: Gripping State Active")
|
||||
if release_input.pressed or release_input.held or not is_instance_valid(current_grip):
|
||||
_release_current_grip(move_input)
|
||||
return
|
||||
|
||||
# Check for launch charge *before* checking for climb, as it's a more specific action.
|
||||
if (reach_input.pressed or reach_input.held) and move_input != Vector2.ZERO:
|
||||
_start_charge(move_input)
|
||||
return
|
||||
elif move_input != Vector2.ZERO and is_instance_valid(current_grip):
|
||||
movement_state = MovementState.CLIMBING
|
||||
MovementState.CLIMBING:
|
||||
if reach_input.pressed or reach_input.held:
|
||||
_start_charge(move_input)
|
||||
return
|
||||
if release_input.pressed or release_input.held or not is_instance_valid(current_grip):
|
||||
_stop_climb(true) # Release grip and stop
|
||||
return
|
||||
if move_input == Vector2.ZERO: # Player stopped giving input
|
||||
_stop_climb(false) # Stop moving, return to GRIPPING
|
||||
return
|
||||
# Continue climbing logic (finding next grip) happens in _process_climbing
|
||||
MovementState.CHARGING_LAUNCH:
|
||||
if move_input == Vector2.ZERO: # Cancel charge while holding interact
|
||||
movement_state = MovementState.GRIPPING
|
||||
print("ZeroGMovementComponent: Cancelled Launch Charge")
|
||||
|
||||
|
||||
# === MOVEMENT PROCESSING ===
|
||||
func _process_idle(_physics_state: PhysicsDirectBodyState3D, _move_input: Vector2, _vertical_input: float, _roll_input: float, _release_input: PlayerController3D.KeyInput):
|
||||
# TODO: Implement free-floating auto orientation against bulkheads to maintain orientation with ship
|
||||
pass
|
||||
|
||||
func _process_reaching(physics_state: PhysicsDirectBodyState3D):
|
||||
# TODO: Drive IK target towards current_grip.get_grip_transform().origin
|
||||
# TODO: Monitor distance / animation state
|
||||
# For now, _we just instantly grip.
|
||||
if _seeking_climb_input != Vector2.ZERO:
|
||||
_attempt_grip(physics_state, next_grip_target) # Complete the seek-reach
|
||||
else:
|
||||
_attempt_grip(physics_state, _find_best_grip())
|
||||
|
||||
func _process_grip_physics(physics_state: PhysicsDirectBodyState3D, _move_input: Vector2, roll_input: float):
|
||||
if not is_instance_valid(pawn) or not is_instance_valid(current_grip):
|
||||
_release_current_grip(); return
|
||||
|
||||
# TODO: Later, replace step 2 and 3 with IK driving the hand bone to the target_transform.origin,
|
||||
# while the physics/orientation logic stops the main body's momentum.
|
||||
|
||||
# --- 2. Calculate Target Transform ---
|
||||
if not is_zero_approx(roll_input):
|
||||
# User is rolling. Stop the reset timer.
|
||||
_manual_roll_timer.stop()
|
||||
|
||||
# Rotate the current target basis around the grip's Z-axis
|
||||
var grip_z_axis = current_grip.global_basis.z
|
||||
_target_basis = _target_basis.rotated(grip_z_axis, -roll_input * manual_roll_speed * physics_state.step)
|
||||
|
||||
# Restart the timer
|
||||
_manual_roll_timer.start()
|
||||
elif _manual_roll_timer.wait_time < 0.0:
|
||||
_on_manual_roll_timeout(physics_state) # Immediate reset if delay is negative
|
||||
|
||||
# --- 3. Apply Linear Force (PD Controller) ---
|
||||
physics_state.apply_central_force(_get_hold_force(physics_state))
|
||||
|
||||
_apply_orientation_torque(physics_state, _target_basis)
|
||||
|
||||
func _process_climb_physics(physics_state: PhysicsDirectBodyState3D, move_input: Vector2):
|
||||
if not is_instance_valid(pawn) or not is_instance_valid(current_grip):
|
||||
_stop_climb(true); return
|
||||
|
||||
# 1. Calculate Climb Direction: For climbing we interpret W as up from the pawns perspective instead of forward
|
||||
var climb_direction = move_input.y * physics_state.transform.basis.y + move_input.x * physics_state.transform.basis.x
|
||||
climb_direction = climb_direction.normalized()
|
||||
|
||||
# 2. Find Next Grip
|
||||
next_grip_target = _find_best_grip(climb_direction, INF, climb_angle_threshold_deg)
|
||||
|
||||
# 3. Check for Handover: This should be more eager to mark a new grip as current than below check is to release when climbing past
|
||||
var performed_handover = _attempt_grip(physics_state, next_grip_target)
|
||||
|
||||
# 4. Check for Release Past Grip (if no handover)
|
||||
if not performed_handover:
|
||||
var current_grip_pos = current_grip.global_position
|
||||
var vector_from_grip_to_pawn = physics_state.transform.origin - current_grip_pos
|
||||
var distance_along_climb_dir = vector_from_grip_to_pawn.dot(climb_direction)
|
||||
if distance_along_climb_dir > release_past_grip_threshold: # Release threshold
|
||||
_release_current_grip(move_input)
|
||||
return # State changed to IDLE
|
||||
|
||||
# 5. Apply Combined Forces for Climbing & Holding
|
||||
|
||||
# --- Force 1: Positional Hold (From _process_grip_physics) ---
|
||||
# Calculate the force needed to stay at that position
|
||||
var force_hold = _get_hold_force(physics_state)
|
||||
|
||||
# --- Force 2: Climbing Movement ---
|
||||
var target_velocity = climb_direction * climb_speed
|
||||
var error_vel = target_velocity - physics_state.linear_velocity
|
||||
var force_climb = error_vel * climb_acceleration # Kp = climb_acceleration
|
||||
|
||||
# Find the part of the "hold" force that is parallel to our climb direction
|
||||
var force_hold_parallel = force_hold.project(climb_direction)
|
||||
|
||||
# Check if that parallel part is pointing *against* our climb
|
||||
if force_hold_parallel.dot(climb_direction) < 0:
|
||||
# If it is, remove it from the hold force.
|
||||
# This leaves only the perpendicular (offset-correcting) force.
|
||||
force_hold = force_hold - force_hold_parallel
|
||||
|
||||
# --- Combine and Apply ---
|
||||
# We apply *both* forces. The hold force will manage the offset,
|
||||
# while the climb force will overpower it in the climb direction.
|
||||
var total_force = force_hold + force_climb
|
||||
physics_state.apply_central_force(total_force)
|
||||
|
||||
# 6. Apply Angular Force (Auto-Orient to current grip)
|
||||
var target_basis = _choose_grip_orientation(physics_state, current_grip.global_basis)
|
||||
_apply_orientation_torque(physics_state, target_basis)
|
||||
|
||||
|
||||
func _process_seeking_climb(physics_state: PhysicsDirectBodyState3D, move_input: Vector2):
|
||||
# If the player's input has changed from what initiated the seek, cancel it.
|
||||
if not move_input.is_equal_approx(_seeking_climb_input):
|
||||
_seeking_climb_input = Vector2.ZERO # Reset for next time
|
||||
if _attempt_grip(physics_state, _find_best_grip()):
|
||||
# Successfully found and grabbed a grip. The state is now GRIPPING.
|
||||
print("Seeking Climb ended, gripped new target.")
|
||||
else:
|
||||
movement_state = MovementState.IDLE
|
||||
# No grip found. Transition to IDLE.
|
||||
print("Seeking Climb ended, no grip found. Reverting to IDLE.")
|
||||
|
||||
# --- Grip Helpers
|
||||
|
||||
## The single, authoritative function for grabbing a grip.
|
||||
func _attempt_grip(physics_state: PhysicsDirectBodyState3D, target_grip: GripArea3D) -> bool:
|
||||
if not is_instance_valid(target_grip):
|
||||
return false
|
||||
|
||||
if target_grip.grab(pawn):
|
||||
# Successfully grabbed the new grip.
|
||||
var old_grip = current_grip
|
||||
if is_instance_valid(old_grip) and old_grip != target_grip:
|
||||
old_grip.release(pawn)
|
||||
|
||||
_manual_roll_timer.stop()
|
||||
_target_basis = _choose_grip_orientation(physics_state, target_grip.global_basis)
|
||||
|
||||
current_grip = target_grip
|
||||
|
||||
current_grip = target_grip
|
||||
next_grip_target = null
|
||||
_seeking_climb_input = Vector2.ZERO
|
||||
|
||||
# If we weren't already climbing, transition to GRIPPING state.
|
||||
if movement_state != MovementState.CLIMBING:
|
||||
movement_state = MovementState.GRIPPING
|
||||
|
||||
print("Successfully gripped: ", current_grip.get_parent().name)
|
||||
return true
|
||||
else:
|
||||
# Failed to grab the new grip.
|
||||
print("Failed to grip: ", target_grip.get_parent().name, " (likely occupied).")
|
||||
if movement_state == MovementState.CLIMBING:
|
||||
_stop_climb(false) # Stop climbing, return to gripping previous one
|
||||
return false
|
||||
|
||||
# --- Grip Orientation Helper ---
|
||||
func _choose_grip_orientation(physics_state: PhysicsDirectBodyState3D, grip_basis: Basis) -> Basis:
|
||||
# 1. Define the two possible target orientations based on the grip.
|
||||
# Both will look away from the grip's surface (-Z).
|
||||
var look_at_dir = - grip_basis.z.normalized()
|
||||
var target_basis_up = Basis.looking_at(look_at_dir, grip_basis.y.normalized()).orthonormalized()
|
||||
var target_basis_down = Basis.looking_at(look_at_dir, -grip_basis.y.normalized()).orthonormalized()
|
||||
|
||||
# 2. Get the pawn's current orientation.
|
||||
var current_basis = physics_state.transform.basis
|
||||
|
||||
# 3. Compare which target orientation is "closer" to the current one.
|
||||
# We can do this by finding the angle of rotation needed to get from current to each target.
|
||||
# The quaternion dot product is related to the angle between orientations. A larger absolute dot product means a smaller angle.
|
||||
var dot_up = current_basis.get_rotation_quaternion().dot(target_basis_up.get_rotation_quaternion())
|
||||
var dot_down = current_basis.get_rotation_quaternion().dot(target_basis_down.get_rotation_quaternion())
|
||||
|
||||
# We choose the basis that results in a larger absolute dot product (smaller rotational distance).
|
||||
return target_basis_up if abs(dot_up) >= abs(dot_down) else target_basis_down
|
||||
|
||||
# --- Grip Selection Logic ---
|
||||
# Finds the best grip based on direction, distance, and angle constraints
|
||||
func _find_best_grip(direction := Vector3.ZERO, max_distance_sq := INF, angle_threshold_deg := 120.0) -> GripArea3D:
|
||||
var best_grip: GripArea3D = null
|
||||
var min_dist_sq = max_distance_sq # Start checking against max allowed distance
|
||||
|
||||
var use_direction_filter = direction != Vector3.ZERO
|
||||
var max_allowed_angle_rad = 0.0 # Initialize
|
||||
if use_direction_filter:
|
||||
# Calculate the maximum allowed angle deviation from the center direction
|
||||
max_allowed_angle_rad = deg_to_rad(angle_threshold_deg) / 2.0
|
||||
|
||||
# Iterate through all grips detected by the pawn
|
||||
for grip in nearby_grips:
|
||||
# Basic validity checks
|
||||
if not is_instance_valid(grip) or grip == current_grip or not grip.can_grab(pawn):
|
||||
continue
|
||||
|
||||
var grip_pos = grip.global_position
|
||||
# Use direction_to which automatically normalizes
|
||||
var dir_to_grip = pawn.global_position.direction_to(grip_pos)
|
||||
var dist_sq = pawn.global_position.distance_squared_to(grip_pos)
|
||||
|
||||
# Check distance first
|
||||
if dist_sq >= min_dist_sq: # Use >= because we update min_dist_sq later
|
||||
continue
|
||||
|
||||
# If using direction filter, check angle constraint
|
||||
if use_direction_filter:
|
||||
# Ensure the direction vector we compare against is normalized
|
||||
var normalized_direction = direction.normalized()
|
||||
# Calculate the dot product
|
||||
var dot = dir_to_grip.dot(normalized_direction)
|
||||
# Clamp dot product to handle potential floating-point errors outside [-1, 1]
|
||||
dot = clamp(dot, -1.0, 1.0)
|
||||
# Calculate the actual angle between the vectors in radians
|
||||
var angle_rad = acos(dot)
|
||||
|
||||
# Check if the calculated angle exceeds the maximum allowed deviation
|
||||
if angle_rad > max_allowed_angle_rad:
|
||||
# print("Grip ", grip.get_parent().name, " outside cone. Angle: ", rad_to_deg(angle_rad), " > ", rad_to_deg(max_allowed_angle_rad))
|
||||
continue # Skip this grip if it's outside the cone
|
||||
|
||||
# If it passes all filters and is closer than the previous best:
|
||||
min_dist_sq = dist_sq
|
||||
best_grip = grip
|
||||
|
||||
# if is_instance_valid(best_grip):
|
||||
# print("Best grip found: ", best_grip.get_parent().name, " at distance squared: ", min_dist_sq)
|
||||
|
||||
return best_grip
|
||||
|
||||
# --- Reaching Helpers ---
|
||||
func _get_hold_distance() -> float:
|
||||
# Use the pawn.grip_detector.position.length() method if you prefer that:
|
||||
if is_instance_valid(pawn) and is_instance_valid(pawn.grip_detector):
|
||||
return pawn.grip_detector.position.length()
|
||||
else:
|
||||
return 0.5 # Fallback distance if detector isn't set up right
|
||||
|
||||
func _release_current_grip(move_input: Vector2 = Vector2.ZERO):
|
||||
if is_instance_valid(current_grip):
|
||||
current_grip.release(pawn)
|
||||
current_grip = null
|
||||
|
||||
_manual_roll_timer.stop()
|
||||
|
||||
# If we were climbing and are still holding a climb input, start seeking.
|
||||
if move_input != Vector2.ZERO:
|
||||
movement_state = MovementState.SEEKING_CLIMB
|
||||
_seeking_climb_input = move_input # Store the input that started the seek
|
||||
# print("ZeroGMovementComponent: Released grip, now SEEKING_CLIMB.")
|
||||
else:
|
||||
movement_state = MovementState.IDLE
|
||||
# print("ZeroGMovementComponent: Released grip, now IDLE.")
|
||||
|
||||
|
||||
func _cancel_reach():
|
||||
# TODO: Logic to stop IK/animation if reach is cancelled mid-way
|
||||
_release_current_grip(Vector2.ZERO) # Ensure grip reference is cleared
|
||||
print("ZeroGMovementComponent: Reach cancelled.")
|
||||
|
||||
# --- Climbing Helpers ---
|
||||
func _stop_climb(release_grip: bool):
|
||||
# print("ZeroGMoveController: Stopping Climb. Release Grip: ", release_grip)
|
||||
# TODO: Implement using forces
|
||||
# pawn.velocity = pawn.velocity.lerp(Vector3.ZERO, 0.5) # Apply some braking
|
||||
next_grip_target = null
|
||||
if release_grip:
|
||||
_release_current_grip() # Transitions to IDLE
|
||||
else:
|
||||
movement_state = MovementState.GRIPPING # Go back to stationary gripping
|
||||
|
||||
func _apply_orientation_torque(physics_state: PhysicsDirectBodyState3D, target_basis: Basis):
|
||||
var torque = MotionUtils.calculate_pd_rotation_torque(
|
||||
target_basis,
|
||||
physics_state.transform.basis,
|
||||
physics_state.angular_velocity, # Use angular_velocity (from RigidBody3D)
|
||||
gripping_orient_speed, # Kp
|
||||
gripping_angular_damping # Kd
|
||||
)
|
||||
|
||||
physics_state.apply_torque(torque)
|
||||
|
||||
# --- Launch helpers ---
|
||||
func _start_charge(move_input: Vector2):
|
||||
if not is_instance_valid(current_grip): return # Safety check
|
||||
movement_state = MovementState.CHARGING_LAUNCH
|
||||
launch_charge = 0.0
|
||||
|
||||
# Calculate launch direction based on input and push-off normal
|
||||
# The direction is based on the pawn's current orientation, not the camera or grip.
|
||||
# This makes it feel like you're pushing off in a direction relative to your body.
|
||||
var pawn_up = pawn.global_basis.y
|
||||
var pawn_right = pawn.global_basis.x
|
||||
launch_direction = (pawn_up * move_input.y + pawn_right * move_input.x).normalized()
|
||||
|
||||
print("ZeroGMovementComponent: Charging Launch")
|
||||
|
||||
|
||||
func _process_launch_charge(physics_state: PhysicsDirectBodyState3D, move_input: Vector2, reach_input: PlayerController3D.KeyInput):
|
||||
if not (reach_input.pressed or reach_input.held):
|
||||
_execute_launch(physics_state, move_input)
|
||||
|
||||
# hold on to current grip
|
||||
physics_state.apply_central_force(_get_hold_force(physics_state))
|
||||
|
||||
launch_charge = min(launch_charge + launch_charge_rate * physics_state.step, max_launch_speed)
|
||||
|
||||
func _execute_launch(physics_state: PhysicsDirectBodyState3D, move_input: Vector2):
|
||||
if not is_instance_valid(current_grip): return # Safety check
|
||||
|
||||
_release_current_grip(move_input) # Release AFTER calculating direction
|
||||
physics_state.apply_impulse(launch_direction * launch_charge)
|
||||
launch_charge = 0.0
|
||||
|
||||
# Instead of going to IDLE, go to SEEKING_CLIMB to find the next grip.
|
||||
# The move_input that started the launch is what we'll use for the seek direction.
|
||||
# _seeking_climb_input = (pawn.global_basis.y.dot(launch_direction) * Vector2.UP) + (pawn.global_basis.x.dot(launch_direction) * Vector2.RIGHT)
|
||||
# movement_state = MovementState.SEEKING_CLIMB
|
||||
print("ZeroGMovementComponent: Launched with speed ", physics_state.linear_velocity.length(), " and now SEEKING_CLIMB")
|
||||
|
||||
|
||||
# --- Force Calculation Helpers ---
|
||||
func _get_hold_force(state) -> Vector3:
|
||||
if not is_instance_valid(pawn) or not is_instance_valid(current_grip):
|
||||
return Vector3.ZERO
|
||||
|
||||
var grip_base_transform = current_grip.global_transform
|
||||
var target_direction = grip_base_transform.basis.z.normalized()
|
||||
var hold_distance = _get_hold_distance()
|
||||
var target_position = grip_base_transform.origin + target_direction * hold_distance
|
||||
|
||||
# Calculate the force needed to stay at that position
|
||||
var force_hold = MotionUtils.calculate_pd_position_force(
|
||||
target_position,
|
||||
state.transform.origin,
|
||||
state.linear_velocity,
|
||||
gripping_linear_damping, # Kp
|
||||
gripping_linear_kd # Kd
|
||||
)
|
||||
return force_hold
|
||||
|
||||
# --- Manual Roll Reset ---
|
||||
func _on_manual_roll_timeout(physics_state: PhysicsDirectBodyState3D):
|
||||
# Timer fired. This means the user hasn't touched roll for [delay] seconds.
|
||||
# We smoothly reset the _target_basis back to the closest grip orientation.
|
||||
if is_instance_valid(current_grip):
|
||||
_target_basis = _choose_grip_orientation(physics_state, current_grip.global_basis)
|
||||
|
||||
|
||||
# --- Signal Handlers ---
|
||||
func on_grip_area_entered(area: Area3D):
|
||||
if area is GripArea3D: # Check if the entered area is actually a GripArea3D node
|
||||
var grip = area as GripArea3D
|
||||
if not grip in nearby_grips:
|
||||
nearby_grips.append(grip)
|
||||
# print("Detected nearby grip: ", grip.get_parent().name if grip.get_parent() else "UNKNOWN") # Print parent name for context
|
||||
|
||||
func on_grip_area_exited(area: Area3D):
|
||||
if area is GripArea3D:
|
||||
var grip = area as GripArea3D
|
||||
if grip in nearby_grips:
|
||||
nearby_grips.erase(grip)
|
||||
# print("Grip out of range: ", grip.get_parent().name if grip.get_parent() else "UNKNOWN")
|
||||
1
scenes/character/zero_g_movement_component.gd.uid
Normal file
1
scenes/character/zero_g_movement_component.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://y3vo40i16ek3
|
||||
@ -1,156 +0,0 @@
|
||||
extends CharacterBody2D
|
||||
class_name PilotBall
|
||||
|
||||
# --- Movement Constants (Friction Simulation) ---
|
||||
# When in open space (no module overlap), movement is zeroed out quickly.
|
||||
const EXTERIOR_DRAG_FACTOR: float = 0.05
|
||||
|
||||
# When pushing off hullplates (low friction, slow acceleration)
|
||||
const INTERIOR_SLUGGISH_SPEED: float = 100.0
|
||||
const INTERIOR_SLUGGISH_ACCEL: float = 0.15 # Low acceleration, simulating mass and small push
|
||||
|
||||
# When gripping a ladder (high friction, direct control)
|
||||
const LADDER_SPEED: float = 350.0
|
||||
const LADDER_ACCEL: float = 0.9 # High acceleration, simulating direct grip
|
||||
|
||||
# --- State Variables ---
|
||||
enum MovementState {
|
||||
NO_CONTROL,
|
||||
ZERO_G_INTERIOR,
|
||||
LADDER_GRIP
|
||||
}
|
||||
|
||||
var current_state: MovementState = MovementState.NO_CONTROL
|
||||
var ladder_area: Area2D = null # Area of the ladder currently overlapped
|
||||
var is_grabbing_ladder: bool = false # True if 'Space' is held while on ladder
|
||||
|
||||
# --- Overlap Detection (Assuming you use Area2D for detection) ---
|
||||
var overlapping_modules: int = 0
|
||||
|
||||
# --- Ladder Constants ---
|
||||
const LAUNCH_VELOCITY: float = 300.0
|
||||
|
||||
# --- New: Physics Initialization (Assuming CharacterBody2D is parented to the scene root or Ship) ---
|
||||
# NOTE: CharacterBody2D cannot inherit OrbitalBody2D, so we manage its velocity manually.
|
||||
|
||||
func _ready():
|
||||
# Set up overlap signals if they aren't already connected in the scene file
|
||||
# You must have an Area2D child on PilotBall to detect overlaps.
|
||||
|
||||
# Placeholder: Assuming the PilotBall has an Area2D named 'OverlapChecker'
|
||||
var overlap_checker = find_child("OverlapChecker")
|
||||
if overlap_checker:
|
||||
overlap_checker.body_entered.connect(on_body_entered)
|
||||
overlap_checker.body_exited.connect(on_body_exited)
|
||||
|
||||
# Ensure this action is set in project settings: "interact" mapped to Space.
|
||||
if !InputMap.has_action("interact"):
|
||||
push_error("Missing 'interact' input action for ladder logic.")
|
||||
|
||||
|
||||
func on_body_entered(body: Node2D):
|
||||
# Detect Modules (which all inherit OrbitalBody2D via StructuralPiece)
|
||||
if body is StructuralPiece:
|
||||
overlapping_modules += 1
|
||||
|
||||
# Detect Ladders
|
||||
if body is Ladder:
|
||||
ladder_area = body.find_child("ClimbArea") # Assuming the Ladder has a specific Area2D for climbing
|
||||
|
||||
func on_body_exited(body: Node2D):
|
||||
if body is StructuralPiece:
|
||||
overlapping_modules -= 1
|
||||
|
||||
if body is Ladder:
|
||||
if body.find_child("ClimbArea") == ladder_area:
|
||||
ladder_area = null
|
||||
is_grabbing_ladder = false # Force detach if the ladder moves away
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
# 1. Update State based on environment
|
||||
_update_movement_state()
|
||||
|
||||
var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
|
||||
match current_state:
|
||||
MovementState.NO_CONTROL:
|
||||
# Apply heavy drag to simulate floating in space without external push
|
||||
_apply_drag(EXTERIOR_DRAG_FACTOR)
|
||||
|
||||
MovementState.ZERO_G_INTERIOR:
|
||||
# Sluggish movement: player is pushing off nearby walls/hullplates
|
||||
_sluggish_movement(input_dir, delta)
|
||||
|
||||
MovementState.LADDER_GRIP:
|
||||
# Snappy movement: direct control and high acceleration
|
||||
_ladder_movement(input_dir, delta)
|
||||
|
||||
# 2. Handle Ladder Grab/Launch Input
|
||||
_handle_ladder_input(input_dir)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
|
||||
# --- State Machine Update ---
|
||||
|
||||
func _update_movement_state():
|
||||
# Priority 1: Ladder Grip
|
||||
if ladder_area and Input.is_action_pressed("interact"):
|
||||
is_grabbing_ladder = true
|
||||
current_state = MovementState.LADDER_GRIP
|
||||
return
|
||||
|
||||
# Priority 2: Interior Zero-G (must overlap a module/piece AND not be grabbing)
|
||||
if overlapping_modules > 0:
|
||||
if is_grabbing_ladder:
|
||||
# If we were grabbing a ladder but released 'interact', we transition to zero-G interior
|
||||
is_grabbing_ladder = false
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
return
|
||||
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
return
|
||||
|
||||
# Priority 3: No Control (floating free)
|
||||
is_grabbing_ladder = false
|
||||
current_state = MovementState.NO_CONTROL
|
||||
|
||||
|
||||
# --- Movement Implementations ---
|
||||
|
||||
func _apply_drag(factor: float):
|
||||
# Gently slow down the velocity (simulating environmental drag)
|
||||
velocity = velocity.lerp(Vector2.ZERO, factor)
|
||||
|
||||
func _sluggish_movement(input_dir: Vector2, delta: float):
|
||||
# Simulates pushing off the wall: slow acceleration, but minimal drag
|
||||
var target_velocity = input_dir * INTERIOR_SLUGGISH_SPEED
|
||||
velocity = velocity.lerp(target_velocity, INTERIOR_SLUGGISH_ACCEL)
|
||||
|
||||
func _ladder_movement(input_dir: Vector2, delta: float):
|
||||
# Simulates direct grip: fast acceleration, perfect control
|
||||
var target_velocity = input_dir * LADDER_SPEED
|
||||
velocity = velocity.lerp(target_velocity, LADDER_ACCEL)
|
||||
|
||||
|
||||
# --- Ladder Input and Launch Logic ---
|
||||
|
||||
func _handle_ladder_input(input_dir: Vector2):
|
||||
# If currently grabbing, SPACE press is handled in _update_movement_state
|
||||
|
||||
if current_state == MovementState.LADDER_GRIP:
|
||||
if Input.is_action_just_released("interact"):
|
||||
# Launch the player away from the ladder
|
||||
|
||||
# Determine launch direction: opposite of input, or default forward
|
||||
var launch_direction = -input_dir.normalized()
|
||||
if launch_direction == Vector2.ZERO:
|
||||
# Default launch: use the character's forward direction (e.g., rotation 0)
|
||||
launch_direction = Vector2.UP.rotated(rotation)
|
||||
|
||||
velocity = launch_direction * LAUNCH_VELOCITY
|
||||
|
||||
# Immediately switch to zero-G interior state
|
||||
is_grabbing_ladder = false
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
@ -1 +0,0 @@
|
||||
uid://dxngvoommn5f1
|
||||
@ -1,14 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://chgycmkkaf7jv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dxngvoommn5f1" path="res://scenes/characters/pilot_ball.gd" id="1_rhbna"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_6jclb"]
|
||||
|
||||
[node name="PilotBall" type="CharacterBody2D"]
|
||||
script = ExtResource("1_rhbna")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("CircleShape2D_6jclb")
|
||||
debug_color = Color(0.61528, 0.358023, 1, 1)
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
@ -1,82 +0,0 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://c77wxeb7gpplw"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_mtskc"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_aovrk"]
|
||||
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="4_dwgsg"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
position = Vector2(-50, 50)
|
||||
script = ExtResource("1_mtskc")
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
|
||||
[node name="@StaticBody2D@31031" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(0, 100)
|
||||
|
||||
[node name="@StaticBody2D@31033" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(100, 100)
|
||||
|
||||
[node name="@StaticBody2D@31035" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(100, 0)
|
||||
|
||||
[node name="@StaticBody2D@31037" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(100, -100)
|
||||
|
||||
[node name="@StaticBody2D@31039" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(100, -200)
|
||||
|
||||
[node name="@StaticBody2D@31041" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(0, -200)
|
||||
|
||||
[node name="@StaticBody2D@31043" parent="StructuralContainer" instance=ExtResource("2_aovrk")]
|
||||
position = Vector2(0, -100)
|
||||
|
||||
[node name="Bulkhead" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(-50, 100)
|
||||
|
||||
[node name="@StaticBody2D@31046" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(-50, 0)
|
||||
|
||||
[node name="@StaticBody2D@31048" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(-50, -100)
|
||||
|
||||
[node name="@StaticBody2D@31050" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(-50, -200)
|
||||
|
||||
[node name="@StaticBody2D@31052" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(150, -200)
|
||||
|
||||
[node name="@StaticBody2D@31054" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(150, -100)
|
||||
|
||||
[node name="@StaticBody2D@31056" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(150, 0)
|
||||
|
||||
[node name="@StaticBody2D@31058" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(150, 100)
|
||||
|
||||
[node name="@StaticBody2D@31060" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(0, 150)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="@StaticBody2D@31062" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(100, 150)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="@StaticBody2D@31064" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(0, -250)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="@StaticBody2D@31066" parent="StructuralContainer" instance=ExtResource("4_dwgsg")]
|
||||
position = Vector2(100, -250)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
position = Vector2(50, -50)
|
||||
26
scenes/orrey_view/debug_marker.gd
Normal file
26
scenes/orrey_view/debug_marker.gd
Normal file
@ -0,0 +1,26 @@
|
||||
# res://scenes/orrey_view/debug_marker.gd
|
||||
class_name DebugMarker
|
||||
extends Control
|
||||
|
||||
signal marker_selected(body: Node2D)
|
||||
|
||||
@onready var icon: ColorRect = $Icon
|
||||
@onready var label: Label = $Label
|
||||
|
||||
var body_reference: Node2D
|
||||
|
||||
func initialize(body: Node2D):
|
||||
body_reference = body
|
||||
label.text = body.name
|
||||
|
||||
# Color-code the marker for easy identification
|
||||
if body is Barycenter:
|
||||
icon.color = Color.YELLOW
|
||||
elif body.get_parent() is Barycenter and body.get_parent().name == "StarBarycenter":
|
||||
icon.color = Color.ORANGE
|
||||
else:
|
||||
icon.color = Color.CYAN
|
||||
|
||||
func _gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
emit_signal("marker_selected", body_reference)
|
||||
1
scenes/orrey_view/debug_marker.gd.uid
Normal file
1
scenes/orrey_view/debug_marker.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b5tcvtjh2050f
|
||||
18
scenes/orrey_view/debug_marker.tscn
Normal file
18
scenes/orrey_view/debug_marker.tscn
Normal file
@ -0,0 +1,18 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b8fmmp4axba1j"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b5tcvtjh2050f" path="res://scenes/orrey_view/debug_marker.gd" id="1_2uxs6"]
|
||||
|
||||
[node name="DebugMarker" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
script = ExtResource("1_2uxs6")
|
||||
|
||||
[node name="Icon" type="ColorRect" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 23.0
|
||||
74
scenes/orrey_view/orrey_view.gd
Normal file
74
scenes/orrey_view/orrey_view.gd
Normal file
@ -0,0 +1,74 @@
|
||||
# res://scenes/debug/orrery_view.gd
|
||||
extends Node2D
|
||||
|
||||
@export_group("Generation Assets")
|
||||
@export var star_scene: PackedScene
|
||||
@export var planet_scene: PackedScene
|
||||
@export var moon_scene: PackedScene
|
||||
@export var barycenter_scene: PackedScene
|
||||
@export var spaceship_scene: PackedScene # Add this even if unused for now
|
||||
|
||||
@export_group("Orrery Settings")
|
||||
@export var debug_marker_scene: PackedScene # Link your new DebugMarker.tscn here
|
||||
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
@onready var system_root: Node2D = $GeneratedSystem
|
||||
@onready var info_label: RichTextLabel = $UI/InfoPanel/MarginContainer/InfoLabel
|
||||
|
||||
var markers: Dictionary = {} # Maps a body to its marker
|
||||
|
||||
func _ready():
|
||||
# 1. Create and configure the generator tool.
|
||||
var generator = StarSystemGenerator.new()
|
||||
|
||||
# Create a temporary StarSystem-like object to pass to the generator
|
||||
var temp_system_node = Node2D.new()
|
||||
temp_system_node.star_scene = star_scene
|
||||
temp_system_node.planet_scene = planet_scene
|
||||
temp_system_node.moon_scene = moon_scene
|
||||
temp_system_node.barycenter_scene = barycenter_scene
|
||||
temp_system_node.spaceship_scene = spaceship_scene
|
||||
|
||||
# 2. Generate the system under our dedicated root node.
|
||||
var generation_result = generator.generate(temp_system_node)
|
||||
system_root.add_child(temp_system_node) # Keep the generated hierarchy
|
||||
|
||||
# 3. Create a debug marker for every generated body.
|
||||
var all_bodies = generation_result.all_bodies + generation_result.all_barycenters
|
||||
for body in all_bodies:
|
||||
if is_instance_valid(body):
|
||||
var marker = debug_marker_scene.instantiate()
|
||||
add_child(marker) # Add markers to the OrreryView, not the generated system
|
||||
marker.initialize(body)
|
||||
marker.marker_selected.connect(_on_marker_selected)
|
||||
markers[body] = marker
|
||||
|
||||
func _process(_delta):
|
||||
# Keep the markers synced with their body's global position.
|
||||
for body in markers:
|
||||
var marker = markers[body]
|
||||
marker.global_position = body.global_position
|
||||
|
||||
func _unhandled_input(event: InputEvent):
|
||||
# Simple camera controls
|
||||
if event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_MIDDLE:
|
||||
camera.offset -= event.relative / camera.zoom.x
|
||||
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
camera.zoom *= 1.2
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
camera.zoom /= 1.2
|
||||
|
||||
func _on_marker_selected(body):
|
||||
# Update the info panel with the selected body's data.
|
||||
var text = "[b]%s[/b]\n" % body.name
|
||||
|
||||
if body is OrbitalBody3D:
|
||||
text += "Mass: %.2f\n" % body.mass
|
||||
text += "Velocity: (%.2f, %.2f)\n" % [body.linear_velocity.x, body.linear_velocity.y]
|
||||
text += "Position: (%.0f, %.0f)\n" % [body.global_position.x, body.global_position.y]
|
||||
if body is Barycenter and is_instance_valid(body.get_parent()):
|
||||
text += "Parent: %s" % body.get_parent().name
|
||||
|
||||
info_label.text = text
|
||||
1
scenes/orrey_view/orrey_view.gd.uid
Normal file
1
scenes/orrey_view/orrey_view.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cwurbqorbwtil
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user