Terrain des sites de plongée : champ de hauteur authoré, Banc du Vigilant, formes variées et rendu sous-marin #4
No files matched your search
@@ -23,6 +23,6 @@ script = ExtResource("3_usfet")
|
|||||||
site_id = &"vehicle_test"
|
site_id = &"vehicle_test"
|
||||||
display_name = "Site de calibration du véhicule"
|
display_name = "Site de calibration du véhicule"
|
||||||
terrain = ExtResource("4_awe7g")
|
terrain = ExtResource("4_awe7g")
|
||||||
playable_bounds_m = Rect2(-90, -176, 180, 204)
|
playable_bounds_m = Rect2(-90, -176, 180, 246)
|
||||||
spawn_position_m = Vector3(0, -12, 12)
|
spawn_position_m = Vector3(0, -12, 12)
|
||||||
placements = Array[ExtResource("1_j2ii4")]([SubResource("Resource_5juth"), SubResource("Resource_tboiu")])
|
placements = Array[ExtResource("1_j2ii4")]([SubResource("Resource_5juth"), SubResource("Resource_tboiu")])
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,43 @@
|
|||||||
|
[gd_scene load_steps=7 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" path="res://game/world/sites/dive_site.tscn" id="1_site"]
|
||||||
|
[ext_resource type="PackedScene" path="res://game/world/submarine/submarine_actor.tscn" id="2_submarine"]
|
||||||
|
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_camera_rig.tscn" id="3_camera"]
|
||||||
|
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_interior.tscn" id="4_cockpit"]
|
||||||
|
|
||||||
|
[sub_resource type="ParticleProcessMaterial" id="SuspendedMotion"]
|
||||||
|
emission_shape = 3
|
||||||
|
emission_box_extents = Vector3(45, 28, 70)
|
||||||
|
direction = Vector3(0.1, 0.04, 0)
|
||||||
|
spread = 180.0
|
||||||
|
initial_velocity_min = 0.02
|
||||||
|
initial_velocity_max = 0.09
|
||||||
|
gravity = Vector3(0, 0, 0)
|
||||||
|
scale_min = 0.025
|
||||||
|
scale_max = 0.08
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="SuspendedParticleMesh"]
|
||||||
|
radius = 0.035
|
||||||
|
height = 0.07
|
||||||
|
|
||||||
|
[node name="DiveWorld" type="Node3D"]
|
||||||
|
|
||||||
|
[node name="DiveSite" parent="." instance=ExtResource("1_site")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="SubmarineActor" parent="." instance=ExtResource("2_submarine")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="SuspendedParticles" type="GPUParticles3D" parent="SubmarineActor"]
|
||||||
|
amount = 360
|
||||||
|
lifetime = 14.0
|
||||||
|
preprocess = 14.0
|
||||||
|
visibility_aabb = AABB(-50, -35, -78, 100, 70, 156)
|
||||||
|
process_material = SubResource("SuspendedMotion")
|
||||||
|
draw_pass_1 = SubResource("SuspendedParticleMesh")
|
||||||
|
|
||||||
|
[node name="CockpitInterior" parent="SubmarineActor" instance=ExtResource("4_cockpit")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="CockpitCameraRig" parent="SubmarineActor" instance=ExtResource("3_camera")]
|
||||||
|
unique_name_in_owner = true
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[gd_scene load_steps=5 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="PackedScene" path="res://game/world/sites/vehicle_test_environment.tscn" id="1_environment"]
|
|
||||||
[ext_resource type="PackedScene" path="res://game/world/submarine/submarine_actor.tscn" id="2_submarine"]
|
|
||||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_camera_rig.tscn" id="3_camera"]
|
|
||||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/cockpit_interior.tscn" id="4_cockpit"]
|
|
||||||
|
|
||||||
[node name="VehicleTestWorld" type="Node3D"]
|
|
||||||
|
|
||||||
[node name="Environment" parent="." instance=ExtResource("1_environment")]
|
|
||||||
|
|
||||||
[node name="SubmarineActor" parent="." instance=ExtResource("2_submarine")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
position = Vector3(0, -12, 12)
|
|
||||||
|
|
||||||
[node name="CockpitInterior" parent="SubmarineActor" instance=ExtResource("4_cockpit")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
|
|
||||||
[node name="CockpitCameraRig" parent="SubmarineActor" instance=ExtResource("3_camera")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
@@ -29,6 +29,12 @@ const SubmarineTelemetryAdapter = preload(
|
|||||||
const ReferenceBathymetryAcquisition = preload(
|
const ReferenceBathymetryAcquisition = preload(
|
||||||
"res://game/world/sonar/reference_bathymetry_acquisition.gd"
|
"res://game/world/sonar/reference_bathymetry_acquisition.gd"
|
||||||
)
|
)
|
||||||
|
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
|
||||||
|
const DiveSite = preload("res://game/world/sites/dive_site.gd")
|
||||||
|
const PlayableBoundsRule = preload("res://game/application/sites/playable_bounds_rule.gd")
|
||||||
|
const PlayableBoundsStatus = preload(
|
||||||
|
"res://game/application/sites/playable_bounds_status.gd"
|
||||||
|
)
|
||||||
const ProductionInputAdapter = preload(
|
const ProductionInputAdapter = preload(
|
||||||
"res://game/infrastructure/input/production_input_adapter.gd"
|
"res://game/infrastructure/input/production_input_adapter.gd"
|
||||||
)
|
)
|
||||||
@@ -51,9 +57,8 @@ const ConfirmationDialogScreen = preload(
|
|||||||
const RebindResult = preload("res://game/application/input/rebind_result.gd")
|
const RebindResult = preload("res://game/application/input/rebind_result.gd")
|
||||||
|
|
||||||
const MAIN_MENU_SCENE = preload("res://game/presentation/main_menu/main_menu_screen.tscn")
|
const MAIN_MENU_SCENE = preload("res://game/presentation/main_menu/main_menu_screen.tscn")
|
||||||
const VEHICLE_TEST_WORLD_SCENE_PATH := (
|
const DIVE_WORLD_SCENE_PATH := "res://game/composition/vertical_slice/dive_world.tscn"
|
||||||
"res://game/composition/vertical_slice/vehicle_test_world.tscn"
|
const DEFAULT_SITE_PATH := "res://content/sites/vehicle_test.tres"
|
||||||
)
|
|
||||||
const COCKPIT_HUD_SCENE = preload("res://game/presentation/cockpit/cockpit_hud.tscn")
|
const COCKPIT_HUD_SCENE = preload("res://game/presentation/cockpit/cockpit_hud.tscn")
|
||||||
const SONAR_DISPLAY_SCENE = preload(
|
const SONAR_DISPLAY_SCENE = preload(
|
||||||
"res://game/presentation/sonar/sonar_display.tscn"
|
"res://game/presentation/sonar/sonar_display.tscn"
|
||||||
@@ -87,6 +92,10 @@ var _sonar_service: SonarService
|
|||||||
var _sonar_display: SonarDisplay
|
var _sonar_display: SonarDisplay
|
||||||
var _latest_update: DiveUpdate
|
var _latest_update: DiveUpdate
|
||||||
var _active_world: Node3D
|
var _active_world: Node3D
|
||||||
|
var _selected_site_path := DEFAULT_SITE_PATH
|
||||||
|
var _dive_site: DiveSite
|
||||||
|
var _bounds_rule: PlayableBoundsRule
|
||||||
|
var _bounds_status: PlayableBoundsStatus
|
||||||
var _active_screen: Control
|
var _active_screen: Control
|
||||||
var _active_overlay: Control
|
var _active_overlay: Control
|
||||||
var _vehicle_command_sequence := 0
|
var _vehicle_command_sequence := 0
|
||||||
@@ -105,7 +114,7 @@ func _ready() -> void:
|
|||||||
_show_main_menu()
|
_show_main_menu()
|
||||||
|
|
||||||
|
|
||||||
func _start_vehicle_test() -> void:
|
func _start_dive() -> void:
|
||||||
var rules = DiveRulesResolver.new().resolve(
|
var rules = DiveRulesResolver.new().resolve(
|
||||||
DiveDifficultyProfile.standard(),
|
DiveDifficultyProfile.standard(),
|
||||||
DiveUpgradeEffects.new(),
|
DiveUpgradeEffects.new(),
|
||||||
@@ -117,12 +126,29 @@ func _start_vehicle_test() -> void:
|
|||||||
if not session.is_ready():
|
if not session.is_ready():
|
||||||
return
|
return
|
||||||
|
|
||||||
var world_scene := load(VEHICLE_TEST_WORLD_SCENE_PATH) as PackedScene
|
var definition := load(_selected_site_path) as DiveSiteDefinition
|
||||||
|
if definition == null:
|
||||||
|
push_error("dive site definition %s cannot be loaded" % _selected_site_path)
|
||||||
|
return
|
||||||
|
var definition_error := definition.validation_error()
|
||||||
|
if not definition_error.is_empty():
|
||||||
|
push_error("dive site %s is invalid: %s" % [_selected_site_path, definition_error])
|
||||||
|
return
|
||||||
|
var world_scene := load(DIVE_WORLD_SCENE_PATH) as PackedScene
|
||||||
if world_scene == null:
|
if world_scene == null:
|
||||||
return
|
return
|
||||||
var world := world_scene.instantiate() as Node3D
|
var world := world_scene.instantiate() as Node3D
|
||||||
_replace_active_world(world)
|
_replace_active_world(world)
|
||||||
|
_dive_site = world.get_node("%DiveSite") as DiveSite
|
||||||
|
var site_error := _dive_site.configure(definition)
|
||||||
|
if not site_error.is_empty():
|
||||||
|
push_error("dive site %s cannot be built: %s" % [_selected_site_path, site_error])
|
||||||
|
_show_main_menu()
|
||||||
|
return
|
||||||
_actor = world.get_node("%SubmarineActor") as SubmarineActor
|
_actor = world.get_node("%SubmarineActor") as SubmarineActor
|
||||||
|
_actor.global_transform = _dive_site.spawn_transform()
|
||||||
|
_bounds_rule = PlayableBoundsRule.new(definition.playable_bounds_m)
|
||||||
|
_bounds_status = _bounds_rule.evaluate(_actor.global_position)
|
||||||
_camera_controller = world.get_node("%CockpitCameraRig") as CockpitCameraController
|
_camera_controller = world.get_node("%CockpitCameraRig") as CockpitCameraController
|
||||||
_instrument_panel = world.get_node("%CockpitInterior") as CockpitInstrumentPanel
|
_instrument_panel = world.get_node("%CockpitInterior") as CockpitInstrumentPanel
|
||||||
_hud = COCKPIT_HUD_SCENE.instantiate() as CockpitHud
|
_hud = COCKPIT_HUD_SCENE.instantiate() as CockpitHud
|
||||||
@@ -154,6 +180,7 @@ func _start_vehicle_test() -> void:
|
|||||||
_runner.next_sequence()
|
_runner.next_sequence()
|
||||||
)
|
)
|
||||||
_hud.present(_latest_update, initial_telemetry)
|
_hud.present(_latest_update, initial_telemetry)
|
||||||
|
_hud.present_bounds(_bounds_status)
|
||||||
_instrument_panel.present(_latest_update, initial_telemetry)
|
_instrument_panel.present(_latest_update, initial_telemetry)
|
||||||
_camera_controller.present_telemetry(initial_telemetry)
|
_camera_controller.present_telemetry(initial_telemetry)
|
||||||
_input_adapter.set_gameplay_enabled(true)
|
_input_adapter.set_gameplay_enabled(true)
|
||||||
@@ -196,6 +223,8 @@ func _physics_process(delta: float) -> void:
|
|||||||
return_route
|
return_route
|
||||||
)
|
)
|
||||||
_hud.present(_latest_update, telemetry)
|
_hud.present(_latest_update, telemetry)
|
||||||
|
_bounds_status = _bounds_rule.evaluate(_actor.global_position)
|
||||||
|
_hud.present_bounds(_bounds_status)
|
||||||
_instrument_panel.present(_latest_update, telemetry)
|
_instrument_panel.present(_latest_update, telemetry)
|
||||||
_sonar_service.advance(delta)
|
_sonar_service.advance(delta)
|
||||||
_present_sonar()
|
_present_sonar()
|
||||||
@@ -243,6 +272,18 @@ func sonar_display() -> SonarDisplay:
|
|||||||
return _sonar_display
|
return _sonar_display
|
||||||
|
|
||||||
|
|
||||||
|
func select_dive_site(definition_path: String) -> void:
|
||||||
|
_selected_site_path = definition_path
|
||||||
|
|
||||||
|
|
||||||
|
func dive_site() -> DiveSite:
|
||||||
|
return _dive_site
|
||||||
|
|
||||||
|
|
||||||
|
func latest_bounds_status() -> PlayableBoundsStatus:
|
||||||
|
return _bounds_status
|
||||||
|
|
||||||
|
|
||||||
func _finish_dive_motion() -> void:
|
func _finish_dive_motion() -> void:
|
||||||
_dive_terminal = true
|
_dive_terminal = true
|
||||||
_input_adapter.set_gameplay_enabled(false)
|
_input_adapter.set_gameplay_enabled(false)
|
||||||
@@ -444,6 +485,9 @@ func _show_main_menu() -> void:
|
|||||||
_pilot_use_case = null
|
_pilot_use_case = null
|
||||||
_telemetry_adapter = null
|
_telemetry_adapter = null
|
||||||
_actor = null
|
_actor = null
|
||||||
|
_dive_site = null
|
||||||
|
_bounds_rule = null
|
||||||
|
_bounds_status = null
|
||||||
_camera_controller = null
|
_camera_controller = null
|
||||||
_instrument_panel = null
|
_instrument_panel = null
|
||||||
_hud = null
|
_hud = null
|
||||||
@@ -457,7 +501,7 @@ func _show_main_menu() -> void:
|
|||||||
_replace_active_world(null)
|
_replace_active_world(null)
|
||||||
var screen := MAIN_MENU_SCENE.instantiate() as MainMenuScreen
|
var screen := MAIN_MENU_SCENE.instantiate() as MainMenuScreen
|
||||||
_replace_active_screen(screen)
|
_replace_active_screen(screen)
|
||||||
screen.new_game_requested.connect(_start_vehicle_test)
|
screen.new_game_requested.connect(_start_dive)
|
||||||
screen.quit_requested.connect(_quit_game)
|
screen.quit_requested.connect(_quit_game)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ const DiveUpdate = preload("res://game/domain/dive/dive_update.gd")
|
|||||||
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
|
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
|
||||||
const DiveTerminalReason = preload("res://game/domain/dive/dive_terminal_reason.gd")
|
const DiveTerminalReason = preload("res://game/domain/dive/dive_terminal_reason.gd")
|
||||||
const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd")
|
const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd")
|
||||||
|
const PlayableBoundsStatus = preload("res://game/application/sites/playable_bounds_status.gd")
|
||||||
|
|
||||||
@onready var _alert_panel: PanelContainer = %AlertPanel
|
@onready var _alert_panel: PanelContainer = %AlertPanel
|
||||||
@onready var _alert_label: Label = %AlertLabel
|
@onready var _alert_label: Label = %AlertLabel
|
||||||
|
@onready var _bounds_panel: PanelContainer = %BoundsPanel
|
||||||
|
@onready var _bounds_label: Label = %BoundsLabel
|
||||||
@onready var _ejection_panel: PanelContainer = %EjectionPanel
|
@onready var _ejection_panel: PanelContainer = %EjectionPanel
|
||||||
@onready var _ejection_label: Label = %EjectionLabel
|
@onready var _ejection_label: Label = %EjectionLabel
|
||||||
@onready var _terminal_panel: PanelContainer = %TerminalPanel
|
@onready var _terminal_panel: PanelContainer = %TerminalPanel
|
||||||
@@ -29,6 +32,21 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
func present_bounds(status: PlayableBoundsStatus) -> void:
|
||||||
|
if status == null or not status.requires_warning():
|
||||||
|
_bounds_panel.visible = false
|
||||||
|
return
|
||||||
|
_bounds_panel.visible = true
|
||||||
|
if status.state == PlayableBoundsStatus.State.OUTSIDE:
|
||||||
|
_bounds_label.text = tr("HORS ZONE · FAITES DEMI-TOUR")
|
||||||
|
else:
|
||||||
|
_bounds_label.text = "%s · %d m" % [tr("LIMITE DE ZONE"), int(status.distance_to_edge_m)]
|
||||||
|
|
||||||
|
|
||||||
|
func bounds_warning_visible() -> bool:
|
||||||
|
return _bounds_panel.visible
|
||||||
|
|
||||||
|
|
||||||
func alert_overlay_visible() -> bool:
|
func alert_overlay_visible() -> bool:
|
||||||
return _alert_panel.visible
|
return _alert_panel.visible
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,27 @@ theme_override_font_sizes/font_size = 20
|
|||||||
text = "ALERTE · ATTENTION"
|
text = "ALERTE · ATTENTION"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="BoundsPanel" type="PanelContainer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 10
|
||||||
|
anchor_right = 1.0
|
||||||
|
offset_left = 560.0
|
||||||
|
offset_top = 88.0
|
||||||
|
offset_right = -560.0
|
||||||
|
offset_bottom = 136.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
theme_override_styles/panel = SubResource("AlertStyle")
|
||||||
|
|
||||||
|
[node name="BoundsLabel" type="Label" parent="BoundsPanel"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 0.82, 0.42, 1)
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "LIMITE DE ZONE"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="EjectionPanel" type="PanelContainer" parent="."]
|
[node name="EjectionPanel" type="PanelContainer" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
class_name VehicleTestEnvironment
|
|
||||||
extends Node3D
|
|
||||||
|
|
||||||
const TERRAIN_COLUMNS := 61
|
|
||||||
const TERRAIN_ROWS := 69
|
|
||||||
const TERRAIN_WIDTH_M := 180.0
|
|
||||||
const TERRAIN_LENGTH_M := 200.0
|
|
||||||
const TERRAIN_NEAR_Z := 28.0
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
_add_bathymetric_terrain()
|
|
||||||
_place_calibration_target()
|
|
||||||
_add_suspended_particles()
|
|
||||||
|
|
||||||
|
|
||||||
func _add_suspended_particles() -> void:
|
|
||||||
var particles := GPUParticles3D.new()
|
|
||||||
particles.name = "SuspendedParticles"
|
|
||||||
particles.position = Vector3(0.0, -34.0, -42.0)
|
|
||||||
particles.amount = 360
|
|
||||||
particles.lifetime = 14.0
|
|
||||||
particles.preprocess = 14.0
|
|
||||||
particles.visibility_aabb = AABB(
|
|
||||||
Vector3(-50.0, -35.0, -78.0),
|
|
||||||
Vector3(100.0, 70.0, 156.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
var motion := ParticleProcessMaterial.new()
|
|
||||||
motion.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
|
|
||||||
motion.emission_box_extents = Vector3(45.0, 28.0, 70.0)
|
|
||||||
motion.direction = Vector3(0.1, 0.04, 0.0)
|
|
||||||
motion.spread = 180.0
|
|
||||||
motion.initial_velocity_min = 0.02
|
|
||||||
motion.initial_velocity_max = 0.09
|
|
||||||
motion.gravity = Vector3.ZERO
|
|
||||||
motion.scale_min = 0.025
|
|
||||||
motion.scale_max = 0.08
|
|
||||||
particles.process_material = motion
|
|
||||||
|
|
||||||
var particle_mesh := SphereMesh.new()
|
|
||||||
particle_mesh.radius = 0.035
|
|
||||||
particle_mesh.height = 0.07
|
|
||||||
particles.draw_pass_1 = particle_mesh
|
|
||||||
add_child(particles)
|
|
||||||
|
|
||||||
|
|
||||||
func _add_bathymetric_terrain() -> void:
|
|
||||||
var surface := SurfaceTool.new()
|
|
||||||
surface.begin(Mesh.PRIMITIVE_TRIANGLES)
|
|
||||||
for row in range(TERRAIN_ROWS - 1):
|
|
||||||
for column in range(TERRAIN_COLUMNS - 1):
|
|
||||||
var top_left := _terrain_vertex(row, column)
|
|
||||||
var top_right := _terrain_vertex(row, column + 1)
|
|
||||||
var bottom_left := _terrain_vertex(row + 1, column)
|
|
||||||
var bottom_right := _terrain_vertex(row + 1, column + 1)
|
|
||||||
_add_terrain_triangle(surface, top_left, top_right, bottom_left)
|
|
||||||
_add_terrain_triangle(surface, top_right, bottom_right, bottom_left)
|
|
||||||
surface.generate_normals()
|
|
||||||
var mesh := surface.commit()
|
|
||||||
|
|
||||||
var body := StaticBody3D.new()
|
|
||||||
body.name = "BathymetricTerrain"
|
|
||||||
body.set_meta("sonar_material", 1)
|
|
||||||
body.set_meta("sonar_reflectivity", 0.72)
|
|
||||||
var mesh_instance := MeshInstance3D.new()
|
|
||||||
mesh_instance.name = "Mesh"
|
|
||||||
mesh_instance.mesh = mesh
|
|
||||||
var material := StandardMaterial3D.new()
|
|
||||||
material.albedo_color = Color(0.075, 0.19, 0.19)
|
|
||||||
material.roughness = 0.92
|
|
||||||
mesh_instance.material_override = material
|
|
||||||
body.add_child(mesh_instance)
|
|
||||||
var collision := CollisionShape3D.new()
|
|
||||||
collision.name = "Collision"
|
|
||||||
var height_map := HeightMapShape3D.new()
|
|
||||||
height_map.map_width = TERRAIN_COLUMNS
|
|
||||||
height_map.map_depth = TERRAIN_ROWS
|
|
||||||
var height_data := PackedFloat32Array()
|
|
||||||
height_data.resize(TERRAIN_COLUMNS * TERRAIN_ROWS)
|
|
||||||
for row in TERRAIN_ROWS:
|
|
||||||
for column in TERRAIN_COLUMNS:
|
|
||||||
var vertex := _terrain_vertex(row, column)
|
|
||||||
height_data[row * TERRAIN_COLUMNS + column] = vertex.y
|
|
||||||
height_map.map_data = height_data
|
|
||||||
collision.shape = height_map
|
|
||||||
collision.position = Vector3(
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
TERRAIN_NEAR_Z - TERRAIN_LENGTH_M * 0.5
|
|
||||||
)
|
|
||||||
collision.scale = Vector3(
|
|
||||||
TERRAIN_WIDTH_M / float(TERRAIN_COLUMNS - 1),
|
|
||||||
1.0,
|
|
||||||
TERRAIN_LENGTH_M / float(TERRAIN_ROWS - 1)
|
|
||||||
)
|
|
||||||
body.add_child(collision)
|
|
||||||
add_child(body)
|
|
||||||
|
|
||||||
|
|
||||||
func _terrain_vertex(row: int, column: int) -> Vector3:
|
|
||||||
var x := lerpf(
|
|
||||||
-TERRAIN_WIDTH_M * 0.5,
|
|
||||||
TERRAIN_WIDTH_M * 0.5,
|
|
||||||
float(column) / float(TERRAIN_COLUMNS - 1)
|
|
||||||
)
|
|
||||||
var z := TERRAIN_NEAR_Z - (
|
|
||||||
TERRAIN_LENGTH_M * float(row) / float(TERRAIN_ROWS - 1)
|
|
||||||
)
|
|
||||||
return Vector3(x, _terrain_height(x, z), z)
|
|
||||||
|
|
||||||
|
|
||||||
func _terrain_height(x: float, z: float) -> float:
|
|
||||||
var distance_ratio := clampf(
|
|
||||||
(TERRAIN_NEAR_Z - z) / TERRAIN_LENGTH_M,
|
|
||||||
0.0,
|
|
||||||
1.0
|
|
||||||
)
|
|
||||||
var base := lerpf(-40.0, -78.0, distance_ratio)
|
|
||||||
var left_ridge := 13.0 * exp(
|
|
||||||
-(pow(x + 24.0, 2.0) / 210.0 + pow(z + 48.0, 2.0) / 980.0)
|
|
||||||
)
|
|
||||||
var right_ridge := 15.0 * exp(
|
|
||||||
-(pow(x - 25.0, 2.0) / 250.0 + pow(z + 58.0, 2.0) / 1150.0)
|
|
||||||
)
|
|
||||||
var channel := -8.5 * exp(
|
|
||||||
-(pow(x, 2.0) / 120.0 + pow(z + 66.0, 2.0) / 2500.0)
|
|
||||||
)
|
|
||||||
var rear_rise := 8.0 * exp(
|
|
||||||
-(pow(x + 5.0, 2.0) / 900.0 + pow(z + 125.0, 2.0) / 620.0)
|
|
||||||
)
|
|
||||||
var undulation := 1.8 * sin(x * 0.075) * cos(z * 0.048)
|
|
||||||
return base + left_ridge + right_ridge + channel + rear_rise + undulation
|
|
||||||
|
|
||||||
|
|
||||||
func _add_terrain_triangle(
|
|
||||||
surface: SurfaceTool,
|
|
||||||
a: Vector3,
|
|
||||||
b: Vector3,
|
|
||||||
c: Vector3
|
|
||||||
) -> void:
|
|
||||||
surface.set_uv(Vector2(a.x, a.z) * 0.02)
|
|
||||||
surface.add_vertex(a)
|
|
||||||
surface.set_uv(Vector2(b.x, b.z) * 0.02)
|
|
||||||
surface.add_vertex(b)
|
|
||||||
surface.set_uv(Vector2(c.x, c.z) * 0.02)
|
|
||||||
surface.add_vertex(c)
|
|
||||||
|
|
||||||
|
|
||||||
func _place_calibration_target() -> void:
|
|
||||||
var target := get_node_or_null("SonarCalibrationTarget") as Node3D
|
|
||||||
if target != null:
|
|
||||||
target.position.y = _terrain_height(target.position.x, target.position.z) + 2.5
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
[gd_scene load_steps=8 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://game/world/sites/vehicle_test_environment.gd" id="1_environment"]
|
|
||||||
|
|
||||||
[sub_resource type="Environment" id="EnvironmentUnderwater"]
|
|
||||||
background_mode = 1
|
|
||||||
background_color = Color(0.005, 0.045, 0.065, 1)
|
|
||||||
ambient_light_source = 3
|
|
||||||
ambient_light_color = Color(0.12, 0.32, 0.37, 1)
|
|
||||||
ambient_light_energy = 0.42
|
|
||||||
fog_enabled = true
|
|
||||||
fog_light_color = Color(0.07, 0.25, 0.3, 1)
|
|
||||||
fog_light_energy = 0.48
|
|
||||||
fog_density = 0.014
|
|
||||||
fog_height = -4.0
|
|
||||||
fog_height_density = 0.025
|
|
||||||
volumetric_fog_enabled = true
|
|
||||||
volumetric_fog_density = 0.025
|
|
||||||
volumetric_fog_length = 72.0
|
|
||||||
|
|
||||||
[sub_resource type="SphereMesh" id="BeaconMesh"]
|
|
||||||
radius = 0.55
|
|
||||||
height = 1.1
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="MetalTargetMaterial"]
|
|
||||||
albedo_color = Color(0.24, 0.18, 0.08, 1)
|
|
||||||
metallic = 0.88
|
|
||||||
roughness = 0.27
|
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="MetalTargetMesh"]
|
|
||||||
size = Vector3(8, 5, 7)
|
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="MetalTargetShape"]
|
|
||||||
size = Vector3(8, 5, 7)
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="BeaconMaterial"]
|
|
||||||
shading_mode = 0
|
|
||||||
albedo_color = Color(0.2, 0.78, 0.9, 1)
|
|
||||||
emission_enabled = true
|
|
||||||
emission = Color(0.2, 0.78, 0.9, 1)
|
|
||||||
emission_energy_multiplier = 5.0
|
|
||||||
|
|
||||||
[node name="VehicleTestEnvironment" type="Node3D"]
|
|
||||||
script = ExtResource("1_environment")
|
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
|
||||||
environment = SubResource("EnvironmentUnderwater")
|
|
||||||
|
|
||||||
[node name="AmbientDirection" type="DirectionalLight3D" parent="."]
|
|
||||||
rotation_degrees = Vector3(-52, -24, 0)
|
|
||||||
light_color = Color(0.39, 0.65, 0.7, 1)
|
|
||||||
light_energy = 0.38
|
|
||||||
shadow_enabled = true
|
|
||||||
|
|
||||||
[node name="NavigationBeacon" type="MeshInstance3D" parent="."]
|
|
||||||
position = Vector3(0, -15, -22)
|
|
||||||
mesh = SubResource("BeaconMesh")
|
|
||||||
material_override = SubResource("BeaconMaterial")
|
|
||||||
|
|
||||||
[node name="BeaconLight" type="OmniLight3D" parent="NavigationBeacon"]
|
|
||||||
omni_range = 12.0
|
|
||||||
light_color = Color(0.2, 0.78, 0.9, 1)
|
|
||||||
light_energy = 4.0
|
|
||||||
|
|
||||||
[node name="SonarCalibrationTarget" type="StaticBody3D" parent="."]
|
|
||||||
position = Vector3(0, -60, -58)
|
|
||||||
metadata/sonar_material = 2
|
|
||||||
metadata/sonar_reflectivity = 1.0
|
|
||||||
|
|
||||||
[node name="Mesh" type="MeshInstance3D" parent="SonarCalibrationTarget"]
|
|
||||||
mesh = SubResource("MetalTargetMesh")
|
|
||||||
material_override = SubResource("MetalTargetMaterial")
|
|
||||||
|
|
||||||
[node name="Collision" type="CollisionShape3D" parent="SonarCalibrationTarget"]
|
|
||||||
shape = SubResource("MetalTargetShape")
|
|
||||||
@@ -5,6 +5,7 @@ const VerticalSliceRoot = preload(
|
|||||||
)
|
)
|
||||||
const MainMenuScreen = preload("res://game/presentation/main_menu/main_menu_screen.gd")
|
const MainMenuScreen = preload("res://game/presentation/main_menu/main_menu_screen.gd")
|
||||||
const CockpitHud = preload("res://game/presentation/cockpit/cockpit_hud.gd")
|
const CockpitHud = preload("res://game/presentation/cockpit/cockpit_hud.gd")
|
||||||
|
const PlayableBoundsStatus = preload("res://game/application/sites/playable_bounds_status.gd")
|
||||||
const CockpitDetailing = preload(
|
const CockpitDetailing = preload(
|
||||||
"res://game/presentation/cockpit/cockpit_detailing.gd"
|
"res://game/presentation/cockpit/cockpit_detailing.gd"
|
||||||
)
|
)
|
||||||
@@ -19,7 +20,7 @@ const ProductionInputAdapter = preload(
|
|||||||
"res://game/infrastructure/input/production_input_adapter.gd"
|
"res://game/infrastructure/input/production_input_adapter.gd"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TEST_COUNT := 78
|
const TEST_COUNT := 83
|
||||||
|
|
||||||
var _failures: Array[String] = []
|
var _failures: Array[String] = []
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
|
|||||||
var continue_button := menu.get_node("%ContinueButton") as Button
|
var continue_button := menu.get_node("%ContinueButton") as Button
|
||||||
_expect(continue_button.disabled, "continue is disabled without save")
|
_expect(continue_button.disabled, "continue is disabled without save")
|
||||||
var new_game_button := menu.get_node("%NewGameButton") as Button
|
var new_game_button := menu.get_node("%NewGameButton") as Button
|
||||||
|
composition.select_dive_site("res://content/sites/vehicle_test.tres")
|
||||||
new_game_button.pressed.emit()
|
new_game_button.pressed.emit()
|
||||||
await process_frame
|
await process_frame
|
||||||
await physics_frame
|
await physics_frame
|
||||||
@@ -110,6 +112,22 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
|
|||||||
_expect(sonar_service != null, "production sonar service is assembled")
|
_expect(sonar_service != null, "production sonar service is assembled")
|
||||||
_expect(sonar_display != null, "shared sonar presentation is assembled")
|
_expect(sonar_display != null, "shared sonar presentation is assembled")
|
||||||
_expect(composition.latest_update() != null, "dive session starts with a typed update")
|
_expect(composition.latest_update() != null, "dive session starts with a typed update")
|
||||||
|
_expect(composition.dive_site() != null, "composition instantiates the selected dive site")
|
||||||
|
_expect(
|
||||||
|
composition.dive_site() != null
|
||||||
|
and composition.dive_site().definition().site_id == &"vehicle_test",
|
||||||
|
"selected site definition drives the world"
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
actor != null and actor.global_position.is_equal_approx(Vector3(0.0, -12.0, 12.0)),
|
||||||
|
"submarine spawns at the site spawn point"
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
composition.latest_bounds_status() != null
|
||||||
|
and composition.latest_bounds_status().state == PlayableBoundsStatus.State.INSIDE,
|
||||||
|
"spawn is inside the playable bounds"
|
||||||
|
)
|
||||||
|
_expect(hud != null and not hud.bounds_warning_visible(), "no bounds warning inside the zone")
|
||||||
if (
|
if (
|
||||||
actor == null
|
actor == null
|
||||||
or camera == null
|
or camera == null
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const SitePlacement = preload("res://game/domain/sites/site_placement.gd")
|
|||||||
static func recipe() -> SiteTerrainBaker.Recipe:
|
static func recipe() -> SiteTerrainBaker.Recipe:
|
||||||
var result := SiteTerrainBaker.Recipe.new()
|
var result := SiteTerrainBaker.Recipe.new()
|
||||||
result.columns = 61
|
result.columns = 61
|
||||||
result.rows = 69
|
result.rows = 83
|
||||||
result.cell_size_m = 3.0
|
result.cell_size_m = 3.0
|
||||||
result.origin_x_m = -90.0
|
result.origin_x_m = -90.0
|
||||||
result.origin_z_m = -176.0
|
result.origin_z_m = -176.0
|
||||||
@@ -35,7 +35,7 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition:
|
|||||||
result.site_id = &"vehicle_test"
|
result.site_id = &"vehicle_test"
|
||||||
result.display_name = "Site de calibration du véhicule"
|
result.display_name = "Site de calibration du véhicule"
|
||||||
result.terrain = terrain
|
result.terrain = terrain
|
||||||
result.playable_bounds_m = Rect2(-90.0, -176.0, 180.0, 204.0)
|
result.playable_bounds_m = Rect2(-90.0, -176.0, 180.0, 246.0)
|
||||||
result.spawn_position_m = Vector3(0.0, -12.0, 12.0)
|
result.spawn_position_m = Vector3(0.0, -12.0, 12.0)
|
||||||
result.spawn_yaw_radians = 0.0
|
result.spawn_yaw_radians = 0.0
|
||||||
var target := SitePlacement.new()
|
var target := SitePlacement.new()
|
||||||
|
|||||||
Reference in new issue
Block a user