WIP OrbitalBody3D rework
This commit is contained in:
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
## 1. Game Vision & Concept
|
## 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.
|
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,19 +14,73 @@ 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.
|
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
|
### 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.
|
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
|
### 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.
|
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.
|
||||||
- This allows for complex and emergent orbital behaviors, such as tidal forces and stable elliptical orbits.
|
|
||||||
|
### 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
|
### 3. Modular Spaceship
|
||||||
|
|
||||||
@ -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.
|
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. 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
|
## 4. Technical Overview
|
||||||
|
- 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.
|
||||||
- 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.
|
|
||||||
- Key Scripts:
|
- Key Scripts:
|
||||||
- OrbitalBody2D.gd: The base class for all physical objects.
|
-OrbitalBody3D.gd: The base class for all physical objects.
|
||||||
- Spaceship.gd: The central hub for a player ship.
|
- Module.gd: The central hub for a player ship, aggregating mass, inertia, and components.
|
||||||
- Thruster.gd: A self-contained, physically simulated thruster component.
|
- HelmLogicShard.gd / AutopilotShard.gd: Databanks that contain the advanced autopilot and manual control logic.
|
||||||
- ThrusterController.gd: Contains advanced autopilot and manual control logic (PD controller, bang-coast-bang maneuvers).
|
- SensorPanel.gd: A Control node that manages the interactive map UI.
|
||||||
- NavigationComputer.gd: Manages the UI and high-level maneuver planning.
|
- CharacterPawn3D.gd / ZeroGMovementComponent.gd: Manages all third-person 3D physics-based character movement.
|
||||||
- MapDrawer.gd: A Control node that manages the interactive map UI.
|
|
||||||
- MapIcon.gd: The reusable UI component for map objects.
|
|
||||||
|
|
||||||
- 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
|
## 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.
|
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
|
## 7. Crew Interaction & Ship Interior
|
||||||
Since co-op and crew management are central, detailing this aspect is crucial.
|
Since co-op and crew management are central, detailing this aspect is crucial.
|
||||||
|
|
||||||
### 1. Ship Interior Management:
|
### 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?
|
- 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? Will fires or toxic gas leaks be a possibility? This ties directly into your LifeSupport system.
|
- Atmospherics & Life Support: How is the ship's interior environment simulated? This will tie into the LifeSupport system.
|
||||||
|
|
||||||
### 2. Character States:
|
### 2. Character States:
|
||||||
- Health & Injury: How are characters affected by hazards? Can they be injured in high-G maneuvers or from system failures?
|
- 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.
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
[gd_scene load_steps=4 format=3 uid="uid://bkwogkfqk2uxo"]
|
[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="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://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"]
|
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_ism2t"]
|
||||||
|
|
||||||
[node name="3dTestShip" type="Node3D"]
|
[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"]
|
||||||
script = ExtResource("1_ktv2t")
|
script = ExtResource("1_ktv2t")
|
||||||
physics_mode = 1
|
physics_mode = 1
|
||||||
mass = 1.0
|
|
||||||
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||||
|
|
||||||
[node name="Hullplate" parent="." instance=ExtResource("2_shb7f")]
|
[node name="Hullplate" parent="." instance=ExtResource("2_shb7f")]
|
||||||
@ -462,6 +466,7 @@ transform = Transform3D(0.99989736, 0, 0.014328662, -0.014328662, -4.371139e-08,
|
|||||||
|
|
||||||
[node name="Spawner" parent="." instance=ExtResource("3_ism2t")]
|
[node name="Spawner" parent="." instance=ExtResource("3_ism2t")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
|
||||||
|
disabled = true
|
||||||
|
|
||||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, -3)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, -3)
|
||||||
@ -478,3 +483,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 1, 4)
|
|||||||
[node name="Camera3D" type="Camera3D" parent="."]
|
[node name="Camera3D" type="Camera3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||||
current = true
|
current = true
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_ism2t")
|
||||||
|
|||||||
26
modules/physics_testing_ship.tscn
Normal file
26
modules/physics_testing_ship.tscn
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[gd_scene load_steps=4 format=3 uid="uid://xcgmicfdqqb1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_ogx5r"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bsyufiv0m1018" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_nyqc6"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dvpy3urgtm62n" path="res://scenes/ship/components/hardware/spawner.tscn" id="3_3bya3"]
|
||||||
|
|
||||||
|
[node name="PhysicsTestingShip" type="RigidBody3D"]
|
||||||
|
script = ExtResource("1_ogx5r")
|
||||||
|
base_mass = 200.0
|
||||||
|
metadata/_custom_type_script = "uid://6co67nfy8ngb"
|
||||||
|
|
||||||
|
[node name="Hullplate" parent="." instance=ExtResource("2_nyqc6")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, -1, 0)
|
||||||
|
|
||||||
|
[node name="Spawner" parent="." instance=ExtResource("3_3bya3")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.021089494, 0)
|
||||||
|
|
||||||
|
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, -2)
|
||||||
|
|
||||||
|
[node name="OmniLight3D2" type="OmniLight3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, -2)
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
|
||||||
|
current = true
|
||||||
@ -21,6 +21,7 @@ OrbitalMechanics="*res://scripts/singletons/orbital_mechanics.gd"
|
|||||||
GameManager="*res://scripts/singletons/game_manager.gd"
|
GameManager="*res://scripts/singletons/game_manager.gd"
|
||||||
Constants="*res://scripts/singletons/constants.gd"
|
Constants="*res://scripts/singletons/constants.gd"
|
||||||
NetworkHandler="*res://scripts/network/network_handler.gd"
|
NetworkHandler="*res://scripts/network/network_handler.gd"
|
||||||
|
MotionUtils="*res://scripts/singletons/motion_utils.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
@ -170,14 +171,15 @@ left_click={
|
|||||||
|
|
||||||
[physics]
|
[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_linear_damp=0.0
|
||||||
|
3d/default_angular_damp=0.0
|
||||||
3d/sleep_threshold_linear=0.0
|
3d/sleep_threshold_linear=0.0
|
||||||
2d/default_gravity=0.0
|
2d/default_gravity=0.0
|
||||||
2d/default_gravity_vector=Vector2(0, 0)
|
2d/default_gravity_vector=Vector2(0, 0)
|
||||||
2d/default_linear_damp=0.0
|
2d/default_linear_damp=0.0
|
||||||
2d/sleep_threshold_linear=0.0
|
2d/sleep_threshold_linear=0.0
|
||||||
common/physics_interpolation=true
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
[gd_scene load_steps=2 format=3 uid="uid://cm0rohkr6khd1"]
|
[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"]
|
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_b1h2b"]
|
||||||
|
|
||||||
[node name="Module" type="Node3D"]
|
[node name="Module" type="RigidBody3D"]
|
||||||
script = ExtResource("1_b1h2b")
|
script = ExtResource("1_b1h2b")
|
||||||
mass = 1.0
|
ship_name = null
|
||||||
|
hull_integrity = null
|
||||||
|
physics_mode = null
|
||||||
|
base_mass = null
|
||||||
|
metadata/_custom_type_script = "uid://wlm40n8ywr"
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
[gd_scene load_steps=4 format=3 uid="uid://bsyufiv0m1018"]
|
[gd_scene load_steps=4 format=3 uid="uid://bsyufiv0m1018"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://wlm40n8ywr" path="res://scripts/orbital_body_2d.gd" id="1_ecow4"]
|
[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"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_ecow4"]
|
||||||
size = Vector3(1, 1, 0.02)
|
size = Vector3(1, 1, 0.02)
|
||||||
|
|
||||||
[node name="Hullplate" type="Node3D"]
|
[sub_resource type="BoxMesh" id="BoxMesh_ecow4"]
|
||||||
script = ExtResource("1_ecow4")
|
size = Vector3(1, 1, 0.02)
|
||||||
physics_mode = 2
|
|
||||||
metadata/_custom_type_script = "uid://wlm40n8ywr"
|
|
||||||
|
|
||||||
[node name="StaticBody3D" type="StaticBody3D" parent="."]
|
[node name="Hullplate" type="CollisionShape3D"]
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D"]
|
|
||||||
mesh = SubResource("BoxMesh_ecow4")
|
|
||||||
skeleton = NodePath("../..")
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
|
|
||||||
shape = SubResource("BoxShape3D_ecow4")
|
shape = SubResource("BoxShape3D_ecow4")
|
||||||
|
script = ExtResource("1_ecow4")
|
||||||
|
metadata/_custom_type_script = "uid://cxnbunw3k7s5j"
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||||
|
mesh = SubResource("BoxMesh_ecow4")
|
||||||
|
|||||||
2
scenes/ship/builder/pieces/ship_piece.gd
Normal file
2
scenes/ship/builder/pieces/ship_piece.gd
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@abstract
|
||||||
|
class_name ShipPiece extends CollisionShape3D
|
||||||
1
scenes/ship/builder/pieces/ship_piece.gd.uid
Normal file
1
scenes/ship/builder/pieces/ship_piece.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dg46wkbv2ep3h
|
||||||
@ -1 +1 @@
|
|||||||
class_name StructuralPiece extends OrbitalBody3D
|
class_name StructuralPiece extends ShipPiece
|
||||||
|
|||||||
@ -2,6 +2,7 @@ extends Area3D
|
|||||||
class_name Spawner
|
class_name Spawner
|
||||||
|
|
||||||
@onready var mp_spawner: MultiplayerSpawner = $MultiplayerSpawner
|
@onready var mp_spawner: MultiplayerSpawner = $MultiplayerSpawner
|
||||||
|
@export var disabled: bool = false
|
||||||
|
|
||||||
# This spawner will register itself with the GameManager when it enters the scene.
|
# This spawner will register itself with the GameManager when it enters the scene.
|
||||||
func _ready():
|
func _ready():
|
||||||
@ -11,6 +12,7 @@ func _ready():
|
|||||||
GameManager.register_spawner(self)
|
GameManager.register_spawner(self)
|
||||||
|
|
||||||
func can_spawn() -> bool:
|
func can_spawn() -> bool:
|
||||||
return get_overlapping_bodies().is_empty()
|
return false if disabled else get_overlapping_bodies().is_empty()
|
||||||
|
|
||||||
# We can add properties to the spawner later, like which faction it belongs to,
|
# We can add properties to the spawner later, like which faction it belongs to,
|
||||||
# or a reference to the body it's orbiting for initial velocity calculation.
|
# or a reference to the body it's orbiting for initial velocity calculation.
|
||||||
|
|||||||
@ -87,7 +87,7 @@ func apply_thrust_force():
|
|||||||
var force_vector = global_transform.basis * local_force
|
var force_vector = global_transform.basis * local_force
|
||||||
|
|
||||||
# 3. Apply the force to itself.
|
# 3. Apply the force to itself.
|
||||||
apply_force(force_vector, global_position)
|
apply_force_recursive(force_vector, global_position)
|
||||||
|
|
||||||
# func _draw():
|
# func _draw():
|
||||||
# # This function is only called if the thruster is firing (due to queue_redraw)
|
# # This function is only called if the thruster is firing (due to queue_redraw)
|
||||||
|
|||||||
@ -214,7 +214,7 @@ func _calibrate_single_thruster(thruster: Thruster) -> DataTypes.ThrusterData:
|
|||||||
|
|
||||||
# --- Calculate Performance ---
|
# --- Calculate Performance ---
|
||||||
# Torque = inertia * angular_acceleration (alpha = dw/dt)
|
# Torque = inertia * angular_acceleration (alpha = dw/dt)
|
||||||
if root_module.inertia > 0:
|
if root_module.inertia.length_squared() > 0:
|
||||||
data.measured_torque_vector = root_module.inertia * (delta_angular_velocity / test_burn_duration)
|
data.measured_torque_vector = root_module.inertia * (delta_angular_velocity / test_burn_duration)
|
||||||
else:
|
else:
|
||||||
data.measured_torque_vector = Vector3.ZERO
|
data.measured_torque_vector = Vector3.ZERO
|
||||||
@ -235,7 +235,7 @@ func _calibrate_single_thruster(thruster: Thruster) -> DataTypes.ThrusterData:
|
|||||||
|
|
||||||
|
|
||||||
# --- Cleanup: Counter the spin from the test fire ---
|
# --- Cleanup: Counter the spin from the test fire ---
|
||||||
if data.measured_torque_vector.length() > 0.001:
|
if data.measured_torque_vector.length_squared() > 0.001:
|
||||||
var counter_torque = -data.measured_torque_vector
|
var counter_torque = -data.measured_torque_vector
|
||||||
var counter_burn_duration = (root_module.inertia * root_module.angular_velocity) / counter_torque
|
var counter_burn_duration = (root_module.inertia * root_module.angular_velocity) / counter_torque
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# CharacterPawn.gd
|
# CharacterPawn.gd
|
||||||
extends CharacterBody3D
|
extends OrbitalBody3D
|
||||||
class_name CharacterPawn3D
|
class_name CharacterPawn3D
|
||||||
|
|
||||||
## Core Parameters
|
## Core Parameters
|
||||||
@ -29,7 +29,7 @@ var _pitch_yaw_input: Vector2 = Vector2.ZERO
|
|||||||
@onready var zero_g_movemement_component: ZeroGMovementComponent = $ZeroGMovementComponent
|
@onready var zero_g_movemement_component: ZeroGMovementComponent = $ZeroGMovementComponent
|
||||||
|
|
||||||
## Physics State (Managed by Pawn)
|
## Physics State (Managed by Pawn)
|
||||||
var angular_velocity: Vector3 = Vector3.ZERO
|
# var angular_velocity: Vector3 = Vector3.ZERO
|
||||||
@export var angular_damping: float = 0.95 # Base damping
|
@export var angular_damping: float = 0.95 # Base damping
|
||||||
|
|
||||||
## Other State Variables
|
## Other State Variables
|
||||||
@ -74,21 +74,14 @@ func _physics_process(delta: float):
|
|||||||
|
|
||||||
# 4. Apply Linear Velocity & Collision (Universal)
|
# 4. Apply Linear Velocity & Collision (Universal)
|
||||||
# Use move_and_slide for states affected by gravity/floor or zero-g collisions
|
# Use move_and_slide for states affected by gravity/floor or zero-g collisions
|
||||||
move_and_slide()
|
move_and_collide(linear_velocity * delta)
|
||||||
|
|
||||||
# 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
|
# 5. Reset Inputs
|
||||||
_reset_inputs()
|
_reset_inputs()
|
||||||
|
|
||||||
|
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||||
|
pass
|
||||||
|
|
||||||
# --- Universal Rotation ---
|
# --- Universal Rotation ---
|
||||||
func _apply_mouse_rotation():
|
func _apply_mouse_rotation():
|
||||||
if _pitch_yaw_input != Vector2.ZERO:
|
if _pitch_yaw_input != Vector2.ZERO:
|
||||||
@ -112,27 +105,20 @@ func _integrate_angular_velocity(delta: float):
|
|||||||
if angular_velocity.length_squared() < 0.0001:
|
if angular_velocity.length_squared() < 0.0001:
|
||||||
angular_velocity = Vector3.ZERO
|
angular_velocity = Vector3.ZERO
|
||||||
|
|
||||||
func _handle_basic_collision(collision: KinematicCollision3D):
|
# func _handle_basic_collision(collision: KinematicCollision3D):
|
||||||
var surface_normal = collision.get_normal()
|
# var surface_normal = collision.get_normal()
|
||||||
velocity = velocity.bounce(surface_normal)
|
# velocity = velocity.bounce(surface_normal)
|
||||||
velocity *= (1.0 - collision_energy_loss * 0.5)
|
# velocity *= (1.0 - collision_energy_loss * 0.5)
|
||||||
|
|
||||||
# --- Public Helper for Controllers ---
|
# --- Public Helper for Controllers ---
|
||||||
# Applies torque affecting angular velocity
|
# Applies torque affecting angular velocity
|
||||||
func add_torque(torque_global: Vector3, delta: float):
|
# func add_torque(torque_global: Vector3, delta: float):
|
||||||
# Calculate effective inertia (base + suit multiplier if applicable)
|
# # 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)
|
# 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
|
# if effective_inertia <= 0: effective_inertia = 1.0 # Safety prevent division by zero
|
||||||
# Apply change directly to angular velocity using the global torque
|
# # Apply change directly to angular velocity using the global torque
|
||||||
|
|
||||||
angular_velocity += (torque_global / effective_inertia) * delta
|
# 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) ---
|
# --- 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_movement_input(move: Vector2, roll: float, vertical: float): _move_input = move; _roll_input = roll; _vertical_input = vertical
|
||||||
|
|||||||
@ -23,7 +23,7 @@ properties/2/path = NodePath("CameraPivot:rotation")
|
|||||||
properties/2/spawn = true
|
properties/2/spawn = true
|
||||||
properties/2/replication_mode = 2
|
properties/2/replication_mode = 2
|
||||||
|
|
||||||
[node name="CharacterPawn3D" type="CharacterBody3D"]
|
[node name="CharacterPawn3D" type="RigidBody3D"]
|
||||||
script = ExtResource("1_4frsu")
|
script = ExtResource("1_4frsu")
|
||||||
metadata/_custom_type_script = "uid://cdmmiixa75f3x"
|
metadata/_custom_type_script = "uid://cdmmiixa75f3x"
|
||||||
|
|
||||||
@ -44,6 +44,7 @@ top_level = true
|
|||||||
spring_length = 3.0
|
spring_length = 3.0
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot/SpringArm"]
|
[node name="Camera3D" type="Camera3D" parent="CameraPivot/SpringArm"]
|
||||||
|
current = true
|
||||||
far = 200000.0
|
far = 200000.0
|
||||||
|
|
||||||
[node name="GripDetector" type="Area3D" parent="."]
|
[node name="GripDetector" type="Area3D" parent="."]
|
||||||
|
|||||||
@ -6,9 +6,9 @@ class_name EVAMovementComponent
|
|||||||
var pawn: CharacterPawn3D
|
var pawn: CharacterPawn3D
|
||||||
|
|
||||||
## EVA Parameters (Moved from ZeroGPawn)
|
## EVA Parameters (Moved from ZeroGPawn)
|
||||||
@export var orientation_speed: float = 2.0 # Used for orienting body to camera
|
@export var orientation_speed: float = 20.0 # Used for orienting body to camera
|
||||||
@export var move_speed: float = 2.0
|
@export var linear_acceleration: float = 20.0
|
||||||
@export var roll_torque: float = 2.5
|
@export var roll_torque_acceleration: float = 2.5
|
||||||
@export var angular_damping: float = 0.95 # Base damping applied by pawn, suit might add more?
|
@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 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_kp: float = 5.0
|
||||||
@ -58,19 +58,11 @@ func apply_thrusters(pawn: CharacterPawn3D, delta: float, move_input: Vector2, v
|
|||||||
var combined_move_dir = move_dir_horizontal + move_dir_vertical
|
var combined_move_dir = move_dir_horizontal + move_dir_vertical
|
||||||
|
|
||||||
if combined_move_dir != Vector3.ZERO:
|
if combined_move_dir != Vector3.ZERO:
|
||||||
pawn.velocity += combined_move_dir.normalized() * move_speed * delta
|
pawn.apply_central_force(combined_move_dir * linear_acceleration * delta)
|
||||||
|
|
||||||
# Apply Roll Torque
|
# Apply Roll Torque
|
||||||
var roll_torque_global = -pawn.global_basis.z * (roll_input) * roll_torque # Sign fixed
|
var roll_torque_global = -pawn.basis.z * (roll_input) * roll_torque_acceleration * delta # Sign fixed
|
||||||
pawn.add_torque(roll_torque_global, delta)
|
pawn.apply_torque(roll_torque_global)
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
## Called by Pawn when entering FLOATING state with suit
|
## Called by Pawn when entering FLOATING state with suit
|
||||||
func on_enter_state():
|
func on_enter_state():
|
||||||
@ -94,13 +86,13 @@ func _apply_floating_movement(delta: float, move_input: Vector2, vertical_input:
|
|||||||
var combined_move_dir = move_dir_horizontal + move_dir_vertical
|
var combined_move_dir = move_dir_horizontal + move_dir_vertical
|
||||||
|
|
||||||
if combined_move_dir != Vector3.ZERO:
|
if combined_move_dir != Vector3.ZERO:
|
||||||
pawn.velocity += combined_move_dir.normalized() * move_speed * delta
|
pawn.apply_central_force(combined_move_dir.normalized() * linear_acceleration * delta)
|
||||||
# --- Apply Roll Torque ---
|
# --- Apply Roll Torque ---
|
||||||
# Calculate torque magnitude based on input
|
# Calculate torque magnitude based on input
|
||||||
var roll_torque_vector = pawn.transform.basis.z * (-roll_input) * roll_torque
|
var roll_acceleration = pawn.basis.z * (-roll_input) * roll_torque_acceleration * delta
|
||||||
|
|
||||||
# Apply the global torque vector using the pawn's helper function
|
# Apply the global torque vector using the pawn's helper function
|
||||||
pawn.add_torque(roll_torque_vector, delta)
|
pawn.apply_torque(roll_acceleration)
|
||||||
|
|
||||||
|
|
||||||
# --- Auto-Orientation Logic ---
|
# --- Auto-Orientation Logic ---
|
||||||
@ -112,8 +104,8 @@ func _orient_pawn(delta: float):
|
|||||||
|
|
||||||
# --- THE FIX: Adjust how target_up is calculated ---
|
# --- THE FIX: Adjust how target_up is calculated ---
|
||||||
# Calculate velocity components relative to camera orientation
|
# Calculate velocity components relative to camera orientation
|
||||||
var _forward_velocity_component = pawn.velocity.dot(target_forward)
|
# var _forward_velocity_component = pawn.velocity.dot(target_forward)
|
||||||
var _right_velocity_component = pawn.velocity.dot(pawn.camera_anchor.global_basis.x)
|
# var _right_velocity_component = pawn.velocity.dot(pawn.camera_anchor.global_basis.x)
|
||||||
|
|
||||||
# Only apply strong "feet trailing" if significant forward/backward movement dominates
|
# Only apply strong "feet trailing" if significant forward/backward movement dominates
|
||||||
# and we are actually moving.
|
# and we are actually moving.
|
||||||
|
|||||||
@ -15,7 +15,8 @@ var nearby_grips: Array[GripArea3D] = []
|
|||||||
@export var reach_orient_speed: float = 10.0 # Speed pawn orients to grip
|
@export var reach_orient_speed: float = 10.0 # Speed pawn orients to grip
|
||||||
|
|
||||||
# --- Grip damping parameters ---
|
# --- Grip damping parameters ---
|
||||||
@export var gripping_linear_damping: float = 5.0 # How quickly velocity stops
|
@export var gripping_linear_damping: float = 50.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 = 5.0 # How quickly spin 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_orient_speed: float = 2.0 # How quickly pawn rotates to face grip
|
||||||
|
|
||||||
@ -32,8 +33,8 @@ var next_grip_target: GripArea3D = null # The grip we are trying to transition t
|
|||||||
# --- Seeking Climb State ---
|
# --- Seeking Climb State ---
|
||||||
var _seeking_climb_input: Vector2 = Vector2.ZERO # The move_input held when seeking started
|
var _seeking_climb_input: Vector2 = Vector2.ZERO # The move_input held when seeking started
|
||||||
|
|
||||||
@export var launch_charge_rate: float = 20.0
|
@export var launch_charge_rate: float = 1.5
|
||||||
@export var max_launch_speed: float = 15.0
|
@export var max_launch_speed: float = 4.0
|
||||||
var launch_direction: Vector3 = Vector3.ZERO
|
var launch_direction: Vector3 = Vector3.ZERO
|
||||||
var launch_charge: float = 0.0
|
var launch_charge: float = 0.0
|
||||||
|
|
||||||
@ -87,19 +88,13 @@ func process_movement(delta: float, move_input: Vector2, vertical_input: float,
|
|||||||
_handle_launch_charge(delta)
|
_handle_launch_charge(delta)
|
||||||
|
|
||||||
|
|
||||||
## 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 ===
|
# === STATE MACHINE ===
|
||||||
func _on_enter_state(state : MovementState):
|
func _on_enter_state(state : MovementState):
|
||||||
print("ZeroGMovementComponent activated for state: ", MovementState.keys()[state])
|
print("ZeroGMovementComponent activated for state: ", MovementState.keys()[state])
|
||||||
if state == MovementState.GRIPPING:
|
# TODO: Use forces to match velocity to grip
|
||||||
pawn.velocity = Vector3.ZERO
|
# if state == MovementState.GRIPPING:
|
||||||
pawn.angular_velocity = Vector3.ZERO
|
# pawn.velocity = Vector3.ZERO
|
||||||
|
# pawn.angular_velocity = Vector3.ZERO
|
||||||
# else: # e.g., REACHING_MOVE?
|
# else: # e.g., REACHING_MOVE?
|
||||||
# state = MovementState.IDLE # Or SEARCHING?
|
# state = MovementState.IDLE # Or SEARCHING?
|
||||||
|
|
||||||
@ -205,17 +200,26 @@ func _apply_grip_physics(delta: float, _move_input: Vector2, roll_input: float):
|
|||||||
# --- 2. Apply Linear Force (PD Controller) ---
|
# --- 2. Apply Linear Force (PD Controller) ---
|
||||||
var error_pos = target_position - pawn.global_position
|
var error_pos = target_position - pawn.global_position
|
||||||
# Simple P-controller for velocity (acts as a spring)
|
# Simple P-controller for velocity (acts as a spring)
|
||||||
var target_velocity_pos = error_pos * gripping_linear_damping # 'damping' here acts as Kp
|
|
||||||
|
# We get the force from the PD controller and apply it as acceleration.
|
||||||
|
var force = MotionUtils.calculate_pd_position_force(
|
||||||
|
target_position,
|
||||||
|
pawn.global_position,
|
||||||
|
pawn.linear_velocity, # Use linear_velocity (from RigidBody3D)
|
||||||
|
gripping_linear_damping, # Kp
|
||||||
|
gripping_linear_damping # Kd
|
||||||
|
)
|
||||||
|
|
||||||
# Simple D-controller (damping)
|
# Simple D-controller (damping)
|
||||||
target_velocity_pos -= pawn.velocity * gripping_angular_damping # 'angular_damping' here acts as Kd
|
# target_velocity_pos -= pawn.linear_velocity * gripping_angular_damping # 'angular_damping' here acts as Kd
|
||||||
# Apply force via acceleration
|
# TODO: Add less force the smaller error_pos is to stop ocillating around target pos
|
||||||
pawn.velocity = pawn.velocity.lerp(target_velocity_pos, delta * 10.0) # Smoothly apply correction
|
pawn.apply_central_force((force / pawn.mass) * delta)
|
||||||
|
|
||||||
# --- 3. Apply Angular Force (PD Controller) ---
|
# --- 3. Apply Angular Force (PD Controller) ---
|
||||||
if not is_zero_approx(roll_input):
|
if not is_zero_approx(roll_input):
|
||||||
# Manual Roll Input (applies torque)
|
# Manual Roll Input (applies torque)
|
||||||
var roll_torque_global = pawn.global_transform.basis.z * (-roll_input) * gripping_orient_speed # Use global Z
|
var roll_torque_global = pawn.global_transform.basis.z * (-roll_input) * gripping_orient_speed # Use global Z
|
||||||
pawn.add_torque(roll_torque_global, delta)
|
pawn.apply_torque(roll_torque_global * delta)
|
||||||
else:
|
else:
|
||||||
# Auto-Orient (PD Controller)
|
# Auto-Orient (PD Controller)
|
||||||
_apply_orientation_torque(target_basis, delta)
|
_apply_orientation_torque(target_basis, delta)
|
||||||
@ -245,7 +249,9 @@ func _apply_climb_physics(delta: float, move_input: Vector2):
|
|||||||
|
|
||||||
# 5. Apply Movement Force
|
# 5. Apply Movement Force
|
||||||
var target_velocity = climb_direction * climb_speed
|
var target_velocity = climb_direction * climb_speed
|
||||||
pawn.velocity = pawn.velocity.lerp(target_velocity, delta * climb_acceleration)
|
var error_vel = target_velocity - pawn.linear_velocity
|
||||||
|
var force = error_vel * climb_acceleration # Kp = climb_acceleration
|
||||||
|
pawn.apply_central_force(force * delta)
|
||||||
|
|
||||||
# 6. Apply Angular Force (Auto-Orient to current grip)
|
# 6. Apply Angular Force (Auto-Orient to current grip)
|
||||||
var grip_base_transform = current_grip.global_transform
|
var grip_base_transform = current_grip.global_transform
|
||||||
@ -266,7 +272,6 @@ func _process_seeking_climb(_delta: float, move_input: Vector2):
|
|||||||
# No grip found. Transition to IDLE.
|
# No grip found. Transition to IDLE.
|
||||||
print("Seeking Climb ended, no grip found. Reverting to IDLE.")
|
print("Seeking Climb ended, no grip found. Reverting to IDLE.")
|
||||||
|
|
||||||
|
|
||||||
# --- Grip Helpers
|
# --- Grip Helpers
|
||||||
|
|
||||||
## The single, authoritative function for grabbing a grip.
|
## The single, authoritative function for grabbing a grip.
|
||||||
@ -410,7 +415,8 @@ func _start_climb(move_input: Vector2):
|
|||||||
|
|
||||||
func _stop_climb(release_grip: bool):
|
func _stop_climb(release_grip: bool):
|
||||||
# print("ZeroGMoveController: Stopping Climb. Release Grip: ", release_grip)
|
# 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
|
next_grip_target = null
|
||||||
if release_grip:
|
if release_grip:
|
||||||
_release_current_grip() # Transitions to IDLE
|
_release_current_grip() # Transitions to IDLE
|
||||||
@ -418,21 +424,15 @@ func _stop_climb(release_grip: bool):
|
|||||||
current_state = MovementState.GRIPPING # Go back to stationary gripping
|
current_state = MovementState.GRIPPING # Go back to stationary gripping
|
||||||
|
|
||||||
func _apply_orientation_torque(target_basis: Basis, delta: float):
|
func _apply_orientation_torque(target_basis: Basis, delta: float):
|
||||||
var current_quat = pawn.global_transform.basis.get_rotation_quaternion()
|
var torque = MotionUtils.calculate_pd_rotation_torque(
|
||||||
var target_quat = target_basis.get_rotation_quaternion()
|
target_basis,
|
||||||
var error_quat = target_quat * current_quat.inverse()
|
pawn.global_basis,
|
||||||
|
pawn.angular_velocity, # Use angular_velocity (from RigidBody3D)
|
||||||
|
gripping_orient_speed, # Kp
|
||||||
|
gripping_orient_speed # Kd
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure we take the shortest path for rotation. If W is negative, the
|
pawn.apply_torque(torque * delta)
|
||||||
# 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)
|
|
||||||
|
|
||||||
# --- Launch helpers ---
|
# --- Launch helpers ---
|
||||||
func _start_charge(move_input: Vector2):
|
func _start_charge(move_input: Vector2):
|
||||||
@ -452,20 +452,19 @@ func _start_charge(move_input: Vector2):
|
|||||||
|
|
||||||
func _handle_launch_charge(delta: float):
|
func _handle_launch_charge(delta: float):
|
||||||
launch_charge = min(launch_charge + launch_charge_rate * delta, max_launch_speed)
|
launch_charge = min(launch_charge + launch_charge_rate * delta, max_launch_speed)
|
||||||
pawn.velocity = Vector3.ZERO
|
|
||||||
pawn.angular_velocity = Vector3.ZERO
|
|
||||||
|
|
||||||
func _execute_launch(move_input: Vector2):
|
func _execute_launch(move_input: Vector2):
|
||||||
if not is_instance_valid(current_grip): return # Safety check
|
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
|
launch_charge = 0.0
|
||||||
_release_current_grip(move_input) # Release AFTER calculating direction
|
_release_current_grip(move_input) # Release AFTER calculating direction
|
||||||
|
pawn.apply_impulse(launch_direction * launch_charge)
|
||||||
|
|
||||||
# Instead of going to IDLE, go to SEEKING_CLIMB to find the next grip.
|
# 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.
|
# 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)
|
# _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
|
# current_state = MovementState.SEEKING_CLIMB
|
||||||
print("ZeroGMovementComponent: Launched with speed ", pawn.velocity.length(), " and now SEEKING_CLIMB")
|
print("ZeroGMovementComponent: Launched with speed ", pawn.linear_velocity.length(), " and now SEEKING_CLIMB")
|
||||||
|
|
||||||
|
|
||||||
# --- Signal Handlers ---
|
# --- Signal Handlers ---
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
# orbital_body_3d.gd
|
# orbital_body_3d.gd
|
||||||
# REFACTOR: Extends Node3D instead of Node2D
|
# REFACTOR: Extends Node3D instead of Node2D
|
||||||
class_name OrbitalBody3D
|
class_name OrbitalBody3D extends RigidBody3D
|
||||||
extends Node3D
|
|
||||||
|
|
||||||
# Defines the physical behavior of this body.
|
# Defines the physical behavior of this body.
|
||||||
enum PhysicsMode {
|
enum PhysicsMode {
|
||||||
@ -16,11 +15,11 @@ var current_grid_authority: OrbitalBody3D = null
|
|||||||
|
|
||||||
# Mass of this individual component
|
# Mass of this individual component
|
||||||
@export var base_mass: float = 1.0
|
@export var base_mass: float = 1.0
|
||||||
@export var mass: float = 0.0 # Aggregated mass of this body and all its OrbitalBody3D children
|
# @export var mass: float = 0.0 # Aggregated mass of this body and all its OrbitalBody3D children
|
||||||
|
|
||||||
# REFACTOR: All physics properties are now Vector3
|
# REFACTOR: All physics properties are now Vector3
|
||||||
@export var linear_velocity: Vector3 = Vector3.ZERO
|
# @export var linear_velocity: Vector3 = Vector3.ZERO
|
||||||
@export var angular_velocity: Vector3 = Vector3.ZERO # Represents angular velocity around X, Y, and Z axes
|
# @export var angular_velocity: Vector3 = Vector3.ZERO # Represents angular velocity around X, Y, and Z axes
|
||||||
|
|
||||||
# Variables to accumulate forces applied during the current physics frame
|
# Variables to accumulate forces applied during the current physics frame
|
||||||
var accumulated_force: Vector3 = Vector3.ZERO
|
var accumulated_force: Vector3 = Vector3.ZERO
|
||||||
@ -30,15 +29,21 @@ var accumulated_torque: Vector3 = Vector3.ZERO
|
|||||||
# REFACTOR: This is a simplification. For true 3D physics, this would be an
|
# REFACTOR: This is a simplification. For true 3D physics, this would be an
|
||||||
# inertia tensor (a Basis). But for game physics, a single float
|
# inertia tensor (a Basis). But for game physics, a single float
|
||||||
# (like your CharacterPawn3D) is much simpler to work with.
|
# (like your CharacterPawn3D) is much simpler to work with.
|
||||||
@export var inertia: float = 1.0
|
# @export var inertia: float = 1.0
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
freeze_mode = FreezeMode.FREEZE_MODE_KINEMATIC
|
||||||
|
if physics_mode == PhysicsMode.ANCHORED:
|
||||||
|
freeze = true
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
recalculate_physical_properties()
|
recalculate_physical_properties()
|
||||||
set_physics_process(not Engine.is_editor_hint())
|
set_physics_process(not Engine.is_editor_hint())
|
||||||
|
|
||||||
# --- PUBLIC FORCE APPLICATION METHODS ---
|
# --- PUBLIC FORCE APPLICATION METHODS ---
|
||||||
# REFACTOR: All arguments are now Vector3
|
# REFACTOR: All arguments are now Vector3
|
||||||
func apply_force(force: Vector3, pos: Vector3 = self.global_position):
|
func apply_force_recursive(force: Vector3, pos: Vector3 = self.global_position):
|
||||||
# This is the force routing logic.
|
# This is the force routing logic.
|
||||||
match physics_mode:
|
match physics_mode:
|
||||||
PhysicsMode.INDEPENDENT:
|
PhysicsMode.INDEPENDENT:
|
||||||
@ -50,14 +55,14 @@ func apply_force(force: Vector3, pos: Vector3 = self.global_position):
|
|||||||
var p = get_parent()
|
var p = get_parent()
|
||||||
while p:
|
while p:
|
||||||
if p is OrbitalBody3D:
|
if p is OrbitalBody3D:
|
||||||
# Recursively call the parent's apply_force method.
|
# Recursively call the parent's apply_force_recursive method.
|
||||||
p.apply_force(force, pos)
|
p.apply_force_recursive(force, pos)
|
||||||
return # Stop at the first OrbitalBody3D parent
|
return # Stop at the first OrbitalBody3D parent
|
||||||
p = p.get_parent()
|
p = p.get_parent()
|
||||||
|
|
||||||
push_error("Anchored OrbitalBody3D has become dislodged and is now Composite.")
|
push_error("Anchored OrbitalBody3D has become dislodged and is now Composite.")
|
||||||
physics_mode = PhysicsMode.COMPOSITE
|
physics_mode = PhysicsMode.COMPOSITE
|
||||||
apply_force(force, position)
|
apply_force_recursive(force, position)
|
||||||
|
|
||||||
func _add_forces(force: Vector3, pos: Vector3 = Vector3.ZERO):
|
func _add_forces(force: Vector3, pos: Vector3 = Vector3.ZERO):
|
||||||
# If we are the root, accumulate the force and calculate torque on the total body.
|
# If we are the root, accumulate the force and calculate torque on the total body.
|
||||||
@ -80,49 +85,43 @@ func _update_mass_and_inertia():
|
|||||||
print("Node: %s, Mass: %f" % [self, mass])
|
print("Node: %s, Mass: %f" % [self, mass])
|
||||||
|
|
||||||
func _physics_process(delta):
|
func _physics_process(delta):
|
||||||
if not Engine.is_editor_hint():
|
pass
|
||||||
match physics_mode:
|
# if not Engine.is_editor_hint():
|
||||||
PhysicsMode.INDEPENDENT:
|
# match physics_mode:
|
||||||
_integrate_forces(delta)
|
# PhysicsMode.INDEPENDENT:
|
||||||
PhysicsMode.COMPOSITE:
|
# _integrate_forces(delta)
|
||||||
_integrate_forces(delta)
|
# PhysicsMode.COMPOSITE:
|
||||||
|
# _integrate_forces(delta)
|
||||||
|
|
||||||
func _integrate_forces(delta):
|
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||||
# Safety Check for Division by Zero
|
# Safety Check for Division by Zero
|
||||||
var sim_mass = mass
|
if mass <= 0.0:
|
||||||
if sim_mass <= 0.0:
|
|
||||||
accumulated_force = Vector3.ZERO
|
accumulated_force = Vector3.ZERO
|
||||||
accumulated_torque = Vector3.ZERO
|
accumulated_torque = Vector3.ZERO
|
||||||
return
|
return
|
||||||
|
|
||||||
# 3. Apply Linear Physics (F = ma)
|
# 3. Apply Linear Physics (F = ma)
|
||||||
var linear_acceleration = accumulated_force / sim_mass # Division is now safe
|
# var linear_acceleration = accumulated_force / mass # Division is now safe
|
||||||
linear_velocity += linear_acceleration * delta
|
state.apply_central_force(accumulated_force)
|
||||||
global_position += linear_velocity * delta
|
|
||||||
|
|
||||||
# 4. Apply Rotational Physics (T = I * angular_acceleration)
|
# 4. Apply Rotational Physics (T = I * angular_acceleration)
|
||||||
# REFACTOR: Use the simplified 3D torque equation from your CharacterPawn3D
|
# REFACTOR: Use the simplified 3D torque equation from your CharacterPawn3D
|
||||||
if inertia > 0:
|
#if inertia.length() > 0:
|
||||||
var angular_acceleration = accumulated_torque / inertia
|
var angular_acceleration = accumulated_torque / inertia
|
||||||
angular_velocity += angular_acceleration * delta
|
# print("Inertia for %s: %s" % [self, inertia])
|
||||||
|
# print("Angular Acceleration for %s: %s" % [self, angular_acceleration])
|
||||||
# REFACTOR: Apply 3D rotation using the integrated angular velocity
|
# angular_velocity += angular_acceleration * state.step
|
||||||
# (This is the same method your CharacterPawn3D uses)
|
|
||||||
if angular_velocity.length_squared() > 0.0001:
|
|
||||||
rotate(angular_velocity.normalized(), angular_velocity.length() * delta)
|
|
||||||
|
|
||||||
# Optional: Add damping
|
|
||||||
# angular_velocity *= (1.0 - 0.1 * delta)
|
|
||||||
|
|
||||||
# 5. Reset accumulated forces for the next frame
|
# 5. Reset accumulated forces for the next frame
|
||||||
accumulated_force = Vector3.ZERO
|
accumulated_force = Vector3.ZERO
|
||||||
accumulated_torque = Vector3.ZERO
|
accumulated_torque = Vector3.ZERO
|
||||||
|
|
||||||
|
|
||||||
func recalculate_physical_properties():
|
func recalculate_physical_properties():
|
||||||
if physics_mode != PhysicsMode.COMPOSITE:
|
if physics_mode != PhysicsMode.COMPOSITE:
|
||||||
mass = base_mass
|
mass = base_mass
|
||||||
if inertia <= 0.0:
|
if inertia == Vector3.ZERO:
|
||||||
inertia = 1.0
|
inertia = Vector3(1.0, 1.0, 1.0)
|
||||||
return
|
return
|
||||||
|
|
||||||
var all_parts: Array[OrbitalBody3D] = []
|
var all_parts: Array[OrbitalBody3D] = []
|
||||||
@ -130,7 +129,7 @@ func recalculate_physical_properties():
|
|||||||
|
|
||||||
if all_parts.is_empty():
|
if all_parts.is_empty():
|
||||||
mass = base_mass
|
mass = base_mass
|
||||||
inertia = 1.0
|
inertia = Vector3(1.0, 1.0, 1.0)
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
||||||
@ -147,7 +146,7 @@ func recalculate_physical_properties():
|
|||||||
local_center_of_mass = weighted_local_pos_sum / total_mass
|
local_center_of_mass = weighted_local_pos_sum / total_mass
|
||||||
|
|
||||||
# --- Step 2: Calculate Total Moment of Inertia around the LOCAL CoM ---
|
# --- Step 2: Calculate Total Moment of Inertia around the LOCAL CoM ---
|
||||||
var total_inertia = 0.0
|
var total_inertia: Vector3 = Vector3.ZERO
|
||||||
for part in all_parts:
|
for part in all_parts:
|
||||||
var local_pos = part.global_position - self.global_position
|
var local_pos = part.global_position - self.global_position
|
||||||
# REFACTOR: This logic (Parallel Axis Theorem) is still correct for Vector3
|
# REFACTOR: This logic (Parallel Axis Theorem) is still correct for Vector3
|
||||||
@ -156,13 +155,13 @@ func recalculate_physical_properties():
|
|||||||
|
|
||||||
# --- Step 3: Assign the final values ---
|
# --- Step 3: Assign the final values ---
|
||||||
self.mass = total_mass
|
self.mass = total_mass
|
||||||
self.inertia = total_inertia * 0.01
|
self.inertia = total_inertia * 0.01
|
||||||
if self.inertia <= 0.0: # Safety check
|
if self.inertia == Vector3.ZERO: # Safety check
|
||||||
self.inertia = 1.0
|
inertia = Vector3(1.0, 1.0, 1.0)
|
||||||
|
|
||||||
# A recursive helper function to get an array of all OrbitalBody3D children
|
# A recursive helper function to get an array of all OrbitalBody3D children
|
||||||
func _collect_anchored_parts(parts_array: Array):
|
func _collect_anchored_parts(parts_array: Array):
|
||||||
parts_array.append(self)
|
parts_array.append(self)
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if child is OrbitalBody3D and child.physics_mode == PhysicsMode.ANCHORED:
|
if child is OrbitalBody3D and child.physics_mode == PhysicsMode.ANCHORED:
|
||||||
child._collect_anchored_parts(parts_array)
|
child._collect_anchored_parts(parts_array)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[gd_resource type="Resource" script_class="GameConfig" load_steps=5 format=3 uid="uid://cv15sck8rl2b7"]
|
[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://chgycmkkaf7jv" path="res://scenes/characters/pilot_ball.tscn" id="1_s0mxw"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bkwogkfqk2uxo" path="res://modules/3d_test_ship.tscn" id="2_75b4c"]
|
[ext_resource type="PackedScene" uid="uid://xcgmicfdqqb1" path="res://modules/physics_testing_ship.tscn" id="2_75b4c"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dnre6svquwdtb" path="res://scenes/characters/player_controller.tscn" id="2_sk8k5"]
|
[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"]
|
[ext_resource type="Script" uid="uid://bfc6u1f8sigxj" path="res://scripts/singletons/game_config.gd" id="3_75b4c"]
|
||||||
|
|
||||||
|
|||||||
97
scripts/singletons/motion_utils.gd
Normal file
97
scripts/singletons/motion_utils.gd
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
## Calculates the required delta-v vector to match a target's velocity.
|
||||||
|
func get_velocity_match_delta_v(
|
||||||
|
current_velocity: Vector3,
|
||||||
|
target_velocity: Vector3
|
||||||
|
) -> Vector3:
|
||||||
|
# The required change in velocity is simply the difference.
|
||||||
|
return target_velocity - current_velocity
|
||||||
|
|
||||||
|
|
||||||
|
## Calculates the torque required to rotate to a target basis and stop.
|
||||||
|
## (A spring-damper system for rotation)
|
||||||
|
func calculate_pd_rotation_torque(
|
||||||
|
target_basis: Basis,
|
||||||
|
current_basis: Basis,
|
||||||
|
current_angular_velocity: Vector3,
|
||||||
|
kp: float, # Proportional gain (the "spring" stiffness)
|
||||||
|
kd: float # Derivative gain (the "damper" strength)
|
||||||
|
) -> Vector3:
|
||||||
|
|
||||||
|
# Find the shortest rotational "error"
|
||||||
|
var current_quat = current_basis.get_rotation_quaternion()
|
||||||
|
var target_quat = target_basis.get_rotation_quaternion()
|
||||||
|
var error_quat = target_quat * current_quat.inverse()
|
||||||
|
if error_quat.w < 0:
|
||||||
|
error_quat = -error_quat
|
||||||
|
|
||||||
|
var error_angle = error_quat.get_angle()
|
||||||
|
var error_axis = error_quat.get_axis()
|
||||||
|
|
||||||
|
# Safety check: if we are already aligned, do nothing
|
||||||
|
if error_axis.is_zero_approx():
|
||||||
|
# Return a torque that damps the current spin
|
||||||
|
return -current_angular_velocity * kd
|
||||||
|
|
||||||
|
# P-Term (Spring): Applies torque to correct the angle
|
||||||
|
var torque_p = error_axis.normalized() * error_angle * kp
|
||||||
|
|
||||||
|
# D-Term (Damper): Applies torque to stop the current spin
|
||||||
|
var torque_d = -current_angular_velocity * kd
|
||||||
|
|
||||||
|
return torque_p + torque_d
|
||||||
|
|
||||||
|
|
||||||
|
## Calculates the torque required to aim at a target position and track it.
|
||||||
|
## This is a high-level function that uses the PD rotation controller.
|
||||||
|
func calculate_tracking_torque(
|
||||||
|
tracker_transform: Transform3D,
|
||||||
|
target_global_position: Vector3,
|
||||||
|
current_angular_velocity: Vector3,
|
||||||
|
tracker_up_vector: Vector3,
|
||||||
|
kp: float, # How "strong" the tracker's motors are
|
||||||
|
kd: float # How "damped" the tracker's motors are
|
||||||
|
) -> Vector3:
|
||||||
|
|
||||||
|
# 1. Determine the direction we *want* to look
|
||||||
|
var look_at_direction = tracker_transform.origin.direction_to(target_global_position)
|
||||||
|
|
||||||
|
# Safety check (if target is at our origin)
|
||||||
|
if look_at_direction.is_zero_approx():
|
||||||
|
return Vector3.ZERO
|
||||||
|
|
||||||
|
# 2. Calculate the desired orientation
|
||||||
|
var target_basis = Basis.looking_at(look_at_direction, tracker_up_vector)
|
||||||
|
|
||||||
|
# 3. Use the generic PD controller to get the torque needed
|
||||||
|
return calculate_pd_rotation_torque(
|
||||||
|
target_basis,
|
||||||
|
tracker_transform.basis,
|
||||||
|
current_angular_velocity,
|
||||||
|
kp,
|
||||||
|
kd
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Calculates the force required to move to a target position and stop.
|
||||||
|
## (A spring-damper system)
|
||||||
|
func calculate_pd_position_force(
|
||||||
|
target_pos: Vector3,
|
||||||
|
current_pos: Vector3,
|
||||||
|
current_velocity: Vector3,
|
||||||
|
kp: float, # Proportional gain (the "spring" stiffness)
|
||||||
|
kd: float # Derivative gain (the "damper" strength)
|
||||||
|
) -> Vector3:
|
||||||
|
|
||||||
|
# P-Term (Spring): Pulls you towards the target
|
||||||
|
# The force is proportional to the distance (error)
|
||||||
|
var error_pos = target_pos - current_pos
|
||||||
|
var force_p = error_pos * kp
|
||||||
|
|
||||||
|
# D-Term (Damper): Pushes against your current velocity
|
||||||
|
# This stops you from overshooting and oscillating
|
||||||
|
var force_d = -current_velocity * kd
|
||||||
|
|
||||||
|
# The final force is the sum of the spring and the damper
|
||||||
|
return force_p + force_d
|
||||||
1
scripts/singletons/motion_utils.gd.uid
Normal file
1
scripts/singletons/motion_utils.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://c465dh6n0s7hy
|
||||||
@ -29,7 +29,7 @@ func _physics_process(_delta: float) -> void:
|
|||||||
apply_n_body_forces(system_attractors)
|
apply_n_body_forces(system_attractors)
|
||||||
|
|
||||||
for star_orbiter in star_system.get_orbital_bodies():
|
for star_orbiter in star_system.get_orbital_bodies():
|
||||||
star_orbiter.apply_force(calculate_n_body_force(star_orbiter, top_level_bodies))
|
star_orbiter.apply_force_recursive(calculate_n_body_force(star_orbiter, top_level_bodies))
|
||||||
|
|
||||||
func calculate_gravitational_force(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> Vector3:
|
func calculate_gravitational_force(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> Vector3:
|
||||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
||||||
@ -65,8 +65,8 @@ func apply_n_body_forces(attractors: Array[OrbitalBody3D]):
|
|||||||
var force_vector = calculate_gravitational_force(body_a, body_b)
|
var force_vector = calculate_gravitational_force(body_a, body_b)
|
||||||
|
|
||||||
if force_vector != Vector3.ZERO:
|
if force_vector != Vector3.ZERO:
|
||||||
body_a.apply_force(force_vector)
|
body_a.apply_force_recursive(force_vector)
|
||||||
body_b.apply_force(-force_vector)
|
body_b.apply_force_recursive(-force_vector)
|
||||||
|
|
||||||
func calculate_n_body_force(body: OrbitalBody3D, attractors: Array[OrbitalBody3D]) -> Vector3:
|
func calculate_n_body_force(body: OrbitalBody3D, attractors: Array[OrbitalBody3D]) -> Vector3:
|
||||||
var total_pull: Vector3 = Vector3.ZERO
|
var total_pull: Vector3 = Vector3.ZERO
|
||||||
@ -123,19 +123,19 @@ func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody3D) -> PackedVecto
|
|||||||
|
|
||||||
var path_points = PackedVector3Array()
|
var path_points = PackedVector3Array()
|
||||||
|
|
||||||
for i in range(num_steps):
|
# for i in range(num_steps):
|
||||||
var ghost_body = OrbitalBody3D.new()
|
# var ghost_body = OrbitalBody3D.new()
|
||||||
ghost_body.global_position = ghost_position
|
# ghost_body.global_position = ghost_position
|
||||||
ghost_body.mass = body_to_trace.mass
|
# ghost_body.mass = body_to_trace.mass
|
||||||
|
|
||||||
var total_force = calculate_n_body_gravity_forces(ghost_body)
|
# var total_force = calculate_n_body_gravity_forces(ghost_body)
|
||||||
var acceleration = total_force / ghost_body.mass
|
# var acceleration = total_force / ghost_body.mass
|
||||||
|
|
||||||
ghost_velocity += acceleration * time_step
|
# ghost_velocity += acceleration * time_step
|
||||||
ghost_position += ghost_velocity * time_step
|
# ghost_position += ghost_velocity * time_step
|
||||||
path_points.append(ghost_position)
|
# path_points.append(ghost_position)
|
||||||
|
|
||||||
ghost_body.free()
|
# ghost_body.free()
|
||||||
|
|
||||||
return path_points
|
return path_points
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user