Skip to content

Commit 2a554ea

Browse files
authored
Fix intermittent ground friction (#31)
1 parent 809bd78 commit 2a554ea

4 files changed

Lines changed: 71 additions & 1 deletion

File tree

scripts/test-godot.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ TEST_SCRIPTS=(
1515
"res://tests/aim_trainer_smoke.gd"
1616
"res://tests/death_camera_test.gd"
1717
"res://tests/elimination_round_state_test.gd"
18+
"res://tests/player_grounding_test.gd"
1819
)
1920

2021
if ! command -v "$GODOT_BIN" >/dev/null 2>&1; then

src/player/player.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ func _apply_gravity(velocity_y: float, delta: float) -> float:
310310
return velocity_y - gravity * delta
311311

312312
func grounded() -> bool:
313-
return test_move(global_transform, Vector3(0, -0.01, 0))
313+
return is_on_floor() or test_move(global_transform, Vector3(0, -0.01, 0))
314314

315315
func _apply_friction(vel_planar: Vector2, delta: float, wish_dir: Vector2, jump_input: bool) -> Vector2:
316316
if not grounded() or jump_input:

tests/player_grounding_test.gd

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
extends SceneTree
2+
3+
const ROOFTOPS_SCENE := "res://src/maps/speedrun/map_rooftops.tscn"
4+
const TestCase = preload("res://tests/support/test_case.gd")
5+
6+
func _init() -> void:
7+
call_deferred("_run")
8+
9+
func _run() -> void:
10+
var t := TestCase.new()
11+
await _wait_for_app_context()
12+
for _frame in range(5):
13+
await process_frame
14+
15+
if current_scene != null:
16+
current_scene.free()
17+
current_scene = null
18+
await physics_frame
19+
20+
var rooftops_scene := load(ROOFTOPS_SCENE) as PackedScene
21+
t.check(rooftops_scene != null, "Rooftops should load for the grounding regression test")
22+
if rooftops_scene == null:
23+
quit(t.finish())
24+
return
25+
26+
var rooftops := rooftops_scene.instantiate()
27+
var map: Node3D = rooftops.get_node("Map")
28+
var player = rooftops.get_node("Player")
29+
rooftops.remove_child(map)
30+
rooftops.remove_child(player)
31+
rooftops.free()
32+
for child in map.get_children():
33+
if child is Area3D:
34+
if "audio_player" in child and is_instance_valid(child.audio_player):
35+
child.audio_player.free()
36+
child.free()
37+
root.add_child(map)
38+
root.add_child(player)
39+
player.setup_as_local_player()
40+
player.set_physics_process(false)
41+
player._time_since_last_run_sound = -100.0
42+
await physics_frame
43+
44+
var missed_floor_contact := false
45+
for frame in range(60):
46+
var input := Vector2(0, -1) if frame < 30 else Vector2.ZERO
47+
player._movement_process(1.0 / 60.0, input, false)
48+
if player.is_on_floor() and not player.grounded():
49+
missed_floor_contact = true
50+
await physics_frame
51+
52+
t.check(
53+
not missed_floor_contact,
54+
"Grounded state should include floor contacts reported by CharacterBody3D",
55+
)
56+
player._run_audio_player.stop()
57+
player._run_audio_player.stream = null
58+
player.free()
59+
map.free()
60+
await process_frame
61+
quit(t.finish())
62+
63+
func _wait_for_app_context() -> void:
64+
for _frame in range(600):
65+
var global_node := root.get_node_or_null("Global")
66+
if global_node != null and global_node.context != null:
67+
return
68+
await process_frame

tests/player_grounding_test.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://d2w2ngxftm2gd

0 commit comments

Comments
 (0)