Validate POC A and POC B foundations
This commit is contained in:
1 parent
44fd4b4b43
commit
c38e09a663
47 files changed
+4990
-35
No files matched your search
@@ -0,0 +1,550 @@
|
||||
class_name SubmarineController
|
||||
extends CharacterBody3D
|
||||
|
||||
signal telemetry_updated(snapshot: Dictionary)
|
||||
signal loss_triggered(reason: StringName)
|
||||
signal abandon_progress_changed(progress: float)
|
||||
|
||||
@export var cruise_speed := 6.0
|
||||
@export var boost_speed := 9.5
|
||||
@export var acceleration := 2.0
|
||||
@export var braking := 1.35
|
||||
@export var mouse_sensitivity := 0.0025
|
||||
@export var keyboard_turn_speed_degrees := 55.0
|
||||
@export var max_turn_speed_degrees := 45.0
|
||||
@export var turn_acceleration_degrees := 30.0
|
||||
@export var turn_braking_degrees := 24.0
|
||||
@export var lateral_grip := 2.2
|
||||
@export var camera_position_response := 5.0
|
||||
@export var camera_rotation_response := 2.8
|
||||
@export var camera_max_lag := 2.5
|
||||
@export var visual_tilt_response := 4.5
|
||||
@export var max_visual_pitch_degrees := 18.0
|
||||
@export var max_visual_roll_degrees := 16.0
|
||||
@export var headlight_range := 58.0
|
||||
@export var headlight_energy := 38.0
|
||||
@export var headlight_angle_degrees := 24.0
|
||||
@export var first_person_view := true
|
||||
@export var first_person_fov := 82.0
|
||||
@export var cockpit_camera_offset := Vector3(0.0, 0.52, -0.48)
|
||||
|
||||
var survival := SurvivalModel.new()
|
||||
var controls_enabled := true
|
||||
var mission_origin := Vector3.ZERO
|
||||
var _yaw := 0.0
|
||||
var _target_yaw := 0.0
|
||||
var _camera_yaw := 0.0
|
||||
var _yaw_velocity := 0.0
|
||||
var _pitch := -0.22
|
||||
var _abandon_progress := 0.0
|
||||
var _collision_cooldown := 0.0
|
||||
var _last_snapshot: Dictionary = {}
|
||||
var _camera_pivot: Node3D
|
||||
var _vehicle_visual: Node3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if first_person_view:
|
||||
_pitch = -0.04
|
||||
_build_vehicle()
|
||||
_yaw = rotation.y
|
||||
_target_yaw = _yaw
|
||||
_camera_yaw = _yaw
|
||||
_reset_camera_transform()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
_last_snapshot = survival.snapshot(current_depth())
|
||||
telemetry_updated.emit(_last_snapshot)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED and controls_enabled:
|
||||
_target_yaw = wrapf(
|
||||
_target_yaw - event.relative.x * mouse_sensitivity,
|
||||
-PI,
|
||||
PI
|
||||
)
|
||||
var min_pitch := -0.30 if first_person_view else -0.75
|
||||
var max_pitch := 0.20 if first_person_view else 0.45
|
||||
_pitch = clampf(_pitch - event.relative.y * mouse_sensitivity, min_pitch, max_pitch)
|
||||
elif event.is_action_pressed("toggle_mouse_capture"):
|
||||
Input.mouse_mode = (
|
||||
Input.MOUSE_MODE_VISIBLE
|
||||
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
|
||||
else Input.MOUSE_MODE_CAPTURED
|
||||
)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not controls_enabled:
|
||||
return
|
||||
|
||||
_collision_cooldown = maxf(0.0, _collision_cooldown - delta)
|
||||
var steering_input := Input.get_axis("move_left", "move_right")
|
||||
_target_yaw = wrapf(
|
||||
_target_yaw - steering_input * deg_to_rad(keyboard_turn_speed_degrees) * delta,
|
||||
-PI,
|
||||
PI
|
||||
)
|
||||
_update_yaw(delta)
|
||||
|
||||
var forward_input := Input.get_axis("move_forward", "move_backward")
|
||||
var vertical_input := Input.get_axis("descend", "ascend")
|
||||
var local_input := Vector3(0.0, vertical_input, forward_input)
|
||||
if local_input.length() > 1.0:
|
||||
local_input = local_input.normalized()
|
||||
|
||||
var has_power := survival.battery > 0.0
|
||||
var boosting := Input.is_action_pressed("boost") and has_power
|
||||
var requested_speed := boost_speed if boosting else cruise_speed
|
||||
var world_input := (global_transform.basis * local_input).normalized() if local_input.length() > 0.0 else Vector3.ZERO
|
||||
var target_velocity := world_input * requested_speed if has_power else Vector3.ZERO
|
||||
var change_rate := acceleration if target_velocity.length() > 0.0 else braking
|
||||
velocity = velocity.move_toward(target_velocity, change_rate * delta)
|
||||
_apply_lateral_grip(delta)
|
||||
|
||||
var velocity_before_move := velocity
|
||||
move_and_slide()
|
||||
|
||||
if global_position.y > -2.0:
|
||||
global_position.y = -2.0
|
||||
velocity.y = minf(velocity.y, 0.0)
|
||||
|
||||
_update_vehicle_visual(delta)
|
||||
_update_camera(delta)
|
||||
|
||||
var collision_damage := _calculate_collision_damage(velocity_before_move)
|
||||
var thrust_ratio := maxf(local_input.length(), absf(steering_input) * 0.35)
|
||||
if boosting:
|
||||
thrust_ratio = minf(1.0, thrust_ratio * 1.8)
|
||||
|
||||
_last_snapshot = survival.tick(delta, thrust_ratio, current_depth(), collision_damage)
|
||||
_last_snapshot["speed"] = velocity.length()
|
||||
_last_snapshot["return_distance"] = global_position.distance_to(mission_origin)
|
||||
_last_snapshot["return_energy"] = survival.estimated_return_energy(
|
||||
_last_snapshot["return_distance"],
|
||||
maxf(current_depth() - 12.0, 0.0)
|
||||
)
|
||||
telemetry_updated.emit(_last_snapshot)
|
||||
|
||||
_update_abandon(delta)
|
||||
var reason: StringName = _last_snapshot["loss_reason"]
|
||||
if not reason.is_empty():
|
||||
_trigger_loss(reason)
|
||||
|
||||
|
||||
func current_depth() -> float:
|
||||
return maxf(-global_position.y, 0.0)
|
||||
|
||||
|
||||
func force_critical_state() -> void:
|
||||
survival.force_critical_state()
|
||||
|
||||
|
||||
func _update_abandon(delta: float) -> void:
|
||||
if Input.is_action_pressed("abandon_vehicle"):
|
||||
_abandon_progress = minf(1.0, _abandon_progress + delta / 3.0)
|
||||
else:
|
||||
_abandon_progress = maxf(0.0, _abandon_progress - delta * 1.5)
|
||||
|
||||
abandon_progress_changed.emit(_abandon_progress)
|
||||
if _abandon_progress >= 1.0:
|
||||
_trigger_loss(&"manual_ejection")
|
||||
|
||||
|
||||
func _trigger_loss(reason: StringName) -> void:
|
||||
if not controls_enabled:
|
||||
return
|
||||
controls_enabled = false
|
||||
velocity = Vector3.ZERO
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
loss_triggered.emit(reason)
|
||||
|
||||
|
||||
func _calculate_collision_damage(previous_velocity: Vector3) -> float:
|
||||
if _collision_cooldown > 0.0:
|
||||
return 0.0
|
||||
|
||||
var strongest_impact := 0.0
|
||||
for collision_index in get_slide_collision_count():
|
||||
var collision := get_slide_collision(collision_index)
|
||||
var impact := absf(previous_velocity.dot(collision.get_normal()))
|
||||
strongest_impact = maxf(strongest_impact, impact)
|
||||
|
||||
if strongest_impact <= 3.5:
|
||||
return 0.0
|
||||
|
||||
_collision_cooldown = 0.45
|
||||
return (strongest_impact - 3.5) * 4.0
|
||||
|
||||
|
||||
func _build_vehicle() -> void:
|
||||
var collision_shape := CollisionShape3D.new()
|
||||
var capsule := CapsuleShape3D.new()
|
||||
capsule.radius = 0.72
|
||||
capsule.height = 3.8
|
||||
collision_shape.shape = capsule
|
||||
collision_shape.rotation.x = PI / 2.0
|
||||
add_child(collision_shape)
|
||||
|
||||
_vehicle_visual = Node3D.new()
|
||||
_vehicle_visual.name = "VehicleVisual"
|
||||
add_child(_vehicle_visual)
|
||||
|
||||
var hull_mesh := MeshInstance3D.new()
|
||||
var hull := CapsuleMesh.new()
|
||||
hull.radius = 0.72
|
||||
hull.height = 3.8
|
||||
hull_mesh.mesh = hull
|
||||
hull_mesh.rotation.x = PI / 2.0
|
||||
hull_mesh.material_override = _material(Color("d8b24c"), 0.18)
|
||||
_vehicle_visual.add_child(hull_mesh)
|
||||
|
||||
var canopy := MeshInstance3D.new()
|
||||
var canopy_mesh := SphereMesh.new()
|
||||
canopy_mesh.radius = 0.58
|
||||
canopy_mesh.height = 1.05
|
||||
canopy.mesh = canopy_mesh
|
||||
canopy.position = Vector3(0.0, 0.36, -0.45)
|
||||
canopy.scale = Vector3(0.85, 0.55, 1.0)
|
||||
canopy.material_override = _material(Color("163c54"), 0.6)
|
||||
_vehicle_visual.add_child(canopy)
|
||||
|
||||
_add_fin(Vector3(0.0, 0.68, 1.15), Vector3(0.12, 0.7, 1.0))
|
||||
_add_fin(Vector3(-0.92, 0.0, 0.85), Vector3(0.9, 0.10, 0.8))
|
||||
_add_fin(Vector3(0.92, 0.0, 0.85), Vector3(0.9, 0.10, 0.8))
|
||||
|
||||
for light_x in [-0.42, 0.42]:
|
||||
_add_headlight(light_x)
|
||||
|
||||
_camera_pivot = Node3D.new()
|
||||
_camera_pivot.name = "CameraPivot"
|
||||
add_child(_camera_pivot)
|
||||
if not first_person_view:
|
||||
_camera_pivot.set_as_top_level(true)
|
||||
|
||||
var camera := Camera3D.new()
|
||||
camera.name = "CockpitCamera" if first_person_view else "ChaseCamera"
|
||||
camera.current = true
|
||||
if first_person_view:
|
||||
camera.fov = first_person_fov
|
||||
camera.near = 0.05
|
||||
_camera_pivot.add_child(camera)
|
||||
_build_cockpit_overlay()
|
||||
else:
|
||||
var spring_arm := SpringArm3D.new()
|
||||
spring_arm.spring_length = 7.0
|
||||
spring_arm.margin = 0.25
|
||||
_camera_pivot.add_child(spring_arm)
|
||||
camera.fov = 72.0
|
||||
spring_arm.add_child(camera)
|
||||
|
||||
|
||||
func _add_fin(fin_position: Vector3, fin_size: Vector3) -> void:
|
||||
var fin := MeshInstance3D.new()
|
||||
var box := BoxMesh.new()
|
||||
box.size = fin_size
|
||||
fin.mesh = box
|
||||
fin.position = fin_position
|
||||
fin.material_override = _material(Color("bb9134"), 0.05)
|
||||
_vehicle_visual.add_child(fin)
|
||||
|
||||
|
||||
func _add_headlight(light_x: float) -> void:
|
||||
var outward_yaw := -signf(light_x) * deg_to_rad(6.0)
|
||||
var downward_pitch := deg_to_rad(-3.0)
|
||||
var housing := MeshInstance3D.new()
|
||||
var housing_mesh := CylinderMesh.new()
|
||||
housing_mesh.top_radius = 0.22
|
||||
housing_mesh.bottom_radius = 0.28
|
||||
housing_mesh.height = 0.48
|
||||
housing.mesh = housing_mesh
|
||||
housing.position = Vector3(light_x, 0.05, -1.72)
|
||||
housing.rotation.x = PI / 2.0
|
||||
housing.material_override = _material(Color("273238"), 0.72)
|
||||
housing.visible = not first_person_view
|
||||
_vehicle_visual.add_child(housing)
|
||||
|
||||
var lens := MeshInstance3D.new()
|
||||
var lens_mesh := SphereMesh.new()
|
||||
lens_mesh.radius = 0.22
|
||||
lens_mesh.height = 0.44
|
||||
lens.mesh = lens_mesh
|
||||
lens.position = Vector3(light_x, 0.05, -1.98)
|
||||
lens.scale = Vector3(1.0, 1.0, 0.16)
|
||||
lens.material_override = _emissive_material(Color("d7f3ff"), 8.0)
|
||||
lens.visible = not first_person_view
|
||||
_vehicle_visual.add_child(lens)
|
||||
|
||||
# Keep the light source beyond the hull so the submarine cannot cast a
|
||||
# shadow over its own beams.
|
||||
var headlight := SpotLight3D.new()
|
||||
headlight.position = Vector3(light_x, 0.05, -2.03)
|
||||
headlight.spot_range = headlight_range
|
||||
headlight.spot_angle = headlight_angle_degrees
|
||||
headlight.light_energy = headlight_energy
|
||||
headlight.light_color = Color("d7f3ff")
|
||||
headlight.rotation = Vector3(downward_pitch, outward_yaw, 0.0)
|
||||
headlight.shadow_enabled = false
|
||||
_vehicle_visual.add_child(headlight)
|
||||
|
||||
_add_light_beam(light_x, outward_yaw, downward_pitch)
|
||||
|
||||
|
||||
func _add_light_beam(light_x: float, outward_yaw: float, downward_pitch: float) -> void:
|
||||
var beam_length := headlight_range * 0.76
|
||||
var beam_direction := (
|
||||
Basis(Vector3.UP, outward_yaw)
|
||||
* Basis(Vector3.RIGHT, downward_pitch)
|
||||
)
|
||||
var beam_origin := Vector3(light_x, 0.05, -2.03)
|
||||
if first_person_view:
|
||||
_add_first_person_beam_haze(beam_origin, beam_direction)
|
||||
return
|
||||
|
||||
var beam_radius := tan(deg_to_rad(headlight_angle_degrees * 0.5)) * beam_length
|
||||
var beam_mesh := CylinderMesh.new()
|
||||
beam_mesh.top_radius = 0.10
|
||||
beam_mesh.bottom_radius = beam_radius
|
||||
beam_mesh.height = beam_length
|
||||
beam_mesh.radial_segments = 32
|
||||
beam_mesh.rings = 1
|
||||
beam_mesh.cap_top = false
|
||||
beam_mesh.cap_bottom = false
|
||||
|
||||
var shader := Shader.new()
|
||||
shader.code = """
|
||||
shader_type spatial;
|
||||
render_mode unshaded, blend_add, cull_disabled, depth_draw_never;
|
||||
|
||||
uniform vec4 beam_color : source_color = vec4(0.42, 0.82, 1.0, 1.0);
|
||||
uniform float beam_alpha = 0.13;
|
||||
uniform float beam_length = 36.0;
|
||||
varying float beam_depth;
|
||||
|
||||
void vertex() {
|
||||
beam_depth = clamp(0.5 - VERTEX.y / beam_length, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
float near_fade = smoothstep(0.0, 0.10, beam_depth);
|
||||
float distance_fade = 1.0 - smoothstep(0.32, 1.0, beam_depth);
|
||||
ALBEDO = beam_color.rgb;
|
||||
EMISSION = beam_color.rgb;
|
||||
ALPHA = beam_alpha * near_fade * distance_fade;
|
||||
}
|
||||
"""
|
||||
|
||||
var beam_material := ShaderMaterial.new()
|
||||
beam_material.shader = shader
|
||||
beam_material.set_shader_parameter("beam_length", beam_length)
|
||||
|
||||
var beam := MeshInstance3D.new()
|
||||
beam.name = "HeadlightBeam"
|
||||
beam.mesh = beam_mesh
|
||||
beam.position = beam_origin + beam_direction * Vector3(0.0, 0.0, -beam_length * 0.5)
|
||||
beam.basis = beam_direction * Basis(Vector3.RIGHT, PI / 2.0)
|
||||
beam.material_override = beam_material
|
||||
beam.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
beam.extra_cull_margin = beam_length
|
||||
_vehicle_visual.add_child(beam)
|
||||
|
||||
|
||||
func _add_first_person_beam_haze(beam_origin: Vector3, beam_direction: Basis) -> void:
|
||||
var haze_shader := Shader.new()
|
||||
haze_shader.code = """
|
||||
shader_type spatial;
|
||||
render_mode unshaded, blend_add, cull_disabled, depth_draw_never;
|
||||
|
||||
uniform vec4 beam_color : source_color = vec4(0.46, 0.86, 1.0, 1.0);
|
||||
uniform float slice_alpha = 0.16;
|
||||
|
||||
void fragment() {
|
||||
vec2 centered_uv = UV * 2.0 - 1.0;
|
||||
float radius = length(centered_uv);
|
||||
float soft_disc = 1.0 - smoothstep(0.18, 1.0, radius);
|
||||
ALBEDO = beam_color.rgb;
|
||||
EMISSION = beam_color.rgb * 2.6;
|
||||
ALPHA = slice_alpha * soft_disc;
|
||||
}
|
||||
"""
|
||||
|
||||
var slice_distances := [4.0, 7.5, 12.0, 18.0, 25.0, 32.0]
|
||||
for slice_index in slice_distances.size():
|
||||
var distance: float = slice_distances[slice_index]
|
||||
var radius := tan(deg_to_rad(headlight_angle_degrees * 0.5)) * distance
|
||||
var slice_mesh := QuadMesh.new()
|
||||
slice_mesh.size = Vector2(radius * 2.0, radius * 2.0)
|
||||
|
||||
var slice_material := ShaderMaterial.new()
|
||||
slice_material.shader = haze_shader
|
||||
slice_material.set_shader_parameter(
|
||||
"slice_alpha",
|
||||
lerpf(0.19, 0.055, float(slice_index) / float(slice_distances.size() - 1))
|
||||
)
|
||||
|
||||
var haze_slice := MeshInstance3D.new()
|
||||
haze_slice.name = "HeadlightHaze"
|
||||
haze_slice.mesh = slice_mesh
|
||||
haze_slice.position = beam_origin + beam_direction * Vector3(0.0, 0.0, -distance)
|
||||
haze_slice.basis = beam_direction
|
||||
haze_slice.material_override = slice_material
|
||||
haze_slice.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
haze_slice.extra_cull_margin = headlight_range
|
||||
_vehicle_visual.add_child(haze_slice)
|
||||
|
||||
|
||||
func _build_cockpit_overlay() -> void:
|
||||
var overlay := CanvasLayer.new()
|
||||
overlay.name = "CockpitOverlay"
|
||||
overlay.layer = 0
|
||||
add_child(overlay)
|
||||
|
||||
var glass := ColorRect.new()
|
||||
glass.name = "PanoramicPorthole"
|
||||
glass.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
glass.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
overlay.add_child(glass)
|
||||
|
||||
var shader := Shader.new()
|
||||
shader.code = """
|
||||
shader_type canvas_item;
|
||||
|
||||
void fragment() {
|
||||
float aspect = SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x;
|
||||
vec2 point = (UV - vec2(0.5)) * vec2(aspect, 1.0);
|
||||
float distance_to_glass = length(point) - 0.435;
|
||||
|
||||
vec3 deep_frame = vec3(0.012, 0.025, 0.030);
|
||||
vec3 frame_highlight = vec3(0.10, 0.16, 0.18);
|
||||
if (distance_to_glass > 0.030) {
|
||||
COLOR = vec4(deep_frame, 0.995);
|
||||
} else if (distance_to_glass > -0.026) {
|
||||
float bevel = smoothstep(-0.026, 0.002, distance_to_glass);
|
||||
float upper_glint = 0.35 + 0.65 * (1.0 - UV.y);
|
||||
vec3 frame_color = mix(deep_frame, frame_highlight, bevel * upper_glint);
|
||||
COLOR = vec4(frame_color, 0.97);
|
||||
} else {
|
||||
float edge_vignette = smoothstep(-0.12, -0.026, distance_to_glass);
|
||||
float glass_sheen = pow(max(0.0, 1.0 - length(UV - vec2(0.40, 0.30)) * 1.7), 5.0);
|
||||
vec3 glass_color = vec3(0.08, 0.25, 0.30);
|
||||
float glass_alpha = 0.012 + edge_vignette * 0.13 + glass_sheen * 0.025;
|
||||
COLOR = vec4(glass_color, glass_alpha);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = shader
|
||||
glass.material = material
|
||||
|
||||
|
||||
func _update_vehicle_visual(delta: float) -> void:
|
||||
var local_velocity := global_transform.basis.inverse() * velocity
|
||||
var pitch_limit := deg_to_rad(max_visual_pitch_degrees)
|
||||
var roll_limit := deg_to_rad(max_visual_roll_degrees)
|
||||
var reference_speed := maxf(cruise_speed, 0.001)
|
||||
|
||||
# The hull points in the direction of vertical travel instead of remaining
|
||||
# perfectly level while the world slides past it.
|
||||
var target_pitch := clampf(local_velocity.y / reference_speed, -1.0, 1.0) * pitch_limit
|
||||
var turn_ratio := clampf(angle_difference(_yaw, _target_yaw) / 0.55, -1.0, 1.0)
|
||||
var strafe_ratio := clampf(local_velocity.x / reference_speed, -1.0, 1.0)
|
||||
var target_roll := clampf(turn_ratio - strafe_ratio * 0.65, -1.0, 1.0) * roll_limit
|
||||
var weight := _response_weight(visual_tilt_response, delta)
|
||||
|
||||
_vehicle_visual.rotation.x = lerp_angle(_vehicle_visual.rotation.x, target_pitch, weight)
|
||||
_vehicle_visual.rotation.z = lerp_angle(_vehicle_visual.rotation.z, target_roll, weight)
|
||||
|
||||
|
||||
func _update_yaw(delta: float) -> void:
|
||||
var heading_error := angle_difference(_yaw, _target_yaw)
|
||||
var max_turn_speed := deg_to_rad(max_turn_speed_degrees)
|
||||
var desired_yaw_velocity := clampf(
|
||||
heading_error * 1.8,
|
||||
-max_turn_speed,
|
||||
max_turn_speed
|
||||
)
|
||||
var change_rate := deg_to_rad(turn_acceleration_degrees)
|
||||
if absf(desired_yaw_velocity) < absf(_yaw_velocity):
|
||||
change_rate = deg_to_rad(turn_braking_degrees)
|
||||
|
||||
_yaw_velocity = move_toward(_yaw_velocity, desired_yaw_velocity, change_rate * delta)
|
||||
var yaw_step := _yaw_velocity * delta
|
||||
if absf(yaw_step) >= absf(heading_error) and signf(yaw_step) == signf(heading_error):
|
||||
_yaw = _target_yaw
|
||||
_yaw_velocity = 0.0
|
||||
else:
|
||||
_yaw = wrapf(_yaw + yaw_step, -PI, PI)
|
||||
rotation.y = _yaw
|
||||
|
||||
|
||||
func _apply_lateral_grip(delta: float) -> void:
|
||||
# A submarine keeps some inertia, but its hull and fins resist a prolonged
|
||||
# sideways slide. This makes the velocity follow the new heading in a turn.
|
||||
var local_velocity := global_transform.basis.inverse() * velocity
|
||||
local_velocity.x = move_toward(local_velocity.x, 0.0, lateral_grip * delta)
|
||||
velocity = global_transform.basis * local_velocity
|
||||
|
||||
|
||||
func _update_camera(delta: float) -> void:
|
||||
if first_person_view:
|
||||
# The POV remains a rigid child of the hull: it can rotate inside the
|
||||
# porthole, but its position can never leave the submarine.
|
||||
_camera_pivot.position = cockpit_camera_offset
|
||||
_camera_yaw = _yaw
|
||||
_camera_pivot.rotation = Vector3(
|
||||
_pitch + _vehicle_visual.rotation.x,
|
||||
0.0,
|
||||
_vehicle_visual.rotation.z * 0.55
|
||||
)
|
||||
return
|
||||
|
||||
# This rig ignores the submarine transform and pursues it in world space.
|
||||
# The small delay lets the vehicle visibly turn and move vertically on screen.
|
||||
var desired_position := global_position + Vector3(0.0, 0.55, 0.0)
|
||||
_camera_pivot.global_position = _camera_pivot.global_position.lerp(
|
||||
desired_position,
|
||||
_response_weight(camera_position_response, delta)
|
||||
)
|
||||
|
||||
var lag := desired_position - _camera_pivot.global_position
|
||||
if lag.length() > camera_max_lag:
|
||||
_camera_pivot.global_position = desired_position - lag.normalized() * camera_max_lag
|
||||
|
||||
_camera_yaw = lerp_angle(
|
||||
_camera_yaw,
|
||||
_yaw,
|
||||
_response_weight(camera_rotation_response, delta)
|
||||
)
|
||||
_camera_pivot.global_rotation = Vector3(_pitch, _camera_yaw, 0.0)
|
||||
|
||||
|
||||
func _reset_camera_transform() -> void:
|
||||
if first_person_view:
|
||||
_camera_pivot.position = cockpit_camera_offset
|
||||
_camera_pivot.rotation = Vector3(_pitch, 0.0, 0.0)
|
||||
else:
|
||||
_camera_pivot.global_position = global_position + Vector3(0.0, 0.55, 0.0)
|
||||
_camera_pivot.global_rotation = Vector3(_pitch, _camera_yaw, 0.0)
|
||||
|
||||
|
||||
func _response_weight(response: float, delta: float) -> float:
|
||||
return 1.0 - exp(-maxf(response, 0.0) * delta)
|
||||
|
||||
|
||||
func _material(color: Color, metallic: float) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = color
|
||||
material.metallic = metallic
|
||||
material.roughness = 0.55
|
||||
return material
|
||||
|
||||
|
||||
func _emissive_material(color: Color, energy: float) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = color
|
||||
material.emission_enabled = true
|
||||
material.emission = color
|
||||
material.emission_energy_multiplier = energy
|
||||
material.roughness = 0.18
|
||||
return material
|
||||
Reference in new issue
Block a user