Ship building basics
This commit is contained in:
@ -11,7 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="space_simulation"
|
||||
run/main_scene="uid://dogqi2c58qdc0"
|
||||
run/main_scene="uid://bvogqgqig1hps"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
|
||||
12
scenes/ship/builder/module.tscn
Normal file
12
scenes/ship/builder/module.tscn
Normal file
@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cm0rohkr6khd1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module_builder_controller.gd" id="1_b1h2b"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
script = ExtResource("1_b1h2b")
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
@ -1,109 +1,184 @@
|
||||
# module_builder_controller.gd
|
||||
# The root script for a player-constructed ship module. This is a RigidBody2D that
|
||||
# acts as a container for all the player-placed structural pieces.
|
||||
@tool
|
||||
class_name ModuleBuilderController
|
||||
extends RigidBody2D
|
||||
|
||||
# These nodes should be children of this RigidBody2D in the scene tree.
|
||||
# --- Editor Tool Properties ---
|
||||
var available_pieces: Dictionary = {}
|
||||
@export var piece_to_place: String = "Refresh":
|
||||
set(value):
|
||||
piece_to_place = value
|
||||
if is_instance_valid(current_piece_preview):
|
||||
current_piece_preview.queue_free()
|
||||
_create_piece_preview()
|
||||
|
||||
@export var grid_size: int = 50.0
|
||||
|
||||
# --- Tool State ---
|
||||
var current_piece_preview: StructuralPiece
|
||||
var piece_rotation: int = 0 # 0, 90, 180, 270 degrees
|
||||
|
||||
@onready var structural_container: Node2D = $StructuralContainer
|
||||
@onready var hull_volume_container: Node2D = $HullVolumeContainer
|
||||
@onready var atmosphere_visualizer: Node2D = $AtmosphereVisualizer # For "gas leak" effects
|
||||
@onready var atmosphere_visualizer: Node2D = $AtmosphereVisualizer
|
||||
|
||||
enum ModuleState { IN_CONSTRUCTION, SEALED, PRESSURIZED, BREACHED }
|
||||
|
||||
var current_state: ModuleState = ModuleState.IN_CONSTRUCTION
|
||||
|
||||
# --- Public API for In-Game Construction ---
|
||||
func _get_property_list() -> Array:
|
||||
var properties = []
|
||||
|
||||
var piece_names = available_pieces.keys()
|
||||
piece_names.sort()
|
||||
piece_names.insert(0, "Refresh")
|
||||
|
||||
# Called by the player's construction tool to add a wall, plate, etc.
|
||||
func add_structural_piece(piece_scene: PackedScene, pos: Vector2, rot: float):
|
||||
var piece = piece_scene.instantiate() as Node2D
|
||||
piece.position = pos
|
||||
piece.rotation = rot
|
||||
structural_container.add_child(piece)
|
||||
# Every time the structure changes, we must update the module's physics.
|
||||
properties.append({
|
||||
"name": "piece_to_place",
|
||||
"type": TYPE_STRING,
|
||||
"hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": ",".join(piece_names),
|
||||
})
|
||||
|
||||
properties.append({
|
||||
"name": "grid_size",
|
||||
"type": TYPE_INT,
|
||||
"hint": PROPERTY_HINT_RANGE,
|
||||
"hint_string": "10,200,10"
|
||||
})
|
||||
|
||||
return properties
|
||||
|
||||
# The fix: This function tells the editor we want to handle all input events.
|
||||
func _handles_input_event(event: InputEvent) -> bool:
|
||||
return Engine.is_editor_hint()
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
_scan_for_pieces()
|
||||
set_physics_process(false)
|
||||
|
||||
func _enter_tree():
|
||||
if Engine.is_editor_hint():
|
||||
if not is_instance_valid(current_piece_preview) and piece_to_place != "Refresh":
|
||||
_create_piece_preview()
|
||||
|
||||
func _exit_tree():
|
||||
if Engine.is_editor_hint() and is_instance_valid(current_piece_preview):
|
||||
current_piece_preview.queue_free()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
print("foo")
|
||||
if not Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if not is_instance_valid(current_piece_preview):
|
||||
return
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
var mouse_pos_in_world = get_viewport().get_mouse_position()
|
||||
var snapped_pos = _snap_to_grid(mouse_pos_in_world)
|
||||
current_piece_preview.position = to_local(snapped_pos)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
if event.is_action_pressed("ui_rotate"):
|
||||
piece_rotation = (piece_rotation + 90) % 360
|
||||
current_piece_preview.rotation_degrees = piece_rotation
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
if event is InputEventMouseButton and event.is_pressed():
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_place_piece_from_preview()
|
||||
_create_piece_preview()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
if is_instance_valid(current_piece_preview):
|
||||
current_piece_preview.queue_free()
|
||||
_create_piece_preview()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _scan_for_pieces():
|
||||
available_pieces.clear()
|
||||
var directory = DirAccess.open("res://scenes/ship/builder/")
|
||||
if directory:
|
||||
directory.list_dir_begin()
|
||||
var file_name = directory.get_next()
|
||||
while file_name != "":
|
||||
if file_name.ends_with(".tscn"):
|
||||
var path = "res://scenes/ship/builder/" + file_name
|
||||
var scene = load(path)
|
||||
if scene and scene is PackedScene:
|
||||
var instance = scene.instantiate()
|
||||
if instance is StructuralPiece:
|
||||
available_pieces[file_name.trim_suffix(".tscn")] = scene
|
||||
instance.queue_free()
|
||||
file_name = directory.get_next()
|
||||
directory.list_dir_end()
|
||||
notify_property_list_changed()
|
||||
else:
|
||||
print("Could not open directory: res://scenes/ship/builder/")
|
||||
|
||||
func _create_piece_preview():
|
||||
if is_instance_valid(current_piece_preview):
|
||||
current_piece_preview.queue_free()
|
||||
|
||||
if available_pieces.has(piece_to_place):
|
||||
var piece_scene: PackedScene = available_pieces[piece_to_place]
|
||||
current_piece_preview = piece_scene.instantiate() as StructuralPiece
|
||||
add_child(current_piece_preview)
|
||||
current_piece_preview.is_preview = true
|
||||
current_piece_preview.set_physics_process(false)
|
||||
|
||||
func _place_piece_from_preview():
|
||||
if not is_instance_valid(current_piece_preview):
|
||||
return
|
||||
|
||||
var placed_piece = available_pieces[piece_to_place].instantiate() as StructuralPiece
|
||||
placed_piece.position = current_piece_preview.position
|
||||
placed_piece.rotation_degrees = current_piece_preview.rotation_degrees
|
||||
|
||||
placed_piece.is_preview = false
|
||||
|
||||
structural_container.add_child(placed_piece)
|
||||
_recalculate_physics_properties()
|
||||
|
||||
# Called by the player to define the area that should contain an atmosphere.
|
||||
func add_hull_volume(volume_scene: PackedScene, pos: Vector2):
|
||||
var volume = volume_scene.instantiate() as Node2D
|
||||
volume.position = pos
|
||||
hull_volume_container.add_child(volume)
|
||||
|
||||
# This function checks if every defined room (Hull Volume) is properly sealed.
|
||||
func check_for_seal() -> bool:
|
||||
if hull_volume_container.get_child_count() == 0:
|
||||
print("Module integrity check failed: No hull volumes defined.")
|
||||
return false
|
||||
|
||||
var all_sealed = true
|
||||
for volume in hull_volume_container.get_children():
|
||||
if volume is HullVolume:
|
||||
if not volume.check_seal():
|
||||
all_sealed = false
|
||||
# The volume can be responsible for showing its own leak effect.
|
||||
if atmosphere_visualizer.has_method("show_leak_at"):
|
||||
atmosphere_visualizer.show_leak_at(volume.get_leak_position())
|
||||
|
||||
if all_sealed:
|
||||
print("Module is sealed! Ready for pressurization and component installation.")
|
||||
current_state = ModuleState.SEALED
|
||||
return true
|
||||
else:
|
||||
print("One or more hull volumes reported a leak.")
|
||||
current_state = ModuleState.IN_CONSTRUCTION
|
||||
return false
|
||||
|
||||
# --- Internal Physics Management ---
|
||||
|
||||
# Recalculates the total mass, center of mass, and combines all collision shapes.
|
||||
func _snap_to_grid(pos: Vector2) -> Vector2:
|
||||
return Vector2(
|
||||
round(pos.x / grid_size) * grid_size,
|
||||
round(pos.y / grid_size) * grid_size
|
||||
)
|
||||
|
||||
func _recalculate_physics_properties():
|
||||
print("Recalculating physics properties...")
|
||||
|
||||
# 1. Sum masses and find a weighted center of mass
|
||||
var total_mass: float = 0.0
|
||||
var total_mass_pos = Vector2.ZERO
|
||||
var all_structural_pieces = structural_container.get_children()
|
||||
|
||||
for piece in all_structural_pieces:
|
||||
if piece is StructuralPiece:
|
||||
total_mass += piece.piece_mass
|
||||
# The position is relative to the structural_container.
|
||||
total_mass_pos += piece.position * piece.piece_mass
|
||||
|
||||
|
||||
if total_mass > 0:
|
||||
var new_center_of_mass = total_mass_pos / total_mass
|
||||
# We set the center of mass in the RigidBody2D.
|
||||
self.set_center_of_mass(new_center_of_mass)
|
||||
self.mass = total_mass
|
||||
self.set_center_of_mass(Vector2.ZERO)
|
||||
|
||||
# --- Combine all collision shapes into one for the RigidBody ---
|
||||
# This is a key part of your design, allowing for efficient physics calculations.
|
||||
# Note: This is an expensive operation and should only be done on demand.
|
||||
|
||||
# Remove any existing collision shapes from this RigidBody2D
|
||||
for child in get_children():
|
||||
if child is CollisionShape2D or child is CollisionPolygon2D:
|
||||
child.queue_free()
|
||||
|
||||
var shape = ConcavePolygonShape2D.new()
|
||||
var all_points: PackedVector2Array = []
|
||||
|
||||
# Iterate through all structural pieces to get their collision polygons
|
||||
for piece in all_structural_pieces:
|
||||
if piece is StructuralPiece:
|
||||
for child in piece.get_children():
|
||||
if child is CollisionPolygon2D:
|
||||
# Get the points in the structural piece's local space
|
||||
var piece_points = child.polygon
|
||||
var piece_xform = piece.transform
|
||||
|
||||
# Transform the points to the module's local space
|
||||
for p in piece_points:
|
||||
all_points.append(piece_xform.xform(p))
|
||||
|
||||
shape.set_segments(all_points)
|
||||
|
||||
for child in get_children():
|
||||
if child is CollisionShape2D or child is CollisionPolygon2D:
|
||||
child.queue_free()
|
||||
|
||||
var new_collision_shape = CollisionShape2D.new()
|
||||
new_collision_shape.shape = shape
|
||||
add_child(new_collision_shape)
|
||||
|
||||
28
scenes/ship/builder/pieces/bulkhead.tscn
Normal file
28
scenes/ship/builder/pieces/bulkhead.tscn
Normal file
@ -0,0 +1,28 @@
|
||||
[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"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
|
||||
size = Vector2(10, 100)
|
||||
|
||||
[node name="Bulkhead" type="StaticBody2D"]
|
||||
collision_layer = 5
|
||||
script = ExtResource("1_1wp2n")
|
||||
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_1wp2n")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -5.0
|
||||
offset_top = -50.0
|
||||
offset_right = 5.0
|
||||
offset_bottom = 50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.6, 0.6, 0.6, 1)
|
||||
28
scenes/ship/builder/pieces/hullplate.tscn
Normal file
28
scenes/ship/builder/pieces/hullplate.tscn
Normal file
@ -0,0 +1,28 @@
|
||||
[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"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1wp2n"]
|
||||
size = Vector2(100, 100)
|
||||
|
||||
[node name="Hullplate" type="StaticBody2D"]
|
||||
collision_mask = 0
|
||||
script = ExtResource("1_ecow4")
|
||||
metadata/_custom_type_script = "uid://b7f8x2qimvn37"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_1wp2n")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -50.0
|
||||
offset_top = -50.0
|
||||
offset_right = 50.0
|
||||
offset_bottom = 50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.4, 0.4, 0.4, 1)
|
||||
@ -1,5 +1,4 @@
|
||||
# structural_piece.gd
|
||||
# A script for the individual, player-placeable pieces like walls, floors, and girders.
|
||||
@tool
|
||||
class_name StructuralPiece
|
||||
extends StaticBody2D
|
||||
|
||||
@ -12,5 +11,16 @@ extends StaticBody2D
|
||||
# The health of this specific piece.
|
||||
@export var health: float = 100.0
|
||||
|
||||
# This setter is triggered by the editor.
|
||||
var is_preview: bool = false:
|
||||
set(value):
|
||||
is_preview = value
|
||||
if is_preview:
|
||||
# Make the piece translucent if it's a preview.
|
||||
modulate = Color(1, 1, 1, 0.5)
|
||||
else:
|
||||
# Make it opaque if it's a permanent piece.
|
||||
modulate = Color(1, 1, 1, 1)
|
||||
|
||||
func get_mass() -> float:
|
||||
return piece_mass
|
||||
15
scenes/ship/builder/pieces/structural_piece.tscn
Normal file
15
scenes/ship/builder/pieces/structural_piece.tscn
Normal file
@ -0,0 +1,15 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ds4eilbvihjy7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7f8x2qimvn37" path="res://scenes/ship/builder/pieces/structural_piece.gd" id="1_6jsoj"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_jsbwo"]
|
||||
|
||||
[node name="StructuralPiece" type="StaticBody2D"]
|
||||
script = ExtResource("1_6jsoj")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("CircleShape2D_jsbwo")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
@ -1,6 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ds4eilbvihjy7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7f8x2qimvn37" path="res://scenes/ship/builder/structural_piece.gd" id="1_6jsoj"]
|
||||
|
||||
[node name="StructuralPiece" type="StaticBody2D"]
|
||||
script = ExtResource("1_6jsoj")
|
||||
@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bscabpucsv88k"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/ship/builder/module_builder.tscn" id="1_h26xi"]
|
||||
[ext_resource type="PackedScene" uid="uid://ds3qq4yg8y86y" path="res://scenes/ship/builder/module_builder.tscn" id="1_h26xi"]
|
||||
|
||||
[node name="IntegrityTest" type="Node2D"]
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bvogqgqig1hps"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d53d4m797g36a" path="res://scenes/ship/builder/module_builder.tscn" id="1_74fwe"]
|
||||
[ext_resource type="PackedScene" uid="uid://ds3qq4yg8y86y" path="res://scenes/ship/builder/module_builder.tscn" id="1_74fwe"]
|
||||
|
||||
[node name="ShipBuildingTest" type="Node2D"]
|
||||
|
||||
[node name="ModuleBuilder" parent="." instance=ExtResource("1_74fwe")]
|
||||
position = Vector2(500, 300)
|
||||
piece_to_place = "bulkhead"
|
||||
piece_to_place = "bulkhead"
|
||||
|
||||
Reference in New Issue
Block a user