Files
millimeters-of-aluminum/scripts/star_system_generator.gd
2025-10-13 13:12:56 +02:00

347 lines
13 KiB
GDScript

class_name StarSystemGenerator
extends Node2D
@export_group("System Generation")
# Minimum and maximum number of planets to generate.
@export var min_planets: int = 2
@export var max_planets: int = 7
# Minimum and maximum number of moons to generate per planet.
@export var min_moons: int = 0
@export var max_moons: int = 4
# Minimum and maximum number of asteroid belts to generate.
@export var min_asteroid_belts: int = 1
@export var max_asteroid_belts: int = 3
# 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 system_data : SystemData
func _ready() -> void:
if not has_generated:
system_data = SystemData.new()
generate_star_system()
has_generated = true
# --- THE CHANGE ---
# After the world is built, tell the GameManager to start the game.
GameManager.start_game()
# The main function to generate the entire star system.
func generate_star_system() -> void:
# Create the star at the center of the system.
var star_instance = _create_star()
add_child(star_instance)
# Generate and place all celestial bodies in randomized order.
_generate_and_place_bodies(star_instance)
# Creates a single star instance.
func _create_star() -> RigidBody2D:
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.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: RigidBody2D) -> 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"})
# 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"]
match body_type:
"planet":
var planet_instance = planet_scene.instantiate() as CelestialBody
system_data.planets.append(planet_instance)
planet_instance.name = "Planet " + str(current_orbit_radius)
planet_instance.primary = primary
planet_instance.mass = PLANETARY_MASS # randf_range(1e24, 1e25)
# Calculate orbit based on the last placed body.
planet_instance.orbit_radius_real = current_orbit_radius + (planet_instance.mass * orbit_buffer)
_create_body_in_ring(primary, planet_instance)
_create_moons_and_stations(planet_instance)
current_orbit_radius = planet_instance.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 CelestialBody
system_data.stations.append(station_instance)
station_instance.name = "Star Station " + str(current_orbit_radius)
station_instance.primary = primary
station_instance.mass = STATION_MASS # A very small mass
station_instance.orbit_radius_real = current_orbit_radius + (station_instance.mass * orbit_buffer)
_create_body_in_ring(primary, station_instance)
current_orbit_radius = station_instance.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: RigidBody2D) -> 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 CelestialBody
system_data.stations.append(station_instance)
station_instance.name = "Station " + str(body.num + 1)
station_instance.primary = primary
station_instance.mass = STATION_MASS
# Use the local orbit radius for this station.
station_instance.orbit_radius_real = next_local_orbit_radius
_create_body_in_ring(primary, station_instance)
# 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 CelestialBody
system_data.moons.append(moon_instance)
moon_instance.name = "Moon " + str(body.num + 1)
moon_instance.primary = primary
moon_instance.mass = MOON_MASS
# Use the local orbit radius for this moon.
moon_instance.orbit_radius_real = next_local_orbit_radius
_create_body_in_ring(primary, moon_instance)
# 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 CelestialBody
system_data.stations.append(station_instance)
station_instance.name = "Station " + str(i + 1)
station_instance.primary = moon_instance
station_instance.mass = STATION_MASS
# Set the orbit relative to the moon.
station_instance.orbit_radius_real = next_moon_station_orbit
_create_body_in_ring(moon_instance, station_instance)
# 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 CelestialBody
#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: RigidBody2D, 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 CelestialBody
asteroid_instance.name = "Asteroid " + str(i + 1)
asteroid_instance.primary = primary
asteroid_instance.mass = ASTEROID_MASS # randf_range(1e10, 1e23)
belt.asteroids.append(asteroid_instance)
belt.mass = belt.asteroids.reduce(func(accum, asteroid : CelestialBody): 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.
asteroid.orbit_radius_real = belt.centered_radius + randf_range(-offset / 2, offset / 2)
_create_body_in_ring(primary, asteroid)
return belt
# Helper function to instantiate and place a body in a ring.
func _create_body_in_ring(primary: CelestialBody, body_instance: CelestialBody) -> 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(body_instance.orbit_radius_real, 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
# 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(body_instance.orbit_radius_real) + " and mass " + str(body_instance.mass))
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 CelestialBody:
bodies.append(child)
# Recursively add children if they are also celestial bodies.
for sub_child in child.get_children():
if sub_child is CelestialBody:
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[CelestialBody]
class SystemData:
var star : CelestialBody
var planets : Array[CelestialBody]
var moons: Array[CelestialBody]
var stations : Array[CelestialBody]
var belts : Array[AsteroidBelt]
func all_bodies() -> Array[CelestialBody]:
var bodies : Array[CelestialBody] = [star]
bodies.append_array(planets)
bodies.append_array(stations)
bodies.append_array(moons)
var all_asteroids : Array[CelestialBody] = []
for belt in belts:
all_asteroids.append_array(belt.asteroids)
bodies.append_array(all_asteroids)
return bodies