Browse Source

Updated 2D character controller physics tests

Added new test for 2D character controller:
Character - Pixels
Functional test for pixel art related issues around KinematicBody and
RigidBody character controllers.

Adjusted existing tests and added more test cases to cover most use
cases from recent fixed issues and regressions for KinematicBody.

Added a more automated way to run all tests with checks to see which
ones failed in character controller tests.

Also fixed some minor issues with the log scrollbar.
PouleyKetchoupp 4 years ago
parent
commit
fa83ee0277

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

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

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

@@ -26,6 +26,10 @@ var _tests = [
 		"id": "Functional Tests/Character - Tilemap",
 		"id": "Functional Tests/Character - Tilemap",
 		"path": "res://tests/functional/test_character_tilemap.tscn",
 		"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",
 		"id": "Functional Tests/One Way Collision",
 		"path": "res://tests/functional/test_one_way_collision.tscn",
 		"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 _initial_velocity = Vector2.ZERO
 export(Vector2) var _constant_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 _snap_distance = 0.0
 export(float) var _floor_max_angle = 45.0
 export(float) var _floor_max_angle = 45.0
 export(E_BodyType) var _body_type = 0
 export(E_BodyType) var _body_type = 0
 
 
+onready var options = $Options
+
 var _use_snap = true
 var _use_snap = true
 var _use_stop_on_slope = true
 var _use_stop_on_slope = true
 
 
+var _body_parent = null
 var _rigid_body_template = null
 var _rigid_body_template = null
 var _kinematic_body_template = null
 var _kinematic_body_template = null
 var _kinematic_body_ray_template = null
 var _kinematic_body_ray_template = null
@@ -31,43 +37,47 @@ var _moving_body = null
 
 
 
 
 func _ready():
 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")
 	_rigid_body_template = find_node("RigidBody2D")
 	if _rigid_body_template:
 	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
 		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")
 	_kinematic_body_template = find_node("KinematicBody2D")
 	if _kinematic_body_template:
 	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
 		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")
 	_kinematic_body_ray_template = find_node("KinematicBodyRay2D")
 	if _kinematic_body_ray_template:
 	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
 		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()
 	_start_test()
 
 
 
 
 func _process(_delta):
 func _process(_delta):
+	var label_floor = $LabelFloor
 	if _moving_body:
 	if _moving_body:
 		if _moving_body.is_on_floor():
 		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:
 		else:
-			$LabelFloor.text = "OFF FLOOR"
-			$LabelFloor.self_modulate = Color.red
+			label_floor.text = "OFF FLOOR"
+			label_floor.self_modulate = Color.red
 	else:
 	else:
-		$LabelFloor.visible = false
+		label_floor.visible = false
 
 
 
 
 func _input(event):
 func _input(event):
@@ -118,7 +128,7 @@ func _on_option_changed(option, checked):
 
 
 func _start_test():
 func _start_test():
 	if _moving_body:
 	if _moving_body:
-		remove_child(_moving_body)
+		_body_parent.remove_child(_moving_body)
 		_moving_body.queue_free()
 		_moving_body.queue_free()
 		_moving_body = null
 		_moving_body = null
 
 
@@ -135,11 +145,15 @@ func _start_test():
 
 
 	test_label += template.name
 	test_label += template.name
 	_moving_body = template.duplicate()
 	_moving_body = template.duplicate()
-	add_child(_moving_body)
+	_body_parent.add_child(_moving_body)
 
 
 	_moving_body._initial_velocity = _initial_velocity
 	_moving_body._initial_velocity = _initial_velocity
 	_moving_body._constant_velocity = _constant_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 _moving_body is KinematicBody2D:
 		if _use_snap:
 		if _use_snap:
 			_moving_body._snap = Vector2(0, _snap_distance)
 			_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"]
 [node name="Test" type="Node2D"]
 script = ExtResource( 1 )
 script = ExtResource( 1 )
 _snap_distance = 32.0
 _snap_distance = 32.0
-_floor_max_angle = 60.0
+_floor_max_angle = 45.0
 
 
 [node name="LabelTestType" type="Label" parent="."]
 [node name="LabelTestType" type="Label" parent="."]
 margin_left = 14.0
 margin_left = 14.0

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

@@ -1,26 +1,196 @@
 extends TestCharacter
 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 = false
+var _test_jump_one_way_corner = false
+var _test_fall_one_way = false
+
+var _extra_body = null
+
+var _failed_reason = ""
 
 
 
 
 func _ready():
 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:
 	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():
 func _start_test():
+	if _extra_body:
+		_body_parent.remove_child(_extra_body)
+		_extra_body.queue_free()
+		_extra_body = null
+
 	._start_test()
 	._start_test()
 
 
 	if _test_jump_one_way:
 	if _test_jump_one_way:
+		_test_jump_one_way = false
 		_moving_body._initial_velocity = Vector2(600, -1000)
 		_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/functional/test_character_tilemap.gd" type="Script" id=1]
 [ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=3]
 [ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=3]
@@ -11,7 +11,7 @@
 friction = 0.0
 friction = 0.0
 
 
 [sub_resource type="RectangleShape2D" id=2]
 [sub_resource type="RectangleShape2D" id=2]
-extents = Vector2( 16, 32 )
+extents = Vector2( 16, 31.9 )
 
 
 [sub_resource type="RectangleShape2D" id=3]
 [sub_resource type="RectangleShape2D" id=3]
 extents = Vector2( 16, 24 )
 extents = Vector2( 16, 24 )
@@ -19,6 +19,9 @@ extents = Vector2( 16, 24 )
 [sub_resource type="RayShape2D" id=4]
 [sub_resource type="RayShape2D" id=4]
 length = 24.0
 length = 24.0
 
 
+[sub_resource type="CircleShape2D" id=5]
+radius = 16.0
+
 [node name="Test" type="Node2D"]
 [node name="Test" type="Node2D"]
 script = ExtResource( 1 )
 script = ExtResource( 1 )
 
 
@@ -96,6 +99,22 @@ shape = SubResource( 4 )
 position = Vector2( 16, 8 )
 position = Vector2( 16, 8 )
 shape = SubResource( 4 )
 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 )]
 [node name="StaticSceneFlat" parent="." instance=ExtResource( 4 )]
 position = Vector2( 0, 12 )
 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
 export(Vector2) var offset = Vector2.ZERO
 
 
+onready var options = $Options
+
 var _update_collision = false
 var _update_collision = false
 var _collision_test_index = 0
 var _collision_test_index = 0
 var _current_offset = Vector2.ZERO
 var _current_offset = Vector2.ZERO
@@ -27,21 +29,21 @@ var _collision_shapes = []
 func _ready():
 func _ready():
 	_initialize_collision_shapes()
 	_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")
 	yield(start_timer(0.5), "timeout")
 	if is_timer_canceled():
 	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)
 const BOX_SIZE = Vector2(64, 64)
 
 
+onready var options = $Options
+
 var _update_joint = false
 var _update_joint = false
 var _selected_joint = null
 var _selected_joint = null
 
 
@@ -25,23 +27,24 @@ var _joint_types = {}
 
 
 
 
 func _ready():
 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
 		joint_node.visible = false
 		var joint_name = joint_node.name
 		var joint_name = joint_node.name
 		var joint_short = joint_name.substr(0, joint_name.length() - 7)
 		var joint_short = joint_name.substr(0, joint_name.length() - 7)
 		var option_name = OPTION_JOINT_TYPE % [joint_short, joint_index + 1]
 		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
 		_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]
 	_selected_joint = _joint_types.values()[0]
 	_update_joint = true
 	_update_joint = true

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

@@ -2,24 +2,40 @@ extends Test
 tool
 tool
 
 
 
 
+signal all_tests_done()
+signal test_done()
+
 const OPTION_OBJECT_TYPE_RIGIDBODY = "Object type/Rigid body (1)"
 const OPTION_OBJECT_TYPE_RIGIDBODY = "Object type/Rigid body (1)"
 const OPTION_OBJECT_TYPE_KINEMATIC = "Object type/Kinematic body (2)"
 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_STEP = 15.0
 const TEST_ALL_ANGLES_MAX = 344.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, 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, 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(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(Vector2) var _body_velocity = Vector2(400.0, 0.0)
 export(bool) var _use_kinematic_body = false
 export(bool) var _use_kinematic_body = false
 
 
+onready var options = $Options
+
 var _rigid_body_template = null
 var _rigid_body_template = null
 var _kinematic_body_template = null
 var _kinematic_body_template = null
 var _moving_body = null
 var _moving_body = null
 
 
+var _platform_template = null
+var _platform_body = null
+var _platform_velocity = Vector2.ZERO
+
 var _contact_detected = false
 var _contact_detected = false
 var _target_entered = false
 var _target_entered = false
 var _test_passed = false
 var _test_passed = false
@@ -31,12 +47,18 @@ var _lock_controls = false
 
 
 func _ready():
 func _ready():
 	if not Engine.editor_hint:
 	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/PlatformSize/HSlider.value = _platform_size
 		$Controls/PlatformAngle/HSlider.value = _platform_angle
 		$Controls/PlatformAngle/HSlider.value = _platform_angle
@@ -51,6 +73,9 @@ func _ready():
 		_kinematic_body_template = $KinematicBody2D
 		_kinematic_body_template = $KinematicBody2D
 		remove_child(_kinematic_body_template)
 		remove_child(_kinematic_body_template)
 
 
+		_platform_template = $OneWayKinematicBody2D
+		remove_child(_platform_template)
+
 		_start_test()
 		_start_test()
 
 
 
 
@@ -60,20 +85,25 @@ func _process(_delta):
 			_reset_test(false)
 			_reset_test(false)
 
 
 
 
-func _physics_process(_delta):
+func _physics_process(delta):
 	if not Engine.editor_hint:
 	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):
 func _input(event):
 	var key_event = event as InputEventKey
 	var key_event = event as InputEventKey
 	if key_event and not key_event.pressed:
 	if key_event and not key_event.pressed:
 		if key_event.scancode == KEY_0:
 		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:
 		if key_event.scancode == KEY_1:
 			_on_option_selected(OPTION_OBJECT_TYPE_RIGIDBODY)
 			_on_option_selected(OPTION_OBJECT_TYPE_RIGIDBODY)
 		elif key_event.scancode == KEY_2:
 		elif key_event.scancode == KEY_2:
@@ -84,41 +114,49 @@ func _exit_tree():
 	if not Engine.editor_hint:
 	if not Engine.editor_hint:
 		_rigid_body_template.free()
 		_rigid_body_template.free()
 		_kinematic_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:
 	if _lock_controls:
 		return
 		return
 	if value == _platform_size:
 	if value == _platform_size:
 		return
 		return
 	_platform_size = value
 	_platform_size = value
 	if is_inside_tree():
 	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:
 	if _lock_controls:
 		return
 		return
 	if value == _platform_angle:
 	if value == _platform_angle:
 		return
 		return
 	_platform_angle = value
 	_platform_angle = value
 	if is_inside_tree():
 	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:
 	if _lock_controls:
 		return
 		return
 	if value == _body_angle:
 	if value == _body_angle:
@@ -133,7 +171,8 @@ func _set_rigidbody_angle(value):
 				_moving_body.rotation = deg2rad(value)
 				_moving_body.rotation = deg2rad(value)
 			_rigid_body_template.rotation = deg2rad(value)
 			_rigid_body_template.rotation = deg2rad(value)
 			_kinematic_body_template.rotation = deg2rad(value)
 			_kinematic_body_template.rotation = deg2rad(value)
-			_reset_test()
+			if reset:
+				_reset_test()
 
 
 
 
 func _on_option_selected(option):
 func _on_option_selected(option):
@@ -144,14 +183,130 @@ func _on_option_selected(option):
 		OPTION_OBJECT_TYPE_RIGIDBODY:
 		OPTION_OBJECT_TYPE_RIGIDBODY:
 			_use_kinematic_body = false
 			_use_kinematic_body = false
 			_reset_test()
 			_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
 			_test_all_angles = true
 			_reset_test(false)
 			_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():
 func _start_test():
 	var test_label = "Testing: "
 	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:
 	if _use_kinematic_body:
 		test_label += _kinematic_body_template.name
 		test_label += _kinematic_body_template.name
 		_moving_body = _kinematic_body_template.duplicate()
 		_moving_body = _kinematic_body_template.duplicate()
@@ -162,6 +317,14 @@ func _start_test():
 		_moving_body.connect("body_entered", self, "_on_contact_detected")
 		_moving_body.connect("body_entered", self, "_on_contact_detected")
 	add_child(_moving_body)
 	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:
 	if _test_all_angles:
 		test_label += " - All angles"
 		test_label += " - All angles"
 
 
@@ -187,9 +350,10 @@ func _reset_test(cancel_test = true):
 		if cancel_test:
 		if cancel_test:
 			Log.print_log("*** Stop around the clock tests")
 			Log.print_log("*** Stop around the clock tests")
 			_test_all_angles = false
 			_test_all_angles = false
+			emit_signal("all_tests_done")
 		else:
 		else:
 			Log.print_log("*** Start around the clock tests")
 			Log.print_log("*** Start around the clock tests")
-		$OneWayRigidBody2D.rotation = deg2rad(_platform_angle)
+		_platform_body.rotation = deg2rad(_platform_angle)
 		_lock_controls = true
 		_lock_controls = true
 		$Controls/PlatformAngle/HSlider.value = _platform_angle
 		$Controls/PlatformAngle/HSlider.value = _platform_angle
 		_lock_controls = false
 		_lock_controls = false
@@ -204,16 +368,17 @@ func _next_test(force_start = false):
 		_moving_body = null
 		_moving_body = null
 
 
 	if _test_all_angles:
 	if _test_all_angles:
-		var angle = rad2deg($OneWayRigidBody2D.rotation)
+		var angle = rad2deg(_platform_body.rotation)
 		if angle >= _platform_angle + TEST_ALL_ANGLES_MAX:
 		if angle >= _platform_angle + TEST_ALL_ANGLES_MAX:
-			$OneWayRigidBody2D.rotation = deg2rad(_platform_angle)
+			_platform_body.rotation = deg2rad(_platform_angle)
 			_lock_controls = true
 			_lock_controls = true
 			$Controls/PlatformAngle/HSlider.value = _platform_angle
 			$Controls/PlatformAngle/HSlider.value = _platform_angle
 			_lock_controls = false
 			_lock_controls = false
+			_test_all_angles = false
 			Log.print_log("*** Done all angles")
 			Log.print_log("*** Done all angles")
 		else:
 		else:
 			angle = _platform_angle + _test_step * TEST_ALL_ANGLES_STEP
 			angle = _platform_angle + _test_step * TEST_ALL_ANGLES_STEP
-			$OneWayRigidBody2D.rotation = deg2rad(angle)
+			_platform_body.rotation = deg2rad(angle)
 			_lock_controls = true
 			_lock_controls = true
 			$Controls/PlatformAngle/HSlider.value = angle
 			$Controls/PlatformAngle/HSlider.value = angle
 			_lock_controls = false
 			_lock_controls = false
@@ -243,7 +408,7 @@ func _on_target_entered(_body):
 
 
 
 
 func _should_collide():
 func _should_collide():
-	var platform_rotation = round(rad2deg($OneWayRigidBody2D.rotation))
+	var platform_rotation = round(rad2deg(_platform_body.rotation))
 
 
 	var angle = fposmod(platform_rotation, 360)
 	var angle = fposmod(platform_rotation, 360)
 	return angle > 180
 	return angle > 180
@@ -258,8 +423,15 @@ func _on_timeout():
 
 
 	yield(get_tree().create_timer(0.5), "timeout")
 	yield(get_tree().create_timer(0.5), "timeout")
 
 
+	var was_all_angles = _test_all_angles
+
 	_next_test()
 	_next_test()
 
 
+	emit_signal("test_done")
+
+	if was_all_angles and not _test_all_angles:
+		emit_signal("all_tests_done")
+
 
 
 func _set_result():
 func _set_result():
 	var result = ""
 	var result = ""
@@ -272,7 +444,7 @@ func _set_result():
 
 
 	$LabelResult.text = 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]
 	result += ": size=%.1f, angle=%.1f, body angle=%.1f" % [_platform_size, platform_angle, _body_angle]
 	Log.print_log("Test %s" % result)
 	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"]
 [node name="CollisionShape2D" type="CollisionShape2D" parent="TargetArea2D"]
 shape = SubResource( 1 )
 shape = SubResource( 1 )
 
 
-[node name="OneWayRigidBody2D" type="RigidBody2D" parent="."]
+[node name="OneWayKinematicBody2D" type="KinematicBody2D" parent="."]
 position = Vector2( 512, 300 )
 position = Vector2( 512, 300 )
-mode = 3
 
 
-[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWayRigidBody2D"]
+[node name="CollisionShape2D" type="CollisionShape2D" parent="OneWayKinematicBody2D"]
 shape = SubResource( 2 )
 shape = SubResource( 2 )
 one_way_collision = true
 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_CAPSULE = "Shape type/Capsule"
 const OPTION_TYPE_CONVEX_POLYGON = "Shape type/Convex Polygon"
 const OPTION_TYPE_CONVEX_POLYGON = "Shape type/Convex Polygon"
 const OPTION_TYPE_CONCAVE_POLYGON = "Shape type/Concave 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) var spawn_count = 100
 export(int, 1, 10) var spawn_multiplier = 5
 export(int, 1, 10) var spawn_multiplier = 5
 
 
+onready var options = $Options
+
 var _object_templates = []
 var _object_templates = []
 
 
 
 
@@ -20,19 +22,20 @@ func _ready():
 	if is_timer_canceled():
 	if is_timer_canceled():
 		return
 		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
 		type_node.position = Vector2.ZERO
 		_object_templates.push_back(type_node)
 		_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()
 	_start_all_types()
 
 

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

@@ -3,6 +3,9 @@ extends KinematicBody2D
 
 
 var _initial_velocity = Vector2.ZERO
 var _initial_velocity = Vector2.ZERO
 var _constant_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 _velocity = Vector2.ZERO
 var _snap = Vector2.ZERO
 var _snap = Vector2.ZERO
 var _floor_max_angle = 45.0
 var _floor_max_angle = 45.0
@@ -24,12 +27,12 @@ func _physics_process(_delta):
 	# Handle horizontal controls.
 	# Handle horizontal controls.
 	if Input.is_action_pressed("character_left"):
 	if Input.is_action_pressed("character_left"):
 		if position.x > 0.0:
 		if position.x > 0.0:
-			_velocity.x = -400.0
+			_velocity.x = -_motion_speed
 			_keep_velocity = false
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 			_constant_velocity = Vector2.ZERO
 	elif Input.is_action_pressed("character_right"):
 	elif Input.is_action_pressed("character_right"):
 		if position.x < 1024.0:
 		if position.x < 1024.0:
-			_velocity.x = 400.0
+			_velocity.x = _motion_speed
 			_keep_velocity = false
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 			_constant_velocity = Vector2.ZERO
 
 
@@ -38,15 +41,15 @@ func _physics_process(_delta):
 		if not _jumping and Input.is_action_just_pressed("character_jump"):
 		if not _jumping and Input.is_action_just_pressed("character_jump"):
 			# Start jumping.
 			# Start jumping.
 			_jumping = true
 			_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 snap = _snap if not _jumping else Vector2.ZERO
 	var max_angle = deg2rad(_floor_max_angle)
 	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 _initial_velocity = Vector2.ZERO
 var _constant_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 _velocity = Vector2.ZERO
 var _on_floor = false
 var _on_floor = false
 var _jumping = false
 var _jumping = false
@@ -22,12 +25,12 @@ func _physics_process(_delta):
 	# Handle horizontal controls.
 	# Handle horizontal controls.
 	if Input.is_action_pressed("character_left"):
 	if Input.is_action_pressed("character_left"):
 		if position.x > 0.0:
 		if position.x > 0.0:
-			_velocity.x = -400.0
+			_velocity.x = -_motion_speed
 			_keep_velocity = false
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 			_constant_velocity = Vector2.ZERO
 	elif Input.is_action_pressed("character_right"):
 	elif Input.is_action_pressed("character_right"):
 		if position.x < 1024.0:
 		if position.x < 1024.0:
-			_velocity.x = 400.0
+			_velocity.x = _motion_speed
 			_keep_velocity = false
 			_keep_velocity = false
 			_constant_velocity = Vector2.ZERO
 			_constant_velocity = Vector2.ZERO
 
 
@@ -36,14 +39,14 @@ func _physics_process(_delta):
 		if not _jumping and Input.is_action_just_pressed("character_jump"):
 		if not _jumping and Input.is_action_just_pressed("character_jump"):
 			# Start jumping.
 			# Start jumping.
 			_jumping = true
 			_jumping = true
-			_velocity.y = -1000.0
+			_velocity.y = -_jump_force
 		elif not _jumping:
 		elif not _jumping:
-			# Apply velocity when standing for floor detection.
-			_velocity.y = 10.0
+			# Reset gravity.
+			_velocity.y = 0.0
 	else:
 	else:
 		# Apply gravity and get jump ready.
 		# Apply gravity and get jump ready.
+		_velocity.y += _gravity_force
 		_jumping = false
 		_jumping = false
-		_velocity.y += 50.0
 
 
 	linear_velocity = _velocity
 	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
 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):
 func _process(_delta):
 	if auto_scroll:
 	if auto_scroll:
 		var scrollbar = get_v_scrollbar()
 		var scrollbar = get_v_scrollbar()
@@ -12,3 +17,8 @@ func _process(_delta):
 
 
 func set_auto_scroll(value):
 func set_auto_scroll(value):
 	auto_scroll = value
 	auto_scroll = value
+
+
+func _on_scrolling():
+	auto_scroll = false
+	$"../CheckBoxScroll".pressed = false