Solar system
Nested Node3D pivots, each spinning at its own rate. A moon orbits its planet and inherits the planet orbit for free.
The editor above is running this project. Change a line and it reloads.
Godot classes used
Node3DMeshInstance3DOmniLight3D
The code
scripts/main.gd
extends Node
const PLANETS: Array[Dictionary] = [
{size = 0.45, orbit = 4.2, speed = 0.85, color = '#8da5f3', moons = 0},
{size = 0.75, orbit = 6.6, speed = 0.55, color = '#8eef97', moons = 1},
{size = 0.6, orbit = 9.2, speed = 0.38, color = '#fc7f7f', moons = 2},
{size = 1.05, orbit = 12.4, speed = 0.24, color = '#f3d48d', moons = 1},
]
var pivots: Array[Node3D] = []
var speeds: Array[float] = []
func _ready() -> void:
_add_environment()
_add_sun()
for i in PLANETS.size():
_add_planet(PLANETS[i], float(i))
var camera: Camera3D = Camera3D.new()
camera.fov = 55.0
camera.position = Vector3(0, 11, 23)
add_child(camera)
camera.look_at(Vector3.ZERO, Vector3.UP)
camera.current = true
print('every level is a Node3D pivot spinning at its own rate, so a moon inherits the planet orbit')
func _process(delta: float) -> void:
for i in pivots.size():
pivots[i].rotate_y(delta * speeds[i])
func _add_planet(data: Dictionary, index: float) -> void:
_add_orbit_ring(data.orbit)
var pivot: Node3D = Node3D.new()
pivot.rotation.y = index * 1.7
add_child(pivot)
_track(pivot, data.speed)
var planet: MeshInstance3D = _make_ball(data.size, data.color, 0.0)
planet.position = Vector3(data.orbit, 0, 0)
pivot.add_child(planet)
for m in data.moons:
var moon_pivot: Node3D = Node3D.new()
moon_pivot.rotation.y = m * 2.1
moon_pivot.rotation.z = 0.35
planet.add_child(moon_pivot)
_track(moon_pivot, 1.8 + m * 0.7)
var moon: MeshInstance3D = _make_ball(data.size * 0.32, '#c8ccd8', 0.0)
moon.position = Vector3(data.size + 0.9 + m * 0.6, 0, 0)
moon_pivot.add_child(moon)
func _add_orbit_ring(radius: float) -> void:
var mesh: TorusMesh = TorusMesh.new()
mesh.inner_radius = radius - 0.03
mesh.outer_radius = radius + 0.03
mesh.ring_segments = 6
mesh.rings = 96
var material: StandardMaterial3D = StandardMaterial3D.new()
material.albedo_color = Color(0.55, 0.62, 0.85, 0.22)
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
var ring: MeshInstance3D = MeshInstance3D.new()
ring.mesh = mesh
ring.material_override = material
ring.rotation_degrees = Vector3(-90, 0, 0)
add_child(ring)
func _add_sun() -> void:
add_child(_make_ball(1.9, '#f3d48d', 0.9))
var light: OmniLight3D = OmniLight3D.new()
light.light_color = Color('#ffe9bd')
light.light_energy = 4.5
light.omni_range = 60.0
light.omni_attenuation = 0.6
add_child(light)
func _make_ball(size: float, hex: String, glow: float) -> MeshInstance3D:
var mesh: SphereMesh = SphereMesh.new()
mesh.radius = size
mesh.height = size * 2.0
var material: StandardMaterial3D = StandardMaterial3D.new()
material.albedo_color = Color(hex)
material.roughness = 0.7
if glow > 0.0:
material.emission_enabled = true
material.emission = Color(hex)
material.emission_energy_multiplier = glow
var ball: MeshInstance3D = MeshInstance3D.new()
ball.mesh = mesh
ball.material_override = material
return ball
func _track(pivot: Node3D, speed: float) -> void:
pivots.append(pivot)
speeds.append(speed)
func _add_environment() -> void:
var env: Environment = Environment.new()
env.background_mode = Environment.BG_COLOR
env.background_color = Color(0.012, 0.014, 0.035)
# a near black background gives no usable ambient, so the night sides need a fill color instead
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.42, 0.48, 0.7)
env.ambient_light_energy = 0.55
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
env.glow_enabled = true
env.glow_intensity = 0.35
env.glow_bloom = 0.1
var world: WorldEnvironment = WorldEnvironment.new()
world.environment = env
add_child(world)