WIP Almost working reworked N-body simulation
This commit is contained in:
24
main.tscn
24
main.tscn
@ -1,24 +1,6 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dogqi2c58qdc0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j3j483itissq" path="res://scripts/star_system_generator.gd" id="1_h2yge"]
|
||||
[ext_resource type="PackedScene" uid="uid://5uqp4amjj7ww" path="res://scenes/celestial_bodies/star.tscn" id="2_7mycd"]
|
||||
[ext_resource type="PackedScene" uid="uid://clt4qlsjcfgln" path="res://scenes/celestial_bodies/planet.tscn" id="3_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://74ppvxcw8an4" path="res://scenes/celestial_bodies/moon.tscn" id="4_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://dm3s33o4xhqfv" path="res://scenes/celestial_bodies/station.tscn" id="5_kek77"]
|
||||
[ext_resource type="PackedScene" uid="uid://bawsujtlpmh5r" path="res://scenes/celestial_bodies/asteroid.tscn" id="6_4c57u"]
|
||||
[ext_resource type="PackedScene" uid="uid://didt2nsdtbmra" path="res://modules/Tube.tscn" id="7_272bh"]
|
||||
[ext_resource type="Script" uid="uid://bkcouefvi7iup" path="res://scripts/star_system.gd" id="1_ig7tw"]
|
||||
|
||||
[node name="StarSystem" type="Node2D"]
|
||||
script = ExtResource("1_h2yge")
|
||||
min_planets = 1
|
||||
max_planets = 4
|
||||
max_moons = 10
|
||||
min_asteroid_belts = 0
|
||||
max_asteroid_belts = 0
|
||||
max_star_stations = 0
|
||||
star_scene = ExtResource("2_7mycd")
|
||||
planet_scene = ExtResource("3_272bh")
|
||||
moon_scene = ExtResource("4_5vw27")
|
||||
station_scene = ExtResource("5_kek77")
|
||||
asteroid_scene = ExtResource("6_4c57u")
|
||||
spaceship_scene = ExtResource("7_272bh")
|
||||
script = ExtResource("1_ig7tw")
|
||||
|
||||
@ -20,108 +20,75 @@
|
||||
[node name="Module" type="Node2D"]
|
||||
script = ExtResource("1_nqe0s")
|
||||
physics_mode = 1
|
||||
mass = 13.0
|
||||
inertia = 1200.92
|
||||
mass = 2.0
|
||||
inertia = 0.5
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
|
||||
is_pressurized = false
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30634" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30635" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30636" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 0)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30637" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30638" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, -100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30639" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, -150)
|
||||
rotation = 1.5708
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30640" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, 150)
|
||||
rotation = 4.71239
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30641" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 100)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="@StaticBody2D@30642" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 0)
|
||||
is_pressurized = false
|
||||
health = 0.0
|
||||
physics_mode = null
|
||||
base_mass = 0.0
|
||||
linear_velocity = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="Station" parent="." instance=ExtResource("5_nqe0s")]
|
||||
position = Vector2(0, -10)
|
||||
|
||||
26
scenes/orrey_view/debug_marker.gd
Normal file
26
scenes/orrey_view/debug_marker.gd
Normal file
@ -0,0 +1,26 @@
|
||||
# res://scenes/orrey_view/debug_marker.gd
|
||||
class_name DebugMarker
|
||||
extends Control
|
||||
|
||||
signal marker_selected(body: Node2D)
|
||||
|
||||
@onready var icon: ColorRect = $Icon
|
||||
@onready var label: Label = $Label
|
||||
|
||||
var body_reference: Node2D
|
||||
|
||||
func initialize(body: Node2D):
|
||||
body_reference = body
|
||||
label.text = body.name
|
||||
|
||||
# Color-code the marker for easy identification
|
||||
if body is Barycenter:
|
||||
icon.color = Color.YELLOW
|
||||
elif body.get_parent() is Barycenter and body.get_parent().name == "StarBarycenter":
|
||||
icon.color = Color.ORANGE
|
||||
else:
|
||||
icon.color = Color.CYAN
|
||||
|
||||
func _gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
emit_signal("marker_selected", body_reference)
|
||||
1
scenes/orrey_view/debug_marker.gd.uid
Normal file
1
scenes/orrey_view/debug_marker.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b5tcvtjh2050f
|
||||
18
scenes/orrey_view/debug_marker.tscn
Normal file
18
scenes/orrey_view/debug_marker.tscn
Normal file
@ -0,0 +1,18 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b8fmmp4axba1j"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b5tcvtjh2050f" path="res://scenes/orrey_view/debug_marker.gd" id="1_2uxs6"]
|
||||
|
||||
[node name="DebugMarker" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
script = ExtResource("1_2uxs6")
|
||||
|
||||
[node name="Icon" type="ColorRect" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 23.0
|
||||
74
scenes/orrey_view/orrey_view.gd
Normal file
74
scenes/orrey_view/orrey_view.gd
Normal file
@ -0,0 +1,74 @@
|
||||
# res://scenes/debug/orrery_view.gd
|
||||
extends Node2D
|
||||
|
||||
@export_group("Generation Assets")
|
||||
@export var star_scene: PackedScene
|
||||
@export var planet_scene: PackedScene
|
||||
@export var moon_scene: PackedScene
|
||||
@export var barycenter_scene: PackedScene
|
||||
@export var spaceship_scene: PackedScene # Add this even if unused for now
|
||||
|
||||
@export_group("Orrery Settings")
|
||||
@export var debug_marker_scene: PackedScene # Link your new DebugMarker.tscn here
|
||||
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
@onready var system_root: Node2D = $GeneratedSystem
|
||||
@onready var info_label: RichTextLabel = $UI/InfoPanel/MarginContainer/InfoLabel
|
||||
|
||||
var markers: Dictionary = {} # Maps a body to its marker
|
||||
|
||||
func _ready():
|
||||
# 1. Create and configure the generator tool.
|
||||
var generator = StarSystemGenerator.new()
|
||||
|
||||
# Create a temporary StarSystem-like object to pass to the generator
|
||||
var temp_system_node = Node2D.new()
|
||||
temp_system_node.star_scene = star_scene
|
||||
temp_system_node.planet_scene = planet_scene
|
||||
temp_system_node.moon_scene = moon_scene
|
||||
temp_system_node.barycenter_scene = barycenter_scene
|
||||
temp_system_node.spaceship_scene = spaceship_scene
|
||||
|
||||
# 2. Generate the system under our dedicated root node.
|
||||
var generation_result = generator.generate(temp_system_node)
|
||||
system_root.add_child(temp_system_node) # Keep the generated hierarchy
|
||||
|
||||
# 3. Create a debug marker for every generated body.
|
||||
var all_bodies = generation_result.all_bodies + generation_result.all_barycenters
|
||||
for body in all_bodies:
|
||||
if is_instance_valid(body):
|
||||
var marker = debug_marker_scene.instantiate()
|
||||
add_child(marker) # Add markers to the OrreryView, not the generated system
|
||||
marker.initialize(body)
|
||||
marker.marker_selected.connect(_on_marker_selected)
|
||||
markers[body] = marker
|
||||
|
||||
func _process(_delta):
|
||||
# Keep the markers synced with their body's global position.
|
||||
for body in markers:
|
||||
var marker = markers[body]
|
||||
marker.global_position = body.global_position
|
||||
|
||||
func _unhandled_input(event: InputEvent):
|
||||
# Simple camera controls
|
||||
if event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_MIDDLE:
|
||||
camera.offset -= event.relative / camera.zoom.x
|
||||
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
camera.zoom *= 1.2
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
camera.zoom /= 1.2
|
||||
|
||||
func _on_marker_selected(body: Node2D):
|
||||
# Update the info panel with the selected body's data.
|
||||
var text = "[b]%s[/b]\n" % body.name
|
||||
|
||||
if body is OrbitalBody2D:
|
||||
text += "Mass: %.2f\n" % body.mass
|
||||
text += "Velocity: (%.2f, %.2f)\n" % [body.linear_velocity.x, body.linear_velocity.y]
|
||||
text += "Position: (%.0f, %.0f)\n" % [body.global_position.x, body.global_position.y]
|
||||
if body is Barycenter and is_instance_valid(body.get_parent()):
|
||||
text += "Parent: %s" % body.get_parent().name
|
||||
|
||||
info_label.text = text
|
||||
1
scenes/orrey_view/orrey_view.gd.uid
Normal file
1
scenes/orrey_view/orrey_view.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cwurbqorbwtil
|
||||
22
scenes/orrey_view/orrey_view.tscn
Normal file
22
scenes/orrey_view/orrey_view.tscn
Normal file
@ -0,0 +1,22 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bysrpulqqlic3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cwurbqorbwtil" path="res://scenes/orrey_view/orrey_view.gd" id="1_4smux"]
|
||||
|
||||
[node name="OrreyView" type="Node2D"]
|
||||
script = ExtResource("1_4smux")
|
||||
|
||||
[node name="GeneratedSystem" type="Node2D" parent="."]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
|
||||
[node name="UI" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="InfoPanel" type="PanelContainer" parent="UI"]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="UI/InfoPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InfoLabel" type="RichTextLabel" parent="UI/InfoPanel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
@ -6,9 +6,7 @@
|
||||
|
||||
[node name="Station" type="Node2D"]
|
||||
script = ExtResource("1_8usqu")
|
||||
physics_mode = null
|
||||
mass = 1.0
|
||||
inertia = 0.0
|
||||
|
||||
[node name="InteractionArea" type="Area2D" parent="."]
|
||||
|
||||
|
||||
@ -58,31 +58,32 @@ func _draw() -> void:
|
||||
var map_center = get_rect().size / 2.0
|
||||
|
||||
var system_data = GameManager.get_system_data()
|
||||
if system_data and system_data.belts:
|
||||
for belt in system_data.belts:
|
||||
var radius = belt.centered_radius * map_scale
|
||||
draw_circle(map_center + map_offset, radius, Color(Color.WHITE, 0.1), false)
|
||||
#if system_data and system_data.belts:
|
||||
#for belt in system_data.belts:
|
||||
#var radius = belt.centered_radius * map_scale
|
||||
#draw_circle(map_center + map_offset, radius, Color(Color.WHITE, 0.1), false)
|
||||
|
||||
for body in icon_map:
|
||||
if body is Asteroid: continue
|
||||
var icon = icon_map[body]
|
||||
if not icon.visible: continue
|
||||
|
||||
var path_points = []
|
||||
if body is CelestialBody: path_points = OrbitalMechanics._calculate_relative_orbital_path(body)
|
||||
elif body is OrbitalBody2D: path_points = OrbitalMechanics._calculate_n_body_orbital_path(body)
|
||||
else: continue
|
||||
var scaled_path_points = PackedVector2Array()
|
||||
|
||||
for point in path_points:
|
||||
# Ensure path is drawn relative to the main focal body (the star)
|
||||
var path_world_pos = point + focal_body.global_position
|
||||
var relative_pos = path_world_pos - focal_body.global_position
|
||||
var scaled_pos = (relative_pos * map_scale) + map_offset + map_center
|
||||
scaled_path_points.append(scaled_pos)
|
||||
|
||||
if scaled_path_points.size() > 1:
|
||||
draw_polyline(scaled_path_points, Color(Color.WHITE, 0.2), 1.0, true)
|
||||
# TODO: Turn this into drawing of trajectories for bodies that are selected
|
||||
#for body in icon_map:
|
||||
#if body is Asteroid: continue
|
||||
#var icon = icon_map[body]
|
||||
#if not icon.visible: continue
|
||||
#
|
||||
#var path_points = []
|
||||
#if body is CelestialBody: path_points = OrbitalMechanics._calculate_relative_orbital_path(body)
|
||||
#elif body is OrbitalBody2D: path_points = OrbitalMechanics._calculate_n_body_orbital_path(body)
|
||||
#else: continue
|
||||
#var scaled_path_points = PackedVector2Array()
|
||||
#
|
||||
#for point in path_points:
|
||||
## Ensure path is drawn relative to the main focal body (the star)
|
||||
#var path_world_pos = point + focal_body.global_position
|
||||
#var relative_pos = path_world_pos - focal_body.global_position
|
||||
#var scaled_pos = (relative_pos * map_scale) + map_offset + map_center
|
||||
#scaled_path_points.append(scaled_pos)
|
||||
#
|
||||
#if scaled_path_points.size() > 1:
|
||||
#draw_polyline(scaled_path_points, Color(Color.WHITE, 0.2), 1.0, true)
|
||||
|
||||
func _populate_map():
|
||||
for child in get_children():
|
||||
|
||||
@ -6,15 +6,12 @@
|
||||
[node name="SensorPanel" type="Control"]
|
||||
clip_contents = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 8
|
||||
anchors_preset = 13
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -350.0
|
||||
offset_top = -180.0
|
||||
offset_right = 350.0
|
||||
offset_bottom = 180.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -300.0
|
||||
offset_right = 300.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_5yxry")
|
||||
|
||||
46
scripts/barycenter.gd
Normal file
46
scripts/barycenter.gd
Normal file
@ -0,0 +1,46 @@
|
||||
# scripts/barycenter.gd
|
||||
class_name Barycenter
|
||||
extends OrbitalBody2D
|
||||
|
||||
var primary_body: OrbitalBody2D
|
||||
var child_systems: Array[Barycenter] = []
|
||||
|
||||
func _ready():
|
||||
physics_mode = PhysicsMode.INDEPENDENT
|
||||
await get_tree().process_frame
|
||||
|
||||
# Find children and calculate total mass (this logic remains the same)
|
||||
for child in get_children():
|
||||
if child is Barycenter:
|
||||
child_systems.append(child)
|
||||
elif primary_body == null and child is OrbitalBody2D:
|
||||
primary_body = child
|
||||
recalculate_total_mass()
|
||||
|
||||
# We no longer run a local simulation here.
|
||||
# We only need physics_process to integrate our own movement.
|
||||
set_physics_process(true)
|
||||
|
||||
func get_internal_attractors() -> Array[OrbitalBody2D]:
|
||||
var internal_attractors: Array[OrbitalBody2D] = []
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
internal_attractors.append(child)
|
||||
|
||||
return internal_attractors
|
||||
|
||||
func recalculate_total_mass():
|
||||
base_mass = 0.0
|
||||
if is_instance_valid(primary_body):
|
||||
base_mass += primary_body.base_mass
|
||||
for system in child_systems:
|
||||
system.recalculate_total_mass()
|
||||
base_mass += system.base_mass
|
||||
mass = base_mass
|
||||
|
||||
# --- API for accessing contents ---
|
||||
func get_primary_body() -> OrbitalBody2D:
|
||||
return primary_body
|
||||
|
||||
func get_child_systems() -> Array[Barycenter]:
|
||||
return child_systems
|
||||
1
scripts/barycenter.gd.uid
Normal file
1
scripts/barycenter.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b2hb3bwrlh40c
|
||||
@ -30,23 +30,25 @@ func _ready():
|
||||
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, position: Vector2 = Vector2.ZERO):
|
||||
func apply_force(force: Vector2, position: Vector2 = self.global_position):
|
||||
# This is the force routing logic.
|
||||
match physics_mode:
|
||||
PhysicsMode.INDEPENDENT:
|
||||
_add_forces(force, position)
|
||||
PhysicsMode.COMPOSITE:
|
||||
# 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
|
||||
_add_forces(force, position)
|
||||
## 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()
|
||||
@ -100,19 +102,17 @@ func _integrate_forces(delta):
|
||||
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 + accumulated_force
|
||||
## 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 = total_force / sim_mass # Division is now safe
|
||||
var linear_acceleration = accumulated_force / sim_mass # Division is now safe
|
||||
linear_velocity += linear_acceleration * delta
|
||||
global_position += linear_velocity * delta
|
||||
|
||||
#if self is Planet:
|
||||
#print(global_position)
|
||||
# 4. Apply Rotational Physics (T = I * angular_acceleration)
|
||||
var angular_acceleration = accumulated_torque / inertia
|
||||
angular_velocity += angular_acceleration * delta
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
[gd_resource type="Resource" script_class="GameConfig" load_steps=4 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://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"]
|
||||
|
||||
@ -8,4 +9,5 @@
|
||||
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"
|
||||
|
||||
@ -4,4 +4,5 @@ extends Resource
|
||||
|
||||
@export var player_controller_scene: PackedScene
|
||||
@export var default_pawn_scene: PackedScene
|
||||
@export var default_ship_scene: PackedScene
|
||||
# Add any other global settings you need here in the future
|
||||
|
||||
@ -8,7 +8,7 @@ var player_controllers: Dictionary = {} # Key: player_id, Value: PlayerControlle
|
||||
var player_pawns: Dictionary = {} # Key: player_id, Value: PilotBall node
|
||||
|
||||
# This variable will hold the reference to the currently active star system.
|
||||
var current_star_system : StarSystemGenerator = null
|
||||
var current_star_system : StarSystem = null
|
||||
var ships_in_system : Array[OrbitalBody2D]
|
||||
|
||||
var registered_spawners: Array[Spawner] = []
|
||||
@ -123,16 +123,11 @@ func register_spawner(spawner_node: Spawner):
|
||||
_try_spawn_waiting_player()
|
||||
|
||||
# A helper function for easily accessing the system's data.
|
||||
func get_system_data():
|
||||
func get_system_data() -> SystemData:
|
||||
if current_star_system:
|
||||
return current_star_system.get_system_data()
|
||||
return null
|
||||
|
||||
func get_system_bodies() -> Array[CelestialBody]:
|
||||
if current_star_system:
|
||||
return current_star_system.get_all_bodies()
|
||||
|
||||
return []
|
||||
|
||||
func get_all_trackable_bodies() -> Array:
|
||||
var all_bodies: Array = []
|
||||
|
||||
@ -4,7 +4,103 @@ extends Node
|
||||
# This singleton holds all universal physics constants and functions.
|
||||
|
||||
# The scaled gravitational constant for the entire simulation.
|
||||
const G = 10.0 # Adjust this to control the "speed" of your 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
|
||||
@ -25,32 +121,6 @@ func calculate_n_body_gravity_forces(body_to_affect: Node2D) -> Vector2:
|
||||
|
||||
return total_force
|
||||
|
||||
# Calculates the force of gravity exerted by a primary on an orbiter.
|
||||
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 direction = orbiter.global_position.direction_to(primary.global_position)
|
||||
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
|
||||
|
||||
if distance_sq < 1.0: # Avoid division by zero
|
||||
return Vector2.ZERO
|
||||
|
||||
var force_magnitude = (G * primary.mass * orbiter.mass) / distance_sq
|
||||
return direction * force_magnitude
|
||||
|
||||
# Simplified n-body gravitational pull on orbiters.
|
||||
# Station orbiting a moon will for instance calculate forces to the moon, the planet, and the star.
|
||||
#func simple_n_body_grav(primary: OrbitalBody2D, orbiter : OrbitalBody2D) -> Vector2:
|
||||
#var pull = calculate_gravitational_force(primary, orbiter)
|
||||
#var inner_primary : CelestialBody = primary
|
||||
#
|
||||
#while (inner_primary.primary is CelestialBody):
|
||||
#pull = pull + calculate_gravitational_force(inner_primary.primary, orbiter)
|
||||
#inner_primary = inner_primary.primary
|
||||
#
|
||||
#return pull
|
||||
|
||||
# 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):
|
||||
@ -65,9 +135,8 @@ func calculate_circular_orbit_velocity(orbiter: OrbitalBody2D, primary: OrbitalB
|
||||
|
||||
var direction_to_orbiter = primary.global_position.direction_to(orbiter.global_position)
|
||||
var perpendicular_direction = Vector2(direction_to_orbiter.y, -direction_to_orbiter.x)
|
||||
|
||||
# The final velocity must include the primary's own velocity.
|
||||
return (perpendicular_direction * speed_magnitude) + primary.linear_velocity
|
||||
|
||||
return perpendicular_direction * speed_magnitude
|
||||
|
||||
func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
|
||||
var num_steps = 10
|
||||
@ -164,3 +233,25 @@ func _calculate_relative_orbital_path(body_to_trace: CelestialBody) -> PackedVec
|
||||
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
|
||||
|
||||
51
scripts/star_system.gd
Normal file
51
scripts/star_system.gd
Normal file
@ -0,0 +1,51 @@
|
||||
# scripts/star_system.gd
|
||||
class_name StarSystem
|
||||
extends Node2D
|
||||
|
||||
@export_group("System Metadata")
|
||||
@export var system_name: String = "Kepler-186"
|
||||
@export var galactic_coordinates: Vector2i = Vector2i.ZERO
|
||||
|
||||
var system_data: SystemData
|
||||
|
||||
func _ready():
|
||||
# 1. Create the generator tool.
|
||||
var generator = StarSystemGenerator.new()
|
||||
|
||||
# 2. Tell the generator to build the system within this StarSystem node.
|
||||
system_data = generator.generate(self)
|
||||
|
||||
# 3. Register the completed system with the GameManager.
|
||||
GameManager.register_star_system(self)
|
||||
GameManager.start_game()
|
||||
|
||||
# --- Public API for accessing system data ---
|
||||
func get_star() -> OrbitalBody2D:
|
||||
if is_instance_valid(system_data):
|
||||
return system_data.star
|
||||
return null
|
||||
|
||||
func get_planetary_systems() -> Array[Barycenter]:
|
||||
var bodies: Array[Barycenter] = []
|
||||
for child in get_children():
|
||||
if child is Barycenter:
|
||||
bodies.append(child)
|
||||
|
||||
return bodies
|
||||
|
||||
func get_orbital_bodies() -> Array[OrbitalBody2D]:
|
||||
var bodies: Array[OrbitalBody2D] = []
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
bodies.append(child)
|
||||
|
||||
return bodies
|
||||
|
||||
func get_system_data() -> SystemData:
|
||||
return system_data
|
||||
|
||||
class AsteroidBelt:
|
||||
var width : float
|
||||
var mass : float
|
||||
var centered_radius : float = 0.0
|
||||
var asteroids : Array[OrbitalBody2D]
|
||||
1
scripts/star_system.gd.uid
Normal file
1
scripts/star_system.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bkcouefvi7iup
|
||||
@ -1,377 +1,130 @@
|
||||
# scripts/star_system_generator.gd
|
||||
class_name StarSystemGenerator
|
||||
extends Node2D
|
||||
extends RefCounted
|
||||
|
||||
@export_group("System Generation")
|
||||
# Minimum and maximum number of planets to generate.
|
||||
@export var min_planets: int = 2
|
||||
@export var max_planets: int = 7
|
||||
# --- 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 = 2.5 # Increase space between orbits
|
||||
|
||||
# Minimum and maximum number of moons to generate per planet.
|
||||
@export var min_moons: int = 0
|
||||
@export var max_moons: int = 4
|
||||
# --- The main public method ---
|
||||
func generate(star_system: StarSystem) -> SystemData:
|
||||
# 1. Create the root Barycenter for the entire system.
|
||||
var system_data = SystemData.new()
|
||||
|
||||
# Minimum and maximum number of asteroid belts to generate.
|
||||
@export var min_asteroid_belts: int = 1
|
||||
@export var max_asteroid_belts: int = 3
|
||||
# 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)
|
||||
|
||||
# Minimum and maximum number of asteroids per belt.
|
||||
@export var min_asteroids_per_belt: int = 20
|
||||
@export var max_asteroids_per_belt: int = 100
|
||||
|
||||
# Minimum and maximum number of star-orbiting stations.
|
||||
@export var min_star_stations: int = 0
|
||||
@export var max_star_stations: int = 2
|
||||
|
||||
@export var min_planetary_stations : int = 0
|
||||
@export var max_planetary_stations: int = 2
|
||||
|
||||
@export var min_moon_stations : int = 0
|
||||
@export var max_moon_stations: int = 1
|
||||
|
||||
# References to the PackedScene resources for each celestial body.
|
||||
@export var star_scene: PackedScene
|
||||
@export var planet_scene: PackedScene
|
||||
@export var moon_scene: PackedScene
|
||||
@export var station_scene: PackedScene
|
||||
@export var asteroid_scene: PackedScene
|
||||
@export var spaceship_scene: PackedScene
|
||||
|
||||
# A factor to scale the initial orbital distances to make the system visually compelling.
|
||||
@export var min_ring_distance: float = 10000
|
||||
@export var min_planetary_orbit_radius : float = 200
|
||||
|
||||
# A factor to determine the buffer between orbits based on mass.
|
||||
@export var orbit_buffer_factor: float = 1
|
||||
const orbit_buffer_constant : float = 1000
|
||||
var orbit_buffer : float = orbit_buffer_constant * orbit_buffer_factor
|
||||
|
||||
# A flag to check if the system has been generated.
|
||||
var has_generated: bool = false
|
||||
|
||||
# Constants for real-world physics calculations.
|
||||
#const G = 6.67430e-11 # Gravitational constant (N·m²/kg²)
|
||||
#const SUN_MASS = 1.989e30 # Mass of the sun (kg)
|
||||
const SUN_MASS: float = 10000000.0
|
||||
const PLANETARY_MASS: float = SUN_MASS / 300000.0
|
||||
const MOON_MASS: float = PLANETARY_MASS / 100.0
|
||||
const ASTEROID_MASS: float = MOON_MASS / 5.0
|
||||
const STATION_MASS: float = MOON_MASS / 1000.0
|
||||
|
||||
var _bodies_to_initialize: Array[Dictionary] = []
|
||||
var system_data : SystemData
|
||||
|
||||
func _ready() -> void:
|
||||
if not has_generated:
|
||||
system_data = SystemData.new()
|
||||
generate_star_system() # This now only creates the bodies
|
||||
|
||||
# --- MODIFIED: Defer the velocity calculations ---
|
||||
# Wait until the next idle frame, ensuring all _ready functions have run
|
||||
# and all global_positions have been calculated.
|
||||
|
||||
_initialize_orbits() # Now, calculate all velocities
|
||||
has_generated = true
|
||||
GameManager.start_game()
|
||||
|
||||
|
||||
func generate_star_system() -> void:
|
||||
# This function is now simpler.
|
||||
var star_instance = _create_star()
|
||||
add_child(star_instance)
|
||||
_generate_and_place_bodies(star_instance)
|
||||
|
||||
# This new function will perform all the velocity calculations at once.
|
||||
func _initialize_orbits():
|
||||
for body_data in _bodies_to_initialize:
|
||||
var orbiter: OrbitalBody2D = body_data.orbiter
|
||||
var primary: OrbitalBody2D = body_data.primary
|
||||
|
||||
if is_instance_valid(orbiter) and is_instance_valid(primary):
|
||||
orbiter.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(orbiter, primary)
|
||||
print("Initialized orbit for: " + orbiter.name)
|
||||
else:
|
||||
push_warning("Could not initialize orbit due to invalid node.")
|
||||
# 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
|
||||
|
||||
print("Star system orbit initialization complete.")
|
||||
|
||||
# Creates a single star instance.
|
||||
func _create_star() -> OrbitalBody2D:
|
||||
var star_instance = star_scene.instantiate() as Star
|
||||
system_data.star = star_instance
|
||||
star_instance.name = "Star"
|
||||
|
||||
# Set the star's properties.
|
||||
#star_instance.orbit_radius_real = 0.0
|
||||
star_instance.base_mass = SUN_MASS
|
||||
star_instance.modulate = Color("ffe066") # Yellow
|
||||
|
||||
return star_instance
|
||||
|
||||
# Generates and places all bodies in randomized order.
|
||||
func _generate_and_place_bodies(primary: OrbitalBody2D) -> void:
|
||||
# 1. Randomize the number of each type of body.
|
||||
var num_planets = randi_range(min_planets, max_planets)
|
||||
var num_belts = randi_range(min_asteroid_belts, max_asteroid_belts)
|
||||
var num_star_stations = randi_range(min_star_stations, max_star_stations)
|
||||
|
||||
# 2. Create an "inventory" of bodies to be placed.
|
||||
var bodies_to_place = []
|
||||
bodies_to_place.append({"type": "wormhole_exit", "num": 0})
|
||||
for i in range(num_planets):
|
||||
bodies_to_place.append({"type": "planet"})
|
||||
for i in range(num_belts):
|
||||
bodies_to_place.append({"type": "asteroid_belt"})
|
||||
for i in range(num_star_stations):
|
||||
bodies_to_place.append({"type": "station"})
|
||||
# A. Create the Barycenter for the new planetary system.
|
||||
var planet_barycenter = Barycenter.new()
|
||||
|
||||
# 3. Randomize the order of placement.
|
||||
bodies_to_place.shuffle()
|
||||
|
||||
var current_orbit_radius = min_ring_distance
|
||||
|
||||
# 4. Place bodies sequentially based on the randomized order.
|
||||
for body_data in bodies_to_place:
|
||||
var body_type = body_data["type"]
|
||||
planet_barycenter.name = "PlanetSystem_%d" % (i + 1)
|
||||
star_system.add_child(planet_barycenter)
|
||||
|
||||
match body_type:
|
||||
"planet":
|
||||
var planet_instance = planet_scene.instantiate() as OrbitalBody2D
|
||||
system_data.planets.append(planet_instance)
|
||||
planet_instance.name = "Planet " + str(current_orbit_radius)
|
||||
planet_instance.base_mass = PLANETARY_MASS # randf_range(1e24, 1e25)
|
||||
|
||||
# Calculate orbit based on the last placed body.
|
||||
var orbit_radius_real = current_orbit_radius + (planet_instance.mass * orbit_buffer)
|
||||
|
||||
_create_body_in_ring(primary, planet_instance, orbit_radius_real)
|
||||
_create_moons_and_stations(planet_instance)
|
||||
|
||||
current_orbit_radius = orbit_radius_real + (planet_instance.mass * orbit_buffer)
|
||||
|
||||
"asteroid_belt":
|
||||
var belt = _create_asteroid_belt(primary, current_orbit_radius)
|
||||
system_data.belts.append(belt)
|
||||
|
||||
current_orbit_radius = current_orbit_radius + (belt.mass * orbit_buffer) * 2
|
||||
|
||||
"station":
|
||||
var station_instance = station_scene.instantiate() as OrbitalBody2D
|
||||
system_data.stations.append(station_instance)
|
||||
station_instance.name = "Star Station " + str(current_orbit_radius)
|
||||
station_instance.base_mass = STATION_MASS # A very small mass
|
||||
|
||||
var orbit_radius_real = current_orbit_radius + (station_instance.mass * orbit_buffer)
|
||||
_create_body_in_ring(primary, station_instance, orbit_radius_real)
|
||||
|
||||
current_orbit_radius = orbit_radius_real + (station_instance.mass * orbit_buffer)
|
||||
"wormhole_exit":
|
||||
if not spaceship_scene:
|
||||
print("Spaceship scene not set in StarSystemGenerator!")
|
||||
continue
|
||||
|
||||
print("Spawning spaceship...")
|
||||
var spaceship_instance = spaceship_scene.instantiate() as Module
|
||||
|
||||
# 3. Position it in a stable orbit
|
||||
var orbit_radius = current_orbit_radius + (spaceship_instance.mass * orbit_buffer)
|
||||
var initial_position_vector = Vector2(orbit_radius, 0).rotated(randf() * TAU)
|
||||
spaceship_instance.global_position = primary.global_position + initial_position_vector
|
||||
|
||||
# 4. Use the OrbitalMechanics library to calculate its initial velocity
|
||||
spaceship_instance.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(spaceship_instance, primary)
|
||||
|
||||
add_child(spaceship_instance)
|
||||
|
||||
current_orbit_radius = orbit_radius + (spaceship_instance.mass * orbit_buffer)
|
||||
|
||||
print("Star system generation complete.")
|
||||
|
||||
GameManager.register_star_system(self)
|
||||
# Creates moons and stations around a primary body.
|
||||
func _create_moons_and_stations(primary: OrbitalBody2D) -> void:
|
||||
var num_moons = randi_range(min_moons, max_moons)
|
||||
var num_planetary_stations = randi_range(min_planetary_stations, max_planetary_stations)
|
||||
|
||||
# Start with a base orbital radius around the parent planet.
|
||||
var next_local_orbit_radius = min_planetary_orbit_radius
|
||||
|
||||
# Create an "inventory" of bodies to be placed.
|
||||
var bodies_to_place = []
|
||||
for i in range(num_planetary_stations):
|
||||
bodies_to_place.append({"type": "station", "num": i})
|
||||
for i in range(num_moons):
|
||||
bodies_to_place.append({"type": "moon", "num": i})
|
||||
|
||||
# Randomize the order of placement.
|
||||
bodies_to_place.shuffle()
|
||||
|
||||
for body in bodies_to_place:
|
||||
match body.type:
|
||||
"station":
|
||||
var station_instance = station_scene.instantiate() as OrbitalBody2D
|
||||
system_data.stations.append(station_instance)
|
||||
station_instance.name = "Station " + str(body.num + 1)
|
||||
station_instance.base_mass= STATION_MASS
|
||||
|
||||
# Use the local orbit radius for this station.
|
||||
_create_body_in_ring(primary, station_instance, next_local_orbit_radius)
|
||||
|
||||
# Increment the *local* orbit radius for the next body.
|
||||
next_local_orbit_radius += randf_range(40, 100)
|
||||
|
||||
"moon":
|
||||
var moon_instance = moon_scene.instantiate() as OrbitalBody2D
|
||||
system_data.moons.append(moon_instance)
|
||||
moon_instance.name = "Moon " + str(body.num + 1)
|
||||
moon_instance.base_mass = MOON_MASS
|
||||
|
||||
# Use the local orbit radius for this moon.
|
||||
_create_body_in_ring(primary, moon_instance, next_local_orbit_radius)
|
||||
|
||||
# Increment the *local* orbit radius for the next body.
|
||||
next_local_orbit_radius += randf_range(40, 100)
|
||||
|
||||
# --- FIX for stations orbiting moons ---
|
||||
var num_moon_stations = randi_range(min_moon_stations, max_moon_stations)
|
||||
var next_moon_station_orbit = randf_range(20, 50) # Start closer for moon stations
|
||||
|
||||
for i in range(num_moon_stations):
|
||||
var station_instance = station_scene.instantiate() as OrbitalBody2D
|
||||
system_data.stations.append(station_instance)
|
||||
station_instance.name = "Station " + str(i + 1)
|
||||
station_instance.base_mass = STATION_MASS
|
||||
|
||||
# Set the orbit relative to the moon.
|
||||
var orbit_radius_real = next_moon_station_orbit
|
||||
_create_body_in_ring(moon_instance, station_instance, orbit_radius_real)
|
||||
|
||||
# Increment for the next station around this *same* moon.
|
||||
next_moon_station_orbit += randf_range(20, 50)
|
||||
|
||||
#var num_moon_stations = randi_range(min_moon_stations, max_moon_stations)
|
||||
#
|
||||
#var moon_inner_orbit = randf_range(2, 5)
|
||||
#
|
||||
#for i in range(num_moon_stations):
|
||||
## Generate a space station.
|
||||
#var station_instance = station_scene.instantiate() as OrbitalBody2D
|
||||
#system_data.stations.append(station_instance)
|
||||
#station_instance.name = "Station " + str(i + 1)
|
||||
#station_instance.primary = moon_instance
|
||||
#station_instance.mass = STATION_MASS
|
||||
#station_instance.orbit_radius_real = moon_inner_orbit
|
||||
#_create_body_in_ring(moon_instance, station_instance)
|
||||
#
|
||||
#moon_inner_orbit = moon_inner_orbit + randf_range(2, 5)
|
||||
|
||||
|
||||
# Creates a single asteroid belt.
|
||||
func _create_asteroid_belt(primary: OrbitalBody2D, initial_offset: float) -> AsteroidBelt:
|
||||
var num_asteroids = randi_range(min_asteroids_per_belt, max_asteroids_per_belt)
|
||||
var belt = AsteroidBelt.new()
|
||||
|
||||
for i in range(num_asteroids):
|
||||
var asteroid_instance = asteroid_scene.instantiate() as OrbitalBody2D
|
||||
asteroid_instance.name = "Asteroid " + str(i + 1)
|
||||
asteroid_instance.base_mass = ASTEROID_MASS # randf_range(1e10, 1e23)
|
||||
# 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
|
||||
|
||||
belt.asteroids.append(asteroid_instance)
|
||||
|
||||
belt.mass = belt.asteroids.reduce(func(accum, asteroid : OrbitalBody2D): return accum + asteroid.mass, 0.0)
|
||||
var offset = belt.mass * orbit_buffer
|
||||
belt.centered_radius = initial_offset + offset
|
||||
|
||||
for asteroid in belt.asteroids:
|
||||
# Asteroids within a belt have a random orbit radius within a certain range.
|
||||
var orbit_radius_real = belt.centered_radius + randf_range(-offset / 2, offset / 2)
|
||||
|
||||
_create_body_in_ring(primary, asteroid, orbit_radius_real)
|
||||
# C. Create moons for this planet.
|
||||
_generate_moons(planet, planet_barycenter, star_system, system_data)
|
||||
|
||||
return belt
|
||||
|
||||
func _create_body_in_ring(primary: OrbitalBody2D, body_instance: OrbitalBody2D, orbit_radius) -> void:
|
||||
# 1. Parent the new body to its primary.
|
||||
primary.add_child(body_instance)
|
||||
|
||||
# 2. Set its LOCAL position.
|
||||
var initial_position_vector = Vector2(orbit_radius, 0).rotated(randf() * TAU)
|
||||
body_instance.position = initial_position_vector
|
||||
|
||||
# 3. --- THE FIX ---
|
||||
# Instead of calculating velocity now, we store the nodes for later.
|
||||
_bodies_to_initialize.append({
|
||||
"orbiter": body_instance,
|
||||
"primary": primary
|
||||
})
|
||||
# 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
|
||||
|
||||
# --- NEW: Spawn the ship at the last planet's L4 point ---
|
||||
if i == num_planets - 1:
|
||||
_spawn_player_ship(star_system, star, planet_barycenter)
|
||||
|
||||
# The print statements about velocity are no longer relevant here.
|
||||
print("Created " + body_instance.name + " with radius " + str(orbit_radius) + " and mass " + str(body_instance.mass))
|
||||
|
||||
# Helper function to instantiate and place a body in a ring.
|
||||
#func _create_body_in_ring(primary: OrbitalBody2D, body_instance: OrbitalBody2D, orbit_radius) -> void:
|
||||
## 1. Parent the new body to its primary FIRST.
|
||||
## This establishes the correct local coordinate space.
|
||||
#primary.add_child(body_instance)
|
||||
#
|
||||
## 2. Calculate the position vector relative to the parent.
|
||||
#var initial_position_vector = Vector2(orbit_radius, 0).rotated(randf() * TAU)
|
||||
#
|
||||
## 3. Set the LOCAL position of the body.
|
||||
## Godot will now correctly calculate its global_position based on the parent's position.
|
||||
#body_instance.position = initial_position_vector
|
||||
#print("Initial global position is: " + str(body_instance.global_position))
|
||||
#
|
||||
## We wait for one physics frame before calculating velocity.
|
||||
## This ensures the body_instance.global_position is updated and correct.
|
||||
##await get_tree().physics_frame
|
||||
#
|
||||
## 4. Now that the global position is correct, calculate the velocity for a stable orbit.
|
||||
## This part remains the same, as physics calculations need the final global positions.
|
||||
#body_instance.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(body_instance, primary)
|
||||
#
|
||||
#print("Created " + body_instance.name + " with radius " + str(orbit_radius) + " and mass " + str(body_instance.mass))
|
||||
##print("Initial global position is: " + str(body_instance.global_position))
|
||||
#print("Initial orbital radius is: " + str(orbit_radius))
|
||||
#print("Initial orbital velocity is: " + str(body_instance.linear_velocity))
|
||||
|
||||
# Recursively finds all celestial bodies in the scene.
|
||||
func get_all_bodies() -> Array:
|
||||
var bodies = []
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
bodies.append(child)
|
||||
# Recursively add children if they are also celestial bodies.
|
||||
for sub_child in child.get_children():
|
||||
if sub_child is OrbitalBody2D:
|
||||
bodies.append(sub_child)
|
||||
return bodies
|
||||
|
||||
func get_system_data() -> SystemData:
|
||||
return system_data
|
||||
|
||||
class AsteroidBelt:
|
||||
var width : float
|
||||
var mass : float
|
||||
var centered_radius : float = 0.0
|
||||
var asteroids : Array[OrbitalBody2D]
|
||||
func _generate_moons(planet: OrbitalBody2D, planet_barycenter: Barycenter, star_system: StarSystem, 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)
|
||||
|
||||
class SystemData:
|
||||
var star : OrbitalBody2D
|
||||
var planets : Array[OrbitalBody2D]
|
||||
var moons: Array[OrbitalBody2D]
|
||||
var stations : Array[OrbitalBody2D]
|
||||
var belts : Array[AsteroidBelt]
|
||||
var current_orbit_radius = 200.0 # OrbitalMechanics.calculate_simplified_roche_limit(planet) # Start with the first orbit
|
||||
|
||||
func all_bodies() -> Array[OrbitalBody2D]:
|
||||
var bodies : Array[OrbitalBody2D] = [star]
|
||||
bodies.append_array(planets)
|
||||
bodies.append_array(stations)
|
||||
for i in range(num_moons):
|
||||
var moon = Moon.new()
|
||||
system_data.moons.append(moon)
|
||||
planet_barycenter.add_child(moon)
|
||||
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
|
||||
|
||||
bodies.append_array(moons)
|
||||
|
||||
var all_asteroids : Array[OrbitalBody2D] = []
|
||||
for belt in belts:
|
||||
all_asteroids.append_array(belt.asteroids)
|
||||
|
||||
bodies.append_array(all_asteroids)
|
||||
return bodies
|
||||
# --- 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()
|
||||
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)
|
||||
|
||||
|
||||
|
||||
# Simplified, local-space velocity calculation.
|
||||
#func _get_orbital_velocity(orbiter: Node2D, primary: Node2D) -> Vector2:
|
||||
#var distance = orbiter.position.length()
|
||||
#if distance == 0: return Vector2.ZERO
|
||||
#
|
||||
## We use the masses of the Barycenters for the calculation
|
||||
#var primary_mass = primary.get_parent().mass if primary.get_parent() is Barycenter else primary.mass
|
||||
#
|
||||
## v = sqrt(G * M / r)
|
||||
#var speed_magnitude = sqrt(OrbitalMechanics.G * primary_mass / distance)
|
||||
#
|
||||
#var perpendicular_dir = orbiter.position.orthogonal().normalized()
|
||||
#return perpendicular_dir * speed_magnitude
|
||||
|
||||
22
scripts/system_data.gd
Normal file
22
scripts/system_data.gd
Normal file
@ -0,0 +1,22 @@
|
||||
class_name SystemData
|
||||
extends Resource
|
||||
|
||||
var star : OrbitalBody2D
|
||||
var planets : Array[OrbitalBody2D]
|
||||
var moons: Array[OrbitalBody2D]
|
||||
#var stations : Array[OrbitalBody2D]
|
||||
#var belts : Array[AsteroidBelt]
|
||||
|
||||
func all_bodies() -> Array[OrbitalBody2D]:
|
||||
var bodies : Array[OrbitalBody2D] = [star]
|
||||
bodies.append_array(planets)
|
||||
#bodies.append_array(stations)
|
||||
|
||||
bodies.append_array(moons)
|
||||
|
||||
#var all_asteroids : Array[OrbitalBody2D] = []
|
||||
#for belt in belts:
|
||||
#all_asteroids.append_array(belt.asteroids)
|
||||
|
||||
#bodies.append_array(all_asteroids)
|
||||
return bodies
|
||||
1
scripts/system_data.gd.uid
Normal file
1
scripts/system_data.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://deskhqcqv20y8
|
||||
Reference in New Issue
Block a user