extends Node3D const MISSION_POINTS := [ { "title": "Rejoignez la balise de navigation", "position": Vector3(0.0, -22.0, -58.0), "color": Color("5ddcff"), "radius": 8.0, }, { "title": "Descendez au-delà de 60 m et inspectez la balise", "position": Vector3(38.0, -66.0, -128.0), "color": Color("ffbd59"), "radius": 4.0, }, { "title": "Revenez dans la zone de récupération", "position": Vector3(0.0, -12.0, 12.0), "color": Color("7af0a5"), "radius": 8.0, }, ] var _submarine: SubmarineController var _hud: PocHud var _alarm_player: AlarmPlayer var _mission_index := 0 var _mission_markers: Array[Node3D] = [] var _mission_guide_groups: Array[Node3D] = [] var _terminal := false var _elapsed_time := 0.0 func _ready() -> void: _ensure_input_actions() _build_environment() _build_course() _hud = PocHud.new() add_child(_hud) _alarm_player = AlarmPlayer.new() add_child(_alarm_player) _submarine = SubmarineController.new() _submarine.name = "Submarine" _submarine.position = Vector3(0.0, -12.0, 12.0) _submarine.mission_origin = _submarine.position _submarine.telemetry_updated.connect(_on_telemetry_updated) _submarine.loss_triggered.connect(_on_loss_triggered) _submarine.abandon_progress_changed.connect(_hud.set_ejection_progress) add_child(_submarine) _update_mission_marker_visibility() _hud.set_objective(MISSION_POINTS[0]["title"]) func _process(delta: float) -> void: if _terminal: return _elapsed_time += delta var target: Vector3 = MISSION_POINTS[_mission_index]["position"] var distance := _submarine.global_position.distance_to(target) _hud.set_objective(MISSION_POINTS[_mission_index]["title"], distance) if distance <= float(MISSION_POINTS[_mission_index]["radius"]): _mission_index += 1 if _mission_index >= MISSION_POINTS.size(): _complete_course() else: _update_mission_marker_visibility() func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("debug_critical") and not _terminal: _submarine.force_critical_state() if _terminal and event.is_action_pressed("restart_poc"): get_tree().reload_current_scene() func _on_telemetry_updated(snapshot: Dictionary) -> void: if _hud == null: return _hud.update_telemetry(snapshot) _alarm_player.update_alerts( get_process_delta_time(), [ int(snapshot["oxygen_alert"]), int(snapshot["battery_alert"]), int(snapshot["hull_alert"]), int(snapshot["depth_alert"]), ] ) func _on_loss_triggered(reason: StringName) -> void: _terminal = true _alarm_player.reset() var message: String = { &"hull_failure": "La coque a cédé sous la pression ou après un impact.", &"oxygen_depleted": "L’oxygène est épuisé. La capsule a été récupérée.", &"manual_ejection": "Vous avez abandonné volontairement le véhicule.", }.get(reason, "Le véhicule a été perdu.") _hud.show_terminal("SOUS-MARIN PERDU", message) func _complete_course() -> void: _terminal = true _submarine.controls_enabled = false Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _alarm_player.reset() _hud.show_terminal( "PARCOURS TERMINÉ", "Temps : %02d:%02d\nOxygène, propulsion, pression et retour ont été exercés." % [int(_elapsed_time) / 60, int(_elapsed_time) % 60] ) func _update_mission_marker_visibility() -> void: for marker_index in _mission_markers.size(): _mission_markers[marker_index].visible = marker_index == _mission_index for guide_index in _mission_guide_groups.size(): _mission_guide_groups[guide_index].visible = guide_index == _mission_index func _build_environment() -> void: var world_environment := WorldEnvironment.new() var environment := Environment.new() environment.background_mode = Environment.BG_COLOR environment.background_color = Color("031823") environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR environment.ambient_light_color = Color("2b6575") environment.ambient_light_energy = 0.46 environment.fog_enabled = true environment.fog_light_color = Color("174553") environment.fog_light_energy = 0.52 environment.fog_density = 0.013 environment.fog_height = -5.0 environment.fog_height_density = 0.03 world_environment.environment = environment add_child(world_environment) var sun := DirectionalLight3D.new() sun.rotation_degrees = Vector3(-55.0, -28.0, 0.0) sun.light_color = Color("82bdc9") sun.light_energy = 0.35 sun.shadow_enabled = true add_child(sun) var surface := MeshInstance3D.new() var surface_mesh := PlaneMesh.new() surface_mesh.size = Vector2(320.0, 420.0) surface.mesh = surface_mesh surface.position = Vector3(0.0, 0.0, -80.0) var surface_material := StandardMaterial3D.new() surface_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA surface_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED surface_material.cull_mode = BaseMaterial3D.CULL_DISABLED surface_material.albedo_color = Color(0.13, 0.55, 0.65, 0.13) surface.material_override = surface_material add_child(surface) _add_water_particles() func _build_course() -> void: _add_static_box( "Seafloor", Vector3(0.0, -96.0, -80.0), Vector3(300.0, 10.0, 360.0), Color("183a3c") ) _add_static_box( "LeftWall", Vector3(-75.0, -50.0, -90.0), Vector3(22.0, 95.0, 260.0), Color("24494b") ) # Nearby formations make speed, distance and heading readable from the # cockpit before the player reaches the first mission beacon. var navigation_rock_specs := [ [Vector3(-10.0, -24.0, -12.0), Vector3(10.0, 24.0, 10.0), -14.0], [Vector3(12.0, -30.0, -28.0), Vector3(11.0, 34.0, 12.0), 18.0], [Vector3(-12.0, -38.0, -42.0), Vector3(9.0, 46.0, 10.0), 8.0], [Vector3(11.0, -44.0, -50.0), Vector3(8.0, 52.0, 10.0), -10.0], [Vector3(7.0, -55.0, -73.0), Vector3(10.0, 38.0, 12.0), -16.0], [Vector3(25.0, -69.0, -96.0), Vector3(12.0, 45.0, 14.0), 13.0], [Vector3(45.0, -72.0, -112.0), Vector3(13.0, 50.0, 15.0), -9.0], ] for rock_index in navigation_rock_specs.size(): var spec: Array = navigation_rock_specs[rock_index] var navigation_rock := _add_static_box( "NavigationRock%02d" % rock_index, spec[0], spec[1], Color("315553") if rock_index % 2 == 0 else Color("294947") ) navigation_rock.rotation_degrees.y = spec[2] _add_static_box( "RightWall", Vector3(75.0, -50.0, -90.0), Vector3(22.0, 95.0, 260.0), Color("24494b") ) var rock_specs := [ [Vector3(-25.0, -70.0, -78.0), Vector3(24.0, 44.0, 28.0), -18.0], [Vector3(28.0, -76.0, -92.0), Vector3(18.0, 34.0, 20.0), 16.0], [Vector3(-12.0, -83.0, -145.0), Vector3(42.0, 22.0, 30.0), 8.0], [Vector3(54.0, -63.0, -150.0), Vector3(20.0, 58.0, 24.0), -12.0], ] for rock_index in rock_specs.size(): var spec: Array = rock_specs[rock_index] var rock := _add_static_box( "Rock%02d" % rock_index, spec[0], spec[1], Color("2d5452") ) rock.rotation_degrees.y = spec[2] for mission_data in MISSION_POINTS: _mission_markers.append( _add_beacon(mission_data["position"], mission_data["color"]) ) # Intermediate lights reveal the direction of the next leg without drawing # a HUD arrow. Each chain only appears when its objective becomes active. _mission_guide_groups.append(_add_guide_chain([], Color("5ddcff"))) _mission_guide_groups.append( _add_guide_chain( [ Vector3(8.0, -28.0, -75.0), Vector3(18.0, -38.0, -94.0), Vector3(29.0, -51.0, -112.0), ], Color("ffbd59") ) ) _mission_guide_groups.append( _add_guide_chain( [ Vector3(30.0, -55.0, -100.0), Vector3(23.0, -44.0, -72.0), Vector3(15.0, -34.0, -44.0), Vector3(8.0, -23.0, -16.0), ], Color("7af0a5") ) ) func _add_water_particles() -> void: var particles := GPUParticles3D.new() particles.name = "SuspendedWaterParticles" particles.position = Vector3(0.0, -45.0, -65.0) particles.amount = 520 particles.lifetime = 14.0 particles.preprocess = 14.0 particles.fixed_fps = 30 particles.visibility_aabb = AABB( Vector3(-85.0, -55.0, -125.0), Vector3(170.0, 110.0, 250.0) ) var particle_motion := ParticleProcessMaterial.new() particle_motion.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX particle_motion.emission_box_extents = Vector3(78.0, 48.0, 118.0) particle_motion.direction = Vector3(0.25, 0.12, 0.08) particle_motion.spread = 35.0 particle_motion.initial_velocity_min = 0.025 particle_motion.initial_velocity_max = 0.11 particle_motion.gravity = Vector3.ZERO particle_motion.scale_min = 0.035 particle_motion.scale_max = 0.11 particles.process_material = particle_motion var mote_mesh := QuadMesh.new() mote_mesh.size = Vector2(0.075, 0.075) var mote_material := StandardMaterial3D.new() mote_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA mote_material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED mote_material.albedo_color = Color(0.62, 0.88, 0.92, 0.42) mote_material.roughness = 0.15 mote_mesh.material = mote_material particles.draw_pass_1 = mote_mesh add_child(particles) func _add_static_box(node_name: String, box_position: Vector3, box_size: Vector3, color: Color) -> StaticBody3D: var body := StaticBody3D.new() body.name = node_name body.position = box_position add_child(body) var mesh_instance := MeshInstance3D.new() var mesh := BoxMesh.new() mesh.size = box_size mesh_instance.mesh = mesh var material := StandardMaterial3D.new() material.albedo_color = color material.roughness = 0.9 mesh_instance.material_override = material body.add_child(mesh_instance) var collision_shape := CollisionShape3D.new() var shape := BoxShape3D.new() shape.size = box_size collision_shape.shape = shape body.add_child(collision_shape) return body func _add_beacon(beacon_position: Vector3, color: Color) -> Node3D: var marker := Node3D.new() marker.position = beacon_position add_child(marker) var sphere_instance := MeshInstance3D.new() var sphere := SphereMesh.new() sphere.radius = 2.2 sphere.height = 4.4 sphere_instance.mesh = sphere var material := StandardMaterial3D.new() material.albedo_color = color material.emission_enabled = true material.emission = color material.emission_energy_multiplier = 4.0 sphere_instance.material_override = material marker.add_child(sphere_instance) var light := OmniLight3D.new() light.omni_range = 18.0 light.light_energy = 5.0 light.light_color = color marker.add_child(light) return marker func _add_guide_chain(guide_positions: Array, color: Color) -> Node3D: var group := Node3D.new() group.name = "MissionGuideChain" add_child(group) for guide_position in guide_positions: var guide := Node3D.new() guide.position = guide_position group.add_child(guide) var guide_mesh_instance := MeshInstance3D.new() var guide_mesh := SphereMesh.new() guide_mesh.radius = 0.38 guide_mesh.height = 0.76 guide_mesh_instance.mesh = guide_mesh var guide_material := StandardMaterial3D.new() guide_material.albedo_color = color guide_material.emission_enabled = true guide_material.emission = color guide_material.emission_energy_multiplier = 1.5 guide_mesh_instance.material_override = guide_material guide.add_child(guide_mesh_instance) var guide_light := OmniLight3D.new() guide_light.omni_range = 7.0 guide_light.light_energy = 1.8 guide_light.light_color = color guide.add_child(guide_light) return group func _ensure_input_actions() -> void: _register_key_action("move_forward", KEY_W) _register_key_action("move_backward", KEY_S) _register_key_action("move_left", KEY_A) _register_key_action("move_right", KEY_D) _register_key_action("ascend", KEY_SPACE) _register_key_action("descend", KEY_CTRL) _register_key_action("boost", KEY_SHIFT) _register_key_action("abandon_vehicle", KEY_X) _register_key_action("debug_critical", KEY_F9) _register_key_action("restart_poc", KEY_ENTER) _register_key_action("toggle_mouse_capture", KEY_ESCAPE) func _register_key_action(action_name: StringName, physical_keycode: Key) -> void: if not InputMap.has_action(action_name): InputMap.add_action(action_name) if InputMap.action_get_events(action_name).is_empty(): var key_event := InputEventKey.new() key_event.physical_keycode = physical_keycode InputMap.action_add_event(action_name, key_event)