Przeglądaj źródła

Merge pull request #584 from nekomatata/physics-tests-2d-character-update

Updated 2D character controller physics tests
Aaron Franke 4 lat temu
rodzic
commit
98ed996780

+ 1 - 0
2d/physics_tests/main.tscn

@@ -190,6 +190,7 @@ margin_right = 408.0
 margin_bottom = 89.0
 text = "Log start"
 valign = 2
+autowrap = true
 max_lines_visible = 5
 __meta__ = {
 "_edit_use_anchors_": false

+ 4 - 0
2d/physics_tests/tests.gd

@@ -26,6 +26,10 @@ var _tests = [
 		"id": "Functional Tests/Character - Tilemap",
 		"path": "res://tests/functional/test_character_tilemap.tscn",
 	},
+	{
+		"id": "Functional Tests/Character - Pixels",
+		"path": "res://tests/functional/test_character_pixels.tscn",
+	},
 	{
 		"id": "Functional Tests/One Way Collision",
 		"path": "res://tests/functional/test_one_way_collision.tscn",

+ 31 - 17
2d/physics_tests/tests/functional/test_character.gd

@@ -17,13 +17,19 @@ const OPTION_MOVE_KINEMATIC_STOP_ON_SLOPE = "Move Options/Use stop on slope (Kin
 
 export(Vector2) var _initial_velocity = Vector2.ZERO
 export(Vector2) var _constant_velocity = Vector2.ZERO
+export(float) var _motion_speed = 400.0
+export(float) var _gravity_force = 50.0
+export(float) var _jump_force = 1000.0
 export(float) var _snap_distance = 0.0
 export(float) var _floor_max_angle = 45.0
 export(E_BodyType) var _body_type = 0
 
+onready var options = $Options
+
 var _use_snap = true
 var _use_stop_on_slope = true
 
+var _body_parent = null
 var _rigid_body_template = null
 var _kinematic_body_template = null
 var _kinematic_body_ray_template = null
@@ -31,43 +37,47 @@ var _moving_body = null
 
 
 func _ready():
-	$Options.connect("option_selected", self, "_on_option_selected")
-	$Options.connect("option_changed", self, "_on_option_changed")
+	options.connect("option_selected", self, "_on_option_selected")
+	options.connect("option_changed", self, "_on_option_changed")
 
 	_rigid_body_template = find_node("RigidBody2D")
 	if _rigid_body_template:
-		remove_child(_rigid_body_template)
+		_body_parent = _rigid_body_template.get_parent()
+		_body_parent.remove_child(_rigid_body_template)
 		var enabled = _body_type == E_BodyType.RIGID_BODY
-		$Options.add_menu_item(OPTION_OBJECT_TYPE_RIGIDBODY, true, enabled, true)
+		options.add_menu_item(OPTION_OBJECT_TYPE_RIGIDBODY, true, enabled, true)
 
 	_kinematic_body_template = find_node("KinematicBody2D")
 	if _kinematic_body_template:
-		remove_child(_kinematic_body_template)
+		_body_parent = _kinematic_body_template.get_parent()
+		_body_parent.remove_child(_kinematic_body_template)
 		var enabled = _body_type == E_BodyType.KINEMATIC_BODY
-		$Options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC, true, enabled, true)
+		options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC, true, enabled, true)
 
 	_kinematic_body_ray_template = find_node("KinematicBodyRay2D")
 	if _kinematic_body_ray_template:
-		remove_child(_kinematic_body_ray_template)
+		_body_parent = _kinematic_body_ray_template.get_parent()
+		_body_parent.remove_child(_kinematic_body_ray_template)
 		var enabled = _body_type == E_BodyType.KINEMATIC_BODY_RAY_SHAPE
-		$Options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC_RAYSHAPE, true, enabled, true)
+		options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC_RAYSHAPE, true, enabled, true)
 
-	$Options.add_menu_item(OPTION_MOVE_KINEMATIC_SNAP, true, _use_snap)
-	$Options.add_menu_item(OPTION_MOVE_KINEMATIC_STOP_ON_SLOPE, true, _use_stop_on_slope)
+	options.add_menu_item(OPTION_MOVE_KINEMATIC_SNAP, true, _use_snap)
+	options.add_menu_item(OPTION_MOVE_KINEMATIC_STOP_ON_SLOPE, true, _use_stop_on_slope)
 
 	_start_test()
 
 
 func _process(_delta):
+	var label_floor = $LabelFloor
 	if _moving_body:
 		if _moving_body.is_on_floor():
-			$LabelFloor.text = "ON FLOOR"
-			$LabelFloor.self_modulate = Color.green
+			label_floor.text = "ON FLOOR"
+			label_floor.self_modulate = Color.green
 		else:
-			$LabelFloor.text = "OFF FLOOR"
-			$LabelFloor.self_modulate = Color.red
+			label_floor.text = "OFF FLOOR"
+			label_floor.self_modulate = Color.red
 	else:
-		$LabelFloor.visible = false
+		label_floor.visible = false
 
 
 func _input(event):
@@ -118,7 +128,7 @@ func _on_option_changed(option, checked):
 
 func _start_test():
 	if _moving_body:
-		remove_child(_moving_body)
+		_body_parent.remove_child(_moving_body)
 		_moving_body.queue_free()
 		_moving_body = null
 
@@ -135,11 +145,15 @@ func _start_test():
 
 	test_label += template.name
 	_moving_body = template.duplicate()
-	add_child(_moving_body)
+	_body_parent.add_child(_moving_body)
 
 	_moving_body._initial_velocity = _initial_velocity
 	_moving_body._constant_velocity = _constant_velocity
 
+	_moving_body._motion_speed = _motion_speed
+	_moving_body._gravity_force = _gravity_force
+	_moving_body._jump_force = _jump_force
+
 	if _moving_body is KinematicBody2D:
 		if _use_snap:
 			_moving_body._snap = Vector2(0, _snap_distance)

+ 147 - 0
2d/physics_tests/tests/functional/test_character_pixels.gd

@@ -0,0 +1,147 @@
+extends TestCharacter
+
+
+const OPTION_TEST_CASE_ALL = "Test Cases/TEST ALL (0)"
+const OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP = "Test Cases/Floor detection (Kinematic Body)"
+const OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES = "Test Cases/Floor detection with motion changes (Kinematic Body)"
+
+const MOTION_CHANGES_DIR = Vector2(1.0, 1.0)
+const MOTION_CHANGES_SPEEDS = [0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0]
+
+var _test_floor_detection = false
+var _test_motion_changes = false
+var _floor_detected = false
+var _floor_lost = false
+
+var _failed_reason = ""
+
+
+func _ready():
+	options.add_menu_item(OPTION_TEST_CASE_ALL)
+	options.add_menu_item(OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP)
+	options.add_menu_item(OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES)
+
+func _physics_process(_delta):
+	if _moving_body:
+		if _moving_body.is_on_floor():
+			_floor_detected = true
+		elif _floor_detected:
+			_floor_lost = true
+			if _test_motion_changes:
+				Log.print_log("Floor lost.")
+
+		if _test_motion_changes:
+			var speed_count = MOTION_CHANGES_SPEEDS.size()
+			var speed_index = randi() % speed_count
+			var speed = MOTION_CHANGES_SPEEDS[speed_index]
+			var velocity = speed * MOTION_CHANGES_DIR
+			_moving_body._constant_velocity = velocity
+			#Log.print_log("Velocity: %s" % velocity)
+
+
+func _input(event):
+	var key_event = event as InputEventKey
+	if key_event and not key_event.pressed:
+		if key_event.scancode == KEY_0:
+			_on_option_selected(OPTION_TEST_CASE_ALL)
+
+
+func _on_option_selected(option):
+	match option:
+		OPTION_TEST_CASE_ALL:
+			_test_all()
+		OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP:
+			_start_test_case(option)
+			return
+		OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES:
+			_start_test_case(option)
+			return
+
+	._on_option_selected(option)
+
+
+func _start_test_case(option):
+	Log.print_log("* Starting " + option)
+
+	match option:
+		OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP:
+			_test_floor_detection = true
+			_test_motion_changes = false
+			_use_snap = false
+			_body_type = E_BodyType.KINEMATIC_BODY
+			_start_test()
+
+			yield(start_timer(1.0), "timeout")
+			if is_timer_canceled():
+				return
+
+			_set_result(not _floor_lost)
+		OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES:
+			_test_floor_detection = true
+			_test_motion_changes = true
+			_use_snap = false
+			_body_type = E_BodyType.KINEMATIC_BODY
+			_start_test()
+
+			yield(start_timer(4.0), "timeout")
+			if is_timer_canceled():
+				_test_motion_changes = false
+				return
+
+			_test_motion_changes = false
+			_moving_body._constant_velocity = Vector2.ZERO
+
+			_set_result(not _floor_lost)
+		_:
+			Log.print_error("Invalid test case.")
+
+
+func _test_all():
+	Log.print_log("* TESTING ALL...")
+
+	# Test floor detection with no snapping.
+	yield(_start_test_case(OPTION_TEST_CASE_DETECT_FLOOR_NO_SNAP), "completed")
+
+	# Test floor detection with no snapping.
+	# In this test case, motion alternates different speeds.
+	yield(_start_test_case(OPTION_TEST_CASE_DETECT_FLOOR_MOTION_CHANGES), "completed")
+
+	Log.print_log("* Done.")
+
+
+func _set_result(test_passed):
+	var result = ""
+	if test_passed:
+		result = "PASSED"
+	else:
+		result = "FAILED"
+
+	if not test_passed and not _failed_reason.empty():
+		result += _failed_reason
+	else:
+		result += "."
+
+	Log.print_log("Test %s" % result)
+
+
+func _start_test():
+	._start_test()
+
+	_failed_reason = ""
+
+	_floor_detected = false
+	_floor_lost = false
+
+	if _test_floor_detection:
+		_failed_reason = ": floor was not detected consistently."
+		if _test_motion_changes:
+			# Always use the same seed for reproducible results.
+			rand_seed(123456789)
+			_moving_body._gravity_force = 0.0
+			_moving_body._motion_speed = 0.0
+			_moving_body._jump_force = 0.0
+		else:
+			_moving_body._initial_velocity = Vector2(30, 0)
+		_test_floor_detection = false
+	else:
+		_test_motion_changes = false

+ 151 - 0
2d/physics_tests/tests/functional/test_character_pixels.tscn

@@ -0,0 +1,151 @@
+[gd_scene load_steps=12 format=2]
+
+[ext_resource path="res://tests/functional/test_character_pixels.gd" type="Script" id=1]
+[ext_resource path="res://utils/rigidbody_controller.gd" type="Script" id=2]
+[ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=3]
+[ext_resource path="res://tests/static_scene_flat.tscn" type="PackedScene" id=4]
+[ext_resource path="res://utils/kinematicbody_controller.gd" type="Script" id=7]
+
+[sub_resource type="PhysicsMaterial" id=1]
+friction = 0.0
+
+[sub_resource type="RectangleShape2D" id=2]
+extents = Vector2( 3, 5 )
+
+[sub_resource type="RectangleShape2D" id=3]
+extents = Vector2( 3, 4.9 )
+
+[sub_resource type="RectangleShape2D" id=4]
+extents = Vector2( 3, 3 )
+
+[sub_resource type="RayShape2D" id=5]
+length = 3.0
+
+[sub_resource type="RectangleShape2D" id=6]
+extents = Vector2( 10, 2 )
+
+[node name="Test" type="Node2D"]
+script = ExtResource( 1 )
+_motion_speed = 30.0
+_gravity_force = 2.0
+_jump_force = 50.0
+_snap_distance = 1.0
+
+[node name="ViewportContainer" type="ViewportContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_right = 1024.0
+margin_bottom = 600.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+stretch = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Viewport" type="Viewport" parent="ViewportContainer"]
+size = Vector2( 128, 75 )
+handle_input_locally = false
+render_target_update_mode = 3
+
+[node name="StaticSceneFlat" parent="ViewportContainer/Viewport" instance=ExtResource( 4 )]
+position = Vector2( 0, -450 )
+
+[node name="RigidBody2D" type="RigidBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 30, 40 )
+collision_mask = 2147483649
+mode = 2
+physics_material_override = SubResource( 1 )
+contacts_reported = 4
+contact_monitor = true
+script = ExtResource( 2 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/RigidBody2D"]
+shape = SubResource( 2 )
+
+[node name="KinematicBody2D" type="KinematicBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 30, 40 )
+collision_mask = 2147483649
+script = ExtResource( 7 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/KinematicBody2D"]
+shape = SubResource( 3 )
+
+[node name="KinematicBodyRay2D" type="KinematicBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 30, 40 )
+collision_mask = 2147483649
+script = ExtResource( 7 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/KinematicBodyRay2D"]
+position = Vector2( 0, -2 )
+shape = SubResource( 4 )
+
+[node name="CollisionShapeRay2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/KinematicBodyRay2D"]
+position = Vector2( 0, -1 )
+shape = SubResource( 5 )
+
+[node name="Wall1" type="StaticBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 20, 40 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/Wall1"]
+rotation = 1.5708
+shape = SubResource( 6 )
+
+[node name="Wall2" type="StaticBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 122, 40 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/Wall2"]
+rotation = 1.5708
+shape = SubResource( 6 )
+
+[node name="Platform1" type="StaticBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 50, 44 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/Platform1"]
+shape = SubResource( 6 )
+one_way_collision = true
+
+[node name="Platform2" type="StaticBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 80, 38 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ViewportContainer/Viewport/Platform2"]
+shape = SubResource( 6 )
+
+[node name="Slope" type="StaticBody2D" parent="ViewportContainer/Viewport"]
+position = Vector2( 84, 36 )
+
+[node name="CollisionShape2D" type="CollisionPolygon2D" parent="ViewportContainer/Viewport/Slope"]
+polygon = PoolVector2Array( 0, 0, 6, 0, 22, 16, 16, 16 )
+
+[node name="LabelTestType" type="Label" parent="."]
+margin_left = 14.0
+margin_top = 79.0
+margin_right = 145.0
+margin_bottom = 93.0
+text = "Testing: "
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Options" parent="." instance=ExtResource( 3 )]
+
+[node name="LabelFloor" type="Label" parent="."]
+margin_left = 14.0
+margin_top = 237.929
+margin_right = 145.0
+margin_bottom = 251.929
+text = "ON FLOOR"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="LabelControls" type="Label" parent="."]
+margin_left = 14.0
+margin_top = 263.291
+margin_right = 145.0
+margin_bottom = 294.291
+text = "LEFT/RIGHT - MOVE
+UP - JUMP"
+__meta__ = {
+"_edit_use_anchors_": false
+}

+ 1 - 1
2d/physics_tests/tests/functional/test_character_slopes.tscn

@@ -26,7 +26,7 @@ length = 64.0
 [node name="Test" type="Node2D"]
 script = ExtResource( 1 )
 _snap_distance = 32.0
-_floor_max_angle = 60.0
+_floor_max_angle = 45.0
 
 [node name="LabelTestType" type="Label" parent="."]
 margin_left = 14.0

+ 177 - 7
2d/physics_tests/tests/functional/test_character_tilemap.gd

@@ -1,26 +1,196 @@
 extends TestCharacter
 
 
-const OPTION_TEST_CASE_JUMP_ONE_WAY = "Test Cases/Jump through one-way tiles"
+const OPTION_TEST_CASE_ALL = "Test Cases/TEST ALL (0)"
+const OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID = "Test Cases/Jump through one-way tiles (Rigid Body)"
+const OPTION_TEST_CASE_JUMP_ONE_WAY_KINEMATIC = "Test Cases/Jump through one-way tiles (Kinematic Body)"
+const OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID = "Test Cases/Jump through one-way corner (Rigid Body)"
+const OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_KINEMATIC = "Test Cases/Jump through one-way corner (Kinematic Body)"
+const OPTION_TEST_CASE_FALL_ONE_WAY_KINEMATIC = "Test Cases/Fall and pushed on one-way tiles (Kinematic Body)"
 
 var _test_jump_one_way = false
+var _test_jump_one_way_corner = false
+var _test_fall_one_way = false
+
+var _extra_body = null
+
+var _failed_reason = ""
 
 
 func _ready():
-	$Options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY, true, false)
+	options.add_menu_item(OPTION_TEST_CASE_ALL)
+	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID)
+	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_KINEMATIC)
+	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID)
+	options.add_menu_item(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_KINEMATIC)
+	options.add_menu_item(OPTION_TEST_CASE_FALL_ONE_WAY_KINEMATIC)
+
+
+func _input(event):
+	var key_event = event as InputEventKey
+	if key_event and not key_event.pressed:
+		if key_event.scancode == KEY_0:
+			_on_option_selected(OPTION_TEST_CASE_ALL)
+
+
+func _on_option_selected(option):
+	match option:
+		OPTION_TEST_CASE_ALL:
+			_test_all()
+		OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID:
+			_start_test_case(option)
+			return
+		OPTION_TEST_CASE_JUMP_ONE_WAY_KINEMATIC:
+			_start_test_case(option)
+			return
+		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID:
+			_start_test_case(option)
+			return
+		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_KINEMATIC:
+			_start_test_case(option)
+			return
+		OPTION_TEST_CASE_FALL_ONE_WAY_KINEMATIC:
+			_start_test_case(option)
+			return
+
+	._on_option_selected(option)
 
 
-func _on_option_changed(option, checked):
+func _start_test_case(option):
+	Log.print_log("* Starting " + option)
+
 	match option:
-		OPTION_TEST_CASE_JUMP_ONE_WAY:
-			_test_jump_one_way = checked
-			_start_test()
+		OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID:
+			_body_type = E_BodyType.RIGID_BODY
+			_test_jump_one_way_corner = false
+			yield(_start_jump_one_way(), "completed")
+		OPTION_TEST_CASE_JUMP_ONE_WAY_KINEMATIC:
+			_body_type = E_BodyType.KINEMATIC_BODY
+			_test_jump_one_way_corner = false
+			yield(_start_jump_one_way(), "completed")
+		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID:
+			_body_type = E_BodyType.RIGID_BODY
+			_test_jump_one_way_corner = true
+			yield(_start_jump_one_way(), "completed")
+		OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_KINEMATIC:
+			_body_type = E_BodyType.KINEMATIC_BODY
+			_test_jump_one_way_corner = true
+			yield(_start_jump_one_way(), "completed")
+		OPTION_TEST_CASE_FALL_ONE_WAY_KINEMATIC:
+			_body_type = E_BodyType.KINEMATIC_BODY
+			yield(_start_fall_one_way(), "completed")
+		_:
+			Log.print_error("Invalid test case.")
+
 
-	._on_option_changed(option, checked)
+func _test_all():
+	Log.print_log("* TESTING ALL...")
+
+	# RigidBody tests.
+	yield(_start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_RIGID), "completed")
+	yield(_start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_RIGID), "completed")
+
+	# KinematicBody tests.
+	yield(_start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_KINEMATIC), "completed")
+	yield(_start_test_case(OPTION_TEST_CASE_JUMP_ONE_WAY_CORNER_KINEMATIC), "completed")
+	yield(_start_test_case(OPTION_TEST_CASE_FALL_ONE_WAY_KINEMATIC), "completed")
+
+	Log.print_log("* Done.")
+
+
+func _set_result(test_passed):
+	var result = ""
+	if test_passed:
+		result = "PASSED"
+	else:
+		result = "FAILED"
+
+	if not test_passed and not _failed_reason.empty():
+		result += _failed_reason
+	else:
+		result += "."
+
+	Log.print_log("Test %s" % result)
 
 
 func _start_test():
+	if _extra_body:
+		_body_parent.remove_child(_extra_body)
+		_extra_body.queue_free()
+		_extra_body = null
+
 	._start_test()
 
 	if _test_jump_one_way:
+		_test_jump_one_way = false
 		_moving_body._initial_velocity = Vector2(600, -1000)
+
+		if _test_jump_one_way_corner:
+			_moving_body.position.x = 147.0
+
+		$JumpTargetArea2D.visible = true
+		$JumpTargetArea2D/CollisionShape2D.disabled = false
+
+	if _test_fall_one_way:
+		_test_fall_one_way = false
+
+		_moving_body.position.y = 350.0
+		_moving_body._gravity_force = 100.0
+		_moving_body._motion_speed = 0.0
+		_moving_body._jump_force = 0.0
+
+		_extra_body = _moving_body.duplicate()
+		_extra_body._gravity_force = 100.0
+		_extra_body._motion_speed = 0.0
+		_extra_body._jump_force = 0.0
+		_extra_body.position -= Vector2(0.0, 200.0)
+		_body_parent.add_child(_extra_body)
+
+		$FallTargetArea2D.visible = true
+		$FallTargetArea2D/CollisionShape2D.disabled = false
+
+
+func _start_jump_one_way():
+	_test_jump_one_way = true
+	_start_test()
+
+	yield(start_timer(1.5), "timeout")
+	if is_timer_canceled():
+		return
+
+	_finalize_jump_one_way()
+
+
+func _start_fall_one_way():
+	_test_fall_one_way = true
+	_start_test()
+
+	yield(start_timer(1.0), "timeout")
+	if is_timer_canceled():
+		return
+
+	_finalize_fall_one_way()
+
+
+func _finalize_jump_one_way():
+	var passed = true
+	if not $JumpTargetArea2D.overlaps_body(_moving_body):
+		passed = false
+		_failed_reason = ": the body wasn't able to jump all the way through."
+
+	_set_result(passed)
+
+	$JumpTargetArea2D.visible = false
+	$JumpTargetArea2D/CollisionShape2D.disabled = true
+
+
+func _finalize_fall_one_way():
+	var passed = true
+	if $FallTargetArea2D.overlaps_body(_moving_body):
+		passed = false
+		_failed_reason = ": the body was pushed through the one-way collision."
+
+	_set_result(passed)
+
+	$FallTargetArea2D.visible = false
+	$FallTargetArea2D/CollisionShape2D.disabled = true

+ 21 - 2
2d/physics_tests/tests/functional/test_character_tilemap.tscn

@@ -1,4 +1,4 @@
-[gd_scene load_steps=11 format=2]
+[gd_scene load_steps=12 format=2]
 
 [ext_resource path="res://tests/functional/test_character_tilemap.gd" type="Script" id=1]
 [ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=3]
@@ -11,7 +11,7 @@
 friction = 0.0
 
 [sub_resource type="RectangleShape2D" id=2]
-extents = Vector2( 16, 32 )
+extents = Vector2( 16, 31.9 )
 
 [sub_resource type="RectangleShape2D" id=3]
 extents = Vector2( 16, 24 )
@@ -19,6 +19,9 @@ extents = Vector2( 16, 24 )
 [sub_resource type="RayShape2D" id=4]
 length = 24.0
 
+[sub_resource type="CircleShape2D" id=5]
+radius = 16.0
+
 [node name="Test" type="Node2D"]
 script = ExtResource( 1 )
 
@@ -96,6 +99,22 @@ shape = SubResource( 4 )
 position = Vector2( 16, 8 )
 shape = SubResource( 4 )
 
+[node name="JumpTargetArea2D" type="Area2D" parent="."]
+visible = false
+position = Vector2( 810, 390 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="JumpTargetArea2D"]
+shape = SubResource( 5 )
+disabled = true
+
+[node name="FallTargetArea2D" type="Area2D" parent="."]
+visible = false
+position = Vector2( 250, 480 )
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="FallTargetArea2D"]
+shape = SubResource( 5 )
+disabled = true
+
 [node name="StaticSceneFlat" parent="." instance=ExtResource( 4 )]
 position = Vector2( 0, 12 )
 

+ 17 - 15
2d/physics_tests/tests/functional/test_collision_pairs.gd

@@ -18,6 +18,8 @@ const OFFSET_RANGE = 120.0
 
 export(Vector2) var offset = Vector2.ZERO
 
+onready var options = $Options
+
 var _update_collision = false
 var _collision_test_index = 0
 var _current_offset = Vector2.ZERO
@@ -27,21 +29,21 @@ var _collision_shapes = []
 func _ready():
 	_initialize_collision_shapes()
 
-	$Options.add_menu_item(OPTION_TYPE_RECTANGLE)
-	$Options.add_menu_item(OPTION_TYPE_SPHERE)
-	$Options.add_menu_item(OPTION_TYPE_CAPSULE)
-	$Options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON)
-	$Options.add_menu_item(OPTION_TYPE_CONCAVE_SEGMENTS)
-
-	$Options.add_menu_item(OPTION_SHAPE_RECTANGLE, true, true)
-	$Options.add_menu_item(OPTION_SHAPE_SPHERE, true, true)
-	$Options.add_menu_item(OPTION_SHAPE_CAPSULE, true, true)
-	$Options.add_menu_item(OPTION_SHAPE_CONVEX_POLYGON, true, true)
-	$Options.add_menu_item(OPTION_SHAPE_CONCAVE_POLYGON, true, true)
-	$Options.add_menu_item(OPTION_SHAPE_CONCAVE_SEGMENTS, true, true)
-
-	$Options.connect("option_selected", self, "_on_option_selected")
-	$Options.connect("option_changed", self, "_on_option_changed")
+	options.add_menu_item(OPTION_TYPE_RECTANGLE)
+	options.add_menu_item(OPTION_TYPE_SPHERE)
+	options.add_menu_item(OPTION_TYPE_CAPSULE)
+	options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON)
+	options.add_menu_item(OPTION_TYPE_CONCAVE_SEGMENTS)
+
+	options.add_menu_item(OPTION_SHAPE_RECTANGLE, true, true)
+	options.add_menu_item(OPTION_SHAPE_SPHERE, true, true)
+	options.add_menu_item(OPTION_SHAPE_CAPSULE, true, true)
+	options.add_menu_item(OPTION_SHAPE_CONVEX_POLYGON, true, true)
+	options.add_menu_item(OPTION_SHAPE_CONCAVE_POLYGON, true, true)
+	options.add_menu_item(OPTION_SHAPE_CONCAVE_SEGMENTS, true, true)
+
+	options.connect("option_selected", self, "_on_option_selected")
+	options.connect("option_changed", self, "_on_option_changed")
 
 	yield(start_timer(0.5), "timeout")
 	if is_timer_canceled():

+ 13 - 10
2d/physics_tests/tests/functional/test_joints.gd

@@ -11,6 +11,8 @@ const OPTION_TEST_CASE_CHANGE_POSITIONS = "Test case/Set body positions after ad
 
 const BOX_SIZE = Vector2(64, 64)
 
+onready var options = $Options
+
 var _update_joint = false
 var _selected_joint = null
 
@@ -25,23 +27,24 @@ var _joint_types = {}
 
 
 func _ready():
-	for joint_index in $Joints.get_child_count():
-		var joint_node = $Joints.get_child(joint_index)
+	var joints = $Joints
+	for joint_index in joints.get_child_count():
+		var joint_node = joints.get_child(joint_index)
 		joint_node.visible = false
 		var joint_name = joint_node.name
 		var joint_short = joint_name.substr(0, joint_name.length() - 7)
 		var option_name = OPTION_JOINT_TYPE % [joint_short, joint_index + 1]
-		$Options.add_menu_item(option_name)
+		options.add_menu_item(option_name)
 		_joint_types[option_name] = joint_node
 
-	$Options.add_menu_item(OPTION_TEST_CASE_BODIES_COLLIDE, true, false)
-	$Options.add_menu_item(OPTION_TEST_CASE_WORLD_ATTACHMENT, true, false)
-	$Options.add_menu_item(OPTION_TEST_CASE_DYNAMIC_ATTACHMENT, true, false)
-	$Options.add_menu_item(OPTION_TEST_CASE_DESTROY_BODY, true, false)
-	$Options.add_menu_item(OPTION_TEST_CASE_CHANGE_POSITIONS, true, false)
+	options.add_menu_item(OPTION_TEST_CASE_BODIES_COLLIDE, true, false)
+	options.add_menu_item(OPTION_TEST_CASE_WORLD_ATTACHMENT, true, false)
+	options.add_menu_item(OPTION_TEST_CASE_DYNAMIC_ATTACHMENT, true, false)
+	options.add_menu_item(OPTION_TEST_CASE_DESTROY_BODY, true, false)
+	options.add_menu_item(OPTION_TEST_CASE_CHANGE_POSITIONS, true, false)
 
-	$Options.connect("option_selected", self, "_on_option_selected")
-	$Options.connect("option_changed", self, "_on_option_changed")
+	options.connect("option_selected", self, "_on_option_selected")
+	options.connect("option_changed", self, "_on_option_changed")
 
 	_selected_joint = _joint_types.values()[0]
 	_update_joint = true

+ 211 - 39
2d/physics_tests/tests/functional/test_one_way_collision.gd

@@ -2,24 +2,40 @@ extends Test
 tool
 
 
+signal all_tests_done()
+signal test_done()
+
 const OPTION_OBJECT_TYPE_RIGIDBODY = "Object type/Rigid body (1)"
 const OPTION_OBJECT_TYPE_KINEMATIC = "Object type/Kinematic body (2)"
 
-const OPTION_TEST_CASE_ALL_ANGLES = "Test case/Around the clock (0)"
+const OPTION_TEST_CASE_ALL = "Test Cases/TEST ALL (0)"
+const OPTION_TEST_CASE_ALL_RIGID = "Test Cases/All Rigid Body tests"
+const OPTION_TEST_CASE_ALL_KINEMATIC = "Test Cases/All Kinematic Body tests"
+const OPTION_TEST_CASE_ALL_ANGLES_RIGID = "Test Cases/Around the clock (Rigid Body)"
+const OPTION_TEST_CASE_ALL_ANGLES_KINEMATIC = "Test Cases/Around the clock (Kinematic Body)"
+const OPTION_TEST_CASE_MOVING_PLATFORM_RIGID = "Test Cases/Moving Platform (Rigid Body)"
+const OPTION_TEST_CASE_MOVING_PLATFORM_KINEMATIC = "Test Cases/Moving Platform (Kinematic Body)"
 
 const TEST_ALL_ANGLES_STEP = 15.0
 const TEST_ALL_ANGLES_MAX = 344.0
 
 export(float, 32, 128, 0.1) var _platform_size = 64.0 setget _set_platform_size
 export(float, 0, 360, 0.1) var _platform_angle = 0.0 setget _set_platform_angle
+export(float) var _platform_speed = 0.0
 export(float, 0, 360, 0.1) var _body_angle = 0.0 setget _set_rigidbody_angle
 export(Vector2) var _body_velocity = Vector2(400.0, 0.0)
 export(bool) var _use_kinematic_body = false
 
+onready var options = $Options
+
 var _rigid_body_template = null
 var _kinematic_body_template = null
 var _moving_body = null
 
+var _platform_template = null
+var _platform_body = null
+var _platform_velocity = Vector2.ZERO
+
 var _contact_detected = false
 var _target_entered = false
 var _test_passed = false
@@ -31,12 +47,18 @@ var _lock_controls = false
 
 func _ready():
 	if not Engine.editor_hint:
-		$Options.add_menu_item(OPTION_OBJECT_TYPE_RIGIDBODY, true, not _use_kinematic_body, true)
-		$Options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC, true, _use_kinematic_body, true)
+		options.add_menu_item(OPTION_OBJECT_TYPE_RIGIDBODY, true, not _use_kinematic_body, true)
+		options.add_menu_item(OPTION_OBJECT_TYPE_KINEMATIC, true, _use_kinematic_body, true)
 
-		$Options.add_menu_item(OPTION_TEST_CASE_ALL_ANGLES)
+		options.add_menu_item(OPTION_TEST_CASE_ALL)
+		options.add_menu_item(OPTION_TEST_CASE_ALL_RIGID)
+		options.add_menu_item(OPTION_TEST_CASE_ALL_KINEMATIC)
+		options.add_menu_item(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
+		options.add_menu_item(OPTION_TEST_CASE_ALL_ANGLES_KINEMATIC)
+		options.add_menu_item(OPTION_TEST_CASE_MOVING_PLATFORM_RIGID)
+		options.add_menu_item(OPTION_TEST_CASE_MOVING_PLATFORM_KINEMATIC)
 
-		$Options.connect("option_selected", self, "_on_option_selected")
+		options.connect("option_selected", self, "_on_option_selected")
 
 		$Controls/PlatformSize/HSlider.value = _platform_size
 		$Controls/PlatformAngle/HSlider.value = _platform_angle
@@ -51,6 +73,9 @@ func _ready():
 		_kinematic_body_template = $KinematicBody2D
 		remove_child(_kinematic_body_template)
 
+		_platform_template = $OneWayKinematicBody2D
+		remove_child(_platform_template)
+
 		_start_test()
 
 
@@ -60,20 +85,25 @@ func _process(_delta):
 			_reset_test(false)
 
 
-func _physics_process(_delta):
+func _physics_process(delta):
 	if not Engine.editor_hint:
-		if _moving_body and _use_kinematic_body:
-			_moving_body.move_and_slide(_body_velocity)
-			if _moving_body.get_slide_count() > 0:
-				var colliding_body = _moving_body.get_slide_collision(0).collider
-				_on_contact_detected(colliding_body)
+		if _moving_body and not _contact_detected:
+			if _use_kinematic_body:
+				var collision = _moving_body.move_and_collide(_body_velocity * delta, false)
+				if collision:
+					var colliding_body = collision.collider
+					_on_contact_detected(colliding_body)
+
+			if _platform_body and _platform_velocity != Vector2.ZERO:
+				var motion = _platform_velocity * delta
+				_platform_body.global_position += motion
 
 
 func _input(event):
 	var key_event = event as InputEventKey
 	if key_event and not key_event.pressed:
 		if key_event.scancode == KEY_0:
-			_on_option_selected(OPTION_TEST_CASE_ALL_ANGLES)
+			_on_option_selected(OPTION_TEST_CASE_ALL)
 		if key_event.scancode == KEY_1:
 			_on_option_selected(OPTION_OBJECT_TYPE_RIGIDBODY)
 		elif key_event.scancode == KEY_2:
@@ -84,41 +114,49 @@ func _exit_tree():
 	if not Engine.editor_hint:
 		_rigid_body_template.free()
 		_kinematic_body_template.free()
+		_platform_template.free()
 
 
-func _set_platform_size(value):
+func _set_platform_size(value, reset = true):
 	if _lock_controls:
 		return
 	if value == _platform_size:
 		return
 	_platform_size = value
 	if is_inside_tree():
-		$OneWayRigidBody2D/CollisionShape2D.shape.extents.x = value
-
-		if not Engine.editor_hint:
-			# Bug: need to re-add when changing shape.
-			var platform = $OneWayRigidBody2D
-			var child_index = platform.get_index()
-			remove_child(platform)
-			add_child(platform)
-			move_child(platform, child_index)
-
-			_reset_test()
-
-
-func _set_platform_angle(value):
+		if Engine.editor_hint:
+			$OneWayKinematicBody2D/CollisionShape2D.shape.extents.x = value
+		else:
+			var platform_collision = _platform_template.get_child(0)
+			platform_collision.shape.extents.x = value
+			if _platform_body:
+				# Bug: need to re-add when changing shape.
+				var child_index = _platform_body.get_index()
+				remove_child(_platform_body)
+				add_child(_platform_body)
+				move_child(_platform_body, child_index)
+			if reset:
+				_reset_test()
+
+
+func _set_platform_angle(value, reset = true):
 	if _lock_controls:
 		return
 	if value == _platform_angle:
 		return
 	_platform_angle = value
 	if is_inside_tree():
-		$OneWayRigidBody2D.rotation = deg2rad(value)
-		if not Engine.editor_hint:
-			_reset_test()
+		if Engine.editor_hint:
+			$OneWayKinematicBody2D.rotation = deg2rad(value)
+		else:
+			if _platform_body:
+				_platform_body.rotation = deg2rad(value)
+			_platform_template.rotation = deg2rad(value)
+			if reset:
+				_reset_test()
 
 
-func _set_rigidbody_angle(value):
+func _set_rigidbody_angle(value, reset = true):
 	if _lock_controls:
 		return
 	if value == _body_angle:
@@ -133,7 +171,8 @@ func _set_rigidbody_angle(value):
 				_moving_body.rotation = deg2rad(value)
 			_rigid_body_template.rotation = deg2rad(value)
 			_kinematic_body_template.rotation = deg2rad(value)
-			_reset_test()
+			if reset:
+				_reset_test()
 
 
 func _on_option_selected(option):
@@ -144,14 +183,130 @@ func _on_option_selected(option):
 		OPTION_OBJECT_TYPE_RIGIDBODY:
 			_use_kinematic_body = false
 			_reset_test()
-		OPTION_TEST_CASE_ALL_ANGLES:
+		OPTION_TEST_CASE_ALL:
+			_test_all()
+		OPTION_TEST_CASE_ALL_RIGID:
+			_test_all_rigid_body()
+		OPTION_TEST_CASE_ALL_KINEMATIC:
+			_test_all_kinematic_body()
+		OPTION_TEST_CASE_ALL_ANGLES_RIGID:
+			_use_kinematic_body = false
+			_test_all_angles = true
+			_reset_test(false)
+		OPTION_TEST_CASE_ALL_ANGLES_KINEMATIC:
+			_use_kinematic_body = true
 			_test_all_angles = true
 			_reset_test(false)
+		OPTION_TEST_CASE_MOVING_PLATFORM_RIGID:
+			_use_kinematic_body = false
+			_test_moving_platform()
+		OPTION_TEST_CASE_MOVING_PLATFORM_KINEMATIC:
+			_use_kinematic_body = true
+			_test_moving_platform()
+
+
+func _start_test_case(option):
+	Log.print_log("* Starting " + option)
+
+	_on_option_selected(option)
+
+	yield(self, "all_tests_done")
+
+
+func _wait_for_test():
+	_reset_test()
+
+	yield(self, "test_done")
+
+
+func _test_all_rigid_body():
+	Log.print_log("* All RigidBody test cases...")
+
+	_set_platform_size(64.0, false)
+	_set_rigidbody_angle(0.0, false)
+	yield(_start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID), "completed")
+
+	_set_platform_size(64.0, false)
+	_set_rigidbody_angle(45.0, false)
+	yield(_start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID), "completed")
+
+	_set_platform_size(32.0, false)
+	_set_rigidbody_angle(45.0, false)
+	yield(_start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID), "completed")
+
+	yield(_start_test_case(OPTION_TEST_CASE_MOVING_PLATFORM_RIGID), "completed")
+
+
+func _test_all_kinematic_body():
+	Log.print_log("* All KinematicBody test cases...")
+
+	_set_platform_size(64.0, false)
+	_set_rigidbody_angle(0.0, false)
+	yield(_start_test_case(OPTION_TEST_CASE_ALL_ANGLES_KINEMATIC), "completed")
+
+	_set_platform_size(64.0, false)
+	_set_rigidbody_angle(45.0, false)
+	yield(_start_test_case(OPTION_TEST_CASE_ALL_ANGLES_KINEMATIC), "completed")
+
+	_set_platform_size(32.0, false)
+	_set_rigidbody_angle(45.0, false)
+	yield(_start_test_case(OPTION_TEST_CASE_ALL_ANGLES_KINEMATIC), "completed")
+
+	yield(_start_test_case(OPTION_TEST_CASE_MOVING_PLATFORM_KINEMATIC), "completed")
+
+
+func _test_moving_platform():
+	Log.print_log("* Start moving platform tests")
+
+	Log.print_log("* Platform moving away from body...")
+	_set_platform_size(64.0, false)
+	_set_rigidbody_angle(0.0, false)
+	_platform_speed = 50.0
+
+	_set_platform_angle(90.0, false)
+	yield(_wait_for_test(), "completed")
+
+	_set_platform_angle(-90.0, false)
+	yield(_wait_for_test(), "completed")
+
+	Log.print_log("* Platform moving towards body...")
+	_set_platform_size(64.0, false)
+	_set_rigidbody_angle(0.0, false)
+	_platform_speed = -50.0
+
+	_set_platform_angle(90.0, false)
+	yield(_wait_for_test(), "completed")
+
+	_set_platform_angle(-90.0, false)
+	yield(_wait_for_test(), "completed")
+
+	_platform_speed = 0.0
+	emit_signal("all_tests_done")
+
+
+func _test_all():
+	Log.print_log("* TESTING ALL...")
+
+	yield(_test_all_rigid_body(), "completed")
+	yield(_test_all_kinematic_body(), "completed")
+
+	Log.print_log("* Done.")
 
 
 func _start_test():
 	var test_label = "Testing: "
 
+	var platform_angle = _platform_template.rotation
+	if _platform_body:
+		platform_angle = _platform_body.rotation
+		remove_child(_platform_body)
+		_platform_body.queue_free()
+		_platform_body = null
+
+	_platform_body = _platform_template.duplicate()
+	_platform_body.rotation = platform_angle
+	add_child(_platform_body)
+
 	if _use_kinematic_body:
 		test_label += _kinematic_body_template.name
 		_moving_body = _kinematic_body_template.duplicate()
@@ -162,6 +317,14 @@ func _start_test():
 		_moving_body.connect("body_entered", self, "_on_contact_detected")
 	add_child(_moving_body)
 
+	if _platform_speed != 0.0:
+		var platform_pos = _platform_body.global_position
+		var body_pos = _moving_body.global_position
+		var dir = (platform_pos - body_pos).normalized()
+		_platform_velocity = dir * _platform_speed
+	else:
+		_platform_velocity = Vector2.ZERO
+
 	if _test_all_angles:
 		test_label += " - All angles"
 
@@ -187,9 +350,10 @@ func _reset_test(cancel_test = true):
 		if cancel_test:
 			Log.print_log("*** Stop around the clock tests")
 			_test_all_angles = false
+			emit_signal("all_tests_done")
 		else:
 			Log.print_log("*** Start around the clock tests")
-		$OneWayRigidBody2D.rotation = deg2rad(_platform_angle)
+		_platform_body.rotation = deg2rad(_platform_angle)
 		_lock_controls = true
 		$Controls/PlatformAngle/HSlider.value = _platform_angle
 		_lock_controls = false
@@ -204,16 +368,17 @@ func _next_test(force_start = false):
 		_moving_body = null
 
 	if _test_all_angles:
-		var angle = rad2deg($OneWayRigidBody2D.rotation)
+		var angle = rad2deg(_platform_body.rotation)
 		if angle >= _platform_angle + TEST_ALL_ANGLES_MAX:
-			$OneWayRigidBody2D.rotation = deg2rad(_platform_angle)
+			_platform_body.rotation = deg2rad(_platform_angle)
 			_lock_controls = true
 			$Controls/PlatformAngle/HSlider.value = _platform_angle
 			_lock_controls = false
+			_test_all_angles = false
 			Log.print_log("*** Done all angles")
 		else:
 			angle = _platform_angle + _test_step * TEST_ALL_ANGLES_STEP
-			$OneWayRigidBody2D.rotation = deg2rad(angle)
+			_platform_body.rotation = deg2rad(angle)
 			_lock_controls = true
 			$Controls/PlatformAngle/HSlider.value = angle
 			_lock_controls = false
@@ -243,7 +408,7 @@ func _on_target_entered(_body):
 
 
 func _should_collide():
-	var platform_rotation = round(rad2deg($OneWayRigidBody2D.rotation))
+	var platform_rotation = round(rad2deg(_platform_body.rotation))
 
 	var angle = fposmod(platform_rotation, 360)
 	return angle > 180
@@ -258,8 +423,15 @@ func _on_timeout():
 
 	yield(get_tree().create_timer(0.5), "timeout")
 
+	var was_all_angles = _test_all_angles
+
 	_next_test()
 
+	emit_signal("test_done")
+
+	if was_all_angles and not _test_all_angles:
+		emit_signal("all_tests_done")
+
 
 func _set_result():
 	var result = ""
@@ -272,7 +444,7 @@ func _set_result():
 
 	$LabelResult.text = result
 
-	var platform_angle = rad2deg($OneWayRigidBody2D.rotation)
+	var platform_angle = rad2deg(_platform_body.rotation)
 
 	result += ": size=%.1f, angle=%.1f, body angle=%.1f" % [_platform_size, platform_angle, _body_angle]
 	Log.print_log("Test %s" % result)

+ 2 - 3
2d/physics_tests/tests/functional/test_one_way_collision.tscn

@@ -205,11 +205,10 @@ position = Vector2( 724, 300 )
 [node name="CollisionShape2D" type="CollisionShape2D" parent="TargetArea2D"]
 shape = SubResource( 1 )
 
-[node name="OneWayRigidBody2D" type="RigidBody2D" parent="."]
+[node name="OneWayKinematicBody2D" type="KinematicBody2D" parent="."]
 position = Vector2( 512, 300 )
-mode = 3
 
-[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWayRigidBody2D"]
+[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWayKinematicBody2D"]
 shape = SubResource( 2 )
 one_way_collision = true
 

+ 15 - 12
2d/physics_tests/tests/performance/test_perf_contacts.gd

@@ -7,11 +7,13 @@ const OPTION_TYPE_SPHERE = "Shape type/Sphere"
 const OPTION_TYPE_CAPSULE = "Shape type/Capsule"
 const OPTION_TYPE_CONVEX_POLYGON = "Shape type/Convex Polygon"
 const OPTION_TYPE_CONCAVE_POLYGON = "Shape type/Concave Polygon"
-export(Array) var spawns = Array()
 
+export(Array) var spawns = Array()
 export(int) var spawn_count = 100
 export(int, 1, 10) var spawn_multiplier = 5
 
+onready var options = $Options
+
 var _object_templates = []
 
 
@@ -20,19 +22,20 @@ func _ready():
 	if is_timer_canceled():
 		return
 
-	while $DynamicShapes.get_child_count():
-		var type_node = $DynamicShapes.get_child(0)
+	var dynamic_shapes = $DynamicShapes
+	while dynamic_shapes.get_child_count():
+		var type_node = dynamic_shapes.get_child(0)
 		type_node.position = Vector2.ZERO
 		_object_templates.push_back(type_node)
-		$DynamicShapes.remove_child(type_node)
-
-	$Options.add_menu_item(OPTION_TYPE_ALL)
-	$Options.add_menu_item(OPTION_TYPE_RECTANGLE)
-	$Options.add_menu_item(OPTION_TYPE_SPHERE)
-	$Options.add_menu_item(OPTION_TYPE_CAPSULE)
-	$Options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON)
-	$Options.add_menu_item(OPTION_TYPE_CONCAVE_POLYGON)
-	$Options.connect("option_selected", self, "_on_option_selected")
+		dynamic_shapes.remove_child(type_node)
+
+	options.add_menu_item(OPTION_TYPE_ALL)
+	options.add_menu_item(OPTION_TYPE_RECTANGLE)
+	options.add_menu_item(OPTION_TYPE_SPHERE)
+	options.add_menu_item(OPTION_TYPE_CAPSULE)
+	options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON)
+	options.add_menu_item(OPTION_TYPE_CONCAVE_POLYGON)
+	options.connect("option_selected", self, "_on_option_selected")
 
 	_start_all_types()
 

+ 14 - 11
2d/physics_tests/utils/kinematicbody_controller.gd

@@ -3,6 +3,9 @@ extends KinematicBody2D
 
 var _initial_velocity = Vector2.ZERO
 var _constant_velocity = Vector2.ZERO
+var _motion_speed = 400.0
+var _gravity_force = 50.0
+var _jump_force = 1000.0
 var _velocity = Vector2.ZERO
 var _snap = Vector2.ZERO
 var _floor_max_angle = 45.0
@@ -24,12 +27,12 @@ func _physics_process(_delta):
 	# Handle horizontal controls.
 	if Input.is_action_pressed("character_left"):
 		if position.x > 0.0:
-			_velocity.x = -400.0
+			_velocity.x = -_motion_speed
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 	elif Input.is_action_pressed("character_right"):
 		if position.x < 1024.0:
-			_velocity.x = 400.0
+			_velocity.x = _motion_speed
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 
@@ -38,15 +41,15 @@ func _physics_process(_delta):
 		if not _jumping and Input.is_action_just_pressed("character_jump"):
 			# Start jumping.
 			_jumping = true
-			_velocity.y = -1000.0
-		elif not _jumping:
-			# Apply velocity when standing for floor detection.
-			_velocity.y = 10.0
-	else:
-		# Apply gravity and get jump ready.
-		_jumping = false
-		_velocity.y += 50.0
+			_velocity.y = -_jump_force
+
+	# Always apply gravity for floor detection.
+	_velocity.y += _gravity_force
 
 	var snap = _snap if not _jumping else Vector2.ZERO
 	var max_angle = deg2rad(_floor_max_angle)
-	move_and_slide_with_snap(_velocity, snap, Vector2.UP, _stop_on_slope, 4, max_angle)
+	_velocity = move_and_slide_with_snap(_velocity, snap, Vector2.UP, _stop_on_slope, 4, max_angle)
+
+	# Get next jump ready.
+	if _jumping:
+		_jumping = false

+ 9 - 6
2d/physics_tests/utils/rigidbody_controller.gd

@@ -3,6 +3,9 @@ extends RigidBody2D
 
 var _initial_velocity = Vector2.ZERO
 var _constant_velocity = Vector2.ZERO
+var _motion_speed = 400.0
+var _gravity_force = 50.0
+var _jump_force = 1000.0
 var _velocity = Vector2.ZERO
 var _on_floor = false
 var _jumping = false
@@ -22,12 +25,12 @@ func _physics_process(_delta):
 	# Handle horizontal controls.
 	if Input.is_action_pressed("character_left"):
 		if position.x > 0.0:
-			_velocity.x = -400.0
+			_velocity.x = -_motion_speed
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 	elif Input.is_action_pressed("character_right"):
 		if position.x < 1024.0:
-			_velocity.x = 400.0
+			_velocity.x = _motion_speed
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 
@@ -36,14 +39,14 @@ func _physics_process(_delta):
 		if not _jumping and Input.is_action_just_pressed("character_jump"):
 			# Start jumping.
 			_jumping = true
-			_velocity.y = -1000.0
+			_velocity.y = -_jump_force
 		elif not _jumping:
-			# Apply velocity when standing for floor detection.
-			_velocity.y = 10.0
+			# Reset gravity.
+			_velocity.y = 0.0
 	else:
 		# Apply gravity and get jump ready.
+		_velocity.y += _gravity_force
 		_jumping = false
-		_velocity.y += 50.0
 
 	linear_velocity = _velocity
 

+ 10 - 0
2d/physics_tests/utils/scroll_log.gd

@@ -4,6 +4,11 @@ extends ScrollContainer
 export(bool) var auto_scroll = false setget set_auto_scroll
 
 
+func _ready():
+	var scrollbar = get_v_scrollbar()
+	scrollbar.connect("scrolling", self, "_on_scrolling")
+
+
 func _process(_delta):
 	if auto_scroll:
 		var scrollbar = get_v_scrollbar()
@@ -12,3 +17,8 @@ func _process(_delta):
 
 func set_auto_scroll(value):
 	auto_scroll = value
+
+
+func _on_scrolling():
+	auto_scroll = false
+	$"../CheckBoxScroll".pressed = false