fix terrain collision and water recovery
This commit is contained in:
parent
da7f072365
commit
1ab2a291d2
13 changed files with 110 additions and 11 deletions
|
|
@ -156,6 +156,7 @@ func _validate_portable_water_body() -> void:
|
||||||
PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN,
|
PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN,
|
||||||
)
|
)
|
||||||
water_body.set("recovery_entry_depth_threshold", 0.1)
|
water_body.set("recovery_entry_depth_threshold", 0.1)
|
||||||
|
water_body.set("recovery_entry_confirmation_seconds", 0.2)
|
||||||
water_body.set("fish_pool", PondPool)
|
water_body.set("fish_pool", PondPool)
|
||||||
root.add_child(water_body)
|
root.add_child(water_body)
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
@ -183,6 +184,34 @@ func _validate_portable_water_body() -> void:
|
||||||
== PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN
|
== PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN
|
||||||
)
|
)
|
||||||
assert(is_equal_approx(recovery_region.entry_depth_threshold, 0.1))
|
assert(is_equal_approx(recovery_region.entry_depth_threshold, 0.1))
|
||||||
|
assert(is_equal_approx(recovery_region.entry_confirmation_seconds, 0.2))
|
||||||
|
|
||||||
|
var recovery_player := PlayerScene.instantiate() as Player
|
||||||
|
assert(recovery_player != null)
|
||||||
|
root.add_child(recovery_player)
|
||||||
|
recovery_player.set_process(false)
|
||||||
|
recovery_player.set_physics_process(false)
|
||||||
|
var recovery_requests: Array[float] = []
|
||||||
|
recovery_region.recovery_requested.connect(
|
||||||
|
func(_player: Player, requested_surface_height: float) -> void:
|
||||||
|
recovery_requests.append(requested_surface_height)
|
||||||
|
)
|
||||||
|
recovery_region.call("_on_body_entered", recovery_player)
|
||||||
|
recovery_player.global_position.y = 6.8
|
||||||
|
recovery_region.call("_physics_process", 0.1)
|
||||||
|
assert(recovery_requests.is_empty())
|
||||||
|
# Breaking the submersion resets confirmation rather than carrying a stale
|
||||||
|
# partial fall into a later jump.
|
||||||
|
recovery_player.global_position.y = 7.0
|
||||||
|
recovery_region.call("_physics_process", 0.1)
|
||||||
|
recovery_player.global_position.y = 6.8
|
||||||
|
recovery_region.call("_physics_process", 0.11)
|
||||||
|
assert(recovery_requests.is_empty())
|
||||||
|
recovery_region.call("_physics_process", 0.1)
|
||||||
|
assert(recovery_requests == [7.0])
|
||||||
|
recovery_region.call("_on_body_exited", recovery_player)
|
||||||
|
recovery_player.queue_free()
|
||||||
|
await process_frame
|
||||||
|
|
||||||
water_body.queue_free()
|
water_body.queue_free()
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
|
||||||
|
|
@ -490,6 +490,10 @@ func _validate_generated_region(
|
||||||
== PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN
|
== PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN
|
||||||
)
|
)
|
||||||
assert(is_equal_approx(fresh_recovery.entry_depth_threshold, 0.1))
|
assert(is_equal_approx(fresh_recovery.entry_depth_threshold, 0.1))
|
||||||
|
assert(is_equal_approx(
|
||||||
|
fresh_recovery.entry_confirmation_seconds,
|
||||||
|
0.2,
|
||||||
|
))
|
||||||
var coordinate_tokens := child.name.trim_prefix("FreshWater_").split("_")
|
var coordinate_tokens := child.name.trim_prefix("FreshWater_").split("_")
|
||||||
assert(coordinate_tokens.size() == 2)
|
assert(coordinate_tokens.size() == 2)
|
||||||
var coordinate := Vector2i(
|
var coordinate := Vector2i(
|
||||||
|
|
@ -1043,6 +1047,25 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
|
||||||
assert(palm.maximum_cluster_radius > palm.minimum_cluster_radius)
|
assert(palm.maximum_cluster_radius > palm.minimum_cluster_radius)
|
||||||
assert(palm.prefer_ocean_facing)
|
assert(palm.prefer_ocean_facing)
|
||||||
assert(not palm.local_overhang_direction.is_zero_approx())
|
assert(not palm.local_overhang_direction.is_zero_approx())
|
||||||
|
var aligned_tree_collisions: Dictionary[StringName, Vector2] = {
|
||||||
|
&"prop_tree_1": Vector2(0.08, 0.01),
|
||||||
|
&"prop_tree_2": Vector2(0.1, 0.075),
|
||||||
|
&"prop_tree_3": Vector2(-0.1, 0.18),
|
||||||
|
&"prop_tree_large": Vector2(0.084, 0.504),
|
||||||
|
&"prop_pine": Vector2(-0.044, 0.083),
|
||||||
|
&"prop_pine_large": Vector2(-0.04, 0.075),
|
||||||
|
&"prop_palm": Vector2(0.15, -0.1),
|
||||||
|
}
|
||||||
|
for tree_id: StringName in aligned_tree_collisions:
|
||||||
|
var aligned_tree := catalog.definition_for_id(tree_id)
|
||||||
|
assert(aligned_tree != null and aligned_tree.has_cylinder_collision())
|
||||||
|
var collision_center := Vector2(
|
||||||
|
aligned_tree.collision_offset.x,
|
||||||
|
aligned_tree.collision_offset.z,
|
||||||
|
)
|
||||||
|
assert(collision_center.is_equal_approx(
|
||||||
|
aligned_tree_collisions[tree_id]
|
||||||
|
))
|
||||||
var forest := region.get_biome_catalog().definition_for_id(&"biome_forest")
|
var forest := region.get_biome_catalog().definition_for_id(&"biome_forest")
|
||||||
var forest_rule := forest.prop_rule_for_group(&"grass_tree")
|
var forest_rule := forest.prop_rule_for_group(&"grass_tree")
|
||||||
var large_tree := catalog.definition_for_id(&"prop_tree_large")
|
var large_tree := catalog.definition_for_id(&"prop_tree_large")
|
||||||
|
|
@ -1139,6 +1162,20 @@ func _validate_decoration_transform(
|
||||||
or collision.shape is BoxShape3D
|
or collision.shape is BoxShape3D
|
||||||
)
|
)
|
||||||
assert(collision.global_basis.get_scale().is_equal_approx(Vector3.ONE))
|
assert(collision.global_basis.get_scale().is_equal_approx(Vector3.ONE))
|
||||||
|
if collision.shape is CylinderShape3D:
|
||||||
|
var cylinder := collision.shape as CylinderShape3D
|
||||||
|
assert(is_equal_approx(
|
||||||
|
cylinder.radius,
|
||||||
|
definition.collision_radius * visual_scale,
|
||||||
|
))
|
||||||
|
assert(is_equal_approx(
|
||||||
|
cylinder.height,
|
||||||
|
definition.collision_height * visual_scale,
|
||||||
|
))
|
||||||
|
assert(collision.position.is_equal_approx(
|
||||||
|
definition.collision_offset * visual_scale
|
||||||
|
+ Vector3.UP * cylinder.height * 0.5
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
assert(collision == null)
|
assert(collision == null)
|
||||||
var query := PhysicsRayQueryParameters3D.create(
|
var query := PhysicsRayQueryParameters3D.create(
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,7 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void:
|
||||||
PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN
|
PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN
|
||||||
)
|
)
|
||||||
body.recovery_entry_depth_threshold = 0.1
|
body.recovery_entry_depth_threshold = 0.1
|
||||||
|
body.recovery_entry_confirmation_seconds = 0.2
|
||||||
body.fish_pool = _fresh_water_pool(tags)
|
body.fish_pool = _fresh_water_pool(tags)
|
||||||
body.location_tags = _fresh_water_location_tags(tags)
|
body.location_tags = _fresh_water_location_tags(tags)
|
||||||
body.selection_priority = 10
|
body.selection_priority = 10
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ packed_scene = ExtResource("2_scene")
|
||||||
procedural_group = &"sand_tree"
|
procedural_group = &"sand_tree"
|
||||||
required_chunk_tags = PackedStringArray("sand")
|
required_chunk_tags = PackedStringArray("sand")
|
||||||
minimum_spawn_chunk_distance = 2
|
minimum_spawn_chunk_distance = 2
|
||||||
clearance_radius = 0.65
|
clearance_radius = 0.75
|
||||||
minimum_visual_scale = 0.5
|
minimum_visual_scale = 0.5
|
||||||
maximum_visual_scale = 1.0
|
maximum_visual_scale = 1.0
|
||||||
minimum_cluster_size = 2
|
minimum_cluster_size = 2
|
||||||
|
|
@ -21,6 +21,7 @@ maximum_cluster_radius = 3.0
|
||||||
prefer_ocean_facing = true
|
prefer_ocean_facing = true
|
||||||
local_overhang_direction = Vector2(-0.883, 0.469)
|
local_overhang_direction = Vector2(-0.883, 0.469)
|
||||||
ocean_facing_spread_degrees = 55.0
|
ocean_facing_spread_degrees = 55.0
|
||||||
collision_radius = 0.4
|
collision_radius = 0.7
|
||||||
collision_height = 6.0
|
collision_height = 6.0
|
||||||
|
collision_offset = Vector3(0.15, 0, -0.1)
|
||||||
gatherable_surface_material_names = PackedStringArray("wood_light")
|
gatherable_surface_material_names = PackedStringArray("wood_light")
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ minimum_spawn_chunk_distance = 2
|
||||||
clearance_radius = 1.55
|
clearance_radius = 1.55
|
||||||
minimum_visual_scale = 0.65
|
minimum_visual_scale = 0.65
|
||||||
maximum_visual_scale = 1.2
|
maximum_visual_scale = 1.2
|
||||||
collision_radius = 0.5
|
collision_radius = 0.85
|
||||||
collision_height = 4.0
|
collision_height = 4.0
|
||||||
|
collision_offset = Vector3(-0.044, 0, 0.083)
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ minimum_spawn_chunk_distance = 2
|
||||||
clearance_radius = 1.65
|
clearance_radius = 1.65
|
||||||
minimum_visual_scale = 0.75
|
minimum_visual_scale = 0.75
|
||||||
maximum_visual_scale = 1.2
|
maximum_visual_scale = 1.2
|
||||||
collision_radius = 0.65
|
collision_radius = 0.75
|
||||||
collision_height = 9.5
|
collision_height = 9.5
|
||||||
|
collision_offset = Vector3(-0.04, 0, 0.075)
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,5 @@ secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light",
|
||||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||||
collision_radius = 0.4
|
collision_radius = 0.4
|
||||||
collision_height = 3.2
|
collision_height = 3.2
|
||||||
|
collision_offset = Vector3(0.08, 0, 0.01)
|
||||||
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark
|
||||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||||
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
||||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||||
collision_radius = 0.5
|
collision_radius = 0.55
|
||||||
collision_height = 3.8
|
collision_height = 3.8
|
||||||
|
collision_offset = Vector3(0.1, 0, 0.075)
|
||||||
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,5 @@ secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light",
|
||||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||||
collision_radius = 0.55
|
collision_radius = 0.55
|
||||||
collision_height = 4.2
|
collision_height = 4.2
|
||||||
|
collision_offset = Vector3(-0.1, 0, 0.18)
|
||||||
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ variant_material_slot_names = PackedStringArray("leaf_light", "leaf", "leaf_dark
|
||||||
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
material_variants = Array[Material]([ExtResource("3_leaf_light"), ExtResource("4_leaf_mid"), ExtResource("5_leaf_dark")])
|
||||||
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
secondary_variant_material_slot_names = PackedStringArray("wood", "wood_light", "wood_mid")
|
||||||
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
secondary_material_variants = Array[Material]([ExtResource("6_wood_light"), ExtResource("7_wood_mid"), ExtResource("8_wood_dark")])
|
||||||
collision_radius = 0.85
|
collision_radius = 1.05
|
||||||
collision_height = 7.5
|
collision_height = 7.5
|
||||||
|
collision_offset = Vector3(0.084, 0, 0.504)
|
||||||
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
gatherable_surface_material_names = PackedStringArray("wood", "wood_light", "wood_mid", "wood_dark")
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,14 @@ enum EntryHeightReference {
|
||||||
)
|
)
|
||||||
## Recovery begins when the selected reference reaches this depth.
|
## Recovery begins when the selected reference reaches this depth.
|
||||||
@export_range(0.0, 2.0, 0.05) var entry_depth_threshold: float = 0.35
|
@export_range(0.0, 2.0, 0.05) var entry_depth_threshold: float = 0.35
|
||||||
|
## The player must remain below the threshold for this long before recovery.
|
||||||
|
## A short freshwater confirmation keeps jumps across stepping stones from
|
||||||
|
## being mistaken for a completed fall into the water.
|
||||||
|
@export_range(0.0, 1.0, 0.05) var entry_confirmation_seconds: float = 0.0
|
||||||
|
|
||||||
var _tracked_players: Array[Player] = []
|
var _tracked_players: Array[Player] = []
|
||||||
var _triggered_players: Dictionary[StringName, bool] = {}
|
var _triggered_players: Dictionary[StringName, bool] = {}
|
||||||
|
var _submerged_seconds: Dictionary[StringName, float] = {}
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -46,7 +51,7 @@ func _ready() -> void:
|
||||||
set_physics_process(false)
|
set_physics_process(false)
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(_delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
var active_surface_height: float = get_surface_height()
|
var active_surface_height: float = get_surface_height()
|
||||||
for player: Player in _tracked_players.duplicate():
|
for player: Player in _tracked_players.duplicate():
|
||||||
if not is_instance_valid(player):
|
if not is_instance_valid(player):
|
||||||
|
|
@ -58,9 +63,17 @@ func _physics_process(_delta: float) -> void:
|
||||||
var entry_height := player.get_body_center_position().y
|
var entry_height := player.get_body_center_position().y
|
||||||
if entry_height_reference == EntryHeightReference.PLAYER_ORIGIN:
|
if entry_height_reference == EntryHeightReference.PLAYER_ORIGIN:
|
||||||
entry_height = player.global_position.y
|
entry_height = player.global_position.y
|
||||||
if entry_height <= active_surface_height - entry_depth_threshold:
|
if entry_height > active_surface_height - entry_depth_threshold:
|
||||||
_triggered_players[player_key] = true
|
_submerged_seconds[player_key] = 0.0
|
||||||
recovery_requested.emit(player, active_surface_height)
|
continue
|
||||||
|
var submerged_seconds: float = (
|
||||||
|
_submerged_seconds.get(player_key, 0.0) + maxf(delta, 0.0)
|
||||||
|
)
|
||||||
|
_submerged_seconds[player_key] = submerged_seconds
|
||||||
|
if submerged_seconds < entry_confirmation_seconds:
|
||||||
|
continue
|
||||||
|
_triggered_players[player_key] = true
|
||||||
|
recovery_requested.emit(player, active_surface_height)
|
||||||
if _tracked_players.is_empty():
|
if _tracked_players.is_empty():
|
||||||
set_physics_process(false)
|
set_physics_process(false)
|
||||||
|
|
||||||
|
|
@ -86,6 +99,7 @@ func _on_body_entered(body: Node3D) -> void:
|
||||||
if player == null or player in _tracked_players:
|
if player == null or player in _tracked_players:
|
||||||
return
|
return
|
||||||
_tracked_players.append(player)
|
_tracked_players.append(player)
|
||||||
|
_submerged_seconds[StringName(str(player.get_instance_id()))] = 0.0
|
||||||
set_physics_process(true)
|
set_physics_process(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,6 +108,8 @@ func _on_body_exited(body: Node3D) -> void:
|
||||||
if player == null:
|
if player == null:
|
||||||
return
|
return
|
||||||
_tracked_players.erase(player)
|
_tracked_players.erase(player)
|
||||||
_triggered_players.erase(StringName(str(player.get_instance_id())))
|
var player_key := StringName(str(player.get_instance_id()))
|
||||||
|
_triggered_players.erase(player_key)
|
||||||
|
_submerged_seconds.erase(player_key)
|
||||||
if _tracked_players.is_empty():
|
if _tracked_players.is_empty():
|
||||||
set_physics_process(false)
|
set_physics_process(false)
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ location_tags = Array[StringName]([&"starter_pond", &"pond"])
|
||||||
selection_priority = 1
|
selection_priority = 1
|
||||||
recovery_entry_height_reference = 1
|
recovery_entry_height_reference = 1
|
||||||
recovery_entry_depth_threshold = 0.1
|
recovery_entry_depth_threshold = 0.1
|
||||||
|
recovery_entry_confirmation_seconds = 0.2
|
||||||
|
|
||||||
[node name="VisualWater" type="MeshInstance3D" parent="WaterBodies/Pond" unique_id=1034265960]
|
[node name="VisualWater" type="MeshInstance3D" parent="WaterBodies/Pond" unique_id=1034265960]
|
||||||
material_override = ExtResource("19_fresh_water")
|
material_override = ExtResource("19_fresh_water")
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,11 @@ var recovery_entry_depth_threshold: float = 0.35:
|
||||||
set(value):
|
set(value):
|
||||||
recovery_entry_depth_threshold = clampf(value, 0.0, 2.0)
|
recovery_entry_depth_threshold = clampf(value, 0.0, 2.0)
|
||||||
_sync_owned_nodes()
|
_sync_owned_nodes()
|
||||||
|
@export_range(0.0, 1.0, 0.05)
|
||||||
|
var recovery_entry_confirmation_seconds: float = 0.0:
|
||||||
|
set(value):
|
||||||
|
recovery_entry_confirmation_seconds = clampf(value, 0.0, 1.0)
|
||||||
|
_sync_owned_nodes()
|
||||||
@export_range(0.1, 20.0, 0.1, "or_greater", "suffix:m")
|
@export_range(0.1, 20.0, 0.1, "or_greater", "suffix:m")
|
||||||
var recovery_depth: float = 4.8:
|
var recovery_depth: float = 4.8:
|
||||||
set(value):
|
set(value):
|
||||||
|
|
@ -173,6 +178,9 @@ func _sync_owned_nodes() -> void:
|
||||||
recovery_region.entry_depth_threshold = (
|
recovery_region.entry_depth_threshold = (
|
||||||
recovery_entry_depth_threshold
|
recovery_entry_depth_threshold
|
||||||
)
|
)
|
||||||
|
recovery_region.entry_confirmation_seconds = (
|
||||||
|
recovery_entry_confirmation_seconds
|
||||||
|
)
|
||||||
if uses_polygon and _sync_polygon_collision_shapes(
|
if uses_polygon and _sync_polygon_collision_shapes(
|
||||||
recovery_region,
|
recovery_region,
|
||||||
recovery_shape_node,
|
recovery_shape_node,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue