Even better map

This commit is contained in:
olof.pettersson
2025-09-21 21:26:27 +02:00
parent 0fb321de2a
commit 8f365d0565
4 changed files with 134 additions and 79 deletions

View File

@ -1,8 +1,7 @@
# MapIcon.gd
# space_simulation/scenes/UI/map_icon.gd
class_name MapIcon
extends Control
# A signal this icon emits when it is clicked, passing its body reference.
signal selected(body: RigidBody2D)
@onready var name_label: Label = $NameLabel
@ -10,12 +9,20 @@ signal selected(body: RigidBody2D)
var body_reference: RigidBody2D
var dot_color: Color = Color.WHITE
# --- Animation Variables ---
var hover_tween: Tween
var outline_progress: float = 0.0 # 0.0 for corners, 1.0 for full outline
const CORNER_BASE_PROGRESS = 0.25
const OUTLINE_FULL_PROGRESS = 1.0
# --- FIX: Create a setter function for the hover_progress variable ---
# This ensures that whenever the value is changed (especially by a Tween),
# the icon is immediately redrawn to show the change.
var hover_progress: float = 0.0:
set(value):
hover_progress = value
queue_redraw()
func _ready() -> void:
# Connect mouse enter/exit signals to handle hover effects.
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
@ -23,7 +30,6 @@ func initialize(body: RigidBody2D):
body_reference = body
name_label.text = body.name
# Determine the dot color based on the body's type.
if body is Spaceship:
dot_color = Color.CYAN
elif body is CelestialBody:
@ -32,26 +38,22 @@ func initialize(body: RigidBody2D):
"Planet": dot_color = Color.DODGER_BLUE
"Moon": dot_color = Color.PURPLE
# Set the rich tooltip text with sensor data.
self.tooltip_text = _generate_tooltip_text()
# --- Custom Drawing ---
func _draw() -> void:
var center = size / 2.0
var dot_radius = 4.0
# Draw the central dot representing the object.
draw_circle(center, dot_radius, dot_color)
# --- Draw the hover outline ---
var outline_size = size * 0.8
var outline_rect = Rect2(Vector2.ZERO, outline_size)
outline_rect.position = center - (outline_rect.size / 2.0)
# This value (0 to 1) is controlled by the hover tween.
var progress = outline_progress
var corner_len = outline_rect.size.x * 0.25 # Length of the corner lines
var draw_progress = lerp(CORNER_BASE_PROGRESS, OUTLINE_FULL_PROGRESS, hover_progress)
var corner_len = outline_rect.size.x * 0.5
var top_left = outline_rect.position
var top_right = outline_rect.position + Vector2(outline_rect.size.x, 0)
@ -60,52 +62,44 @@ func _draw() -> void:
var line_color = Color(Color.GRAY, 0.75)
# Draw horizontal and vertical lines for each corner based on progress.
# Top-left corner
draw_line(top_left, top_left + Vector2(corner_len * progress, 0), line_color, 1.0)
draw_line(top_left, top_left + Vector2(0, corner_len * progress), line_color, 1.0)
draw_line(top_left, top_left + Vector2(corner_len * draw_progress, 0), line_color, 1.0)
draw_line(top_left, top_left + Vector2(0, corner_len * draw_progress), line_color, 1.0)
# Top-right corner
draw_line(top_right, top_right - Vector2(corner_len * progress, 0), line_color, 1.0)
draw_line(top_right, top_right + Vector2(0, corner_len * progress), line_color, 1.0)
draw_line(top_right, top_right - Vector2(corner_len * draw_progress, 0), line_color, 1.0)
draw_line(top_right, top_right + Vector2(0, corner_len * draw_progress), line_color, 1.0)
# Bottom-left corner
draw_line(bottom_left, bottom_left + Vector2(corner_len * progress, 0), line_color, 1.0)
draw_line(bottom_left, bottom_left - Vector2(0, corner_len * progress), line_color, 1.0)
draw_line(bottom_left, bottom_left + Vector2(corner_len * draw_progress, 0), line_color, 1.0)
draw_line(bottom_left, bottom_left - Vector2(0, corner_len * draw_progress), line_color, 1.0)
# Bottom-right corner
draw_line(bottom_right, bottom_right - Vector2(corner_len * progress, 0), line_color, 1.0)
draw_line(bottom_right, bottom_right - Vector2(0, corner_len * progress), line_color, 1.0)
draw_line(bottom_right, bottom_right - Vector2(corner_len * draw_progress, 0), line_color, 1.0)
draw_line(bottom_right, bottom_right - Vector2(0, corner_len * draw_progress), line_color, 1.0)
# --- Input and Hover Handling ---
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
emit_signal("selected", body_reference)
# No changes are needed here; the tweens will automatically use the new setter function.
func _on_mouse_entered():
if hover_tween: hover_tween.kill()
hover_tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
hover_tween.tween_property(self, "outline_progress", 1.0, 0.2)
hover_tween.tween_property(self, "hover_progress", 1.0, 0.2)
func _on_mouse_exited():
if hover_tween: hover_tween.kill()
hover_tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN)
hover_tween.tween_property(self, "outline_progress", 0.0, 0.3)
hover_tween.tween_property(self, "hover_progress", 0.0, 0.3)
# --- Tooltip Data Generation ---
func _generate_tooltip_text() -> String:
var info = [body_reference.name]
if body_reference is CelestialBody:
var celestial = body_reference as CelestialBody
# Calculate orbital period (Kepler's 3rd Law) - simplified for tooltip
if is_instance_valid(celestial.primary):
var mu = OrbitalMechanics.G * celestial.primary.mass
var r = celestial.global_position.distance_to(celestial.primary.global_position)
var period_seconds = TAU * sqrt(pow(r, 3) / mu)
info.append("Orbital Period: %s" % _format_seconds_to_mmss(period_seconds))
# Count moons if it's a planet
var moon_count = 0
for child in celestial.get_children():
if child is CelestialBody and child.get_class_name() == "Moon":
@ -119,7 +113,6 @@ func _generate_tooltip_text() -> String:
return "\n".join(info)
# Helper function for formatting time in the tooltip
func _format_seconds_to_mmss(seconds: float) -> String:
var total_seconds = int(seconds)
var minutes = total_seconds / 60

View File

@ -25,7 +25,7 @@ layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="NavigationUI/NavigationScreen/HBoxContainer"]
custom_minimum_size = Vector2(250, 0)
layout_mode = 2
size_flags_horizontal = 0
size_flags_horizontal = 4
[node name="TargetLabel" type="Label" parent="NavigationUI/NavigationScreen/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
@ -71,22 +71,26 @@ unique_name_in_owner = true
clip_contents = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 3.0
script = ExtResource("2_ys00n")
map_icon_scene = ExtResource("3_378us")
[node name="RightHandPanel" type="VBoxContainer" parent="NavigationUI/NavigationScreen/HBoxContainer"]
custom_minimum_size = Vector2(0, 300)
layout_mode = 2
size_flags_horizontal = 3
size_flags_horizontal = 4
[node name="ShipStatusLabel" type="Label" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 0
[node name="MarginContainer" type="MarginContainer" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel"]
layout_mode = 2
size_flags_vertical = 3
[node name="SubViewportContainer" type="SubViewportContainer" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel"]
layout_mode = 2
size_flags_vertical = 8
[node name="SubViewport" type="SubViewport" parent="NavigationUI/NavigationScreen/HBoxContainer/RightHandPanel/SubViewportContainer"]
unique_name_in_owner = true

View File

@ -8,8 +8,10 @@ signal body_selected_for_planning(body: RigidBody2D)
# You will need to drag your MapIcon.tscn file into this slot in the Inspector.
@export var map_icon_scene: PackedScene
const CULLING_DISTANCE_THRESHOLD = 30 # Pixels. If icons are closer than this, they might be culled.
const CULLING_ZOOM_THRESHOLD = 0.5 # Only start culling when zoomed out past this level.
# The minimum distance in PIXELS between icons before a label is culled.
const LABEL_CULLING_PIXEL_THRESHOLD = 40.0
# The minimum distance in PIXELS before the *entire icon* is culled.
const ICON_CULLING_PIXEL_THRESHOLD = 20.0 # Should be smaller than the label threshold
# --- Map State ---
var map_scale: float = 0.001 # Start zoomed out
@ -82,33 +84,54 @@ func _update_icon_positions():
var map_center = get_rect().size / 2.0
var icon_positions = [] # For culling
# --- Step 1: Calculate all icon screen positions ---
var icon_data_for_frame = []
for body in icon_map:
var icon = icon_map[body]
# Calculate the position of the body relative to the map's focal point.
var relative_pos = body.global_position - focal_body.global_position
# Reset all icons and labels to be visible at the start of the frame.
icon.visible = true
icon.name_label.visible = true
# Apply map scale (zoom) and offset (pan), then center it on its pivot.
icon.position = (relative_pos * map_scale) + map_offset + map_center - (icon.size / 2)
if map_scale < CULLING_ZOOM_THRESHOLD:
for i in range(icon_positions.size()):
var item_a = icon_positions[i]
if not item_a.icon.visible: continue
var relative_pos = body.global_position - focal_body.global_position
var final_screen_pos = (relative_pos * map_scale) + map_offset + map_center
# Store the data we need for the culling pass.
icon_data_for_frame.append({
"screen_pos": final_screen_pos,
"body": body,
"icon": icon
})
# Set the icon's position for rendering.
icon.position = final_screen_pos - (icon.size / 2)
# --- Step 2: Perform screen-space culling ---
for i in range(icon_data_for_frame.size()):
var data_a = icon_data_for_frame[i]
# Skip this icon if it has already been hidden by a previous check.
if not data_a.icon.visible:
continue
for j in range(i + 1, icon_data_for_frame.size()):
var data_b = icon_data_for_frame[j]
if not data_b.icon.visible: continue
var distance = data_a.screen_pos.distance_to(data_b.screen_pos)
for j in range(i + 1, icon_positions.size()):
var item_b = icon_positions[j]
if not item_b.icon.visible: continue
if item_a.pos.distance_to(item_b.pos) < CULLING_DISTANCE_THRESHOLD:
# Icons are overlapping, hide the one with less mass
if item_a.body.mass > item_b.body.mass:
item_b.icon.visible = false
else:
item_a.icon.visible = false
# --- Tier 1: Cull the entire icon if they are very close ---
if distance < ICON_CULLING_PIXEL_THRESHOLD:
if data_a.body.mass > data_b.body.mass:
data_b.icon.visible = false
else:
data_a.icon.visible = false
# --- Tier 2: If not culled, check if we should cull just the label ---
elif distance < LABEL_CULLING_PIXEL_THRESHOLD:
if data_a.body.mass > data_b.body.mass:
data_b.icon.name_label.visible = false
else:
data_a.icon.name_label.visible = false
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:

View File

@ -171,62 +171,87 @@ func _generate_and_place_bodies(primary: RigidBody2D) -> void:
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)
var moon_orbit_radius = min_planetary_orbit_radius
# 2. Create an "inventory" of bodies to be placed.
# 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})
# 3. Randomize the order of placement.
# Randomize the order of placement.
bodies_to_place.shuffle()
for body in bodies_to_place:
match body.type:
"station":
# Generate a space 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
var station_extra_orbit = randf_range(0, 20)
station_instance.orbit_radius_real = moon_orbit_radius + station_extra_orbit
moon_orbit_radius = station_instance.orbit_radius_real + randf_range(40, 100)
# 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
var moon_extra_orbit = randf_range(0, 20)
moon_instance.orbit_radius_real = moon_orbit_radius + moon_extra_orbit
# Use the local orbit radius for this moon.
moon_instance.orbit_radius_real = next_local_orbit_radius
_create_body_in_ring(primary, moon_instance)
moon_orbit_radius = moon_instance.orbit_radius_real + randf_range(40, 100)
# 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 moon_inner_orbit = randf_range(2, 5)
var next_moon_station_orbit = randf_range(20, 50) # Start closer for moon stations
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
# Set the orbit relative to the moon.
station_instance.orbit_radius_real = next_moon_station_orbit
_create_body_in_ring(moon_instance, station_instance)
moon_inner_orbit = moon_inner_orbit + randf_range(2, 5)
# 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.
@ -256,11 +281,21 @@ func _create_asteroid_belt(primary: RigidBody2D, initial_offset: float) -> Aster
# Helper function to instantiate and place a body in a ring.
func _create_body_in_ring(primary: CelestialBody, body_instance: CelestialBody) -> void:
var initial_position_vector = Vector2(body_instance.orbit_radius_real, 0).rotated(randf() * TAU)
body_instance.global_position = primary.global_position + initial_position_vector
# 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))