Merge branch 'tech-test/3d-system-refactor'
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "godot_engine"]
|
||||
path = godot_engine
|
||||
url = https://codeberg.org/seedlingattempt/godot.git
|
||||
@ -2,7 +2,9 @@
|
||||
|
||||
## 1. Game Vision & Concept
|
||||
|
||||
Project Millimeters of Aluminum 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.
|
||||
@ -78,19 +134,17 @@ To move beyond pre-defined ship parts, the game will feature an in-game system f
|
||||
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.
|
||||
@ -126,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.
|
||||
|
||||
96
README.md
Normal file
96
README.md
Normal file
@ -0,0 +1,96 @@
|
||||
# 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.
|
||||
@ -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("..")
|
||||
@ -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,145 +0,0 @@
|
||||
[gd_scene load_steps=20 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"]
|
||||
[ext_resource type="PackedScene" uid="uid://2n42nstcj1n0" path="res://scenes/ship/components/hardware/system_station.tscn" id="5_nqe0s"]
|
||||
[ext_resource type="Script" uid="uid://diu2tgusi3vmt" path="res://scenes/ship/computer/shards/sensor_databank.gd" id="9_ixntg"]
|
||||
[ext_resource type="PackedScene" uid="uid://dt1t2n7dewucw" path="res://scenes/ship/computer/UI/button_panel.tscn" id="10_px2ne"]
|
||||
[ext_resource type="Script" uid="uid://cfbyqvnvf3hna" path="res://scenes/ship/computer/shards/helm_logic_databank.gd" id="10_wkxbw"]
|
||||
[ext_resource type="PackedScene" uid="uid://cdbqjkgsj02or" path="res://scenes/ship/computer/UI/readout_screen_panel.tscn" id="11_erhv3"]
|
||||
[ext_resource type="Script" uid="uid://t12etsdx2h38" path="res://scenes/ship/computer/shards/nav_selection_databank.gd" id="11_xwy4s"]
|
||||
[ext_resource type="Script" uid="uid://ceqdi6jobefnc" path="res://scenes/ship/computer/shards/helm_autopilot_databank.gd" id="12_4epkn"]
|
||||
[ext_resource type="PackedScene" uid="uid://rd1c22nsru8y" path="res://scenes/ship/computer/UI/sensor_panel.tscn" id="12_q1rtr"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0bb77rmyatr0" path="res://scenes/ship/components/hardware/thruster.tscn" id="12_vmx8o"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="13_83bu1"]
|
||||
[ext_resource type="PackedScene" uid="uid://pq55j75t3fda" path="res://scenes/ship/computer/UI/throttle_lever_panel.tscn" id="13_rsa1x"]
|
||||
[ext_resource type="Script" uid="uid://ctgl5kxyagw0f" path="res://scenes/ship/computer/shards/helm_ship_status.gd" id="13_wkxbw"]
|
||||
[ext_resource type="Script" uid="uid://ghluwjd5c5ul" path="res://scenes/ship/computer/shards/nav_brachistochrone_planner.gd" id="14_xwy4s"]
|
||||
[ext_resource type="Script" uid="uid://bghu5lhcbcfmh" path="res://scenes/ship/computer/shards/nav_hohman_planner.gd" id="15_fll2s"]
|
||||
[ext_resource type="Script" uid="uid://dsbn7ushwqrko" path="res://scenes/ship/computer/shards/nav_intercept_solver.gd" id="16_vufgi"]
|
||||
[ext_resource type="Script" uid="uid://0f6v6iu3o5qo" path="res://scenes/ship/computer/shards/nav_projection_shard.gd" id="17_34v0b"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
physics_interpolation_mode = 2
|
||||
script = ExtResource("1_nqe0s")
|
||||
physics_mode = 1
|
||||
mass = 1.0
|
||||
inertia = 0.0
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
|
||||
physics_interpolation_mode = 2
|
||||
is_pressurized = false
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30634" parent="." instance=ExtResource("2_foqop")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(0, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30635" parent="." instance=ExtResource("2_foqop")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(0, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(-50, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30636" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(-50, 0)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30637" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(-50, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30638" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(50, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30639" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(0, -150)
|
||||
rotation = 1.5708
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30640" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(0, 150)
|
||||
rotation = 4.71239
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30641" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(50, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30642" parent="." instance=ExtResource("4_dmrms")]
|
||||
physics_interpolation_mode = 2
|
||||
position = Vector2(50, 0)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
base_mass = 0.0
|
||||
|
||||
[node name="Station" parent="." instance=ExtResource("5_nqe0s")]
|
||||
position = Vector2(0, -10)
|
||||
panel_scenes = Array[PackedScene]([ExtResource("11_erhv3"), ExtResource("11_erhv3"), ExtResource("12_q1rtr"), ExtResource("10_px2ne"), ExtResource("13_rsa1x")])
|
||||
databank_installations = Array[Script]([ExtResource("10_wkxbw"), ExtResource("12_4epkn"), ExtResource("13_wkxbw"), ExtResource("9_ixntg"), ExtResource("11_xwy4s"), ExtResource("14_xwy4s"), ExtResource("15_fll2s"), ExtResource("16_vufgi"), ExtResource("17_34v0b")])
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Thruster" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(-95, -130)
|
||||
rotation = 1.5708
|
||||
main_thruster = false
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Thruster2" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(-95, 130)
|
||||
rotation = 1.5708
|
||||
main_thruster = false
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Thruster3" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(95, 130)
|
||||
rotation = -1.5708
|
||||
main_thruster = false
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Thruster4" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(95, -130)
|
||||
rotation = -1.5708
|
||||
main_thruster = false
|
||||
physics_mode = 2
|
||||
|
||||
[node name="MainEngine" parent="." instance=ExtResource("12_vmx8o")]
|
||||
position = Vector2(0, 195)
|
||||
max_thrust = 10.0
|
||||
physics_mode = 2
|
||||
|
||||
[node name="Spawner" parent="." instance=ExtResource("13_83bu1")]
|
||||
position = Vector2(0, 27)
|
||||
physics_mode = 2
|
||||
@ -1,19 +0,0 @@
|
||||
class_name Asteroid
|
||||
extends OrbitalBody2D
|
||||
|
||||
# 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,8 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bawsujtlpmh5r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://0isnsk356que" path="res://scripts/orbital_body_2d.gd" id="1_4q05e"]
|
||||
|
||||
[node name="Asteroid" type="Node2D"]
|
||||
script = ExtResource("1_4q05e")
|
||||
base_mass = 50000.0
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
@ -1,17 +0,0 @@
|
||||
class_name Moon
|
||||
extends OrbitalBody2D
|
||||
|
||||
# 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.
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/moon_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://b1xsx7er22nxn
|
||||
@ -1,8 +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="Node2D"]
|
||||
script = ExtResource("1_530pw")
|
||||
base_mass = 1e+06
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
@ -1,15 +0,0 @@
|
||||
class_name Planet
|
||||
extends OrbitalBody2D
|
||||
|
||||
# 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:
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/planet_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://5f6ipgu65urb
|
||||
@ -1,10 +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="Node2D"]
|
||||
script = ExtResource("1_cktii")
|
||||
base_mass = 2.5e+07
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
@ -1,14 +0,0 @@
|
||||
class_name Star
|
||||
extends OrbitalBody2D
|
||||
|
||||
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.
|
||||
|
||||
# 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,15 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://5uqp4amjj7ww"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://um2sfghmii42" path="res://scenes/celestial_bodies/star.gd" id="1_mcqwg"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_508pf"]
|
||||
radius = 200.0
|
||||
|
||||
[node name="Star" type="Node2D"]
|
||||
script = ExtResource("1_mcqwg")
|
||||
base_mass = 5e+08
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("CircleShape2D_508pf")
|
||||
debug_color = Color(0.863865, 0.471779, 0.162305, 1)
|
||||
@ -1,17 +0,0 @@
|
||||
class_name Station
|
||||
extends OrbitalBody2D
|
||||
|
||||
# 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.
|
||||
|
||||
# You can set a default texture here.
|
||||
# texture = preload("res://assets/station_texture.png")
|
||||
|
||||
super._ready()
|
||||
@ -1 +0,0 @@
|
||||
uid://ulw61oxppwdu
|
||||
@ -1,8 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dm3s33o4xhqfv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ulw61oxppwdu" path="res://scenes/celestial_bodies/station.gd" id="1_rod8h"]
|
||||
|
||||
[node name="Station" type="Node2D"]
|
||||
script = ExtResource("1_rod8h")
|
||||
base_mass = 5000.0
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
@ -1,205 +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 = 5 # Low acceleration, simulating mass and small push
|
||||
|
||||
# When gripping a ladder (high friction, direct control)
|
||||
const LADDER_SPEED: float = 100.0
|
||||
const LADDER_ACCEL: float = 20 # High acceleration, simulating direct grip
|
||||
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
@onready var overlap_area: Area2D = $OverlapDetector
|
||||
@onready var ui_container: Control = $CanvasLayer/UIContainer
|
||||
|
||||
var nearby_station: SystemStation = null
|
||||
var current_station: SystemStation = null
|
||||
|
||||
# --- State Variables ---
|
||||
enum MovementState {
|
||||
NO_CONTROL,
|
||||
ZERO_G_INTERIOR,
|
||||
LADDER_GRIP,
|
||||
IN_STATION
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
var _movement_input: Vector2 = Vector2.ZERO
|
||||
var _interact_just_pressed: bool = false
|
||||
var _interact_held: bool = false
|
||||
|
||||
# --- PUBLIC INPUT METHODS (Called by the PlayerController) ---
|
||||
func set_movement_input(input_dir: Vector2):
|
||||
_movement_input = input_dir
|
||||
|
||||
func set_interaction_input(just_pressed: bool, is_held: bool):
|
||||
_interact_just_pressed = just_pressed
|
||||
_interact_held = is_held
|
||||
|
||||
# --- 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.
|
||||
overlap_area.body_entered.connect(on_body_entered)
|
||||
overlap_area.body_exited.connect(on_body_exited)
|
||||
overlap_area.area_entered.connect(_on_station_area_entered)
|
||||
overlap_area.area_exited.connect(_on_station_area_exited)
|
||||
|
||||
camera.make_current()
|
||||
|
||||
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
|
||||
|
||||
# --- NEW: Functions to be called by the Station ---
|
||||
func enter_station_state():
|
||||
current_state = MovementState.IN_STATION
|
||||
velocity = Vector2.ZERO # FIX: Stop all movement when entering a station
|
||||
|
||||
func exit_station_state():
|
||||
# When leaving, transition to a sensible default state.
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
|
||||
func _physics_process(delta):
|
||||
# This script now runs on the server and its state is synced to clients.
|
||||
# It no longer checks for local input authority.
|
||||
if current_state == MovementState.IN_STATION:
|
||||
move_and_slide()
|
||||
return
|
||||
|
||||
|
||||
_update_movement_state() # This function now uses the new variables
|
||||
process_interaction() # Process any interaction presses
|
||||
|
||||
# Reset input flags for the next frame
|
||||
_interact_just_pressed = false
|
||||
_interact_held = false
|
||||
# The 'input_dir' now comes from our variable, not the Input singleton.
|
||||
var input_dir = _movement_input
|
||||
|
||||
match current_state:
|
||||
MovementState.ZERO_G_INTERIOR:
|
||||
_sluggish_movement(input_dir, delta)
|
||||
MovementState.LADDER_GRIP:
|
||||
_ladder_movement(input_dir, delta)
|
||||
|
||||
# Reset input for the next frame
|
||||
_movement_input = Vector2.ZERO
|
||||
|
||||
move_and_slide()
|
||||
|
||||
# This function is called every physics frame by _physics_process().
|
||||
func process_interaction():
|
||||
# If the interact button was not pressed this frame, do nothing.
|
||||
if not _interact_just_pressed:
|
||||
return
|
||||
|
||||
# Priority 1: Disengage from a station if we are in one.
|
||||
if current_station:
|
||||
current_station.disengage(self)
|
||||
current_station = null
|
||||
return
|
||||
# Priority 2: Occupy a nearby station if we are not in one.
|
||||
elif is_instance_valid(nearby_station):
|
||||
current_station = nearby_station
|
||||
current_station.occupy(self)
|
||||
return
|
||||
|
||||
# Priority 3: Handle ladder launch logic.
|
||||
# This part of the old logic was in _handle_interaction_input,
|
||||
# but it's cleaner to check for the release of the button here.
|
||||
if current_state == MovementState.LADDER_GRIP and not _interact_held:
|
||||
# Launch the player away from the ladder when the interact button is released.
|
||||
var launch_direction = - _movement_input.normalized()
|
||||
if launch_direction == Vector2.ZERO:
|
||||
# Default launch: use the character's forward direction
|
||||
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
|
||||
|
||||
# --- State Machine Update ---
|
||||
|
||||
func _update_movement_state():
|
||||
# Priority 1: Ladder Grip
|
||||
# This now checks the variable instead of the Input singleton.
|
||||
if ladder_area and _interact_held:
|
||||
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 _sluggish_movement(input_dir: Vector2, delta: float):
|
||||
# Simulates pushing off the wall: slow acceleration, but minimal drag
|
||||
var target_velocity = input_dir * INTERIOR_SLUGGISH_ACCEL
|
||||
velocity = velocity + target_velocity * delta
|
||||
#velocity.lerp(velocity + interi, 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 * delta)
|
||||
|
||||
# --- New Functions for Station Interaction ---
|
||||
func _on_station_area_entered(area: Area2D):
|
||||
if area.get_parent() is SystemStation:
|
||||
nearby_station = area.get_parent()
|
||||
print("Near station: ", nearby_station.name)
|
||||
|
||||
func _on_station_area_exited(area: Area2D):
|
||||
if area.get_parent() == nearby_station:
|
||||
nearby_station = null
|
||||
|
||||
# Stations will call this to get the node where they should place their UIs.
|
||||
func get_ui_container() -> Control:
|
||||
return ui_container
|
||||
@ -1 +0,0 @@
|
||||
uid://dxngvoommn5f1
|
||||
@ -1,36 +0,0 @@
|
||||
[gd_scene load_steps=4 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"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_rhbna"]
|
||||
|
||||
[node name="PilotBall" type="CharacterBody2D"]
|
||||
collision_layer = 32
|
||||
collision_mask = 16
|
||||
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="."]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
zoom = Vector2(4, 4)
|
||||
|
||||
[node name="OverlapDetector" type="Area2D" parent="."]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="OverlapDetector"]
|
||||
shape = SubResource("CircleShape2D_rhbna")
|
||||
|
||||
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="UIContainer" type="Control" parent="CanvasLayer"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
@ -1,37 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name PlayerController
|
||||
|
||||
# TODO: Change this to custom pawn type
|
||||
var possessed_pawn: Node # The character this controller is currently driving
|
||||
|
||||
func _ready():
|
||||
# --- FIX: Manually enable input processing for this node ---
|
||||
set_process_input(true)
|
||||
|
||||
func _physics_process (delta):
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
|
||||
# 1. Gather all input states for this frame.
|
||||
var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
var is_interact_just_pressed = Input.is_action_just_pressed("interact")
|
||||
var is_interact_held = Input.is_action_pressed("interact")
|
||||
#print(is_interact_just_pressed)
|
||||
#print(input_dir)
|
||||
# 2. Send the collected input state to the server via RPC.
|
||||
server_process_input.rpc_id(1, input_dir, is_interact_just_pressed, is_interact_held)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_input(input_dir: Vector2, is_interact_just_pressed: bool, is_interact_held: bool):
|
||||
if is_instance_valid(possessed_pawn):
|
||||
possessed_pawn.set_movement_input(input_dir)
|
||||
# Pass both interact states to the pawn
|
||||
possessed_pawn.set_interaction_input(is_interact_just_pressed, is_interact_held)
|
||||
|
||||
func possess(pawn_to_control: Node):
|
||||
possessed_pawn = pawn_to_control
|
||||
reparent(pawn_to_control, false)
|
||||
self.owner = pawn_to_control
|
||||
print("PlayerController possessed: ", possessed_pawn.name)
|
||||
@ -1 +0,0 @@
|
||||
uid://dmhwqmbwk0t8k
|
||||
@ -1,6 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dnre6svquwdtb"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dmhwqmbwk0t8k" path="res://scenes/characters/player_controller.gd" id="1_b8jga"]
|
||||
|
||||
[node name="PlayerController" type="Node"]
|
||||
script = ExtResource("1_b8jga")
|
||||
@ -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)
|
||||
@ -1,13 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cm0rohkr6khd1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_b1h2b"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
script = ExtResource("1_b1h2b")
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
@ -1,6 +0,0 @@
|
||||
@tool
|
||||
class_name Bulkhead
|
||||
extends StructuralPiece
|
||||
|
||||
# This piece represents a wall or edge.
|
||||
# No additional logic is needed right now, we just need the class_name.
|
||||
@ -1 +0,0 @@
|
||||
uid://b4g288mje38nj
|
||||
@ -1,29 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://d3hitk62fice4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4g288mje38nj" path="res://scenes/ship/builder/pieces/bulkhead.gd" id="1_1wp2n"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
|
||||
size = Vector2(10, 100)
|
||||
|
||||
[node name="Bulkhead" type="StaticBody2D"]
|
||||
collision_layer = 16
|
||||
collision_mask = 60
|
||||
script = ExtResource("1_1wp2n")
|
||||
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_1wp2n")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -5.0
|
||||
offset_top = -50.0
|
||||
offset_right = 5.0
|
||||
offset_bottom = 50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.6, 0.6, 0.6, 1)
|
||||
@ -1,6 +0,0 @@
|
||||
@tool
|
||||
class_name Hullplate
|
||||
extends StructuralPiece
|
||||
|
||||
# This piece represents an interior surface.
|
||||
# No additional logic is needed right now, we just need the class_name.
|
||||
@ -1 +0,0 @@
|
||||
uid://crmwm623rh1ps
|
||||
@ -1,28 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bho8x10x4oab7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://crmwm623rh1ps" path="res://scenes/ship/builder/pieces/hullplate.gd" id="1_ecow4"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
|
||||
size = Vector2(100, 100)
|
||||
|
||||
[node name="Hullplate" type="StaticBody2D"]
|
||||
collision_mask = 0
|
||||
script = ExtResource("1_ecow4")
|
||||
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_1wp2n")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -50.0
|
||||
offset_top = -50.0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.4, 0.4, 0.4, 1)
|
||||
@ -1,20 +0,0 @@
|
||||
@tool
|
||||
class_name StructuralPiece
|
||||
extends OrbitalBody2D
|
||||
|
||||
# Does this piece block atmosphere? (e.g., a hull plate would, a girder would not).
|
||||
@export var is_pressurized: bool = true
|
||||
|
||||
# The health of this specific piece.
|
||||
@export var health: float = 100.0
|
||||
|
||||
# This setter is triggered by the editor.
|
||||
var is_preview: bool = false:
|
||||
set(value):
|
||||
is_preview = value
|
||||
if is_preview:
|
||||
# Make the piece translucent if it's a preview.
|
||||
modulate = Color(1, 1, 1, 0.5)
|
||||
else:
|
||||
# Make it opaque if it's a permanent piece.
|
||||
modulate = Color(1, 1, 1, 1)
|
||||
@ -1 +0,0 @@
|
||||
uid://b7f8x2qimvn37
|
||||
@ -1,16 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ds4eilbvihjy7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7f8x2qimvn37" path="res://scenes/ship/builder/pieces/structural_piece.gd" id="1_6jsoj"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_jsbwo"]
|
||||
|
||||
[node name="StructuralPiece" type="Node2D"]
|
||||
script = ExtResource("1_6jsoj")
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("CircleShape2D_jsbwo")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
@ -1,37 +0,0 @@
|
||||
extends Databank
|
||||
|
||||
class_name ShipStatusShard
|
||||
|
||||
## This shard emits a signal with the formatted ship status text.
|
||||
signal status_updated(text: String)
|
||||
|
||||
# Called by the Station when it's created.
|
||||
func initialize(ship_root: Module):
|
||||
self.root_module = ship_root
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
return []
|
||||
|
||||
## Describes the signals this shard can output.
|
||||
func get_output_sockets() -> Array[String]:
|
||||
return ["status_updated"]
|
||||
|
||||
func _physics_process(delta):
|
||||
if not is_instance_valid(root_module):
|
||||
return
|
||||
# 1. Gather all the data from the root module.
|
||||
var rotation_deg = rad_to_deg(root_module.rotation)
|
||||
var angular_vel_dps = rad_to_deg(root_module.angular_velocity)
|
||||
var linear_vel_mps = root_module.linear_velocity.length()
|
||||
|
||||
# 2. Build the string that will be displayed.
|
||||
var status_text = """
|
||||
[font_size=24]Ship Status[/font_size]
|
||||
[font_size=18]Rotation: %.1f deg[/font_size]
|
||||
[font_size=18]Ang. Vel.: %.2f deg/s[/font_size]
|
||||
[font_size=18]Velocity: %.2f m/s[/font_size]
|
||||
""" % [rotation_deg, angular_vel_dps, linear_vel_mps]
|
||||
|
||||
# 3. Emit the signal with the formatted text.
|
||||
status_updated.emit(status_text)
|
||||
@ -1,182 +0,0 @@
|
||||
extends Node2D
|
||||
class_name OrbitalBody2D
|
||||
|
||||
# Defines the physical behavior of this body.
|
||||
enum PhysicsMode {
|
||||
INDEPENDENT, # An independent body with its own physics simulation (planets, characters in EVA).
|
||||
COMPOSITE, # A body that aggregates mass and forces from ANCHORED children (ships, modules).
|
||||
ANCHORED # A component that is "bolted on" and defers physics to a COMPOSITE parent.
|
||||
}
|
||||
|
||||
@export var physics_mode: PhysicsMode = PhysicsMode.INDEPENDENT
|
||||
|
||||
var current_grid_authority: OrbitalBody2D = null
|
||||
|
||||
# Mass of this individual component
|
||||
@export var base_mass: float = 1.0
|
||||
@export var mass: float = 0.0 # Aggregated mass of this body and all its OrbitalBody2D children
|
||||
@export var linear_velocity: Vector2 = Vector2.ZERO
|
||||
@export var angular_velocity: float = 0.0
|
||||
|
||||
# Variables to accumulate forces applied during the current physics frame
|
||||
var accumulated_force: Vector2 = Vector2.ZERO
|
||||
var accumulated_torque: float = 0.0
|
||||
|
||||
# Placeholder for Moment of Inertia.
|
||||
@export var inertia: float = 1.0
|
||||
|
||||
func _ready():
|
||||
# Ensure mass update runs immediately before the first _physics_process.
|
||||
recalculate_physical_properties()
|
||||
|
||||
set_physics_process(not Engine.is_editor_hint())
|
||||
physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF
|
||||
|
||||
# --- PUBLIC FORCE APPLICATION METHODS ---
|
||||
# This method is called by a component (like Thruster) at its global position.
|
||||
func apply_force(force: Vector2, pos: Vector2 = self.global_position):
|
||||
# This is the force routing logic.
|
||||
match physics_mode:
|
||||
PhysicsMode.INDEPENDENT:
|
||||
_add_forces(force, pos)
|
||||
PhysicsMode.COMPOSITE:
|
||||
_add_forces(force, pos)
|
||||
## If we are the root, accumulate the force and calculate torque on the total body.
|
||||
#accumulated_force += force
|
||||
#
|
||||
## Calculate torque (2D cross product: T = r x F = r.x * F.y - r.y * F.x)
|
||||
## 'r' is the vector from the center of mass (global_position) to the point of force application (position).
|
||||
#var r = position - global_position
|
||||
#var torque = r.x * force.y - r.y * force.x
|
||||
#accumulated_torque += torque
|
||||
PhysicsMode.ANCHORED:
|
||||
# If we are not the root, we must route the force to the next OrbitalBody2D parent.
|
||||
var p = get_parent()
|
||||
while p:
|
||||
if p is OrbitalBody2D:
|
||||
# Recursively call the parent's apply_force method.
|
||||
# This sends the force (and its original global position) up the chain.
|
||||
p.apply_force(force, pos)
|
||||
return # Stop at the first OrbitalBody2D parent
|
||||
p = p.get_parent()
|
||||
|
||||
push_error("Anchored OrbitalBody2D has become dislodged and is now Composite.")
|
||||
physics_mode = PhysicsMode.COMPOSITE
|
||||
apply_force(force, position)
|
||||
|
||||
func _add_forces(force: Vector2, pos: Vector2 = Vector2.ZERO):
|
||||
# If we are the root, accumulate the force and calculate torque on the total body.
|
||||
accumulated_force += force
|
||||
|
||||
# Calculate torque (2D cross product: T = r x F = r.x * F.y - r.y * F.x)
|
||||
# 'r' is the vector from the center of mass (global_position) to the point of force application (position).
|
||||
var r = pos - global_position
|
||||
var torque = r.x * force.y - r.y * force.x
|
||||
accumulated_torque += torque
|
||||
|
||||
func _update_mass_and_inertia():
|
||||
mass = base_mass
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
child._update_mass_and_inertia() # Recurse into children
|
||||
mass += child.mass
|
||||
|
||||
print("Node: %s, Mass: %f" % [self, mass])
|
||||
|
||||
func _physics_process(delta):
|
||||
if not Engine.is_editor_hint():
|
||||
# Note: We're not integrating forces for anchored bodies
|
||||
# anchored bodies add forces to their parents and
|
||||
match physics_mode:
|
||||
PhysicsMode.INDEPENDENT:
|
||||
_integrate_forces(delta)
|
||||
PhysicsMode.COMPOSITE:
|
||||
_integrate_forces(delta)
|
||||
|
||||
func _integrate_forces(delta):
|
||||
# Safety Check for Division by Zero
|
||||
var sim_mass = mass
|
||||
if sim_mass <= 0.0:
|
||||
# If mass is zero, stop all physics to prevent NaN explosion.
|
||||
accumulated_force = Vector2.ZERO
|
||||
accumulated_torque = 0.0
|
||||
return
|
||||
|
||||
## 1. Calculate and accumulate gravitational force (F_g)
|
||||
#var total_gravity_force = OrbitalMechanics.calculate_n_body_gravity_forces(self)
|
||||
#
|
||||
## 2. Total all forces: F_total = F_g + F_accumulated_from_thrusters
|
||||
#var total_force = total_gravity_force +
|
||||
|
||||
# 3. Apply Linear Physics (F = ma)
|
||||
var linear_acceleration = accumulated_force / sim_mass # Division is now safe
|
||||
linear_velocity += linear_acceleration * delta
|
||||
global_position += linear_velocity * delta
|
||||
|
||||
# 4. Apply Rotational Physics (T = I * angular_acceleration)
|
||||
var angular_acceleration = accumulated_torque / inertia
|
||||
angular_velocity += angular_acceleration * delta
|
||||
rotation += angular_velocity * delta
|
||||
|
||||
# 5. Reset accumulated forces for the next frame
|
||||
accumulated_force = Vector2.ZERO
|
||||
accumulated_torque = 0.0
|
||||
|
||||
# This is the new, corrected function.
|
||||
func recalculate_physical_properties():
|
||||
# For non-composite bodies, the calculation is simple.
|
||||
if physics_mode != PhysicsMode.COMPOSITE:
|
||||
mass = base_mass
|
||||
# --- THE FIX ---
|
||||
# An independent body doesn't calculate inertia from parts.
|
||||
# We ensure it has a non-zero default value to prevent division by zero.
|
||||
if inertia <= 0.0:
|
||||
inertia = 1.0
|
||||
return
|
||||
|
||||
var all_parts: Array[OrbitalBody2D] = []
|
||||
_collect_anchored_parts(all_parts)
|
||||
|
||||
if all_parts.is_empty():
|
||||
mass = base_mass
|
||||
inertia = 1.0
|
||||
return
|
||||
|
||||
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
||||
var total_mass = 0.0
|
||||
var weighted_local_pos_sum = Vector2.ZERO
|
||||
for part in all_parts:
|
||||
total_mass += part.base_mass
|
||||
# We get the part's position *relative to the root module*
|
||||
var local_pos = part.global_position - self.global_position
|
||||
weighted_local_pos_sum += local_pos * part.base_mass
|
||||
|
||||
var local_center_of_mass = Vector2.ZERO
|
||||
if total_mass > 0:
|
||||
local_center_of_mass = weighted_local_pos_sum / total_mass
|
||||
|
||||
# --- Step 2: Calculate Total Moment of Inertia around the LOCAL CoM ---
|
||||
var total_inertia = 0.0
|
||||
for part in all_parts:
|
||||
# Get the part's position relative to the root module again
|
||||
var local_pos = part.global_position - self.global_position
|
||||
# The radius is the distance from the part's local position to the ship's local center of mass
|
||||
var r_squared = (local_pos - local_center_of_mass).length_squared()
|
||||
total_inertia += part.base_mass * r_squared
|
||||
|
||||
# --- Step 3: Assign the final values ---
|
||||
self.mass = total_mass
|
||||
# We apply a scaling factor here because our "units" are pixels.
|
||||
# This brings the final value into a range that feels good for gameplay.
|
||||
# You can tune this factor to make ships feel heavier or lighter.
|
||||
self.inertia = total_inertia * 0.01
|
||||
|
||||
#print("Physics Recalculated: Mass=%.2f kg, Inertia=%.2f" % [mass, inertia])
|
||||
|
||||
# A recursive helper function to get an array of all OrbitalBody2D children
|
||||
func _collect_anchored_parts(parts_array: Array):
|
||||
parts_array.append(self)
|
||||
for child in get_children():
|
||||
# TODO: this assumes that all OrbitalBody2D that are attached are done in a clean chain without breaks, which may not be the case
|
||||
if child is OrbitalBody2D and child.physics_mode == PhysicsMode.ANCHORED:
|
||||
child._collect_anchored_parts(parts_array)
|
||||
@ -1 +0,0 @@
|
||||
uid://0isnsk356que
|
||||
@ -1,13 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="GameConfig" load_steps=5 format=3 uid="uid://cv15sck8rl2b7"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://chgycmkkaf7jv" path="res://scenes/characters/pilot_ball.tscn" id="1_s0mxw"]
|
||||
[ext_resource type="PackedScene" uid="uid://didt2nsdtbmra" path="res://modules/Tube.tscn" id="2_75b4c"]
|
||||
[ext_resource type="PackedScene" uid="uid://dnre6svquwdtb" path="res://scenes/characters/player_controller.tscn" id="2_sk8k5"]
|
||||
[ext_resource type="Script" uid="uid://bfc6u1f8sigxj" path="res://scripts/singletons/game_config.gd" id="3_75b4c"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("3_75b4c")
|
||||
player_controller_scene = ExtResource("2_sk8k5")
|
||||
default_pawn_scene = ExtResource("1_s0mxw")
|
||||
default_ship_scene = ExtResource("2_75b4c")
|
||||
metadata/_custom_type_script = "uid://bfc6u1f8sigxj"
|
||||
@ -1,241 +0,0 @@
|
||||
# OrbitalMechanics.gd
|
||||
extends Node
|
||||
|
||||
# This singleton holds all universal physics constants and functions.
|
||||
|
||||
# The scaled gravitational constant for the entire simulation.
|
||||
const G = 1.0 # Adjust this to control the "speed" of your simulation
|
||||
|
||||
const MIN_INFLUENCE_THRESHOLD = 0.00001
|
||||
const ROCHE_LIMIT_MASS_MULTIPLIER = 0.5
|
||||
|
||||
class BodyTuple:
|
||||
var body_a: OrbitalBody2D
|
||||
var body_b: OrbitalBody2D
|
||||
|
||||
var cached_forces: Dictionary[BodyTuple, Vector2] = {
|
||||
|
||||
}
|
||||
|
||||
# --- Centralized Physics Process ---
|
||||
func _physics_process(_delta: float) -> void:
|
||||
var star_system: StarSystem = GameManager.current_star_system
|
||||
if not star_system:
|
||||
return
|
||||
|
||||
var star = star_system.get_star()
|
||||
var planetary_systems = star_system.get_planetary_systems()
|
||||
|
||||
# TODO: Would this be true in case we are working with a system that is just a rouge planet or a brown dwarf?
|
||||
if not star:
|
||||
return
|
||||
|
||||
# 1: Calculate star system pull
|
||||
# a: Get the star and top level Barycenters
|
||||
var top_level_bodies: Array[OrbitalBody2D] = [star]
|
||||
top_level_bodies.append_array(planetary_systems)
|
||||
# b: calculate and apply pull between these
|
||||
apply_n_body_forces(top_level_bodies)
|
||||
|
||||
# 2: Calculate Barycenters local pull
|
||||
for system in planetary_systems:
|
||||
# a: Get each Planetary Barycenters OrbitalBody2Ds (including Ships, Satelites, and Stations fully within the Barycenter)
|
||||
var system_attractors = system.get_internal_attractors()
|
||||
# b: Calculate and apply pull within each Barycenter
|
||||
apply_n_body_forces(system_attractors)
|
||||
|
||||
# 3: Calculate top level Ships, Satelites, and Stations pull
|
||||
# a: Get top level OrbitalBody2Ds of non-celestial classes
|
||||
for star_orbiter in star_system.get_orbital_bodies():
|
||||
# b: Split into Star Orbiting and On-Approach using mass/distance ratios to Barycenters
|
||||
# TODO: Check for distance to Barycenter
|
||||
# c: For Star Orbiting objects -> Calculate and apply pull to star and Barycenter
|
||||
star_orbiter.apply_force(calculate_n_body_force(star_orbiter, top_level_bodies))
|
||||
# d: For On Approach -> Calculate and apply pull to star and distant Barycenters
|
||||
# as well as individual bodies within approaching Barycenter
|
||||
|
||||
func calculate_gravitational_force(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> Vector2:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
|
||||
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
|
||||
|
||||
if distance_sq < 1.0:
|
||||
return Vector2.ZERO
|
||||
|
||||
# --- Influence Pruning (Culling) ---
|
||||
# We check both directions of influence
|
||||
var influence_a = primary.mass / distance_sq
|
||||
var influence_b = orbiter.mass / distance_sq
|
||||
|
||||
if influence_a < MIN_INFLUENCE_THRESHOLD and influence_b < MIN_INFLUENCE_THRESHOLD:
|
||||
return Vector2.ZERO
|
||||
|
||||
var force_magnitude = (G * primary.mass * orbiter.mass) / distance_sq
|
||||
var direction = orbiter.global_position.direction_to(primary.global_position)
|
||||
return direction * force_magnitude
|
||||
|
||||
# Calculates the pull between a set number of bodies
|
||||
# Use carefully to simulate each level of the simulation
|
||||
# Iterate through every unique pair of bodies (i, j) where j > i
|
||||
func apply_n_body_forces(attractors: Array[OrbitalBody2D]):
|
||||
# Iterate through every unique pair of bodies (i, j) where j > i
|
||||
for i in range(attractors.size()):
|
||||
var body_a: OrbitalBody2D = attractors[i]
|
||||
if not is_instance_valid(body_a): continue
|
||||
|
||||
for j in range(i + 1, attractors.size()):
|
||||
var body_b: OrbitalBody2D = attractors[j]
|
||||
if not is_instance_valid(body_b): continue
|
||||
|
||||
# Calculate the force vector ONCE
|
||||
var force_vector = calculate_gravitational_force(body_a, body_b)
|
||||
|
||||
# Apply the force symmetrically
|
||||
if force_vector != Vector2.ZERO:
|
||||
body_a.apply_force(force_vector)
|
||||
body_b.apply_force(-force_vector)
|
||||
|
||||
func calculate_n_body_force(body: OrbitalBody2D, attractors: Array[OrbitalBody2D]) -> Vector2:
|
||||
var total_pull: Vector2 = Vector2.ZERO
|
||||
for attractor in attractors:
|
||||
total_pull += calculate_gravitational_force(body, attractor)
|
||||
return total_pull
|
||||
|
||||
func calculate_n_body_gravity_forces(body_to_affect: Node2D) -> Vector2:
|
||||
var total_force = Vector2.ZERO
|
||||
if not is_instance_valid(body_to_affect):
|
||||
return total_force
|
||||
|
||||
# Get the list of all major gravitational bodies from the GameManager.
|
||||
var system_data = GameManager.get_system_data()
|
||||
if not system_data:
|
||||
return total_force
|
||||
|
||||
# We only consider planets and the star as major attractors for performance.
|
||||
var attractors = system_data.all_bodies()
|
||||
|
||||
for attractor in attractors:
|
||||
if is_instance_valid(attractor) and attractor != body_to_affect:
|
||||
total_force += calculate_gravitational_force(body_to_affect, attractor)
|
||||
|
||||
return total_force
|
||||
|
||||
# Calculates the perfect initial velocity for a stable circular orbit.
|
||||
func calculate_circular_orbit_velocity(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> Vector2:
|
||||
if not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
if distance == 0:
|
||||
return Vector2.ZERO
|
||||
|
||||
# v = sqrt(G * M / r)
|
||||
var speed_magnitude = sqrt(G * primary.mass / distance)
|
||||
|
||||
var direction_to_orbiter = primary.global_position.direction_to(orbiter.global_position)
|
||||
var perpendicular_direction = Vector2(direction_to_orbiter.y, -direction_to_orbiter.x)
|
||||
|
||||
return perpendicular_direction * speed_magnitude
|
||||
|
||||
func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
|
||||
var num_steps = 10
|
||||
var time_step = 60
|
||||
|
||||
var ghost_position = body_to_trace.global_position
|
||||
var ghost_velocity = body_to_trace.linear_velocity
|
||||
|
||||
var path_points = PackedVector2Array()
|
||||
|
||||
for i in range(num_steps):
|
||||
# Create a temporary "ghost" body to calculate forces on.
|
||||
var ghost_body = OrbitalBody2D.new()
|
||||
ghost_body.global_position = ghost_position
|
||||
ghost_body.mass = body_to_trace.mass
|
||||
|
||||
# Use our library to get the total gravitational force at the ghost's position.
|
||||
var total_force = calculate_n_body_gravity_forces(ghost_body)
|
||||
var acceleration = total_force / ghost_body.mass
|
||||
|
||||
ghost_velocity += acceleration * time_step
|
||||
ghost_position += ghost_velocity * time_step
|
||||
path_points.append(ghost_position)
|
||||
|
||||
ghost_body.free() # Clean up the temporary node
|
||||
|
||||
return path_points
|
||||
|
||||
# Calculates an array of points for the orbit RELATIVE to the primary body.
|
||||
func _calculate_relative_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
|
||||
if not is_instance_valid(body_to_trace) or not body_to_trace.has_method("get_primary") or not is_instance_valid(body_to_trace.get_primary()):
|
||||
return PackedVector2Array()
|
||||
|
||||
var primary = body_to_trace.get_primary()
|
||||
var primary_mass = primary.mass
|
||||
var body_mass = body_to_trace.mass
|
||||
|
||||
var ghost_relative_pos = body_to_trace.global_position - primary.global_position
|
||||
var ghost_relative_vel = body_to_trace.linear_velocity - primary.linear_velocity
|
||||
|
||||
var r_magnitude = ghost_relative_pos.length()
|
||||
if r_magnitude == 0:
|
||||
return PackedVector2Array()
|
||||
|
||||
var v_sq = ghost_relative_vel.length_squared()
|
||||
var mu = G * primary_mass
|
||||
|
||||
var specific_energy = v_sq / 2.0 - mu / r_magnitude
|
||||
|
||||
var num_steps = 200
|
||||
var time_step: float
|
||||
if specific_energy >= 0:
|
||||
time_step = 0.1
|
||||
else:
|
||||
var semi_major_axis = -mu / (2.0 * specific_energy)
|
||||
var orbital_period = 2.0 * PI * sqrt(pow(semi_major_axis, 3) / mu)
|
||||
time_step = orbital_period / float(num_steps)
|
||||
|
||||
var path_points = PackedVector2Array()
|
||||
|
||||
for i in range(num_steps):
|
||||
var distance_sq = ghost_relative_pos.length_squared()
|
||||
if distance_sq < 1.0:
|
||||
break
|
||||
|
||||
var direction = -ghost_relative_pos.normalized()
|
||||
var force_magnitude = (G * primary_mass * body_mass) / distance_sq
|
||||
var force_vector = direction * force_magnitude
|
||||
var acceleration = force_vector / body_mass
|
||||
|
||||
ghost_relative_vel += acceleration * time_step
|
||||
ghost_relative_pos += ghost_relative_vel * time_step
|
||||
path_points.append(ghost_relative_pos)
|
||||
|
||||
return path_points
|
||||
|
||||
# Calculates the Hill Sphere radius for a satellite.
|
||||
# This is the region where the satellite's gravity is dominant over its primary's.
|
||||
func calculate_hill_sphere(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> float:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary) or primary.mass <= 0:
|
||||
return 0.0
|
||||
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
# The formula is: a * (m / 3M)^(1/3)
|
||||
var mass_ratio = orbiter.mass / (3.0 * primary.mass)
|
||||
if mass_ratio < 0: return 0.0
|
||||
|
||||
return distance * pow(mass_ratio, 1.0/3.0)
|
||||
|
||||
# Calculates a simplified Roche Limit, or minimum safe orbital distance.
|
||||
func calculate_simplified_roche_limit(primary: OrbitalBody2D) -> float:
|
||||
if not is_instance_valid(primary) or primary.mass <= 0:
|
||||
return 100.0 # Return a small default if primary is invalid
|
||||
|
||||
# We approximate a "radius" from the square root of the mass, then apply a multiplier.
|
||||
# This ensures more massive stars and planets have larger "keep-out" zones.
|
||||
return sqrt(primary.mass) * ROCHE_LIMIT_MASS_MULTIPLIER
|
||||
|
||||
func get_orbital_time_in_seconds(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> float:
|
||||
var mu = OrbitalMechanics.G * primary.mass
|
||||
var r = orbiter.global_position.distance_to(primary.global_position)
|
||||
return TAU * sqrt(pow(r, 3) / mu)
|
||||
@ -1,115 +0,0 @@
|
||||
# scripts/star_system_generator.gd
|
||||
class_name StarSystemGenerator
|
||||
extends RefCounted
|
||||
|
||||
# --- Stable Mass Ratios & Generation Rules ---
|
||||
const STAR_MASS = 50000000.0
|
||||
const PLANET_MASS = STAR_MASS / 10000.0 # Planet is 10,000x less massive than the star.
|
||||
const MOON_MASS = PLANET_MASS / 1000.0 # Moon is 1,000x less massive than its planet.
|
||||
const MIN_PLANETS = 3
|
||||
const MAX_PLANETS = 8
|
||||
const MAX_MOONS_PER_PLANET = 5
|
||||
const ORBIT_SAFETY_FACTOR = 5 # Increase space between orbits
|
||||
|
||||
# --- The main public method ---
|
||||
func generate(star_system: StarSystem) -> SystemData:
|
||||
# 1. Create the root Barycenter for the entire system.
|
||||
var system_data = SystemData.new()
|
||||
|
||||
# 2. Create the star itself inside the root Barycenter.
|
||||
var star = Star.new()
|
||||
system_data.star = star
|
||||
star.name = "Star"
|
||||
star.base_mass = STAR_MASS
|
||||
star_system.add_child(star)
|
||||
|
||||
# 3. Procedurally generate and place the planetary systems.
|
||||
var num_planets = randi_range(MIN_PLANETS, MAX_PLANETS)
|
||||
var current_orbit_radius = 15000.0 # OrbitalMechanics.calculate_simplified_roche_limit(star) # Start with the first orbit
|
||||
|
||||
for i in range(num_planets):
|
||||
# A. Create the Barycenter for the new planetary system.
|
||||
var planet_barycenter = Barycenter.new()
|
||||
|
||||
planet_barycenter.name = "PlanetSystem_%d" % (i + 1)
|
||||
star_system.add_child(planet_barycenter)
|
||||
|
||||
# B. Create the planet itself inside its Barycenter.
|
||||
var planet = Planet.new()
|
||||
system_data.planets.append(planet)
|
||||
planet.name = "Planet_%d" % (i + 1)
|
||||
planet.base_mass = randf_range(PLANET_MASS * 0.2, PLANET_MASS * 5.0)
|
||||
planet_barycenter.add_child(planet)
|
||||
planet.owner = planet_barycenter
|
||||
planet.position = Vector2.ZERO
|
||||
planet_barycenter.recalculate_total_mass()
|
||||
# C. Create moons for this planet.
|
||||
_generate_moons(planet, planet_barycenter, system_data)
|
||||
|
||||
# D. Place the entire planetary system in a stable orbit.
|
||||
planet_barycenter.global_position = Vector2(current_orbit_radius, 0).rotated(randf_range(0, TAU))
|
||||
planet_barycenter.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(planet_barycenter, star)
|
||||
|
||||
# Update the new edge of the star's influence
|
||||
# 1. Calculate the Hill Sphere (gravitational influence) for the planet we just placed.
|
||||
var hill_sphere = OrbitalMechanics.calculate_hill_sphere(planet_barycenter, star)
|
||||
# 2. Add this zone of influence (plus a safety margin) to the current radius
|
||||
# to determine the starting point for the NEXT planet. This ensures orbits never overlap.
|
||||
current_orbit_radius += hill_sphere * ORBIT_SAFETY_FACTOR
|
||||
|
||||
# --- Spawn the ship at the last planet's L4 point ---
|
||||
if i == num_planets - 1:
|
||||
_spawn_player_ship(star_system, star, planet_barycenter)
|
||||
|
||||
return system_data
|
||||
|
||||
func _generate_moons(planet: OrbitalBody2D, planet_barycenter: Barycenter, system_data: SystemData):
|
||||
var num_moons = randi_range(0, int(planet.mass / MOON_MASS / 2.0)) # Heavier planets get more moons
|
||||
num_moons = min(num_moons, MAX_MOONS_PER_PLANET)
|
||||
|
||||
var current_orbit_radius = 200.0 # OrbitalMechanics.calculate_simplified_roche_limit(planet) # Start with the first orbit
|
||||
|
||||
for i in range(num_moons):
|
||||
var moon = Moon.new()
|
||||
system_data.moons.append(moon)
|
||||
planet_barycenter.add_child(moon)
|
||||
planet_barycenter.recalculate_total_mass()
|
||||
moon.owner = planet_barycenter
|
||||
moon.name = "Moon_%d" % (i + 1)
|
||||
moon.base_mass = randf_range(MOON_MASS * 0.1, MOON_MASS * 2.0)
|
||||
|
||||
moon.position = Vector2(current_orbit_radius, 0).rotated(randf_range(0, TAU))
|
||||
# Velocity is calculated relative to the parent (the planet)
|
||||
moon.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(moon, planet_barycenter)
|
||||
|
||||
# Update the new edge of the planets's influence
|
||||
# 1. Calculate the Hill Sphere (gravitational influence) for the moon we just placed.
|
||||
var hill_sphere = OrbitalMechanics.calculate_hill_sphere(moon, planet_barycenter)
|
||||
# 2. Add this zone of influence (plus a safety margin) to the current radius
|
||||
# to determine the starting point for the NEXT planet. This ensures orbits never overlap.
|
||||
current_orbit_radius += hill_sphere * ORBIT_SAFETY_FACTOR
|
||||
|
||||
|
||||
# --- NEW FUNCTION: Spawns the player ship at a Lagrange point ---
|
||||
func _spawn_player_ship(star_system: StarSystem, star: OrbitalBody2D, planet_system: Barycenter):
|
||||
# L4 and L5 Lagrange points form an equilateral triangle with the star and planet.
|
||||
# We'll calculate L4 by rotating the star-planet vector by +60 degrees.
|
||||
var star_to_planet_vec = planet_system.global_position - star.global_position
|
||||
var l4_position = star.global_position + star_to_planet_vec.rotated(PI / 3.0)
|
||||
|
||||
# The ship's velocity at L4 must match the orbital characteristics of that point.
|
||||
# This is an approximation where we rotate the planet's velocity vector by 60 degrees.
|
||||
var l4_velocity = planet_system.linear_velocity.rotated(PI / 3.0)
|
||||
|
||||
# Instantiate, position, and configure the ship.
|
||||
var ship_instance = GameManager.config.default_ship_scene.instantiate()
|
||||
GameManager.register_ship(ship_instance)
|
||||
ship_instance.name = "PlayerShip"
|
||||
star_system.add_child(ship_instance) # Add ship to the root StarSystem node
|
||||
ship_instance.global_position = l4_position
|
||||
ship_instance.linear_velocity = l4_velocity
|
||||
ship_instance.rotation = l4_velocity.angle() + (PI / 2.0) # Point prograde
|
||||
|
||||
# Make sure the new ship is included in the physics simulation
|
||||
#_system_data_dict.all_bodies.append(ship_instance)
|
||||
print("Player ship spawned at L4 point of %s" % planet_system.name)
|
||||
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 994 B |
@ -2,5 +2,5 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://bkcouefvi7iup" path="res://scripts/star_system.gd" id="1_ig7tw"]
|
||||
|
||||
[node name="StarSystem" type="Node2D"]
|
||||
[node name="StarSystem" type="Node3D"]
|
||||
script = ExtResource("1_ig7tw")
|
||||
566
src/modules/3d_test_ship.tscn
Normal file
566
src/modules/3d_test_ship.tscn
Normal file
@ -0,0 +1,566 @@
|
||||
[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
|
||||
|
||||
[node name="3dTestShip" type="RigidBody3D" unique_id=246037729]
|
||||
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="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="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="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=2096937457]
|
||||
replication_config = SubResource("SceneReplicationConfig_ism2t")
|
||||
26
src/modules/physics_testing_ship.tscn
Normal file
26
src/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
src/modules/test_ship.tscn
Normal file
7
src/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")]
|
||||
@ -12,7 +12,7 @@ config_version=5
|
||||
|
||||
config/name="space_simulation"
|
||||
run/main_scene="uid://dogqi2c58qdc0"
|
||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||
config/features=PackedStringArray("4.6", "Double Precision", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
@ -21,6 +21,7 @@ OrbitalMechanics="*res://scripts/singletons/orbital_mechanics.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]
|
||||
|
||||
@ -166,18 +167,20 @@ left_click={
|
||||
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]
|
||||
|
||||
common/physics_jitter_fix=0.0
|
||||
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)
|
||||
2d/default_linear_damp=0.0
|
||||
2d/sleep_threshold_linear=0.0
|
||||
common/physics_interpolation=true
|
||||
|
||||
[plugins]
|
||||
|
||||
@ -7,7 +7,7 @@ signal follow_requested(body: Node2D)
|
||||
|
||||
@onready var name_label: Label = $NameLabel
|
||||
|
||||
var body_reference: OrbitalBody2D
|
||||
var body_reference: OrbitalBody3D
|
||||
var dot_color: Color = Color.WHITE
|
||||
|
||||
var hover_tween: Tween
|
||||
@ -27,18 +27,11 @@ func _ready() -> void:
|
||||
mouse_entered.connect(_on_mouse_entered)
|
||||
mouse_exited.connect(_on_mouse_exited)
|
||||
|
||||
func initialize(body: OrbitalBody2D):
|
||||
func initialize(body: OrbitalBody3D):
|
||||
body_reference = body
|
||||
name_label.text = body.name
|
||||
|
||||
if body is Star:
|
||||
dot_color = Color.GOLD
|
||||
elif body is Planet:
|
||||
dot_color = Color.DODGER_BLUE
|
||||
elif body is Moon:
|
||||
dot_color = Color.PURPLE
|
||||
else:
|
||||
dot_color = Color.CYAN
|
||||
dot_color = Color.CYAN
|
||||
|
||||
self.tooltip_text = _generate_tooltip_text()
|
||||
|
||||
@ -98,7 +91,7 @@ func _on_mouse_exited():
|
||||
func _generate_tooltip_text() -> String:
|
||||
var info = [body_reference.name]
|
||||
|
||||
if body_reference is Planet:
|
||||
if body_reference is CelestialBody:
|
||||
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)
|
||||
|
||||
@ -106,18 +99,11 @@ func _generate_tooltip_text() -> String:
|
||||
|
||||
var moon_count = 0
|
||||
for child in planet_system.get_internal_attractors():
|
||||
if child is Moon:
|
||||
if child is CelestialBody:
|
||||
moon_count += 1
|
||||
if moon_count > 0:
|
||||
info.append("Moons: %d" % moon_count)
|
||||
|
||||
|
||||
if body_reference is Moon:
|
||||
var planet_system = body_reference.get_parent() as Barycenter
|
||||
var period_seconds = OrbitalMechanics.get_orbital_time_in_seconds(body_reference as Moon, planet_system)
|
||||
|
||||
info.append("Orbital Period: %s" % _format_seconds_to_mmss(period_seconds))
|
||||
|
||||
if body_reference is Module:
|
||||
info.append("Class: Player Vessel")
|
||||
info.append("Mass: %.0f kg" % body_reference.mass)
|
||||
15
src/scenes/celestial_bodies/celestial_body.gd
Normal file
15
src/scenes/celestial_bodies/celestial_body.gd
Normal file
@ -0,0 +1,15 @@
|
||||
class_name CelestialBody extends OrbitalBody3D
|
||||
|
||||
# --- Set in corresponding scene ---
|
||||
# var auto_proxy_gravity = false
|
||||
@export var radius: float = 100.0
|
||||
|
||||
func set_radius(value: float):
|
||||
radius = value
|
||||
|
||||
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
src/scenes/celestial_bodies/celestial_body.gd.uid
Normal file
1
src/scenes/celestial_bodies/celestial_body.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dok35h0q4pseh
|
||||
28
src/scenes/celestial_bodies/celestial_body.tscn
Normal file
28
src/scenes/celestial_bodies/celestial_body.tscn
Normal file
@ -0,0 +1,28 @@
|
||||
[gd_scene load_steps=5 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"]
|
||||
|
||||
[node name="CelestialBody" type="RigidBody3D"]
|
||||
script = ExtResource("1_uxu4s")
|
||||
auto_proxy_gravity = false
|
||||
metadata/_custom_type_script = "uid://dok35h0q4pseh"
|
||||
|
||||
[node name="Surface" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("SphereMesh_vi0nt")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("SphereShape3D_uxu4s")
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||
light_color = Color(0.958646, 0.7997282, 0.55087835, 1)
|
||||
omni_range = 200000.0
|
||||
omni_attenuation = 2.0
|
||||
52
src/scenes/celestial_bodies/materials/sun_mat.gdshader
Normal file
52
src/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;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
uid://0cjdd62t25g1
|
||||
27
src/scenes/celestial_bodies/materials/sun_mat.tres
Normal file
27
src/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,5 +1,5 @@
|
||||
# CharacterPawn.gd
|
||||
extends CharacterBody3D
|
||||
extends OrbitalBody3D
|
||||
class_name CharacterPawn3D
|
||||
|
||||
## Core Parameters
|
||||
@ -29,7 +29,6 @@ var _pitch_yaw_input: Vector2 = Vector2.ZERO
|
||||
@onready var zero_g_movemement_component: ZeroGMovementComponent = $ZeroGMovementComponent
|
||||
|
||||
## Physics State (Managed by Pawn)
|
||||
var angular_velocity: Vector3 = Vector3.ZERO
|
||||
@export var angular_damping: float = 0.95 # Base damping
|
||||
|
||||
## Other State Variables
|
||||
@ -58,37 +57,23 @@ func _ready():
|
||||
camera.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
func _process(_delta: float) -> void:
|
||||
camera_pivot.global_transform = camera_anchor.get_global_transform_interpolated()
|
||||
|
||||
|
||||
func _physics_process(delta: float):
|
||||
# 1. Apply Mouse Rotation (Universal head look)
|
||||
func _physics_process(_delta: float):
|
||||
_apply_mouse_rotation()
|
||||
|
||||
if zero_g_movemement_component: # Fallback to ZeroG controller (for initiating reach)
|
||||
zero_g_movemement_component.process_movement(delta, _move_input, _vertical_input, _roll_input, _l_click_input, _r_click_input)
|
||||
|
||||
# 3. Integrate Angular Velocity (Universal)
|
||||
_integrate_angular_velocity(delta)
|
||||
|
||||
# 4. Apply Linear Velocity & Collision (Universal)
|
||||
# Use move_and_slide for states affected by gravity/floor or zero-g collisions
|
||||
move_and_slide()
|
||||
|
||||
# Check for collision response AFTER move_and_slide
|
||||
var collision_count = get_slide_collision_count()
|
||||
if collision_count > 0:
|
||||
var collision = get_slide_collision(collision_count - 1) # Get last collision
|
||||
# Delegate or handle basic bounce
|
||||
if eva_suit_component:
|
||||
eva_suit_component.handle_collision(collision, collision_energy_loss)
|
||||
else:
|
||||
_handle_basic_collision(collision)
|
||||
|
||||
# 5. Reset Inputs
|
||||
_reset_inputs()
|
||||
|
||||
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||
# Let the active movement controller apply its forces
|
||||
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)
|
||||
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:
|
||||
@ -112,28 +97,6 @@ func _integrate_angular_velocity(delta: float):
|
||||
if angular_velocity.length_squared() < 0.0001:
|
||||
angular_velocity = Vector3.ZERO
|
||||
|
||||
func _handle_basic_collision(collision: KinematicCollision3D):
|
||||
var surface_normal = collision.get_normal()
|
||||
velocity = velocity.bounce(surface_normal)
|
||||
velocity *= (1.0 - collision_energy_loss * 0.5)
|
||||
|
||||
# --- Public Helper for Controllers ---
|
||||
# Applies torque affecting angular velocity
|
||||
func add_torque(torque_global: Vector3, delta: float):
|
||||
# Calculate effective inertia (base + suit multiplier if applicable)
|
||||
var effective_inertia = base_inertia * (eva_suit_component.inertia_multiplier if eva_suit_component else 1.0)
|
||||
if effective_inertia <= 0: effective_inertia = 1.0 # Safety prevent division by zero
|
||||
# Apply change directly to angular velocity using the global torque
|
||||
|
||||
angular_velocity += (torque_global / effective_inertia) * delta
|
||||
|
||||
# --- Movement Implementations (Keep non-EVA ones here) ---
|
||||
func _apply_walking_movement(_delta: float): pass # TODO
|
||||
func _apply_ladder_floating_drag(delta: float):
|
||||
velocity = velocity.lerp(Vector3.ZERO, delta * 2.0);
|
||||
angular_velocity = angular_velocity.lerp(Vector3.ZERO, delta * 2.0)
|
||||
func _apply_ladder_movement(_delta: float): pass # TODO
|
||||
|
||||
# --- 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
|
||||
@ -1,9 +1,9 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://7yc6a07xoccy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cdmmiixa75f3x" path="res://scenes/tests/3d/character_pawn_3d.gd" id="1_4frsu"]
|
||||
[ext_resource type="PackedScene" uid="uid://bm1rbv4tuppbc" path="res://eva_suit_controller.tscn" id="3_gnddn"]
|
||||
[ext_resource type="Script" uid="uid://y3vo40i16ek3" path="res://scenes/tests/3d/zero_g_movement_component.gd" id="4_8jhjh"]
|
||||
[ext_resource type="PackedScene" uid="uid://ba3ijdstp2bvt" path="res://scenes/tests/3d/player_controller_3d.tscn" id="4_bcy3l"]
|
||||
[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"]
|
||||
|
||||
@ -23,44 +23,45 @@ properties/2/path = NodePath("CameraPivot:rotation")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 2
|
||||
|
||||
[node name="CharacterPawn3D" type="CharacterBody3D"]
|
||||
[node name="CharacterPawn3D" type="RigidBody3D" unique_id=288275840]
|
||||
script = ExtResource("1_4frsu")
|
||||
metadata/_custom_type_script = "uid://cdmmiixa75f3x"
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1967015232]
|
||||
shape = SubResource("CapsuleShape3D_6vm80")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1703183586]
|
||||
mesh = SubResource("CapsuleMesh_6vm80")
|
||||
|
||||
[node name="CameraAnchor" type="Marker3D" parent="."]
|
||||
[node name="CameraAnchor" type="Marker3D" parent="." unique_id=462168232]
|
||||
|
||||
[node name="CameraPivot" type="Node3D" parent="."]
|
||||
[node name="CameraPivot" type="Node3D" parent="." unique_id=794640520]
|
||||
physics_interpolation_mode = 1
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0)
|
||||
top_level = true
|
||||
|
||||
[node name="SpringArm" type="SpringArm3D" parent="CameraPivot"]
|
||||
[node name="SpringArm" type="SpringArm3D" parent="CameraPivot" unique_id=1399441728]
|
||||
spring_length = 3.0
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot/SpringArm"]
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot/SpringArm" unique_id=1779046272]
|
||||
far = 200000.0
|
||||
|
||||
[node name="GripDetector" type="Area3D" parent="."]
|
||||
[node name="GripDetector" type="Area3D" parent="." unique_id=734413990]
|
||||
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"]
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GripDetector" unique_id=1939219836]
|
||||
shape = SubResource("SphereShape3D_gnddn")
|
||||
|
||||
[node name="ZeroGMovementComponent" type="Node3D" parent="."]
|
||||
[node name="ZeroGMovementComponent" type="Node3D" parent="." unique_id=594953523]
|
||||
script = ExtResource("4_8jhjh")
|
||||
metadata/_custom_type_script = "uid://y3vo40i16ek3"
|
||||
|
||||
[node name="EVAMovementComponent" parent="." instance=ExtResource("3_gnddn")]
|
||||
[node name="EVAMovementComponent" parent="." unique_id=1806288315 instance=ExtResource("3_gnddn")]
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." unique_id=732324183]
|
||||
replication_config = SubResource("SceneReplicationConfig_gnddn")
|
||||
|
||||
[node name="PlayerController3d" parent="." instance=ExtResource("4_bcy3l")]
|
||||
[node name="PlayerController3d" parent="." unique_id=1450011826 instance=ExtResource("4_bcy3l")]
|
||||
@ -6,14 +6,18 @@ class_name EVAMovementComponent
|
||||
var pawn: CharacterPawn3D
|
||||
|
||||
## EVA Parameters (Moved from ZeroGPawn)
|
||||
@export var orientation_speed: float = 2.0 # Used for orienting body to camera
|
||||
@export var move_speed: float = 2.0
|
||||
@export var roll_torque: float = 2.5
|
||||
@export var orientation_speed: float = 1.0 # Used for orienting body to camera
|
||||
@export var linear_acceleration: float = 1.0
|
||||
@export var roll_torque_acceleration: float = 0.25
|
||||
@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 = 5.0
|
||||
@export var stabilization_kd: float = 1.0
|
||||
|
||||
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
|
||||
@ -23,54 +27,21 @@ func _ready():
|
||||
if not pawn:
|
||||
printerr("EVAMovementComponent must be a child of a CharacterBody3D pawn.")
|
||||
return
|
||||
# Make sure the paths match your CharacterPawn scene structure
|
||||
# if camera_anchor:
|
||||
# camera = camera_anchor.get_node_or_null("SpringArm/Camera3D") # Adjusted path for SpringArm
|
||||
|
||||
# if not camera_anchor or not camera:
|
||||
# printerr("EVAMovementComponent could not find CameraPivot/SpringArm/Camera3D on pawn.")
|
||||
## 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)
|
||||
|
||||
# --- Standardized Movement API ---
|
||||
|
||||
## Called by Pawn's _physics_process when in FLOATING state with suit equipped
|
||||
func process_movement(delta: float, move_input: Vector2, vertical_input: float, roll_input: float, orienting_input: PlayerController3D.KeyInput):
|
||||
var orienting = orienting_input.held
|
||||
if not is_instance_valid(pawn): return
|
||||
if orienting:
|
||||
_orient_pawn(delta)
|
||||
_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(delta)
|
||||
_apply_stabilization_torques(state)
|
||||
else:
|
||||
# Apply regular movement/torque only if not stabilizing
|
||||
_apply_floating_movement(delta, move_input, vertical_input, roll_input)
|
||||
|
||||
func apply_thrusters(pawn: CharacterPawn3D, delta: float, move_input: Vector2, vertical_input: float, roll_input: float):
|
||||
if not is_instance_valid(pawn): return
|
||||
|
||||
# Apply Linear Velocity
|
||||
var pawn_forward = -pawn.global_basis.z
|
||||
var pawn_right = pawn.global_basis.x
|
||||
var pawn_up = pawn.global_basis.y
|
||||
var move_dir_horizontal = (pawn_forward * move_input.y + pawn_right * move_input.x)
|
||||
var move_dir_vertical = pawn_up * vertical_input
|
||||
var combined_move_dir = move_dir_horizontal + move_dir_vertical
|
||||
|
||||
if combined_move_dir != Vector3.ZERO:
|
||||
pawn.velocity += combined_move_dir.normalized() * move_speed * delta
|
||||
|
||||
# Apply Roll Torque
|
||||
var roll_torque_global = -pawn.global_basis.z * (roll_input) * roll_torque # Sign fixed
|
||||
pawn.add_torque(roll_torque_global, delta)
|
||||
|
||||
## Called by Pawn to handle collision response in FLOATING state
|
||||
func handle_collision(collision: KinematicCollision3D, collision_energy_loss: float):
|
||||
if not is_instance_valid(pawn): return
|
||||
var surface_normal = collision.get_normal()
|
||||
var reflected_velocity = pawn.velocity.bounce(surface_normal)
|
||||
reflected_velocity *= (1.0 - collision_energy_loss)
|
||||
pawn.velocity = reflected_velocity # Update pawn's velocity directly
|
||||
_apply_floating_movement(state, move_input, vertical_input, roll_input)
|
||||
|
||||
## Called by Pawn when entering FLOATING state with suit
|
||||
func on_enter_state():
|
||||
@ -84,78 +55,68 @@ func on_exit_state():
|
||||
|
||||
# --- Internal Logic ---
|
||||
|
||||
func _apply_floating_movement(delta: float, move_input: Vector2, vertical_input: float, roll_input: float):
|
||||
func _apply_floating_movement(state: PhysicsDirectBodyState3D, move_input: Vector2, vertical_input: float, roll_input: float):
|
||||
# Apply Linear Velocity
|
||||
var pawn_forward = -pawn.global_basis.z
|
||||
var pawn_right = pawn.global_basis.x # Use pawn's right for consistency
|
||||
var pawn_up = pawn.global_basis.y
|
||||
var move_dir_horizontal = (pawn_forward * move_input.y + pawn_right * move_input.x)
|
||||
var move_dir_vertical = pawn_up * vertical_input
|
||||
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:
|
||||
pawn.velocity += combined_move_dir.normalized() * move_speed * delta
|
||||
state.apply_central_force(combined_move_dir.normalized() * linear_acceleration)
|
||||
|
||||
# --- Apply Roll Torque ---
|
||||
# Calculate torque magnitude based on input
|
||||
var roll_torque_vector = pawn.transform.basis.z * (-roll_input) * roll_torque
|
||||
if roll_input != 0.0:
|
||||
_is_auto_orienting = false # Cancel auto-orientation if rolling manually
|
||||
|
||||
# Apply the global torque vector using the pawn's helper function
|
||||
pawn.add_torque(roll_torque_vector, delta)
|
||||
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 _orient_pawn(delta: float):
|
||||
# 1. Determine Target Orientation Basis
|
||||
var initial_cam_basis = pawn.camera_anchor.global_basis
|
||||
var target_forward = -pawn.camera_anchor.global_basis.z # Look where camera looks
|
||||
var target_up = Vector3.UP # Default up initially
|
||||
func _process_auto_orientation(state: PhysicsDirectBodyState3D):
|
||||
# This function runs every physics frame
|
||||
if not _is_auto_orienting:
|
||||
return # Not orienting, do nothing
|
||||
|
||||
# --- 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)
|
||||
# 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
|
||||
orientation_speed, # Kp
|
||||
2 * sqrt(orientation_speed) # Kd (Critically Damped)
|
||||
)
|
||||
|
||||
# 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).
|
||||
target_up = pawn.transform.basis.y
|
||||
# 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()
|
||||
|
||||
# Create the target basis
|
||||
var target_basis = Basis.looking_at(target_forward, target_up)
|
||||
# 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
|
||||
|
||||
# Optional Pitch Offset (Experimental):
|
||||
# Apply the desired 70-degree pitch relative to the forward direction
|
||||
# var target_pitch_rad = deg_to_rad(target_body_pitch_degrees)
|
||||
# target_basis = target_basis.rotated(target_basis.x, target_pitch_rad) # Rotate around the target right vector
|
||||
|
||||
# 2. Smoothly Interpolate Towards Target Basis
|
||||
var current_basis = pawn.global_basis
|
||||
var new_basis = current_basis.slerp(target_basis, delta * orientation_speed).get_rotation_quaternion()
|
||||
|
||||
# Store the body's yaw *before* applying the new basis
|
||||
var _old_body_yaw = current_basis.get_euler().y
|
||||
var _old_body_pitch = current_basis.get_euler().x
|
||||
|
||||
# 3. Apply the new orientation
|
||||
pawn.global_basis = new_basis
|
||||
|
||||
# 4. Reset camera pivot to rotation to what it was before we rotated the parent
|
||||
pawn.camera_anchor.global_basis = initial_cam_basis
|
||||
|
||||
# --- Add new function placeholder ---
|
||||
# TODO: Implement Rotation Stabilization Logic
|
||||
func _apply_stabilization_torques(_delta: float):
|
||||
func _apply_stabilization_torques(_state: PhysicsDirectBodyState3D):
|
||||
if not is_instance_valid(stabilization_target):
|
||||
stabilization_enabled = false
|
||||
return
|
||||
@ -176,7 +137,7 @@ func _apply_stabilization_torques(_delta: float):
|
||||
# - 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)
|
||||
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:
|
||||
@ -186,7 +147,27 @@ func _apply_stabilization_torques(_delta: float):
|
||||
# - Apply the forces/torques (similar to how _apply_floating_movement applies roll torque).
|
||||
# Example (highly simplified, assumes direct torque application possible):
|
||||
|
||||
# angular_velocity += (required_torque / inertia) * delta
|
||||
|
||||
# --- 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):
|
||||
@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bm1rbv4tuppbc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d4jka2etva22s" path="res://scenes/tests/3d/eva_movement_component.gd" id="1_mb22m"]
|
||||
[ext_resource type="Script" uid="uid://d4jka2etva22s" path="res://scenes/character/eva_movement_component.gd" id="1_mb22m"]
|
||||
|
||||
[node name="EVASuitController" type="Node3D"]
|
||||
script = ExtResource("1_mb22m")
|
||||
@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ba3ijdstp2bvt"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://vjfk3xnapfti" path="res://scenes/tests/3d/player_controller_3d.gd" id="1_elh6f"]
|
||||
[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")
|
||||
@ -4,41 +4,41 @@ class_name ZeroGMovementComponent
|
||||
|
||||
## References
|
||||
var pawn: CharacterPawn3D
|
||||
var camera_pivot: Node3D
|
||||
|
||||
## State & Parameters
|
||||
var current_grip: GripArea3D = null # Use GripArea3D type hint
|
||||
var nearby_grips: Array[GripArea3D] = []
|
||||
|
||||
# --- Reach Parameters ---
|
||||
@export var reach_speed: float = 10.0 # Speed pawn moves towards grip
|
||||
@export var reach_orient_speed: float = 10.0 # Speed pawn orients to grip
|
||||
|
||||
# --- Grip damping parameters ---
|
||||
@export var gripping_linear_damping: float = 5.0 # How quickly velocity stops
|
||||
@export var gripping_angular_damping: float = 5.0 # How quickly spin stops
|
||||
@export var gripping_orient_speed: float = 2.0 # How quickly pawn rotates to face grip
|
||||
@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 = 10.0 # How quickly pawn reaches climb_speed
|
||||
@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
|
||||
|
||||
# --- Launch Parameters ---
|
||||
|
||||
# --- Seeking Climb State ---
|
||||
var _seeking_climb_input: Vector2 = Vector2.ZERO # The move_input held when seeking started
|
||||
|
||||
@export var launch_charge_rate: float = 20.0
|
||||
@export var max_launch_speed: float = 15.0
|
||||
# --- 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 {
|
||||
enum MovementState {
|
||||
IDLE,
|
||||
REACHING,
|
||||
GRIPPING,
|
||||
@ -46,82 +46,65 @@ enum MovementState {
|
||||
SEEKING_CLIMB,
|
||||
CHARGING_LAUNCH
|
||||
}
|
||||
var current_state: MovementState = MovementState.IDLE:
|
||||
var movement_state: MovementState = MovementState.IDLE:
|
||||
set(new_state):
|
||||
if new_state == current_state: return
|
||||
_on_exit_state(current_state) # Call exit logic for old state
|
||||
current_state = new_state
|
||||
_on_enter_state(current_state) # Call enter logic for 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")
|
||||
camera_pivot = pawn.get_node_or_null("CameraPivot")
|
||||
if not camera_pivot: printerr("ZeroGMovementComponent couldn't find CameraPivot")
|
||||
|
||||
_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(delta: float, move_input: Vector2, vertical_input: float, roll_input: float, reach_input: PlayerController3D.KeyInput, release_input: PlayerController3D.KeyInput):
|
||||
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(
|
||||
delta,
|
||||
move_input,
|
||||
reach_input,
|
||||
release_input
|
||||
)
|
||||
_update_state(move_input, reach_input, release_input)
|
||||
|
||||
match current_state:
|
||||
match movement_state:
|
||||
MovementState.IDLE:
|
||||
_process_idle(delta, move_input, vertical_input, roll_input, release_input)
|
||||
_process_idle(physics_state, move_input, vertical_input, roll_input, release_input)
|
||||
MovementState.REACHING:
|
||||
_process_reaching(delta)
|
||||
_process_reaching(physics_state)
|
||||
MovementState.GRIPPING:
|
||||
_apply_grip_physics(delta, move_input, roll_input)
|
||||
_process_grip_physics(physics_state, move_input, roll_input)
|
||||
MovementState.CLIMBING:
|
||||
_apply_climb_physics(delta, move_input)
|
||||
_process_climb_physics(physics_state, move_input)
|
||||
MovementState.SEEKING_CLIMB:
|
||||
_process_seeking_climb(delta, move_input)
|
||||
_process_seeking_climb(physics_state, move_input)
|
||||
MovementState.CHARGING_LAUNCH:
|
||||
_handle_launch_charge(delta)
|
||||
_process_launch_charge(physics_state, move_input, reach_input)
|
||||
|
||||
|
||||
## Called by Pawn for collision (optional, might not be needed if grabbing stops movement)
|
||||
func handle_collision(collision: KinematicCollision3D, collision_energy_loss: float):
|
||||
# Basic bounce if somehow colliding while using this controller
|
||||
var surface_normal = collision.get_normal()
|
||||
pawn.velocity = pawn.velocity.bounce(surface_normal)
|
||||
pawn.velocity *= (1.0 - collision_energy_loss * 0.5)
|
||||
|
||||
# === STATE MACHINE ===
|
||||
func _on_enter_state(state : MovementState):
|
||||
print("ZeroGMovementComponent activated for state: ", MovementState.keys()[state])
|
||||
if state == MovementState.GRIPPING:
|
||||
pawn.velocity = Vector3.ZERO
|
||||
pawn.angular_velocity = Vector3.ZERO
|
||||
# else: # e.g., REACHING_MOVE?
|
||||
# state = MovementState.IDLE # Or SEARCHING?
|
||||
|
||||
func _on_exit_state(state: MovementState):
|
||||
print("ZeroGMovementComponent deactivated for state: ", MovementState.keys()[state])
|
||||
pass
|
||||
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 state == MovementState.GRIPPING:
|
||||
#_release_current_grip()
|
||||
# if movement_state == MovementState.GRIPPING:
|
||||
# _release_current_grip()
|
||||
|
||||
func _update_state(
|
||||
_delta: float,
|
||||
move_input: Vector2,
|
||||
reach_input: PlayerController3D.KeyInput,
|
||||
release_input: PlayerController3D.KeyInput,
|
||||
):
|
||||
match current_state:
|
||||
match movement_state:
|
||||
MovementState.IDLE:
|
||||
# Already handled initiating reach in process_movement
|
||||
if reach_input.pressed or reach_input.held:
|
||||
current_state = MovementState.REACHING
|
||||
movement_state = MovementState.REACHING
|
||||
MovementState.REACHING:
|
||||
# TODO: If reach animation completes/hand near target -> GRIPPING
|
||||
# If interact released during reach -> CANCEL -> IDLE
|
||||
@ -144,8 +127,8 @@ func _update_state(
|
||||
if (reach_input.pressed or reach_input.held) and move_input != Vector2.ZERO:
|
||||
_start_charge(move_input)
|
||||
return
|
||||
elif move_input != Vector2.ZERO:
|
||||
_start_climb(move_input) # This is overshadowed by the above check.
|
||||
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)
|
||||
@ -158,119 +141,121 @@ func _update_state(
|
||||
return
|
||||
# Continue climbing logic (finding next grip) happens in _process_climbing
|
||||
MovementState.CHARGING_LAUNCH:
|
||||
if not (reach_input.pressed or reach_input.held):
|
||||
_execute_launch(move_input)
|
||||
elif move_input == Vector2.ZERO: # Cancel charge while holding interact
|
||||
current_state = MovementState.GRIPPING
|
||||
if move_input == Vector2.ZERO: # Cancel charge while holding interact
|
||||
movement_state = MovementState.GRIPPING
|
||||
print("ZeroGMovementComponent: Cancelled Launch Charge")
|
||||
|
||||
|
||||
# === MOVEMENT PROCESSING ===
|
||||
func _process_idle(delta: float, move_input: Vector2, vertical_input: float, roll_input: float, release_input: PlayerController3D.KeyInput):
|
||||
# State is IDLE (free-floating).
|
||||
# Check for EVA suit usage.
|
||||
var has_movement_input = (move_input != Vector2.ZERO or vertical_input != 0.0 or roll_input != 0.0)
|
||||
if has_movement_input and is_instance_valid(pawn.eva_suit_component):
|
||||
# Use EVA suit
|
||||
pawn.eva_suit_component.apply_thrusters(pawn, delta, move_input, vertical_input, roll_input)
|
||||
|
||||
# Check for body orientation (if applicable)
|
||||
if release_input.held and is_instance_valid(pawn.eva_suit_component):
|
||||
pawn.eva_suit_component._orient_pawn(delta) # Use suit's orient
|
||||
|
||||
func _process_reaching(_delta: float):
|
||||
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.
|
||||
# For now, _we just instantly grip.
|
||||
if _seeking_climb_input != Vector2.ZERO:
|
||||
_attempt_grip(next_grip_target) # Complete the seek-reach
|
||||
_attempt_grip(physics_state, next_grip_target) # Complete the seek-reach
|
||||
else:
|
||||
_attempt_grip(_find_best_grip())
|
||||
_attempt_grip(physics_state, _find_best_grip())
|
||||
|
||||
func _apply_grip_physics(delta: float, _move_input: Vector2, roll_input: float):
|
||||
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.
|
||||
|
||||
# --- 1. Calculate Target Transform (Same as before) ---
|
||||
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
|
||||
|
||||
var target_basis = _choose_grip_orientation(grip_base_transform.basis)
|
||||
|
||||
# --- 2. Apply Linear Force (PD Controller) ---
|
||||
var error_pos = target_position - pawn.global_position
|
||||
# Simple P-controller for velocity (acts as a spring)
|
||||
var target_velocity_pos = error_pos * gripping_linear_damping # 'damping' here acts as Kp
|
||||
# Simple D-controller (damping)
|
||||
target_velocity_pos -= pawn.velocity * gripping_angular_damping # 'angular_damping' here acts as Kd
|
||||
# Apply force via acceleration
|
||||
pawn.velocity = pawn.velocity.lerp(target_velocity_pos, delta * 10.0) # Smoothly apply correction
|
||||
|
||||
# --- 3. Apply Angular Force (PD Controller) ---
|
||||
# --- 2. Calculate Target Transform ---
|
||||
if not is_zero_approx(roll_input):
|
||||
# Manual Roll Input (applies torque)
|
||||
var roll_torque_global = pawn.global_transform.basis.z * (-roll_input) * gripping_orient_speed # Use global Z
|
||||
pawn.add_torque(roll_torque_global, delta)
|
||||
else:
|
||||
# Auto-Orient (PD Controller)
|
||||
_apply_orientation_torque(target_basis, delta)
|
||||
# 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)
|
||||
|
||||
func _apply_climb_physics(delta: float, move_input: Vector2):
|
||||
# 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 * pawn.global_basis.y + move_input.x * pawn.global_basis.x
|
||||
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(next_grip_target)
|
||||
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 = pawn.global_position - current_grip_pos
|
||||
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 Movement Force
|
||||
# 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
|
||||
pawn.velocity = pawn.velocity.lerp(target_velocity, delta * climb_acceleration)
|
||||
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 grip_base_transform = current_grip.global_transform
|
||||
var target_basis = _choose_grip_orientation(grip_base_transform.basis)
|
||||
_apply_orientation_torque(target_basis, delta)
|
||||
var target_basis = _choose_grip_orientation(physics_state, current_grip.global_basis)
|
||||
_apply_orientation_torque(physics_state, target_basis)
|
||||
|
||||
|
||||
func _process_seeking_climb(_delta: float, move_input: Vector2):
|
||||
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):
|
||||
var target_grip = _find_best_grip()
|
||||
_seeking_climb_input = Vector2.ZERO # Reset for next time
|
||||
if _attempt_grip(target_grip):
|
||||
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:
|
||||
current_state = MovementState.IDLE
|
||||
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(target_grip: GripArea3D) -> bool:
|
||||
func _attempt_grip(physics_state: PhysicsDirectBodyState3D, target_grip: GripArea3D) -> bool:
|
||||
if not is_instance_valid(target_grip):
|
||||
return false
|
||||
|
||||
@ -280,33 +265,38 @@ func _attempt_grip(target_grip: GripArea3D) -> bool:
|
||||
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 current_state != MovementState.CLIMBING:
|
||||
current_state = MovementState.GRIPPING
|
||||
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 current_state == MovementState.CLIMBING:
|
||||
if movement_state == MovementState.CLIMBING:
|
||||
_stop_climb(false) # Stop climbing, return to gripping previous one
|
||||
return false
|
||||
|
||||
# --- Grip Orientation Helper ---
|
||||
func _choose_grip_orientation(grip_basis: Basis) -> Basis:
|
||||
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 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 = pawn.global_basis
|
||||
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.
|
||||
@ -382,13 +372,15 @@ func _release_current_grip(move_input: Vector2 = Vector2.ZERO):
|
||||
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:
|
||||
current_state = MovementState.SEEKING_CLIMB
|
||||
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:
|
||||
current_state = MovementState.IDLE
|
||||
movement_state = MovementState.IDLE
|
||||
# print("ZeroGMovementComponent: Released grip, now IDLE.")
|
||||
|
||||
|
||||
@ -398,46 +390,31 @@ func _cancel_reach():
|
||||
print("ZeroGMovementComponent: Reach cancelled.")
|
||||
|
||||
# --- Climbing Helpers ---
|
||||
func _start_climb(move_input: Vector2):
|
||||
if not is_instance_valid(current_grip): return
|
||||
current_state = MovementState.CLIMBING
|
||||
|
||||
# Calculate initial climb direction based on input relative to camera/grip
|
||||
var pawn_up = pawn.global_basis.y
|
||||
var pawn_right = pawn.global_basis.x
|
||||
|
||||
print("ZeroGMoveController: Started Climbing in direction: ", (pawn_up * move_input.y + pawn_right * move_input.x).normalized())
|
||||
|
||||
func _stop_climb(release_grip: bool):
|
||||
# print("ZeroGMoveController: Stopping Climb. Release Grip: ", release_grip)
|
||||
pawn.velocity = pawn.velocity.lerp(Vector3.ZERO, 0.5) # Apply some braking
|
||||
# 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:
|
||||
current_state = MovementState.GRIPPING # Go back to stationary gripping
|
||||
movement_state = MovementState.GRIPPING # Go back to stationary gripping
|
||||
|
||||
func _apply_orientation_torque(target_basis: Basis, delta: float):
|
||||
var current_quat = pawn.global_transform.basis.get_rotation_quaternion()
|
||||
var target_quat = target_basis.get_rotation_quaternion()
|
||||
var error_quat = target_quat * current_quat.inverse()
|
||||
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
|
||||
)
|
||||
|
||||
# Ensure we take the shortest path for rotation. If W is negative, the
|
||||
# quaternion represents the "long way around". Negating it gives the same
|
||||
# orientation but via the shorter rotational path.
|
||||
if error_quat.w < 0: error_quat = -error_quat
|
||||
var error_angle = error_quat.get_angle()
|
||||
var error_axis = error_quat.get_axis()
|
||||
|
||||
var torque_proportional = error_axis.normalized() * error_angle * gripping_orient_speed
|
||||
var torque_derivative = -pawn.angular_velocity * gripping_angular_damping
|
||||
var total_torque_global = (torque_proportional + torque_derivative)
|
||||
pawn.add_torque(total_torque_global, delta)
|
||||
physics_state.apply_torque(torque)
|
||||
|
||||
# --- Launch helpers ---
|
||||
func _start_charge(move_input: Vector2):
|
||||
if not is_instance_valid(current_grip): return # Safety check
|
||||
current_state = MovementState.CHARGING_LAUNCH
|
||||
movement_state = MovementState.CHARGING_LAUNCH
|
||||
launch_charge = 0.0
|
||||
|
||||
# Calculate launch direction based on input and push-off normal
|
||||
@ -450,22 +427,55 @@ func _start_charge(move_input: Vector2):
|
||||
print("ZeroGMovementComponent: Charging Launch")
|
||||
|
||||
|
||||
func _handle_launch_charge(delta: float):
|
||||
launch_charge = min(launch_charge + launch_charge_rate * delta, max_launch_speed)
|
||||
pawn.velocity = Vector3.ZERO
|
||||
pawn.angular_velocity = Vector3.ZERO
|
||||
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))
|
||||
|
||||
func _execute_launch(move_input: Vector2):
|
||||
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
|
||||
pawn.velocity = launch_direction * launch_charge # Apply launch velocity to pawn
|
||||
launch_charge = 0.0
|
||||
|
||||
_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)
|
||||
# current_state = MovementState.SEEKING_CLIMB
|
||||
print("ZeroGMovementComponent: Launched with speed ", pawn.velocity.length(), " and now SEEKING_CLIMB")
|
||||
# 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 ---
|
||||
@ -60,11 +60,11 @@ func _unhandled_input(event: InputEvent):
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
camera.zoom /= 1.2
|
||||
|
||||
func _on_marker_selected(body: Node2D):
|
||||
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 OrbitalBody2D:
|
||||
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]
|
||||
@ -1,13 +1,11 @@
|
||||
@tool
|
||||
class_name Module
|
||||
extends OrbitalBody2D
|
||||
class_name Module extends OrbitalBody3D
|
||||
|
||||
@export var ship_name: String = "Unnamed Ship" # Only relevant for the root module
|
||||
@export var hull_integrity: float = 100.0 # This could also be a calculated property later
|
||||
|
||||
const COMPONENT_GRID_SIZE = 64.0
|
||||
|
||||
# --- NEW: Helper functions to get children by type ---
|
||||
# --- Helper functions to get children by type ---
|
||||
func get_structural_pieces() -> Array[StructuralPiece]:
|
||||
var pieces: Array[StructuralPiece]
|
||||
for child in get_children():
|
||||
@ -22,6 +20,11 @@ func get_components() -> Array[Component]:
|
||||
components.append(child)
|
||||
return components
|
||||
|
||||
func set_initial_velocity(velocity: Vector3):
|
||||
linear_velocity = velocity
|
||||
for piece in get_structural_pieces():
|
||||
piece.linear_velocity = velocity
|
||||
|
||||
# --- UPDATED: Logic now uses the helper function ---
|
||||
func get_attachment_points() -> Array:
|
||||
var points = []
|
||||
@ -31,36 +34,36 @@ func get_attachment_points() -> Array:
|
||||
var piece_center = piece.global_position
|
||||
|
||||
# --- Hullplates (Interior Grid) ---
|
||||
if piece is Hullplate:
|
||||
for i in range(-1, 2, 2):
|
||||
for j in range(-1, 2, 2):
|
||||
var offset = Vector2(i, j) * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": piece_center + offset,
|
||||
"type": Component.AttachmentType.INTERIOR_WALL,
|
||||
"piece": piece
|
||||
})
|
||||
# if piece is Hullplate:
|
||||
# for i in range(-1, 2, 2):
|
||||
# for j in range(-1, 2, 2):
|
||||
# var offset = Vector2(i, j) * (COMPONENT_GRID_SIZE / 2.0)
|
||||
# points.append({
|
||||
# "position": piece_center + offset,
|
||||
# "type": Component.AttachmentType.INTERIOR_WALL,
|
||||
# "piece": piece
|
||||
# })
|
||||
|
||||
# --- Bulkheads (Interior and Exterior Edge Attachments) ---
|
||||
elif piece is Bulkhead:
|
||||
var interior_point = piece_center + piece.transform.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": interior_point,
|
||||
"type": Component.AttachmentType.INTERIOR_WALL,
|
||||
"piece": piece
|
||||
})
|
||||
# # --- Bulkheads (Interior and Exterior Edge Attachments) ---
|
||||
# elif piece is Bulkhead:
|
||||
# var interior_point = piece_center + piece.transform.origin.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
# points.append({
|
||||
# "position": interior_point,
|
||||
# "type": Component.AttachmentType.INTERIOR_WALL,
|
||||
# "piece": piece
|
||||
# })
|
||||
|
||||
var exterior_point = piece_center - piece.transform.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": exterior_point,
|
||||
"type": Component.AttachmentType.EXTERIOR_HULL,
|
||||
"piece": piece
|
||||
})
|
||||
# var exterior_point = piece_center - piece.transform.origin.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
# points.append({
|
||||
# "position": exterior_point,
|
||||
# "type": Component.AttachmentType.EXTERIOR_HULL,
|
||||
# "piece": piece
|
||||
# })
|
||||
|
||||
return points
|
||||
|
||||
# --- This function remains largely the same ---
|
||||
func attach_component(component: Component, global_pos: Vector2, parent_piece: StructuralPiece):
|
||||
func attach_component(component: Component, global_pos: Vector3, parent_piece: StructuralPiece):
|
||||
component.position = global_pos - global_position
|
||||
component.attached_piece = parent_piece
|
||||
add_child(component)
|
||||
@ -82,7 +85,7 @@ func _recalculate_collision_shape():
|
||||
# combined_polygons.append(piece_collision_shape.shape.points)
|
||||
pass
|
||||
|
||||
# NOTE: The OrbitalBody2D's _update_mass_and_inertia() takes care of mass!
|
||||
# NOTE: The OrbitalBody3D's _update_mass_and_inertia() takes care of mass!
|
||||
pass
|
||||
|
||||
# --- UPDATED: Clear module now iterates over all relevant children ---
|
||||
11
src/scenes/ship/builder/module.tscn
Normal file
11
src/scenes/ship/builder/module.tscn
Normal file
@ -0,0 +1,11 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dfnc0ipvwuhwd"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_b1h2b"]
|
||||
|
||||
[node name="Module" type="RigidBody3D"]
|
||||
script = ExtResource("1_b1h2b")
|
||||
ship_name = null
|
||||
hull_integrity = null
|
||||
physics_mode = null
|
||||
base_mass = null
|
||||
metadata/_custom_type_script = "uid://wlm40n8ywr"
|
||||
57
src/scenes/ship/builder/pieces/hullplate.tscn
Normal file
57
src/scenes/ship/builder/pieces/hullplate.tscn
Normal file
@ -0,0 +1,57 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://bsyufiv0m1018"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cxnbunw3k7s5j" path="res://scenes/ship/builder/pieces/structural_piece.gd" id="1_ecow4"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_ecow4"]
|
||||
size = Vector3(1, 1, 0.02)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_ecow4"]
|
||||
size = Vector3(1, 1, 0.02)
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ecow4"]
|
||||
radius = 0.1
|
||||
height = 0.9
|
||||
|
||||
[node name="StructuralPiece" type="RigidBody3D"]
|
||||
script = ExtResource("1_ecow4")
|
||||
base_mass = 50.0
|
||||
metadata/_custom_type_script = "uid://cxnbunw3k7s5j"
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("BoxMesh_ecow4")
|
||||
skeleton = NodePath("")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("BoxShape3D_ecow4")
|
||||
|
||||
[node name="AttachmentPoint" type="Area3D" parent="."]
|
||||
transform = Transform3D(-4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, 0, 1, 0, 0.5, 0)
|
||||
collision_layer = 16384
|
||||
collision_mask = 16384
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="AttachmentPoint"]
|
||||
shape = SubResource("CapsuleShape3D_ecow4")
|
||||
|
||||
[node name="AttachmentPoint2" type="Area3D" parent="."]
|
||||
transform = Transform3D(-4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, 0, 1, 0, -0.5, 0)
|
||||
collision_layer = 16384
|
||||
collision_mask = 16384
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="AttachmentPoint2"]
|
||||
shape = SubResource("CapsuleShape3D_ecow4")
|
||||
|
||||
[node name="AttachmentPoint3" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 0)
|
||||
collision_layer = 16384
|
||||
collision_mask = 16384
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="AttachmentPoint3"]
|
||||
shape = SubResource("CapsuleShape3D_ecow4")
|
||||
|
||||
[node name="AttachmentPoint4" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0)
|
||||
collision_layer = 16384
|
||||
collision_mask = 16384
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="AttachmentPoint4"]
|
||||
shape = SubResource("CapsuleShape3D_ecow4")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user