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.
This commit is contained in:
PouleyKetchoupp
2021-02-19 19:16:54 -07:00
parent 0b3e046953
commit fa83ee0277
16 changed files with 819 additions and 118 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 _on_option_changed(option, checked):
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_JUMP_ONE_WAY:
_test_jump_one_way = checked
_start_test()
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_changed(option, checked)
._on_option_selected(option)
func _start_test_case(option):
Log.print_log("* Starting " + option)
match option:
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.")
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

View File

@@ -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 )

View File

@@ -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_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.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.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():

View File

@@ -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

View File

@@ -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()
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):
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)

View File

@@ -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

View File

@@ -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)
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")
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()

View File

@@ -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

View File

@@ -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

View File

@@ -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