Compare commits
4 Commits
refactor/o
...
feature/pl
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c383425f5 | |||
| c2a0b6c026 | |||
| 09a4003839 | |||
| 7c689f6023 |
@ -1,33 +1,82 @@
|
||||
# Project "Stardust Drifter" Status Summary
|
||||
Project "Stardust Drifter" Development Status (Phase 0.2: Custom Physics & Ship Interior)
|
||||
Overview
|
||||
|
||||
## Implemented Systems
|
||||
The project is currently focused on Phase 0.2, which involves migrating from Godot's built-in RigidBody2D physics to a custom, manually integrated OrbitalBody2D model. This is critical for supporting the multi-scale physics simulation (orbital vs. local) and preparing for networked multiplayer. The core physics model is now stable and functional.
|
||||
I. Fully Implemented & Stable Systems
|
||||
Custom Physics Core
|
||||
|
||||
- Core Physics Simulation: The OrbitalMechanics singleton successfully handles a robust N-body gravity simulation. All celestial bodies and the ship correctly inherit from OrbitalBody2D, ensuring they are part of the same physics simulation.
|
||||
Physics Unification: All physically simulated objects (Spaceship, Module, Thruster, StructuralPiece) now inherit from the custom OrbitalBody2D class. This ensures a consistent hierarchy and simplified force routing.
|
||||
|
||||
- Procedural World Generation: The StarSystemGenerator creates a dynamic solar system with stars, planets, moons, and stations in stable, nested orbits.
|
||||
Asymmetric Gravity Model (WIP Foundation): The OrbitalMechanics singleton is refactored to check if a body is a simulation root, applying forces manually (linear_velocity, angular_velocity). This is the foundation for implementing the SCALE_FACTOR for miniature ship gravity.
|
||||
|
||||
- Modular Ship Systems: A foundation for modular ships is in place. The Spaceship class is a central hub for subsystems like FuelSystem and LifeSupport. The ThrusterController is highly advanced, capable of self-calibrating to a ship's unique mass and inertia.
|
||||
Modular Force Integration: Thrusters are now OrbitalBody2D children that correctly apply force to themselves. The force is then routed up the hierarchy to the root ship node, calculating torque based on its application point. This solved the uncontrollable spinning bug.
|
||||
|
||||
- Navigation & Map UI: A functional map UI exists, with zoom-to-cursor and click-and-drag panning. It includes dynamic culling and can be locked onto any celestial body. The NavigationComputer can calculate and execute Hohmann transfers.
|
||||
Modular Mass Aggregation (Top-Down): The OrbitalBody2D class recursively calculates the total mass of the ship by summing the base_mass of all its component children, even those nested behind non-physics containers.
|
||||
|
||||
- Editor Plugin (WIP): We've started building an editor plugin to handle ship construction. The core functionality for creating a custom dock and listening for editor input has been implemented, but the full UI and piece-placement logic still needs to be completed.
|
||||
Ship Interior & Crew Movement
|
||||
|
||||
## Designed but Unimplemented Systems
|
||||
Character Zero-G Movement: The PilotBall character now has complex, state-based movement logic to simulate zero-G physics inside the ship:
|
||||
|
||||
- Free-Form Module Construction: The core building system is designed but not yet fully implemented. The Module and StructuralPiece scripts are in place, but the physics recalculation and room sealing logic is not yet finished.
|
||||
No Control: Applies heavy drag if not overlapping a structural piece.
|
||||
|
||||
- Unified RigidBody2D Character Controller: The CrewMember.tscn scene and the logic for Spaceship.gd to simulate G-forces still need to be created and integrated.
|
||||
Zero-G Interior: Allows sluggish control (simulating pushing off hullplates).
|
||||
|
||||
- Economy, Missions, and Factions: The high-level design for a multi-faction world, a mission system, and an economy exists, but no code or assets have been created for these features yet.
|
||||
Ladder Grip: Provides snappy, direct control (simulating climbing/gripping).
|
||||
|
||||
- Multi-Depth Modules: Your idea for a 2.5D building system with multiple depth layers and a "flip" function is designed but has been tabled for later.
|
||||
Ladder/Component Interaction: The character uses the interact input (Spacebar) to switch to the LADDER_GRIP state and uses the same input release to perform a velocity-based launch into the zero-G interior.
|
||||
|
||||
### Planned Systems: Asymmetric N-Body Physics Simulation
|
||||
Ship Builder Foundation (Structural)
|
||||
|
||||
To handle the vast difference in scale between celestial bodies and player ships, the physics simulation will be refactored to use an asymmetric N-body approach. This means that while all celestial bodies will affect each other, smaller bodies like ships and stations will only be affected by the gravity of larger celestial bodies. This will allow the use of realistic masses for ship components while maintaining stable and predictable orbits for planets and moons.
|
||||
Component Base Class (Component.gd): Created to standardize component attachment, defining properties like grid_size and AttachmentType (Interior/Exterior).
|
||||
|
||||
- Objective: Modify the OrbitalMechanics singleton to apply a scaling factor to gravitational calculations for player-controlled ships and components.
|
||||
Module Attachment Grid: The Module.gd script now exposes a get_attachment_points() method that calculates valid grid locations based on the placement and type of underlying structural pieces (Hullplate, Bulkhead).
|
||||
|
||||
- Implementation: Introduce const SCALE_FACTOR = 100000.0 to the OrbitalMechanics.gd script. When calculating gravity for ships and other scaled bodies, multiply the masses of celestial bodies by this factor.
|
||||
II. Work-In-Progress (WIP) and Planned Systems
|
||||
|
||||
- New Class: A new abstract class, ScaledOrbitalBody2D, will be created that inherits from OrbitalBody2D and handles the new scaled physics.
|
||||
System
|
||||
|
||||
|
||||
Status
|
||||
|
||||
|
||||
Next Steps / Required Work
|
||||
|
||||
Ship Builder Plugin (UI)
|
||||
|
||||
|
||||
WIP / Priority
|
||||
|
||||
|
||||
Integrate the new attachment logic (Module.get_attachment_points()) into the CustomGrid.gd to visualize placement points. Implement the logic for the Component Tab in the dock.
|
||||
|
||||
Physics Cleanup (Celestial)
|
||||
|
||||
|
||||
WIP / Required
|
||||
|
||||
|
||||
Unify the inheritance of all celestial bodies (Star, Planet, etc.) to inherit from OrbitalBody2D, removing reliance on deprecated RigidBody2D base nodes.
|
||||
|
||||
Collision Handling
|
||||
|
||||
|
||||
Planned
|
||||
|
||||
|
||||
Implement the _recalculate_collision_shape() function in Module.gd to merge the structural piece collision shapes into a single, cohesive shape for the root ship.
|
||||
|
||||
Hull Sealing Logic
|
||||
|
||||
|
||||
Designed
|
||||
|
||||
|
||||
Implement the pressure and hull sealing logic using the HullVolume nodes.
|
||||
|
||||
Scaled Gravity Implementation
|
||||
|
||||
|
||||
Designed
|
||||
|
||||
|
||||
Implement the SCALE_FACTOR and ScaledOrbitalBody2D inheritance to test the multi-scale simulation (low-speed local movement vs. high-speed orbital movement).
|
||||
@ -28,14 +28,25 @@ var save_button: Button
|
||||
var builder_scene_root: Node2D
|
||||
var builder_camera: Camera2D
|
||||
|
||||
# --- State Management Enum ---
|
||||
enum BuilderState {
|
||||
IDLE,
|
||||
PLACING_STRUCTURAL,
|
||||
PLACING_COMPONENT,
|
||||
WIRING # For future use
|
||||
}
|
||||
var current_state: BuilderState = BuilderState.IDLE
|
||||
|
||||
# --- State Variables ---
|
||||
var preview_piece: StructuralPiece = null
|
||||
var active_piece_scene: PackedScene = null
|
||||
var preview_node = null # Can be either StructuralPiece or Component
|
||||
var active_scene: PackedScene = null # Can be either StructuralPiece or Component
|
||||
var rotation_angle: float = 0.0
|
||||
var grid_size: float = 50.0
|
||||
|
||||
var undo_redo: EditorUndoRedoManager
|
||||
|
||||
# --- Most of the setup functions remain the same ---
|
||||
|
||||
func _enter_tree():
|
||||
main_screen = MAIN_EDITOR_SCENE.instantiate()
|
||||
EditorInterface.get_editor_main_screen().add_child(main_screen)
|
||||
@ -87,13 +98,11 @@ func _setup_docks():
|
||||
add_control_to_dock(DOCK_SLOT_RIGHT_UL, construction_inspector_dock)
|
||||
|
||||
func switch_to_dock_tab(dock_control: Control, tab_name: String):
|
||||
# Find the TabContainer within the dock's control node.
|
||||
var tab_container = dock_control.find_child("TabContainer")
|
||||
if not is_instance_valid(tab_container):
|
||||
print("Error: TabContainer not found in dock control.")
|
||||
return
|
||||
|
||||
# Iterate through the tabs to find the one with the correct name.
|
||||
for i in range(tab_container.get_tab_count()):
|
||||
if tab_container.get_tab_title(i) == tab_name:
|
||||
tab_container.current_tab = i
|
||||
@ -159,7 +168,7 @@ func _update_ui_labels():
|
||||
func _process(_delta):
|
||||
_update_ui_labels()
|
||||
_refresh_tree_display()
|
||||
|
||||
|
||||
func _on_viewport_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_RIGHT:
|
||||
builder_camera.position -= event.relative / builder_camera.zoom
|
||||
@ -168,56 +177,97 @@ func _on_viewport_input(event: InputEvent) -> void:
|
||||
builder_camera.zoom *= 1.1
|
||||
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
builder_camera.zoom /= 1.1
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
if is_instance_valid(preview_piece):
|
||||
on_clear_preview_piece()
|
||||
else:
|
||||
_remove_piece_under_mouse()
|
||||
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
_update_preview_position()
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
_place_piece_from_preview()
|
||||
match current_state:
|
||||
BuilderState.IDLE:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
_remove_piece_under_mouse()
|
||||
|
||||
BuilderState.PLACING_STRUCTURAL:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
_place_piece_from_preview()
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
on_clear_preview()
|
||||
|
||||
BuilderState.PLACING_COMPONENT:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
|
||||
_place_component_from_preview()
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
|
||||
on_clear_preview()
|
||||
|
||||
BuilderState.WIRING:
|
||||
pass
|
||||
|
||||
|
||||
func _unhandled_key_input(event: InputEvent):
|
||||
if not event.is_pressed(): return
|
||||
if event is InputEventKey and event.as_text() == "R":
|
||||
if event is InputEventKey and event.as_text().to_lower() == "r":
|
||||
_on_rotate_button_pressed()
|
||||
get_tree().set_input_as_handled()
|
||||
|
||||
|
||||
func on_active_piece_set(scene: PackedScene):
|
||||
if is_instance_valid(preview_piece):
|
||||
preview_piece.queue_free()
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.queue_free()
|
||||
|
||||
active_piece_scene = scene
|
||||
preview_piece = scene.instantiate() as StructuralPiece
|
||||
preview_piece.is_preview = true
|
||||
builder_scene_root.add_child(preview_piece)
|
||||
current_state = BuilderState.PLACING_STRUCTURAL
|
||||
active_scene = scene
|
||||
preview_node = scene.instantiate() as StructuralPiece
|
||||
preview_node.is_preview = true
|
||||
builder_scene_root.add_child(preview_node)
|
||||
_update_preview_position()
|
||||
|
||||
func on_clear_preview_piece():
|
||||
if is_instance_valid(preview_piece):
|
||||
preview_piece.queue_free()
|
||||
preview_piece = null
|
||||
active_piece_scene = null
|
||||
func _on_component_selected(component_scene: PackedScene):
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.queue_free()
|
||||
|
||||
current_state = BuilderState.PLACING_COMPONENT
|
||||
active_scene = component_scene
|
||||
preview_node = component_scene.instantiate() as Component
|
||||
builder_scene_root.add_child(preview_node)
|
||||
|
||||
print("Now placing component: ", component_scene.resource_path)
|
||||
|
||||
func on_clear_preview():
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.queue_free()
|
||||
preview_node = null
|
||||
active_scene = null
|
||||
current_state = BuilderState.IDLE
|
||||
|
||||
|
||||
func _update_preview_position():
|
||||
if not is_instance_valid(preview_piece):
|
||||
if not is_instance_valid(preview_node):
|
||||
return
|
||||
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
var snapped_pos = world_mouse_pos.snapped(Vector2(grid_size, grid_size))
|
||||
preview_piece.global_position = snapped_pos
|
||||
preview_piece.rotation = rotation_angle
|
||||
|
||||
match current_state:
|
||||
BuilderState.PLACING_STRUCTURAL:
|
||||
var snapped_pos = world_mouse_pos.snapped(Vector2(grid_size, grid_size))
|
||||
preview_node.global_position = snapped_pos
|
||||
preview_node.rotation = rotation_angle
|
||||
BuilderState.PLACING_COMPONENT:
|
||||
var target_module = _find_first_module()
|
||||
if target_module:
|
||||
var closest_point = _find_closest_attachment_point(target_module, world_mouse_pos)
|
||||
if closest_point:
|
||||
preview_node.global_position = closest_point.position
|
||||
else:
|
||||
preview_node.global_position = world_mouse_pos
|
||||
else:
|
||||
preview_node.global_position = world_mouse_pos
|
||||
|
||||
|
||||
# --- REFACTORED: Piece Placement ---
|
||||
func _place_piece_from_preview():
|
||||
if not is_instance_valid(preview_piece):
|
||||
if not is_instance_valid(preview_node) or not is_instance_valid(active_scene):
|
||||
return
|
||||
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
@ -233,38 +283,69 @@ func _place_piece_from_preview():
|
||||
target_module.global_position = snapped_pos
|
||||
target_module.owner = builder_scene_root
|
||||
|
||||
var piece_to_place = active_piece_scene.instantiate()
|
||||
target_module.structural_container.add_child(piece_to_place)
|
||||
var piece_to_place = active_scene.instantiate()
|
||||
|
||||
# --- The main change: Add as a direct child of the module ---
|
||||
target_module.add_child(piece_to_place)
|
||||
piece_to_place.owner = target_module
|
||||
piece_to_place.rotation = rotation_angle
|
||||
piece_to_place.global_position = snapped_pos
|
||||
|
||||
# --- The Undo/Redo Logic ---
|
||||
undo_redo.create_action("Place Structural Piece")
|
||||
|
||||
# DO method: adds the piece to the scene.
|
||||
undo_redo.add_do_method(target_module.structural_container, "add_child", piece_to_place)
|
||||
undo_redo.add_do_method(target_module, "add_child", piece_to_place)
|
||||
undo_redo.add_do_method(piece_to_place, "set_owner", target_module)
|
||||
|
||||
# DO method: recalculates physics.
|
||||
undo_redo.add_do_method(target_module, "_recalculate_collision_shape")
|
||||
|
||||
# UNDO method: removes the piece from the scene parent.
|
||||
undo_redo.add_undo_method(target_module.structural_container, "remove_child", piece_to_place)
|
||||
# UNDO method: recalculates physics.
|
||||
undo_redo.add_undo_method(target_module, "remove_child", piece_to_place)
|
||||
undo_redo.add_undo_method(target_module, "_recalculate_collision_shape")
|
||||
|
||||
undo_redo.commit_action()
|
||||
|
||||
# --- Component Placement remains the same ---
|
||||
func _place_component_from_preview():
|
||||
if not is_instance_valid(preview_node) or not is_instance_valid(active_scene):
|
||||
push_error("Cannot place component: Invalid preview or scene.")
|
||||
return
|
||||
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
|
||||
var target_module = _find_first_module()
|
||||
if not target_module:
|
||||
push_error("No module found to attach component to.")
|
||||
return
|
||||
|
||||
var closest_point = _find_closest_attachment_point(target_module, world_mouse_pos)
|
||||
if not closest_point:
|
||||
print("No valid attachment point nearby.")
|
||||
return
|
||||
|
||||
var component_to_place = active_scene.instantiate() as Component
|
||||
|
||||
target_module.attach_component(component_to_place, closest_point.position, closest_point.piece)
|
||||
|
||||
undo_redo.create_action("Place Component")
|
||||
undo_redo.add_do_method(target_module, "attach_component", component_to_place, closest_point.position, closest_point.piece)
|
||||
undo_redo.add_undo_method(target_module, "remove_child", component_to_place)
|
||||
undo_redo.add_do_method(target_module, "_update_mass_and_inertia")
|
||||
undo_redo.add_undo_method(target_module, "_update_mass_and_inertia")
|
||||
undo_redo.commit_action()
|
||||
|
||||
preview_node.global_position = closest_point.position
|
||||
|
||||
# --- Find Nearby Modules remains the same ---
|
||||
func _find_nearby_modules(position: Vector2) -> Module:
|
||||
# Define a margin for the overlap check.
|
||||
const OVERLAP_MARGIN = 20.0
|
||||
|
||||
# Get the shape from the active piece scene.
|
||||
var piece_shape = active_piece_scene.instantiate().find_child("CollisionShape2D").shape
|
||||
if not active_scene or not active_scene.can_instantiate(): return null
|
||||
var piece_instance = active_scene.instantiate()
|
||||
var shape_node = piece_instance.find_child("CollisionShape2D")
|
||||
if not shape_node:
|
||||
piece_instance.queue_free()
|
||||
return null
|
||||
var piece_shape = shape_node.shape
|
||||
piece_instance.queue_free()
|
||||
|
||||
# Create a temporary, slightly larger shape for the overlap check.
|
||||
var enlarged_shape
|
||||
if piece_shape is RectangleShape2D:
|
||||
enlarged_shape = RectangleShape2D.new()
|
||||
@ -274,63 +355,78 @@ func _find_nearby_modules(position: Vector2) -> Module:
|
||||
enlarged_shape.radius = piece_shape.radius + OVERLAP_MARGIN
|
||||
enlarged_shape.height = piece_shape.height + OVERLAP_MARGIN
|
||||
else:
|
||||
# Fallback for other shapes
|
||||
return null
|
||||
|
||||
# Use a PhysicsShapeQuery to find overlapping pieces.
|
||||
var space_state = builder_world.direct_space_state
|
||||
var query = PhysicsShapeQueryParameters2D.new()
|
||||
query.set_shape(enlarged_shape)
|
||||
query.transform = Transform2D(0, position)
|
||||
|
||||
var result = space_state.intersect_shape(query, 1) # Limit to a single result
|
||||
var result = space_state.intersect_shape(query, 1)
|
||||
|
||||
if not result.is_empty():
|
||||
var collider = result[0].get("collider")
|
||||
if collider is StructuralPiece:
|
||||
if collider.get_parent() and collider.get_parent().get_parent() is Module:
|
||||
return collider.get_parent().get_parent()
|
||||
# --- REFACTORED: The module is now the direct parent/owner ---
|
||||
if is_instance_valid(collider.owner) and collider.owner is Module:
|
||||
return collider.owner
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func _find_first_module() -> Module:
|
||||
for node in builder_scene_root.get_children():
|
||||
if node is Module:
|
||||
return node
|
||||
return null
|
||||
|
||||
func _remove_piece_under_mouse():
|
||||
var viewport: SubViewport = main_screen.find_child("SubViewport")
|
||||
if not viewport: return
|
||||
var world_mouse_pos = viewport.get_canvas_transform().affine_inverse() * viewport.get_mouse_position()
|
||||
var snapped_pos = world_mouse_pos.snapped(Vector2(grid_size, grid_size))
|
||||
|
||||
var space_state = builder_world.direct_space_state
|
||||
var query = PhysicsPointQueryParameters2D.new()
|
||||
query.position = world_mouse_pos
|
||||
var result = space_state.intersect_point(query, 1)
|
||||
|
||||
for node in builder_scene_root.get_children():
|
||||
if node is Module:
|
||||
for piece in node.structural_container.get_children():
|
||||
if piece is StructuralPiece and piece.global_position == snapped_pos:
|
||||
_remove_piece_with_undo_redo(piece)
|
||||
return
|
||||
if not result.is_empty():
|
||||
var collider = result[0].get("collider")
|
||||
if collider is StructuralPiece:
|
||||
_remove_piece_with_undo_redo(collider)
|
||||
elif collider is Component:
|
||||
pass
|
||||
|
||||
|
||||
# --- REFACTORED: Piece Removal ---
|
||||
func _remove_piece_with_undo_redo(piece: StructuralPiece):
|
||||
var module = piece.owner as Module
|
||||
var parent = piece.get_parent()
|
||||
if not is_instance_valid(module) or not module is Module:
|
||||
return
|
||||
|
||||
undo_redo.create_action("Remove Structural Piece")
|
||||
print(module.structural_container.get_child_count(false))
|
||||
if module.structural_container.get_child_count(false) >= 1:
|
||||
|
||||
# If this is the last structural piece of the module...
|
||||
if module.get_structural_pieces().size() == 1:
|
||||
# ...remove the entire module.
|
||||
undo_redo.add_do_method(builder_scene_root, "remove_child", module)
|
||||
undo_redo.add_undo_method(builder_scene_root, "add_child", module)
|
||||
undo_redo.add_undo_method(module, "set_owner", builder_scene_root)
|
||||
else:
|
||||
undo_redo.add_do_method(parent, "remove_child", piece)
|
||||
# Otherwise, just remove the single piece from its parent (the module).
|
||||
undo_redo.add_do_method(module, "remove_child", piece)
|
||||
undo_redo.add_do_method(module, "_recalculate_collision_shape")
|
||||
|
||||
undo_redo.add_undo_method(parent, "add_child", piece)
|
||||
undo_redo.add_undo_method(module, "add_child", piece)
|
||||
undo_redo.add_undo_method(piece, "set_owner", module) # Re-assign owner on undo
|
||||
undo_redo.add_undo_method(module, "_recalculate_collision_shape")
|
||||
|
||||
undo_redo.commit_action()
|
||||
|
||||
# --- Toolbar Button Functions ---
|
||||
# --- Toolbar Button Functions (No changes needed) ---
|
||||
func _on_rotate_button_pressed():
|
||||
rotation_angle = wrapf(rotation_angle + PI / 2, 0, TAU)
|
||||
if preview_piece:
|
||||
preview_piece.rotation = rotation_angle
|
||||
if is_instance_valid(preview_node):
|
||||
preview_node.rotation = rotation_angle
|
||||
_update_preview_position()
|
||||
|
||||
func _on_center_button_pressed():
|
||||
@ -341,52 +437,33 @@ func _on_pressurise_button_pressed():
|
||||
pass
|
||||
|
||||
func _on_save_button_pressed():
|
||||
# Find a module to save. We'll prioritize the selected one.
|
||||
var module_to_save: Module
|
||||
var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
|
||||
if not selected_nodes.is_empty() and selected_nodes[0] is Module:
|
||||
module_to_save = selected_nodes[0]
|
||||
elif builder_scene_root.get_child_count() > 0:
|
||||
for node in builder_scene_root.get_children():
|
||||
if node is Module:
|
||||
module_to_save = node
|
||||
break
|
||||
|
||||
else:
|
||||
module_to_save = _find_first_module()
|
||||
|
||||
if not is_instance_valid(module_to_save):
|
||||
push_error("Error: No Module node found or selected to save.")
|
||||
return
|
||||
|
||||
# Create and configure the save dialog
|
||||
var save_dialog = EditorFileDialog.new()
|
||||
save_dialog.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE
|
||||
save_dialog.add_filter("*.tscn; Godot Scene")
|
||||
save_dialog.current_path = "res://modules/" + module_to_save.name + ".tscn"
|
||||
|
||||
# FIX: Add the dialog to the main editor screen, not the plugin's control node
|
||||
EditorInterface.get_editor_main_screen().add_child(save_dialog)
|
||||
save_dialog.popup_centered_ratio()
|
||||
|
||||
# Connect the signal to our new save function
|
||||
save_dialog.file_selected.connect(Callable(self, "_perform_save").bind(module_to_save))
|
||||
|
||||
func _perform_save(file_path: String, module_to_save: Module):
|
||||
# Make sure the directory exists before attempting to save.
|
||||
var save_dir = file_path.get_base_dir()
|
||||
var dir = DirAccess.open("res://")
|
||||
if not dir.dir_exists(save_dir):
|
||||
dir.make_dir_recursive(save_dir)
|
||||
#
|
||||
## FIX: Manually get the structural container reference from the duplicated module.
|
||||
#var duplicate_structural_container = duplicate_module.find_child("StructuralContainer")
|
||||
#
|
||||
#if is_instance_valid(duplicate_structural_container):
|
||||
## FIX: Correctly set the owner of all child nodes to the new root.
|
||||
## This is the crucial step to ensure the children are packed correctly.
|
||||
#for piece in duplicate_structural_container.get_children():
|
||||
#print(piece)
|
||||
#piece.owner = duplicate_module
|
||||
|
||||
# Pack the node into a PackedScene.
|
||||
var packed_scene = PackedScene.new()
|
||||
var error = packed_scene.pack(module_to_save)
|
||||
|
||||
@ -394,10 +471,6 @@ func _perform_save(file_path: String, module_to_save: Module):
|
||||
push_error("Error packing scene: ", error_string(error))
|
||||
return
|
||||
|
||||
# FIX: Reset the duplicated module's position so it's centered in its own scene.
|
||||
#duplicate_module.global_position = Vector2.ZERO
|
||||
|
||||
# Save the PackedScene to a file.
|
||||
var save_result = ResourceSaver.save(packed_scene, file_path)
|
||||
|
||||
if save_result == OK:
|
||||
@ -409,7 +482,8 @@ func _perform_save(file_path: String, module_to_save: Module):
|
||||
|
||||
func _on_undo_redo_action_committed():
|
||||
_refresh_tree_display()
|
||||
|
||||
|
||||
# --- REFACTORED: Tree Display ---
|
||||
func _refresh_tree_display():
|
||||
if not is_instance_valid(tree_control):
|
||||
return
|
||||
@ -425,8 +499,26 @@ func _refresh_tree_display():
|
||||
module_item.set_text(0, module.name)
|
||||
module_item.set_meta("node", module)
|
||||
|
||||
for piece in module.structural_container.get_children():
|
||||
if piece is StructuralPiece:
|
||||
var piece_item = tree_control.create_item(module_item)
|
||||
piece_item.set_text(0, piece.name)
|
||||
piece_item.set_meta("node", piece)
|
||||
# Use the module's helper functions to find children
|
||||
for piece in module.get_structural_pieces():
|
||||
var piece_item = tree_control.create_item(module_item)
|
||||
piece_item.set_text(0, piece.name)
|
||||
piece_item.set_meta("node", piece)
|
||||
|
||||
for component in module.get_components():
|
||||
var component_item = tree_control.create_item(module_item)
|
||||
component_item.set_text(0, component.name)
|
||||
component_item.set_meta("node", component)
|
||||
|
||||
|
||||
func _find_closest_attachment_point(module: Module, world_pos: Vector2):
|
||||
var min_distance_sq = module.COMPONENT_GRID_SIZE * module.COMPONENT_GRID_SIZE * 0.5
|
||||
var closest_point = null
|
||||
|
||||
for point in module.get_attachment_points():
|
||||
var dist_sq = point.position.distance_squared_to(world_pos)
|
||||
if dist_sq < min_distance_sq:
|
||||
min_distance_sq = dist_sq
|
||||
closest_point = point
|
||||
|
||||
return closest_point
|
||||
|
||||
31
modules/Module.tscn
Normal file
31
modules/Module.tscn
Normal file
@ -0,0 +1,31 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b1kpyek60vyof"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1abiy"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_risxe"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
position = Vector2(-50, 50)
|
||||
mass = null
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector2(-50, 0)
|
||||
inertia = null
|
||||
linear_velocity = null
|
||||
angular_velocity = null
|
||||
script = ExtResource("1_1abiy")
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_risxe")]
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_risxe")]
|
||||
position = Vector2(-100, 0)
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
31
modules/New_module.tscn
Normal file
31
modules/New_module.tscn
Normal file
@ -0,0 +1,31 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://baeikwxkh26fh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1rae4"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_fbnt1"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
position = Vector2(-50, 50)
|
||||
mass = null
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector2(-50, 0)
|
||||
inertia = null
|
||||
linear_velocity = null
|
||||
angular_velocity = null
|
||||
script = ExtResource("1_1rae4")
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
|
||||
position = Vector2(-100, 0)
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
56
modules/Tube.tscn
Normal file
56
modules/Tube.tscn
Normal file
@ -0,0 +1,56 @@
|
||||
[gd_scene load_steps=6 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"]
|
||||
[ext_resource type="PackedScene" uid="uid://d3hitk62fice4" path="res://scenes/ship/builder/pieces/bulkhead.tscn" id="4_dmrms"]
|
||||
[ext_resource type="PackedScene" uid="uid://chgycmkkaf7jv" path="res://scenes/characters/pilot_ball.tscn" id="4_i3kos"]
|
||||
[ext_resource type="PackedScene" uid="uid://2n42nstcj1n0" path="res://scenes/ship/components/station_component.tscn" id="5_nqe0s"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
script = ExtResource("1_nqe0s")
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="." instance=ExtResource("2_foqop")]
|
||||
|
||||
[node name="@StaticBody2D@30634" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, 100)
|
||||
|
||||
[node name="@StaticBody2D@30635" parent="." instance=ExtResource("2_foqop")]
|
||||
position = Vector2(0, -100)
|
||||
|
||||
[node name="Bulkhead" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 100)
|
||||
|
||||
[node name="@StaticBody2D@30636" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, 0)
|
||||
|
||||
[node name="@StaticBody2D@30637" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(-50, -100)
|
||||
|
||||
[node name="@StaticBody2D@30638" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, -100)
|
||||
|
||||
[node name="@StaticBody2D@30639" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, -150)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="@StaticBody2D@30640" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(0, 150)
|
||||
rotation = 4.71239
|
||||
|
||||
[node name="@StaticBody2D@30641" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 100)
|
||||
|
||||
[node name="@StaticBody2D@30642" parent="." instance=ExtResource("4_dmrms")]
|
||||
position = Vector2(50, 0)
|
||||
|
||||
[node name="PilotBall" parent="." instance=ExtResource("4_i3kos")]
|
||||
|
||||
[node name="Station" parent="." instance=ExtResource("5_nqe0s")]
|
||||
position = Vector2(0, -100)
|
||||
@ -57,13 +57,40 @@ time_reset={
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
interact={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
2d_physics/layer_1="ship_hull"
|
||||
2d_physics/layer_1="hullplates"
|
||||
2d_physics/layer_2="ship_components"
|
||||
2d_physics/layer_3="celestial_bodies"
|
||||
2d_physics/layer_4="projectiles"
|
||||
2d_physics/layer_5="bulkheads"
|
||||
2d_physics/layer_6="characters"
|
||||
|
||||
[physics]
|
||||
|
||||
|
||||
@ -1,71 +1,199 @@
|
||||
extends CharacterBody2D
|
||||
class_name PilotBall
|
||||
|
||||
# Local movement speed when unattached (e.g., inside a ship or during EVA)
|
||||
const LOCAL_SPEED = 200.0
|
||||
# --- Movement Constants (Friction Simulation) ---
|
||||
# When in open space (no module overlap), movement is zeroed out quickly.
|
||||
const EXTERIOR_DRAG_FACTOR: float = 0.05
|
||||
|
||||
var attached_to_station: Node2D = null
|
||||
var owning_ship: RigidBody2D = null # The ship the character is currently anchored to.
|
||||
# When pushing off hullplates (low friction, slow acceleration)
|
||||
const INTERIOR_SLUGGISH_SPEED: float = 100.0
|
||||
const INTERIOR_SLUGGISH_ACCEL: float = 5 # Low acceleration, simulating mass and small push
|
||||
|
||||
# When gripping a ladder (high friction, direct control)
|
||||
const LADDER_SPEED: float = 100.0
|
||||
const LADDER_ACCEL: float = 20 # High acceleration, simulating direct grip
|
||||
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
@onready var overlap_area: Area2D = $OverlapDetector
|
||||
|
||||
var nearby_station: StationComponent = null
|
||||
var current_station: StationComponent = null
|
||||
|
||||
# --- State Variables ---
|
||||
enum MovementState {
|
||||
NO_CONTROL,
|
||||
ZERO_G_INTERIOR,
|
||||
LADDER_GRIP
|
||||
}
|
||||
|
||||
var current_state: MovementState = MovementState.NO_CONTROL
|
||||
var ladder_area: Area2D = null # Area of the ladder currently overlapped
|
||||
var is_grabbing_ladder: bool = false # True if 'Space' is held while on ladder
|
||||
|
||||
# --- Overlap Detection (Assuming you use Area2D for detection) ---
|
||||
var overlapping_modules: int = 0
|
||||
|
||||
# --- Ladder Constants ---
|
||||
const LAUNCH_VELOCITY: float = 300.0
|
||||
|
||||
# --- New: Physics Initialization (Assuming CharacterBody2D is parented to the scene root or Ship) ---
|
||||
# NOTE: CharacterBody2D cannot inherit OrbitalBody2D, so we manage its velocity manually.
|
||||
|
||||
func _ready():
|
||||
# Assume the ship is a parent somewhere up the tree for now.
|
||||
# This should be set upon spawning inside a ship.
|
||||
owning_ship = get_parent().find_parent("Spaceship")
|
||||
# Set up overlap signals if they aren't already connected in the scene file
|
||||
# You must have an Area2D child on PilotBall to detect overlaps.
|
||||
|
||||
# Placeholder: Assuming the PilotBall has an Area2D named 'OverlapChecker'
|
||||
#var overlap_checker = find_child("OverlapChecker")
|
||||
#if overlap_checker:
|
||||
overlap_area.body_entered.connect(on_body_entered)
|
||||
overlap_area.body_exited.connect(on_body_exited)
|
||||
overlap_area.area_entered.connect(_on_station_area_entered)
|
||||
overlap_area.area_exited.connect(_on_station_area_exited)
|
||||
# Ensure this action is set in project settings: "interact" mapped to Space.
|
||||
if !InputMap.has_action("interact"):
|
||||
push_error("Missing 'interact' input action for ladder logic.")
|
||||
|
||||
camera.make_current()
|
||||
|
||||
# New function to handle global inputs
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
# --- Map Toggling ---
|
||||
if event.is_action_pressed("ui_map_mode"):
|
||||
SignalBus.emit_signal("map_mode_toggled")
|
||||
|
||||
# --- Time Scale Controls ---
|
||||
if event.is_action_pressed("time_increase"):
|
||||
Engine.time_scale = min(Engine.time_scale * 1.2, 1000)
|
||||
|
||||
elif event.is_action_pressed("time_decrease"):
|
||||
Engine.time_scale = max(Engine.time_scale * 0.833, 0.1)
|
||||
|
||||
elif event.is_action_pressed("time_reset"):
|
||||
Engine.time_scale = 1.0
|
||||
|
||||
func on_body_entered(body: Node2D):
|
||||
# Detect Modules (which all inherit OrbitalBody2D via StructuralPiece)
|
||||
if body is StructuralPiece:
|
||||
overlapping_modules += 1
|
||||
|
||||
# Detect Ladders
|
||||
if body is Ladder:
|
||||
ladder_area = body.find_child("ClimbArea") # Assuming the Ladder has a specific Area2D for climbing
|
||||
|
||||
func on_body_exited(body: Node2D):
|
||||
if body is StructuralPiece:
|
||||
overlapping_modules -= 1
|
||||
|
||||
if body is Ladder:
|
||||
if body.find_child("ClimbArea") == ladder_area:
|
||||
ladder_area = null
|
||||
is_grabbing_ladder = false # Force detach if the ladder moves away
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
# If attached, do not move locally. The station handles movement control (thrusters).
|
||||
if is_attached():
|
||||
velocity = Vector2.ZERO
|
||||
return
|
||||
# 1. Update State based on environment
|
||||
_update_movement_state()
|
||||
|
||||
var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
|
||||
match current_state:
|
||||
#MovementState.NO_CONTROL:
|
||||
## Apply heavy drag to simulate floating in space without external push
|
||||
#_sluggish_movement(input_dir, delta)
|
||||
MovementState.ZERO_G_INTERIOR:
|
||||
# Sluggish movement: player is pushing off nearby walls/hullplates
|
||||
_sluggish_movement(input_dir, delta)
|
||||
|
||||
MovementState.LADDER_GRIP:
|
||||
# Snappy movement: direct control and high acceleration
|
||||
_ladder_movement(input_dir, delta)
|
||||
|
||||
# Local movement: Use A/D/W/S to move the ball around.
|
||||
var input_direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
velocity = input_direction * LOCAL_SPEED
|
||||
# --- Update Ladder/Station Input Handling ---
|
||||
# We'll replace the old _handle_ladder_input with a more general one
|
||||
_handle_interaction_input(input_dir)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
func is_attached():
|
||||
return attached_to_station != null
|
||||
|
||||
func attach(station: Node2D):
|
||||
if is_attached(): return
|
||||
|
||||
attached_to_station = station
|
||||
|
||||
# 1. Store the global position before changing parent.
|
||||
var old_global_pos = global_position
|
||||
|
||||
# 2. Change the parent to the station/ship (Anchoring)
|
||||
# This makes the ball's movement purely relative to the ship.
|
||||
reparent(station)
|
||||
|
||||
# 3. Reset local position. Its new global position is defined relative to the ship/station.
|
||||
global_position = old_global_pos
|
||||
|
||||
# 4. Notify the station it is now in control
|
||||
station.set_pilot(self)
|
||||
# --- State Machine Update ---
|
||||
|
||||
func detach():
|
||||
if not is_attached(): return
|
||||
|
||||
var station = attached_to_station
|
||||
attached_to_station = null
|
||||
|
||||
# 1. Notify the station to release control
|
||||
station.set_pilot(null)
|
||||
|
||||
# 2. Store the current global transform (which is relative to the ship)
|
||||
var new_global_pos = global_position
|
||||
|
||||
# 3. Reparent back to the main world (or a dedicated 'space' node)
|
||||
# This is the critical moment: the node re-enters the high-velocity global space.
|
||||
if owning_ship and owning_ship.get_parent():
|
||||
reparent(owning_ship.get_parent())
|
||||
func _update_movement_state():
|
||||
# Priority 1: Ladder Grip
|
||||
if ladder_area and Input.is_action_pressed("interact"):
|
||||
is_grabbing_ladder = true
|
||||
current_state = MovementState.LADDER_GRIP
|
||||
return
|
||||
|
||||
# 4. Restore global position and inherit the ship's massive orbital velocity
|
||||
global_position = new_global_pos
|
||||
velocity = owning_ship.linear_velocity
|
||||
else:
|
||||
# Fallback if ship structure is unknown
|
||||
reparent(get_tree().root)
|
||||
global_position = new_global_pos
|
||||
velocity = Vector2.ZERO
|
||||
# Priority 2: Interior Zero-G (must overlap a module/piece AND not be grabbing)
|
||||
if overlapping_modules > 0:
|
||||
if is_grabbing_ladder:
|
||||
# If we were grabbing a ladder but released 'interact', we transition to zero-G interior
|
||||
is_grabbing_ladder = false
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
return
|
||||
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
return
|
||||
|
||||
# Priority 3: No Control (floating free)
|
||||
is_grabbing_ladder = false
|
||||
current_state = MovementState.NO_CONTROL
|
||||
|
||||
|
||||
# --- Movement Implementations ---
|
||||
|
||||
func _sluggish_movement(input_dir: Vector2, delta: float):
|
||||
# Simulates pushing off the wall: slow acceleration, but minimal drag
|
||||
var target_velocity = input_dir * INTERIOR_SLUGGISH_ACCEL
|
||||
velocity = velocity + target_velocity * delta
|
||||
#velocity.lerp(velocity + interi, INTERIOR_SLUGGISH_ACCEL)
|
||||
|
||||
func _ladder_movement(input_dir: Vector2, delta: float):
|
||||
# Simulates direct grip: fast acceleration, perfect control
|
||||
var target_velocity = input_dir * LADDER_SPEED
|
||||
velocity = velocity.lerp(target_velocity, LADDER_ACCEL * delta)
|
||||
|
||||
|
||||
# --- Ladder Input and Launch Logic ---
|
||||
|
||||
func _handle_interaction_input(input_dir: Vector2):
|
||||
# If we are currently using a station
|
||||
if current_station:
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
current_station.disengage()
|
||||
current_station = null
|
||||
return # Do nothing else while in a station
|
||||
|
||||
# If we are near a station and press interact
|
||||
if is_instance_valid(nearby_station) and Input.is_action_just_pressed("interact"):
|
||||
current_station = nearby_station
|
||||
current_station.occupy(self)
|
||||
return
|
||||
|
||||
# If currently grabbing, SPACE press is handled in _update_movement_state
|
||||
if current_state == MovementState.LADDER_GRIP:
|
||||
if Input.is_action_just_released("interact"):
|
||||
# Launch the player away from the ladder
|
||||
|
||||
# Determine launch direction: opposite of input, or default forward
|
||||
var launch_direction = -input_dir.normalized()
|
||||
if launch_direction == Vector2.ZERO:
|
||||
# Default launch: use the character's forward direction (e.g., rotation 0)
|
||||
launch_direction = Vector2.UP.rotated(rotation)
|
||||
|
||||
velocity = launch_direction * LAUNCH_VELOCITY
|
||||
|
||||
# Immediately switch to zero-G interior state
|
||||
is_grabbing_ladder = false
|
||||
current_state = MovementState.ZERO_G_INTERIOR
|
||||
|
||||
# --- New Functions for Station Interaction ---
|
||||
func _on_station_area_entered(area: Area2D):
|
||||
if area.get_parent() is StationComponent:
|
||||
nearby_station = area.get_parent()
|
||||
print("Near station: ", nearby_station.name)
|
||||
|
||||
func _on_station_area_exited(area: Area2D):
|
||||
if area.get_parent() == nearby_station:
|
||||
nearby_station = null
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://chgycmkkaf7jv"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://chgycmkkaf7jv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dxngvoommn5f1" path="res://scenes/characters/pilot_ball.gd" id="1_rhbna"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_6jclb"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_rhbna"]
|
||||
|
||||
[node name="PilotBall" type="CharacterBody2D"]
|
||||
collision_layer = 32
|
||||
collision_mask = 16
|
||||
script = ExtResource("1_rhbna")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
@ -12,3 +16,11 @@ shape = SubResource("CircleShape2D_6jclb")
|
||||
debug_color = Color(0.61528, 0.358023, 1, 1)
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
zoom = Vector2(3, 3)
|
||||
|
||||
[node name="OverlapDetector" type="Area2D" parent="."]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="OverlapDetector"]
|
||||
shape = SubResource("CircleShape2D_rhbna")
|
||||
|
||||
@ -1,48 +1,115 @@
|
||||
@tool
|
||||
class_name Module
|
||||
extends OrbitalBody2D
|
||||
|
||||
@onready var structural_container: Node2D = $StructuralContainer
|
||||
@onready var hull_volume_container: Node2D = $HullVolumeContainer
|
||||
# --- New properties inherited from Spaceship ---
|
||||
@export var ship_name: String = "Unnamed Ship" # Only relevant for the root module
|
||||
@export var hull_integrity: float = 100.0 # This could also be a calculated property later
|
||||
|
||||
# The function name is updated to reflect its new, limited scope.
|
||||
func _recalculate_collision_shape():
|
||||
# This logic should typically be on the main Spaceship node,
|
||||
# but for modularity, the Module can trigger it on its children.
|
||||
const COMPONENT_GRID_SIZE = 64.0
|
||||
|
||||
# --- NEW: Helper functions to get children by type ---
|
||||
func get_structural_pieces() -> Array[StructuralPiece]:
|
||||
var pieces: Array[StructuralPiece]
|
||||
for child in get_children():
|
||||
if child is StructuralPiece:
|
||||
pieces.append(child)
|
||||
return pieces
|
||||
|
||||
func get_components() -> Array[Component]:
|
||||
var components: Array[Component]
|
||||
for child in get_children():
|
||||
if child is Component:
|
||||
components.append(child)
|
||||
return components
|
||||
|
||||
# --- UPDATED: Logic now uses the helper function ---
|
||||
func get_attachment_points() -> Array:
|
||||
var points = []
|
||||
|
||||
# 1. Clear any existing combined collision shape on this module.
|
||||
# (You would likely have a central CollisionShape2D node for the combined shape)
|
||||
# var combined_shape_node = find_child("CombinedCollisionShape")
|
||||
# if combined_shape_node:
|
||||
# for child in combined_shape_node.get_children(): child.queue_free()
|
||||
# Iterate through all StructuralPiece children directly
|
||||
for piece in get_structural_pieces():
|
||||
var piece_center = piece.global_position
|
||||
|
||||
# --- Hullplates (Interior Grid) ---
|
||||
if piece is Hullplate:
|
||||
for i in range(-1, 2, 2):
|
||||
for j in range(-1, 2, 2):
|
||||
var offset = Vector2(i, j) * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": piece_center + offset,
|
||||
"type": Component.AttachmentType.INTERIOR_WALL,
|
||||
"piece": piece
|
||||
})
|
||||
|
||||
# --- Bulkheads (Interior and Exterior Edge Attachments) ---
|
||||
elif piece is Bulkhead:
|
||||
var interior_point = piece_center + piece.transform.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": interior_point,
|
||||
"type": Component.AttachmentType.INTERIOR_WALL,
|
||||
"piece": piece
|
||||
})
|
||||
|
||||
var exterior_point = piece_center - piece.transform.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": exterior_point,
|
||||
"type": Component.AttachmentType.EXTERIOR_HULL,
|
||||
"piece": piece
|
||||
})
|
||||
|
||||
# 2. Iterate through all StructuralPiece children (which are now OrbitalBody2D)
|
||||
# and gather their global collision transforms/shapes.
|
||||
return points
|
||||
|
||||
# --- This function remains largely the same ---
|
||||
func attach_component(component: Component, global_pos: Vector2, parent_piece: StructuralPiece):
|
||||
component.position = global_pos - global_position
|
||||
component.attached_piece = parent_piece
|
||||
add_child(component)
|
||||
component.owner = self
|
||||
_update_mass_and_inertia()
|
||||
|
||||
# --- UPDATED: Logic now uses the helper function ---
|
||||
func _recalculate_collision_shape():
|
||||
# This logic is much simpler now. We just iterate over relevant children.
|
||||
var combined_polygons = []
|
||||
|
||||
for child in get_children():
|
||||
# StructuralPiece now inherits OrbitalBody2D, so this check is valid
|
||||
if child is StructuralPiece:
|
||||
# You would use logic here to transform the piece's local shape
|
||||
# into the Module's local space and add it to the list.
|
||||
|
||||
# Example Placeholder (requires full implementation):
|
||||
# var piece_collision_shape = child.find_child("CollisionShape2D")
|
||||
# if piece_collision_shape:
|
||||
# combined_polygons.append(piece_collision_shape.shape.points)
|
||||
pass
|
||||
for piece in get_structural_pieces():
|
||||
# You would use logic here to transform the piece's local shape
|
||||
# into the Module's local space and add it to the list.
|
||||
# Example Placeholder (requires full implementation):
|
||||
# var piece_collision_shape = piece.find_child("CollisionShape2D")
|
||||
# if piece_collision_shape:
|
||||
# combined_polygons.append(piece_collision_shape.shape.points)
|
||||
pass
|
||||
|
||||
# 3. Create a new shape (e.g., ConcavePolygonShape2D) and assign it.
|
||||
# This part is complex and usually requires an external library or custom code
|
||||
# to merge multiple 2D shapes efficiently.
|
||||
|
||||
# After implementation, you may want to signal the change:
|
||||
# SignalBus.module_structure_changed.emit(self)
|
||||
|
||||
# NOTE: The OrbitalBody2D's _update_mass_and_inertia() takes care of mass and center of mass!
|
||||
# NOTE: The OrbitalBody2D's _update_mass_and_inertia() takes care of mass!
|
||||
pass
|
||||
|
||||
# --- UPDATED: Clear module now iterates over all relevant children ---
|
||||
func clear_module():
|
||||
for piece in structural_container.get_children():
|
||||
# We queue_free both structural pieces and components
|
||||
for piece in get_structural_pieces():
|
||||
piece.queue_free()
|
||||
for component in get_components():
|
||||
component.queue_free()
|
||||
|
||||
_recalculate_collision_shape()
|
||||
|
||||
# --- New function inherited from Spaceship ---
|
||||
# Damage can have a position for breach effects.
|
||||
func take_damage(amount: float, damage_position: Vector2):
|
||||
hull_integrity -= amount
|
||||
print("%s hull integrity at %.1f%%" % [ship_name, hull_integrity])
|
||||
|
||||
if hull_integrity <= 0:
|
||||
destroy_ship()
|
||||
else:
|
||||
# Find the LifeSupport component and check for a breach
|
||||
for child in get_children():
|
||||
if child is LifeSupport: # Assuming LifeSupport becomes a Component class
|
||||
child.check_for_breach(damage_position, self)
|
||||
|
||||
func destroy_ship():
|
||||
print("%s has been destroyed!" % ship_name)
|
||||
# Add explosion/destruction effects here
|
||||
queue_free()
|
||||
|
||||
6
scenes/ship/builder/pieces/bulkhead.gd
Normal file
6
scenes/ship/builder/pieces/bulkhead.gd
Normal file
@ -0,0 +1,6 @@
|
||||
@tool
|
||||
class_name Bulkhead
|
||||
extends StructuralPiece
|
||||
|
||||
# This piece represents a wall or edge.
|
||||
# No additional logic is needed right now, we just need the class_name.
|
||||
1
scenes/ship/builder/pieces/bulkhead.gd.uid
Normal file
1
scenes/ship/builder/pieces/bulkhead.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b4g288mje38nj
|
||||
@ -1,12 +1,13 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://d3hitk62fice4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7f8x2qimvn37" path="res://scenes/ship/builder/pieces/structural_piece.gd" id="1_1wp2n"]
|
||||
[ext_resource type="Script" uid="uid://b4g288mje38nj" path="res://scenes/ship/builder/pieces/bulkhead.gd" id="1_1wp2n"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
|
||||
size = Vector2(10, 100)
|
||||
|
||||
[node name="Bulkhead" type="StaticBody2D"]
|
||||
collision_layer = 5
|
||||
collision_layer = 16
|
||||
collision_mask = 60
|
||||
script = ExtResource("1_1wp2n")
|
||||
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
|
||||
|
||||
|
||||
6
scenes/ship/builder/pieces/hullplate.gd
Normal file
6
scenes/ship/builder/pieces/hullplate.gd
Normal file
@ -0,0 +1,6 @@
|
||||
@tool
|
||||
class_name Hullplate
|
||||
extends StructuralPiece
|
||||
|
||||
# This piece represents an interior surface.
|
||||
# No additional logic is needed right now, we just need the class_name.
|
||||
1
scenes/ship/builder/pieces/hullplate.gd.uid
Normal file
1
scenes/ship/builder/pieces/hullplate.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://crmwm623rh1ps
|
||||
@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bho8x10x4oab7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7f8x2qimvn37" path="res://scenes/ship/builder/pieces/structural_piece.gd" id="1_ecow4"]
|
||||
[ext_resource type="Script" uid="uid://crmwm623rh1ps" path="res://scenes/ship/builder/pieces/hullplate.gd" id="1_ecow4"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
|
||||
size = Vector2(100, 100)
|
||||
|
||||
@ -1,2 +1,25 @@
|
||||
class_name ShipComponent
|
||||
extends OrbitalBody2D
|
||||
class_name Component
|
||||
|
||||
# Defines the size of the component in terms of the grid (e.g., 1x1, 1x2, 2x2)
|
||||
@export_range(1, 4, 1, "suffix:x") var grid_size_x: int = 1
|
||||
@export_range(1, 4, 1, "suffix:x") var grid_size_y: int = 1
|
||||
|
||||
# Specifies the type of structural piece surface this component can attach to.
|
||||
# Used by the editor to validate placement.
|
||||
enum AttachmentType { INTERIOR_WALL, EXTERIOR_HULL, FLOOR_OR_CEILING }
|
||||
@export var attachment_type: AttachmentType = AttachmentType.INTERIOR_WALL
|
||||
|
||||
# Reference to the StructuralPiece this component is physically attached to
|
||||
var attached_piece: StructuralPiece = null
|
||||
|
||||
func _ready():
|
||||
# OrbitalBody2D will handle mass initialization and physics setup.
|
||||
pass
|
||||
|
||||
# Components can implement activation logic here (e.g., Thruster fires, Life Support starts)
|
||||
func activate():
|
||||
pass
|
||||
|
||||
func deactivate():
|
||||
pass
|
||||
|
||||
8
scenes/ship/components/databank.gd
Normal file
8
scenes/ship/components/databank.gd
Normal file
@ -0,0 +1,8 @@
|
||||
@tool
|
||||
class_name Databank
|
||||
extends Resource
|
||||
|
||||
## The name displayed in the UI (e.g., "Flight Helm MK1").
|
||||
@export var display_name: String = "Unnamed Databank"
|
||||
## The UI scene this databank provides when installed.
|
||||
@export var ui_scene: PackedScene
|
||||
1
scenes/ship/components/databank.gd.uid
Normal file
1
scenes/ship/components/databank.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://osk1l75vlikn
|
||||
32
scenes/ship/components/ladder.gd
Normal file
32
scenes/ship/components/ladder.gd
Normal file
@ -0,0 +1,32 @@
|
||||
extends Component
|
||||
class_name Ladder
|
||||
|
||||
# --- Component Properties ---
|
||||
# A standard ladder is typically one grid unit wide.
|
||||
# We make the height variable to allow for multi-story climbing in one piece.
|
||||
@export var ladder_grid_height: int = 4 # Height in grid units (e.g., 4x64px)
|
||||
|
||||
# --- Inherited OrbitalBody2D & Component Setup ---
|
||||
|
||||
func _ready():
|
||||
# Set the base mass based on its material/size
|
||||
base_mass = float(ladder_grid_height) * 25.0 # Example: 25kg per grid unit height
|
||||
|
||||
# Set inherited Component properties
|
||||
grid_size_x = 1
|
||||
grid_size_y = ladder_grid_height
|
||||
attachment_type = AttachmentType.INTERIOR_WALL
|
||||
|
||||
# Call the parent's _ready to ensure mass calculation is triggered
|
||||
super._ready()
|
||||
|
||||
# You would add logic here to dynamically resize the ladder's visual and collision
|
||||
# shape to match 'ladder_grid_height' if necessary.
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
# Hide mass from the inspector if the base class doesn't need to see it
|
||||
# You can also set a custom icon for the editor here.
|
||||
pass
|
||||
|
||||
# The player character's script will be responsible for checking if it overlaps
|
||||
# this component and entering a 'climbing' state, using the ladder's position and height.
|
||||
1
scenes/ship/components/ladder.gd.uid
Normal file
1
scenes/ship/components/ladder.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bh1t0cqdjm5ye
|
||||
13
scenes/ship/components/ladder.tscn
Normal file
13
scenes/ship/components/ladder.tscn
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dxtxb2p7lpt51"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bh1t0cqdjm5ye" path="res://scenes/ship/components/ladder.gd" id="1_ygkvf"]
|
||||
|
||||
[node name="Ladder" type="Node2D"]
|
||||
script = ExtResource("1_ygkvf")
|
||||
metadata/_custom_type_script = "uid://bh1t0cqdjm5ye"
|
||||
|
||||
[node name="ClimbArea" type="Area2D" parent="."]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="ClimbArea"]
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
82
scenes/ship/components/station_component.gd
Normal file
82
scenes/ship/components/station_component.gd
Normal file
@ -0,0 +1,82 @@
|
||||
@tool
|
||||
class_name StationComponent
|
||||
extends Component
|
||||
|
||||
signal occupancy_changed(is_occupied: bool)
|
||||
|
||||
## The "software" currently installed in this station.
|
||||
@export var installed_databanks: Array[Databank]
|
||||
|
||||
var occupant: PilotBall = null
|
||||
var active_ui_instances: Array[Control] = []
|
||||
@onready var interaction_area: Area2D = $InteractionArea
|
||||
|
||||
func _process(delta):
|
||||
# NEW: The station now checks for the disengage input.
|
||||
if is_occupied() and Input.is_action_just_pressed("interact"):
|
||||
disengage()
|
||||
|
||||
func is_occupied() -> bool:
|
||||
return is_instance_valid(occupant)
|
||||
|
||||
func occupy(character: PilotBall):
|
||||
print("foo")
|
||||
if is_occupied(): return
|
||||
|
||||
occupant = character
|
||||
#occupant.process_mode = Node.PROCESS_MODE_DISABLED
|
||||
|
||||
# Position character at the station (no reparenting needed)
|
||||
occupant.global_position = global_position
|
||||
|
||||
# Launch the UIs from the installed databanks
|
||||
launch_interfaces()
|
||||
|
||||
occupancy_changed.emit(true)
|
||||
|
||||
func disengage():
|
||||
if not is_occupied(): return
|
||||
|
||||
# Close all open UIs
|
||||
close_interfaces()
|
||||
|
||||
#occupant.process_mode = Node.PROCESS_MODE_INHERIT
|
||||
occupant = null
|
||||
|
||||
occupancy_changed.emit(false)
|
||||
|
||||
func launch_interfaces():
|
||||
var root_module = get_root_module()
|
||||
if not is_instance_valid(root_module): return
|
||||
|
||||
# Find the main game UI to add these to.
|
||||
# This assumes you have a CanvasLayer with the name "MainGameUI" in your main scene.
|
||||
var main_ui = get_tree().get_first_node_in_group("main_ui_container")
|
||||
if not main_ui:
|
||||
push_error("No main UI container found for station interfaces!")
|
||||
return
|
||||
|
||||
for db in installed_databanks:
|
||||
if not db or not db.ui_scene: continue
|
||||
|
||||
var ui_instance = db.ui_scene.instantiate()
|
||||
active_ui_instances.append(ui_instance)
|
||||
main_ui.add_child(ui_instance)
|
||||
|
||||
# VERY IMPORTANT: Give the UI a reference to the ship it needs to control.
|
||||
if ui_instance.has_method("initialize"):
|
||||
ui_instance.initialize(root_module)
|
||||
|
||||
func close_interfaces():
|
||||
for ui in active_ui_instances:
|
||||
if is_instance_valid(ui):
|
||||
ui.queue_free()
|
||||
active_ui_instances.clear()
|
||||
# Helper to find the main ship/module this station belongs to
|
||||
func get_root_module() -> Module:
|
||||
var current_node = self
|
||||
while is_instance_valid(current_node):
|
||||
if current_node is Module and current_node.is_sim_root:
|
||||
return current_node
|
||||
current_node = current_node.get_parent()
|
||||
return null
|
||||
1
scenes/ship/components/station_component.gd.uid
Normal file
1
scenes/ship/components/station_component.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://2reyxkr78ra0
|
||||
14
scenes/ship/components/station_component.tscn
Normal file
14
scenes/ship/components/station_component.tscn
Normal file
@ -0,0 +1,14 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://2n42nstcj1n0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://2reyxkr78ra0" path="res://scenes/ship/components/station_component.gd" id="1_8usqu"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_8usqu"]
|
||||
|
||||
[node name="Station" type="Node2D"]
|
||||
script = ExtResource("1_8usqu")
|
||||
|
||||
[node name="InteractionArea" type="Area2D" parent="."]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="InteractionArea"]
|
||||
shape = SubResource("CircleShape2D_8usqu")
|
||||
debug_color = Color(0, 0.551549, 0.918484, 0.42)
|
||||
@ -1,6 +1,6 @@
|
||||
# Thruster.gd
|
||||
class_name Thruster
|
||||
extends ShipComponent
|
||||
extends Component
|
||||
|
||||
@onready var pin_joint_a: PinJoint2D = $PinJointA
|
||||
@onready var pin_joint_b: PinJoint2D = $PinJointB
|
||||
@ -69,7 +69,7 @@ func turn_off():
|
||||
is_firing = false
|
||||
|
||||
# --- Godot Physics Callback ---
|
||||
func _physics_process(delta: float):
|
||||
func _physics_process(_delta: float):
|
||||
if not enabled:
|
||||
is_firing = false
|
||||
|
||||
|
||||
@ -75,10 +75,11 @@ func destroy_ship():
|
||||
#update_total_mass()
|
||||
|
||||
func _on_hull_breach(breach_position: Vector2, force_vector: Vector2):
|
||||
pass
|
||||
# A hull breach applies a continuous force at a specific point
|
||||
# For simplicity, we can apply it as a central force and torque here
|
||||
var force = force_vector * 100 # Scale the force
|
||||
|
||||
# Calculate torque: Torque = r x F (cross product of position vector and force)
|
||||
var position_relative_to_center = breach_position - self.global_position
|
||||
var torque = position_relative_to_center.cross(force)
|
||||
#var force = force_vector * 100 # Scale the force
|
||||
#
|
||||
## Calculate torque: Torque = r x F (cross product of position vector and force)
|
||||
#var position_relative_to_center = breach_position - self.global_position
|
||||
#var torque = position_relative_to_center.cross(force)
|
||||
|
||||
@ -289,11 +289,7 @@ func apply_rotational_thrust(desired_torque: float):
|
||||
# ... (your existing calculation for produced_torque is correct)
|
||||
var thruster_local_pos = thruster.position
|
||||
var thruster_data: ThrusterData = thruster_data_map.get(thruster)
|
||||
thruster_data.measured_torque
|
||||
#var force_local_vec = thruster.thrust_direction * thruster.max_thrust
|
||||
#var produced_torque = thruster_local_pos.cross(force_local_vec)
|
||||
|
||||
# --- THE FIX ---
|
||||
# If this thruster can help, turn it on. Otherwise, explicitly turn it off.
|
||||
if sign(thruster_data.measured_torque) == sign(desired_torque) and desired_torque != 0:
|
||||
thruster.turn_on()
|
||||
|
||||
@ -31,7 +31,7 @@ func _ready():
|
||||
|
||||
# FIX: Enable _physics_process for ALL OrbitalBody2D nodes (including Thrusters).
|
||||
# The 'if is_sim_root' inside _physics_process will prevent integration for children.
|
||||
set_physics_process(true)
|
||||
set_physics_process(Engine.is_editor_hint())
|
||||
|
||||
# --- PUBLIC FORCE APPLICATION METHODS ---
|
||||
# This method is called by a component (like Thruster) at its global position.
|
||||
@ -69,7 +69,7 @@ func _update_mass_and_inertia():
|
||||
print("Node: %s, Mass: %f" % [self, mass])
|
||||
|
||||
func _physics_process(delta):
|
||||
if is_sim_root:
|
||||
if is_sim_root and not Engine.is_editor_hint():
|
||||
|
||||
# FIX 2: Safety Check for Division by Zero
|
||||
var sim_mass = mass
|
||||
|
||||
@ -51,11 +51,11 @@ 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
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user