343 lines
13 KiB
GDScript
343 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 = 10000000
|
|
const PLANETARY_MASS = SUN_MASS / 300000
|
|
const MOON_MASS = PLANETARY_MASS / 100
|
|
const ASTEROID_MASS = MOON_MASS / 5
|
|
const STATION_MASS = MOON_MASS / 1000
|
|
|
|
var system_data : SystemData
|
|
|
|
func _ready() -> void:
|
|
if not has_generated:
|
|
system_data = SystemData.new()
|
|
generate_star_system()
|
|
has_generated = true
|
|
|
|
# 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 Spaceship
|
|
|
|
# 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
|