Add fishing and shoreline sound effects
This commit is contained in:
parent
de1df5ea21
commit
930acdb1a6
21 changed files with 497 additions and 1 deletions
|
|
@ -58,6 +58,10 @@ func get_pelican_landmark() -> Node3D:
|
|||
return get_node_or_null(pelican_landmark_path) as Node3D
|
||||
|
||||
|
||||
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||
return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D
|
||||
|
||||
|
||||
func has_terrain_collision() -> bool:
|
||||
var collision_shape: CollisionShape3D = (
|
||||
get_node_or_null(terrain_collision_shape_path) as CollisionShape3D
|
||||
|
|
|
|||
156
world/shoreline_ambience.gd
Normal file
156
world/shoreline_ambience.gd
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
class_name ShorelineAmbience
|
||||
extends Node
|
||||
|
||||
@export_range(0.0, 20.0, 0.1) var near_distance: float = 2.0
|
||||
@export_range(1.0, 100.0, 0.5) var far_distance: float = 24.0
|
||||
@export_range(-40.0, 12.0, 0.5) var near_volume_db: float = 5.0
|
||||
@export_range(-80.0, 0.0, 0.5) var far_volume_db: float = -18.0
|
||||
@export_range(0.05, 1.0, 0.05) var distance_update_interval: float = 0.2
|
||||
@export_range(0.1, 20.0, 0.1) var volume_smoothing_speed: float = 4.0
|
||||
|
||||
@onready var _waves_audio: AudioStreamPlayer = %WavesAudio
|
||||
|
||||
var _listener: Node3D
|
||||
var _shoreline_segments: Array[PackedVector2Array] = []
|
||||
var _distance_update_remaining: float = 0.0
|
||||
var _target_volume_db: float = -80.0
|
||||
var _is_active := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_configure_audio_loop(_waves_audio.stream)
|
||||
_waves_audio.volume_db = -80.0
|
||||
set_process(false)
|
||||
|
||||
|
||||
func configure(listener: Node3D, shoreline_mesh: MeshInstance3D) -> void:
|
||||
_listener = listener
|
||||
_shoreline_segments = _extract_shoreline_segments(shoreline_mesh)
|
||||
_distance_update_remaining = 0.0
|
||||
if _shoreline_segments.is_empty():
|
||||
push_warning("Saltwater shoreline ambience has no coastline geometry.")
|
||||
|
||||
|
||||
func set_active(active: bool) -> void:
|
||||
_is_active = active
|
||||
set_process(active)
|
||||
if active:
|
||||
_distance_update_remaining = 0.0
|
||||
_update_target_volume()
|
||||
_waves_audio.volume_db = _target_volume_db
|
||||
if not _waves_audio.playing and _waves_audio.stream != null:
|
||||
_waves_audio.play()
|
||||
else:
|
||||
_waves_audio.stop()
|
||||
_waves_audio.volume_db = -80.0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not _is_active:
|
||||
return
|
||||
_distance_update_remaining -= delta
|
||||
if _distance_update_remaining <= 0.0:
|
||||
_distance_update_remaining = distance_update_interval
|
||||
_update_target_volume()
|
||||
_waves_audio.volume_db = lerpf(
|
||||
_waves_audio.volume_db,
|
||||
_target_volume_db,
|
||||
1.0 - exp(-volume_smoothing_speed * delta),
|
||||
)
|
||||
|
||||
|
||||
func _update_target_volume() -> void:
|
||||
if _listener == null or _shoreline_segments.is_empty():
|
||||
_target_volume_db = -80.0
|
||||
return
|
||||
var listener_position := Vector2(
|
||||
_listener.global_position.x,
|
||||
_listener.global_position.z,
|
||||
)
|
||||
var distance := distance_to_segments(listener_position, _shoreline_segments)
|
||||
var blend := clampf(
|
||||
inverse_lerp(near_distance, maxf(near_distance + 0.01, far_distance), distance),
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
_target_volume_db = lerpf(near_volume_db, far_volume_db, smoothstep(0.0, 1.0, blend))
|
||||
|
||||
|
||||
func _extract_shoreline_segments(
|
||||
shoreline_mesh: MeshInstance3D,
|
||||
) -> Array[PackedVector2Array]:
|
||||
var segments: Array[PackedVector2Array] = []
|
||||
var seen_segments: Dictionary = {}
|
||||
if shoreline_mesh == null or shoreline_mesh.mesh == null:
|
||||
return segments
|
||||
for surface_index in shoreline_mesh.mesh.get_surface_count():
|
||||
var arrays := shoreline_mesh.mesh.surface_get_arrays(surface_index)
|
||||
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||
var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
|
||||
if indices.is_empty():
|
||||
continue
|
||||
for index_offset in range(0, indices.size() - 2, 3):
|
||||
var triangle := PackedVector2Array()
|
||||
for corner in 3:
|
||||
var vertex := shoreline_mesh.to_global(
|
||||
vertices[indices[index_offset + corner]]
|
||||
)
|
||||
triangle.append(Vector2(vertex.x, vertex.z))
|
||||
_append_unique_segment(segments, seen_segments, triangle[0], triangle[1])
|
||||
_append_unique_segment(segments, seen_segments, triangle[1], triangle[2])
|
||||
_append_unique_segment(segments, seen_segments, triangle[2], triangle[0])
|
||||
return segments
|
||||
|
||||
|
||||
func _append_unique_segment(
|
||||
segments: Array[PackedVector2Array],
|
||||
seen_segments: Dictionary,
|
||||
start: Vector2,
|
||||
end: Vector2,
|
||||
) -> void:
|
||||
if start.distance_squared_to(end) <= 0.000001:
|
||||
return
|
||||
# The baked ribbon repeats shared triangle edges. Quantized canonical keys
|
||||
# keep only distinct segments without an O(n²) startup pass.
|
||||
var first := start
|
||||
var second := end
|
||||
if first.x > second.x or (is_equal_approx(first.x, second.x) and first.y > second.y):
|
||||
first = end
|
||||
second = start
|
||||
var key := "%d,%d:%d,%d" % [
|
||||
roundi(first.x * 10000.0),
|
||||
roundi(first.y * 10000.0),
|
||||
roundi(second.x * 10000.0),
|
||||
roundi(second.y * 10000.0),
|
||||
]
|
||||
if seen_segments.has(key):
|
||||
return
|
||||
seen_segments[key] = true
|
||||
segments.append(PackedVector2Array([start, end]))
|
||||
|
||||
|
||||
static func distance_to_segments(
|
||||
point: Vector2,
|
||||
segments: Array[PackedVector2Array],
|
||||
) -> float:
|
||||
var closest_squared := INF
|
||||
for segment in segments:
|
||||
if segment.size() < 2:
|
||||
continue
|
||||
var closest := Geometry2D.get_closest_point_to_segment(
|
||||
point,
|
||||
segment[0],
|
||||
segment[1],
|
||||
)
|
||||
closest_squared = minf(closest_squared, point.distance_squared_to(closest))
|
||||
return sqrt(closest_squared) if closest_squared < INF else INF
|
||||
|
||||
|
||||
func _configure_audio_loop(stream: AudioStream) -> void:
|
||||
var wav := stream as AudioStreamWAV
|
||||
if wav == null:
|
||||
return
|
||||
wav.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
if wav.loop_end <= wav.loop_begin:
|
||||
wav.loop_begin = 0
|
||||
wav.loop_end = int(round(wav.get_length() * wav.mix_rate))
|
||||
1
world/shoreline_ambience.gd.uid
Normal file
1
world/shoreline_ambience.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b0c3o2vy76fg3
|
||||
|
|
@ -61,6 +61,10 @@ func get_pelican_convenience_landmark() -> Node3D:
|
|||
return _starter_island.get_pelican_landmark()
|
||||
|
||||
|
||||
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||
return _starter_island.get_saltwater_shoreline_mesh()
|
||||
|
||||
|
||||
func _get_regions() -> Array[WorldRegion]:
|
||||
var regions: Array[WorldRegion] = []
|
||||
for child: Node in _regions_root.get_children():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue