Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Git LFS file not shown
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[remap]

importer="wav"
type="AudioStreamWAV"
uid="uid://ddrhppyp6mmdf"
path="res://.godot/imported/SandSound.wav-6516c359f7d7316653f6666842461144.sample"

[deps]

source_file="res://assets/first_party/sounds/characters/player/FootStepsSounds/SandSound.wav"
dest_files=["res://.godot/imported/SandSound.wav-6516c359f7d7316653f6666842461144.sample"]

[params]

force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Git LFS file not shown
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[remap]

importer="wav"
type="AudioStreamWAV"
uid="uid://cbxuw1d1v2w2u"
path="res://.godot/imported/StoneSound.wav-578509cf6a7f7f293a06566d5006f006.sample"

[deps]

source_file="res://assets/first_party/sounds/characters/player/FootStepsSounds/StoneSound.wav"
dest_files=["res://.godot/imported/StoneSound.wav-578509cf6a7f7f293a06566d5006f006.sample"]

[params]

force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Git LFS file not shown
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[remap]

importer="wav"
type="AudioStreamWAV"
uid="uid://s4xqcdhebvsj"
path="res://.godot/imported/WoodSound.wav-91f3d45719b6eb24d88bf2dae0c81cd8.sample"

[deps]

source_file="res://assets/first_party/sounds/characters/player/FootStepsSounds/WoodSound.wav"
dest_files=["res://.godot/imported/WoodSound.wav-91f3d45719b6eb24d88bf2dae0c81cd8.sample"]

[params]

force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
383 changes: 383 additions & 0 deletions scenes/dev/footstep_sound/footstep_sound_test.tscn

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends Node2D

@export var sound_for_material: Dictionary[String, AudioStream]

var _footstep_layers: Array[TileMapLayer] = []
var _default_walk_sound: AudioStream

@onready var walk_sound: AudioStreamPlayer2D = %WalkSound


## Stores the default footstep sound and caches TileMapLayers whose TileSets
## define a "footstep_material" custom data layer.
func _ready() -> void:
_default_walk_sound = walk_sound.stream

var scene_root := get_tree().current_scene
var tile_map_layers := scene_root.find_children("*", "TileMapLayer", true, false)

for node in tile_map_layers:
var layer := node as TileMapLayer
if not layer:
continue

if not layer.tile_set:
continue

if not layer.tile_set.has_custom_data_layer_by_name("footstep_material"):
continue

_footstep_layers.append(layer)


## Detects the terrain material beneath the player and plays its configured
## footstep sound. Falls back to the default sound when no match is configured.
## Called from the footstep frames defined in the walk animation.
func play_footstep() -> void:
if Engine.is_editor_hint():
return

var current_footstep_material: String = ""

for layer in _footstep_layers:
var coord := layer.local_to_map(layer.to_local(global_position))
var tile_data := layer.get_cell_tile_data(coord)
Comment on lines +44 to +46

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All TileMapLayers will be iterated every time a footstep is played from AnimationPlayer? This doesn't sound performant at all.

Also we use several tilemap layers. I wonder what will happen if there is a "stone material" layer below a "grass material" layer. Will this algorithm play the grass material sound effect?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my side, I also considered the performance aspect while developing the system, mainly because of the complexity of integrating the sounds with the different tiles and TileMapLayers.

To develop the system, I first reviewed how similar situations had been handled in other parts of the project. From that review, I found some options that I could use, such as identifying specific TileMapLayers through groups or using scripts associated with the layers so that FootstepSound could recognize which ones it should check. However, these alternatives would also require manually configuring the TileMapLayers that should participate in the system.

I also took into account that scenes can have more complex TileMapLayer structures, with the terrain distributed across different layers. Because of this, the system was designed to adapt to this structure without depending on a specific TileMapLayer.

I ultimately chose the current approach as a more centralized solution. When the scene starts, the system finds the TileMapLayers whose TileSets contain a Custom Data Layer named "material" and stores them. Then, only when a footstep is triggered by the animation, those TileMapLayers are checked to identify the material at the player's position.

When multiple materials overlap, the last valid material found during that check is currently used. This was also one of the reasons why I used Fray's End for testing, due to its map structure and the more complex distribution of its TileMapLayers. This allowed me to test the system in situations involving different materials and overlapping layers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I briefly researched how to do terrain-specific footstep sounds, this is one of the ways that I came across. I also share the concern about performance but that is something that the profiler can tell us: what's the cost of iterating a (small) list of TileMapLayer nodes? Is it significant compared to the 16.6ms budget we get per 60 FPS frame? I think we should check that.

Another way you could imagine doing it is:

  • Have a new physics layer for "footstep-affecting surface"
  • Paint it over every tile that has a footstep material
  • Put an Area2D on the player's feet that detects entering/exiting
  • Only when entering/exiting, list the layers this detector is colliding with and pick the "top" one, update the stream on the player
  • Now each footstep doesn't involve a for loop, just play() on the audiostreamplayer

I actually suspect that this might be more costly for the engine because it has to deal with more absolutely enormous physics shapes for the floor layers, but this is guesswork!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for enlighten us, @wjt! Yes I agree that this should be verified with the Profiler, and against a solution that uses the physics engine. Instead of an Area2D, a RayCast2D with a single point of touch can be used. Presumably this solution will be better on performance, but it would be nice to confirm.

@Leonardo-UTP what do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it would be interesting to test the proposed RayCast2D approach and compare it with the current implementation. First, I'd like to finish the tests I'm currently doing with the existing system, and then investigate whether the RayCast2D approach is viable within Threadbare's current structure. If so, I'll try implementing it so I can compare both approaches.


if not tile_data:
continue

var footstep_material: Variant = tile_data.get_custom_data("footstep_material")
if footstep_material is String and not footstep_material.is_empty():
current_footstep_material = footstep_material

var footstep_sound: AudioStream = sound_for_material.get(
current_footstep_material, _default_walk_sound
)

walk_sound.stream = footstep_sound
walk_sound.play()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://cvjpavggqssir
31 changes: 30 additions & 1 deletion scenes/game_elements/characters/player/player.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
[ext_resource type="PackedScene" uid="uid://b3111eergq5yg" path="res://scenes/game_elements/characters/player/components/player_hook.tscn" id="13_ecbbk"]
[ext_resource type="Script" uid="uid://bk52qjv58locq" path="res://scenes/game_logic/light2d_behaviors/artificial_light_behavior.gd" id="16_ecbbk"]
[ext_resource type="Texture2D" uid="uid://b5ooaiyxdrp6a" path="res://scenes/game_elements/components/light_texture_256x256.tres" id="16_tnibl"]
[ext_resource type="Script" uid="uid://cvjpavggqssir" path="res://scenes/game_elements/characters/player/components/footstep_sound.gd" id="17_tnibl"]
[ext_resource type="AudioStream" uid="uid://ddrhppyp6mmdf" path="res://assets/first_party/sounds/characters/player/FootStepsSounds/SandSound.wav" id="18_cvb4d"]
[ext_resource type="AudioStream" uid="uid://s4xqcdhebvsj" path="res://assets/first_party/sounds/characters/player/FootStepsSounds/WoodSound.wav" id="19_ubfef"]
[ext_resource type="AudioStream" uid="uid://cbxuw1d1v2w2u" path="res://assets/first_party/sounds/characters/player/FootStepsSounds/StoneSound.wav" id="20_2ge3f"]

[sub_resource type="Resource" id="Resource_cvb4d"]
script = ExtResource("3_cvb4d")
Expand Down Expand Up @@ -293,11 +297,28 @@ tracks/2/path = NodePath("WalkSound:playing")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0.115043, 0.4),
"times": PackedFloat32Array(0.115, 0.4),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [true, true]
}
tracks/3/type = "method"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("FootstepSound")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0.115, 0.4),
"transitions": PackedFloat32Array(1, 1),
"values": [{
"args": [],
"method": &"play_footstep"
}, {
"args": [],
"method": &"play_footstep"
}]
}

[sub_resource type="AnimationLibrary" id="AnimationLibrary_qek5x"]
_data = {
Expand Down Expand Up @@ -473,6 +494,14 @@ unique_name_in_owner = true
root_node = NodePath("../../PlayerRepel")
libraries/ = SubResource("AnimationLibrary_1n58k")

[node name="FootstepSound" type="Node2D" parent="." unique_id=1578687431]
script = ExtResource("17_tnibl")
sound_for_material = Dictionary[String, AudioStream]({
"sand": ExtResource("18_cvb4d"),
"stone": ExtResource("20_2ge3f"),
"wood": ExtResource("19_ubfef")
})

[connection signal="running_changed" from="InputWalkBehavior" to="PlayerDustParticles" method="set_emitting"]
[connection signal="running_changed" from="InputWalkBehavior" to="AnimationPlayer" method="_on_input_walk_behavior_running_changed"]
[connection signal="aiming_changed" from="PlayerHook" to="." method="_on_player_hook_aiming_changed"]
Expand Down
8 changes: 8 additions & 0 deletions tiles/bridges.tres
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,37 @@ texture_region_size = Vector2i(64, 64)
0:0/0/terrain_set = 0
0:0/0/terrain = 0
0:0/0/terrains_peering_bit/right_side = 0
0:0/0/custom_data_0 = "wood"
1:0/0 = 0
1:0/0/terrain_set = 0
1:0/0/terrain = 0
1:0/0/terrains_peering_bit/right_side = 0
1:0/0/terrains_peering_bit/left_side = 0
1:0/0/custom_data_0 = "wood"
2:0/0 = 0
2:0/0/terrain_set = 0
2:0/0/terrain = 0
2:0/0/terrains_peering_bit/left_side = 0
2:0/0/custom_data_0 = "wood"
0:1/0 = 0
0:1/0/terrain_set = 0
0:1/0/terrain = 0
0:1/0/terrains_peering_bit/bottom_side = 0
0:1/0/custom_data_0 = "wood"
1:1/0 = 0
2:1/0 = 0
0:2/0 = 0
0:2/0/terrain_set = 0
0:2/0/terrain = 0
0:2/0/terrains_peering_bit/bottom_side = 0
0:2/0/terrains_peering_bit/top_side = 0
0:2/0/custom_data_0 = "wood"
1:2/0 = 0
0:3/0 = 0
0:3/0/terrain_set = 0
0:3/0/terrain = 0
0:3/0/terrains_peering_bit/top_side = 0
0:3/0/custom_data_0 = "wood"
2:3/0 = 0
2:2/0 = 0
2:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-19.593307, -26.124409, -18.799213, 22.622784, 23.897308, 22.941414, 23.791874, -26.124409, -20.059814, -26.124409)
Expand Down Expand Up @@ -61,4 +67,6 @@ physics_layer_2/collision_mask = 0
terrain_set_0/mode = 2
terrain_set_0/terrain_0/name = "Bridge"
terrain_set_0/terrain_0/color = Color(0.404068, 0.229965, 0.149571, 1)
custom_data_layer_0/name = "footstep_material"
custom_data_layer_0/type = 4
sources/3 = SubResource("TileSetAtlasSource_jmclm")
Loading
Loading