WIP Persistent panels

This commit is contained in:
olof.pettersson
2025-10-17 14:27:11 +02:00
parent 425e857ba9
commit 1228a79cae
6 changed files with 164 additions and 128 deletions

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=23 format=3 uid="uid://didt2nsdtbmra"]
[gd_scene load_steps=22 format=3 uid="uid://didt2nsdtbmra"]
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_nqe0s"]
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_foqop"]
@ -9,7 +9,6 @@
[ext_resource type="Resource" uid="uid://dghg3pbws42yu" path="res://scenes/ship/computer/shards/helm_logic_databank.tres" id="7_dmrms"]
[ext_resource type="Resource" uid="uid://c4wyouanvf86c" path="res://scenes/ship/computer/panels/button_panel.tres" id="7_vmx8o"]
[ext_resource type="Resource" uid="uid://57y6igb07e10" path="res://scenes/ship/computer/panels/readout_screen.tres" id="8_83bu1"]
[ext_resource type="Resource" uid="uid://dcyr6utrk376h" path="res://scenes/ship/computer/panels/throttle_lever.tres" id="9_ixntg"]
[ext_resource type="Resource" uid="uid://dl7g67mtqkfx2" path="res://scenes/ship/computer/panels/sensor_panel.tres" id="9_xwy4s"]
[ext_resource type="PackedScene" uid="uid://dt1t2n7dewucw" path="res://scenes/ship/computer/UI/button_panel.tscn" id="10_px2ne"]
[ext_resource type="Resource" uid="uid://bx7wgunvy5hfa" path="res://scenes/ship/computer/shards/helm_ship_status.tres" id="11_83bu1"]
@ -27,8 +26,8 @@
physics_interpolation_mode = 2
script = ExtResource("1_nqe0s")
physics_mode = 1
mass = 2.0
inertia = 0.5
mass = 1.0
inertia = 0.0
metadata/_custom_type_script = "uid://0isnsk356que"
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
@ -110,10 +109,17 @@ base_mass = 0.0
[node name="Station" parent="." instance=ExtResource("5_nqe0s")]
position = Vector2(0, -10)
panels = Array[ExtResource("6_oqcn4")]([ExtResource("7_vmx8o"), ExtResource("8_83bu1"), ExtResource("9_ixntg"), ExtResource("9_xwy4s")])
panel_scenes = Array[PackedScene]([ExtResource("10_px2ne"), ExtResource("11_erhv3"), ExtResource("12_q1rtr"), ExtResource("13_rsa1x")])
panels = Array[ExtResource("6_oqcn4")]([ExtResource("8_83bu1"), ExtResource("8_83bu1"), ExtResource("9_xwy4s"), ExtResource("7_vmx8o")])
panel_scenes = Array[PackedScene]([ExtResource("11_erhv3"), ExtResource("11_erhv3"), ExtResource("12_q1rtr"), ExtResource("10_px2ne"), ExtResource("13_rsa1x")])
installed_databanks = Array[ExtResource("6_ft4kn")]([ExtResource("11_83bu1"), ExtResource("7_dmrms"), ExtResource("13_xfp3q"), ExtResource("12_wkxbw"), ExtResource("15_fll2s"), ExtResource("13_xwy4s")])
grid_size_x = 1
grid_size_y = 1
attachment_type = 0
physics_mode = 2
base_mass = 1.0
linear_velocity = Vector2(0, 0)
angular_velocity = 0.0
inertia = 1.0
[node name="Thruster" parent="." instance=ExtResource("12_vmx8o")]
position = Vector2(-95, -130)

View File

@ -9,8 +9,7 @@ signal occupancy_changed(is_occupied: bool)
@export var panel_scenes: Array[PackedScene]
@export var installed_databanks: Array[Databank]
@onready var panel_frame = $PanelFrame
@onready var panel_frame: PanelFrame = $PanelFrame
# --- State ---
var occupants: Array[PilotBall] = []
@ -30,21 +29,17 @@ func _ready():
push_error("Station could not find its root module!")
return
var panel_world: World2D = World2D.new()
for panel_scene in panel_scenes:
if not panel_scene: continue
var panel_instance = panel_scene.instantiate()
if not panel_instance is BasePanel:
panel_instance.queue_free()
continue
panel_frame.add_child(panel_instance)
persistent_panel_instances.append(panel_instance)
# Make the panels invisible in the main game world
#panel_instance.visible = false
panel_frame.populate_panels(panel_scenes)
#for panel_scene in panel_scenes:
#if not panel_scene: continue
#
#var panel_instance = panel_scene.instantiate()
#if not panel_instance is BasePanel:
#panel_instance.queue_free()
#continue
#
#panel_frame.add_child(panel_instance)
#persistent_panel_instances.append(panel_instance)
for shard_resource in installed_databanks:
if not shard_resource or not shard_resource.logic_script: continue
@ -58,6 +53,7 @@ func _ready():
shard_instance.initialize(root_module)
_connect_internals(persistent_panel_instances, active_shard_instances)
# Future: Connections wbetween shards and other hardware would be made here.
func _process(_delta):
@ -82,7 +78,7 @@ func occupy(character: PilotBall):
character.global_position = global_position # Move character to the station
# --- FIX: Launch UI for THIS character only ---
launch_interfaces_for_occupant(character)
on_player_interact(character)
occupancy_changed.emit(true)
@ -100,49 +96,18 @@ func disengage(character: PilotBall):
# --- UI MANAGEMENT ---
func launch_interfaces_for_occupant(character: PilotBall):
print("foo")
var ui_container = character.get_ui_container()
if not ui_container is Control: return
print("bar")
var viewports = []
# This function is called when a player character interacts with the station.
func on_player_interact(player: PilotBall):
# 1. Ask our PanelWorld for a new window view.
var window_view = await panel_frame.create_window_view()
# 1. Create a container for our render setup
var viewport_container = SubViewportContainer.new()
viewport_container.stretch = true
viewport_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
viewport_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
ui_container.add_child(viewport_container)
# 2. Pass this fully-formed window to the player.
var player_ui_container = player.get_ui_container()
if is_instance_valid(player_ui_container):
player_ui_container.add_child(window_view)
window_view.popup_centered()
# 2. Create the SubViewport
var sub_viewport = SubViewport.new()
sub_viewport.world_2d = get_world_2d() # Render the same world
sub_viewport.size = panel_frame.size # Match the panel's size
viewport_container.add_child(sub_viewport)
# 3. Create a camera to look at the panel
var camera = Camera2D.new()
# This camera will ONLY see Layer 2 ("UI_Panels").
camera.set_cull_mask_value(1, false) # Hide Layer 1 (Default World)
camera.set_cull_mask_value(2, true) # Show Layer 2 (UI_Panels)
camera.global_position = panel_frame.global_position
var panel_max_dim = max(panel_frame.size.x, panel_frame.size.y)
var viewport_min_dim = min(sub_viewport.size.x, sub_viewport.size.y)
if panel_max_dim > 0 and viewport_min_dim > 0:
camera.zoom = Vector2.ONE * (panel_max_dim / viewport_min_dim) * 1.1
sub_viewport.add_child(camera)
# 4. Connect input forwarding
viewport_container.gui_input.connect(
func(event): sub_viewport.push_input(event)
)
viewports.append(viewport_container)
occupant_panel_map[character] = viewports # We now store the viewports to clean up
occupant_panel_map[player] = window_view # We now store the viewports to clean up
func close_interfaces_for_occupant(character: PilotBall):
if occupant_panel_map.has(character):

View File

@ -1,6 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://2n42nstcj1n0"]
[gd_scene load_steps=4 format=3 uid="uid://2n42nstcj1n0"]
[ext_resource type="Script" uid="uid://2reyxkr78ra0" path="res://scenes/ship/components/hardware/system_station.gd" id="1_8usqu"]
[ext_resource type="Script" uid="uid://cgryue4aay4oa" path="res://scenes/ship/computer/panels/panel_world.gd" id="2_3288w"]
[sub_resource type="CircleShape2D" id="CircleShape2D_8usqu"]
@ -15,8 +16,6 @@ mass = 1.0
shape = SubResource("CircleShape2D_8usqu")
debug_color = Color(0, 0.551549, 0.918484, 0.42)
[node name="PanelFrame" type="GridContainer" parent="."]
visibility_layer = 2
offset_right = 40.0
offset_bottom = 40.0
columns = 12
[node name="PanelFrame" type="Node" parent="."]
script = ExtResource("2_3288w")
metadata/_custom_type_script = "uid://cgryue4aay4oa"

View File

@ -0,0 +1,88 @@
# scripts/panel_world.gd
class_name PanelFrame
extends Node
# This PanelWorld owns a separate world just for its UI panels.
var ui_world: World2D = World2D.new()
# The root node for all panels within the UI world.
var panel_root: Control
var grid_container: GridContainer
func _ready():
# We need a SubViewport to host our separate world and keep it running.
var viewport = SubViewport.new()
viewport.world_2d = ui_world
add_child(viewport)
# Create the root node for the panels inside the new world.
panel_root = Control.new()
panel_root.name = "PanelRoot"
# We add the panel_root to the scene tree via the viewport to make it active.
viewport.add_child(panel_root)
## The station calls this to populate the world with its panels.
func populate_panels(panel_scenes: Array[PackedScene]):
if not is_instance_valid(panel_root): return
# Add the GridContainer to the panel_root to manage the layout.
grid_container = GridContainer.new()
panel_root.add_child(grid_container)
for panel_scene in panel_scenes:
if not panel_scene: continue
var panel_instance = panel_scene.instantiate()
grid_container.add_child(panel_instance)
# Any other setup for the panels would go here.
## The station calls this to get a view for a player.
func create_window_view() -> Window:
# Create a draggable Window for the player's UI.
var window = Window.new()
window.title = get_parent().name # Title the window after the station
window.unresizable = true
window.size = grid_container.size # Give it a default minimum size
window.close_requested.connect(window.queue_free)
# Create the SubViewport setup to render the view.
var vp_container = SubViewportContainer.new()
window.add_child(vp_container)
vp_container.stretch = true
vp_container.anchors_preset = Control.LayoutPreset.PRESET_FULL_RECT
vp_container.size = grid_container.size
var sub_viewport = SubViewport.new()
sub_viewport.world_2d = ui_world # IMPORTANT: Point to our private UI world
sub_viewport.size = Vector2(400,300)
sub_viewport.transparent_bg = true
vp_container.add_child(sub_viewport)
# Wait for the next idle frame. By this time, the GridContainer will have
# calculated its size based on the panels added to it.
await get_tree().process_frame
# Create a camera for this specific view.
var camera = Camera2D.new()
# Center the camera on the panel layout.
#camera.position = panel_root.size / 2
# 2. Calculate the correct zoom to fit all the panels in the view.
var bounds = panel_root.get_rect()
var zoom_x = bounds.size.x / sub_viewport.size.x
var zoom_y = bounds.size.y / sub_viewport.size.y
# 2. Set the OFFSET to be the center of the panel grid.
# This shifts the camera's VIEW to be centered on the panels.
sub_viewport.add_child(camera)
camera.offset = grid_container.size
# Center the camera on the grid of panels.
#camera.position = panel_root.size / 2.0
# Use the larger zoom factor to ensure everything fits, and add a 10% margin.
#camera.zoom = Vector2.ONE * max(zoom_x, zoom_y) * 1.1
# Forward input from the screen overlay to the world-space panel.
vp_container.gui_input.connect(func(event): sub_viewport.push_input(event.duplicate()))
return window

View File

@ -0,0 +1 @@
uid://cgryue4aay4oa

View File

@ -6,60 +6,37 @@ extends Node
var active_panels: Dictionary = {}
## The main entry point for the system.
func request_popup(panel_scene: PackedScene, context_object: Node, player: Node):
var panel_instance: Control
# 1. Check if a panel for this object already exists.
if active_panels.has(context_object):
panel_instance = active_panels[context_object]
else:
# 2. If not, create the single, persistent UI panel.
panel_instance = panel_scene.instantiate()
add_child(panel_instance) # Add it to the PopupManager to keep it in the scene tree
# Assign it to the "UI_Panels" render layer (Project Settings -> Layer Names -> 2D Render)
panel_instance.layer = 2
# Initialize it with the context object
if panel_instance.has_method("initialize"):
panel_instance.initialize(context_object)
active_panels[context_object] = panel_instance
# 3. Create a viewport "window" for the specific player who requested it.
_create_viewport_for_player(panel_instance, player)
func _create_viewport_for_player(panel_instance: Control, player: Node, world: World2D):
# Get the player's personal UI container (e.g., from PilotBall.gd)
var ui_container = player.get_ui_container()
if not ui_container: return
# Create a draggable Window for the UI
var window = Window.new()
window.title = panel_instance.name
window.size = panel_instance.size + Vector2(20, 40) # Add space for title bar
window.close_requested.connect(window.queue_free) # Close button works automatically
ui_container.add_child(window)
# Create the SubViewport setup
var vp_container = SubViewportContainer.new()
vp_container.stretch = true
vp_container.anchors_preset = Control.PRESET_FULL_RECT
window.add_child(vp_container)
var sub_viewport = SubViewport.new()
sub_viewport.world_2d = world
sub_viewport.size = panel_instance.size
sub_viewport.transparent_bg = true
vp_container.add_child(sub_viewport)
# Create and configure the camera
var camera = Camera2D.new()
camera.set_cull_mask_value(1, false) # Don't see the default world
camera.set_cull_mask_value(2, true) # ONLY see the "UI_Panels" layer
camera.global_position = panel_instance.global_position
# You can add zoom logic here if needed, similar to the station implementation
sub_viewport.add_child(camera)
# Forward input from the screen overlay to the world-space panel
vp_container.gui_input.connect(func(event): sub_viewport.push_input(event.duplicate()))
#func request_popup(panel_world: World2D, player: PilotBall):
## Get the player's personal UI container (e.g., from PilotBall.gd)
#var ui_container = player.get_ui_container()
#if not ui_container: return
#
## Create a draggable Window for the UI
#var window = Window.new()
#window.title = panel_instance.name
#window.size = panel_instance.size + Vector2(20, 40) # Add space for title bar
#window.close_requested.connect(window.queue_free) # Close button works automatically
#ui_container.add_child(window)
#
## Create the SubViewport setup
#var vp_container = SubViewportContainer.new()
#vp_container.stretch = true
#vp_container.anchors_preset = Control.PRESET_FULL_RECT
#window.add_child(vp_container)
#
#var sub_viewport = SubViewport.new()
#sub_viewport.world_2d
#sub_viewport.size = panel_instance.size
#sub_viewport.transparent_bg = true
#vp_container.add_child(sub_viewport)
#
## Create and configure the camera
#var camera = Camera2D.new()
#camera.set_cull_mask_value(1, false) # Don't see the default world
#camera.set_cull_mask_value(2, true) # ONLY see the "UI_Panels" layer
#camera.global_position = panel_instance.global_position
## You can add zoom logic here if needed, similar to the station implementation
#sub_viewport.add_child(camera)
#
## Forward input from the screen overlay to the world-space panel
#vp_container.gui_input.connect(func(event): sub_viewport.push_input(event.duplicate()))