Add demo: Physics Tests 2D

Similar to its 3D counterpart, with tests using 2D physics.
This commit is contained in:
PouleyKetchoupp
2020-12-09 20:01:30 -07:00
parent 3147c6c5bd
commit 8241be5817
37 changed files with 1465 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# 2D Physics Tests
This demo contains a series of tests for the 2D
physics engine.
They can be used for different purpose:
- Functional tests to check for regressions and
behavior of the 2D physics engine
- Performance tests to evaluate performance
of the 2D physics engine
Language: GDScript
Renderer: GLES 2
## Screenshots
![Screenshot](screenshots/screenshot.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/godot-head.png-cc6844293d74c4f45ec6c4ab08de67ee.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/godot-head.png"
dest_files=[ "res://.import/godot-head.png-cc6844293d74c4f45ec6c4ab08de67ee.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

BIN
2d/physics_tests/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

174
2d/physics_tests/main.tscn Normal file
View File

@@ -0,0 +1,174 @@
[gd_scene load_steps=10 format=2]
[ext_resource path="res://utils/label_fps.gd" type="Script" id=1]
[ext_resource path="res://utils/label_version.gd" type="Script" id=2]
[ext_resource path="res://utils/label_engine.gd" type="Script" id=3]
[ext_resource path="res://tests_menu.gd" type="Script" id=4]
[ext_resource path="res://utils/label_test.gd" type="Script" id=5]
[ext_resource path="res://utils/container_log.gd" type="Script" id=10]
[ext_resource path="res://utils/scroll_log.gd" type="Script" id=11]
[ext_resource path="res://tests.gd" type="Script" id=12]
[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0, 0, 0, 0.176471 )
[node name="Main" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
script = ExtResource( 12 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TestsMenu" type="MenuButton" parent="."]
margin_left = 10.0
margin_top = 10.0
margin_right = 125.0
margin_bottom = 30.0
text = "TESTS"
flat = false
script = ExtResource( 4 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LabelControls" type="Label" parent="."]
margin_left = 157.0
margin_top = 13.0
margin_right = 646.0
margin_bottom = 27.0
text = "R - RESTART / D - TOGGLE COLLISION / F - TOGGLE FULL SCREEN / ESC - QUIT"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LabelFPS" type="Label" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = -19.0
margin_right = 50.0
margin_bottom = -5.0
text = "FPS: 0"
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LabelEngine" type="Label" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = -39.0
margin_right = 50.0
margin_bottom = -25.0
text = "Physics engine:"
script = ExtResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LabelVersion" type="Label" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = -59.0
margin_right = 50.0
margin_bottom = -45.0
text = "Godot Version:"
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LabelTest" type="Label" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = -79.0
margin_right = 50.0
margin_bottom = -65.0
text = "Test:"
script = ExtResource( 5 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PanelLog" type="Panel" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -428.0
margin_top = -125.0
custom_styles/panel = SubResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ButtonClear" type="Button" parent="PanelLog"]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -48.0
margin_top = -25.0
margin_right = -5.0
margin_bottom = -5.0
focus_mode = 0
enabled_focus_mode = 0
text = "clear"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CheckBoxScroll" type="CheckBox" parent="PanelLog"]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -150.0
margin_top = -27.0
margin_right = -54.0
margin_bottom = -3.0
focus_mode = 0
pressed = true
enabled_focus_mode = 0
text = "auto-scroll"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ScrollLog" type="ScrollContainer" parent="PanelLog"]
margin_left = 10.0
margin_top = 5.0
margin_right = 418.0
margin_bottom = 94.0
scroll_horizontal_enabled = false
script = ExtResource( 11 )
__meta__ = {
"_edit_use_anchors_": false
}
auto_scroll = true
[node name="VBoxLog" type="VBoxContainer" parent="PanelLog/ScrollLog"]
margin_right = 408.0
margin_bottom = 89.0
size_flags_horizontal = 3
size_flags_vertical = 3
alignment = 2
script = ExtResource( 10 )
[node name="LabelLog" type="Label" parent="PanelLog/ScrollLog/VBoxLog"]
margin_top = 75.0
margin_right = 408.0
margin_bottom = 89.0
text = "Log start"
valign = 2
max_lines_visible = 5
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="pressed" from="PanelLog/ButtonClear" to="PanelLog/ScrollLog/VBoxLog" method="clear"]
[connection signal="toggled" from="PanelLog/CheckBoxScroll" to="PanelLog/ScrollLog" method="set_auto_scroll"]

View File

@@ -0,0 +1,75 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
_global_script_classes=[ {
"base": "MenuButton",
"class": "OptionMenu",
"language": "GDScript",
"path": "res://utils/option_menu.gd"
}, {
"base": "Node2D",
"class": "Test",
"language": "GDScript",
"path": "res://test.gd"
} ]
_global_script_class_icons={
"OptionMenu": "",
"Test": ""
}
[application]
config/name="2D Physics Tests"
run/main_scene="res://main.tscn"
config/icon="res://icon.png"
[autoload]
Log="*res://utils/system_log.gd"
System="*res://utils/system.gd"
[debug]
gdscript/warnings/return_value_discarded=false
[display]
window/dpi/allow_hidpi=true
window/stretch/mode="2d"
window/stretch/aspect="expand"
[input]
toggle_full_screen={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":70,"unicode":0,"echo":false,"script":null)
]
}
exit={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null)
]
}
toggle_debug_collision={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
]
}
restart_test={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":82,"unicode":0,"echo":false,"script":null)
]
}
[rendering]
quality/driver/driver_name="GLES2"
environment/default_clear_color=Color( 0.184314, 0.184314, 0.184314, 1 )
quality/filters/msaa=2

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

92
2d/physics_tests/test.gd Normal file
View File

@@ -0,0 +1,92 @@
class_name Test
extends Node2D
signal wait_done()
var _timer
var _timer_started = false
var _wait_physics_ticks_counter = 0
class Line:
var pos_start
var pos_end
var color
var _lines = []
func _physics_process(_delta):
if (_wait_physics_ticks_counter > 0):
_wait_physics_ticks_counter -= 1
if (_wait_physics_ticks_counter == 0):
emit_signal("wait_done")
func _draw():
for line in _lines:
draw_line(line.pos_start, line.pos_end, line.color, 1.5)
func add_line(pos_start, pos_end, color):
var line = Line.new()
line.pos_start = pos_start
line.pos_end = pos_end
line.color = color
_lines.push_back(line)
update()
func clear_lines():
_lines.clear()
update()
func create_rigidbody_box(size):
var template_shape = RectangleShape2D.new()
template_shape.extents = 0.5 * size
var template_collision = CollisionShape2D.new()
template_collision.shape = template_shape
var template_body = RigidBody2D.new()
template_body.add_child(template_collision)
return template_body
func start_timer(timeout):
if _timer == null:
_timer = Timer.new()
_timer.one_shot = true
add_child(_timer)
_timer.connect("timeout", self, "_on_timer_done")
else:
cancel_timer()
_timer.start(timeout)
_timer_started = true
return _timer
func cancel_timer():
if _timer_started:
_timer.paused = true
_timer.emit_signal("timeout")
_timer.paused = false
func is_timer_canceled():
return _timer.paused
func wait_for_physics_ticks(tick_count):
_wait_physics_ticks_counter = tick_count
return self
func _on_timer_done():
_timer_started = false

35
2d/physics_tests/tests.gd Normal file
View File

@@ -0,0 +1,35 @@
extends Node
var _tests = [
{
"id": "Functional Tests/Shapes",
"path": "res://tests/functional/test_shapes.tscn",
},
{
"id": "Functional Tests/Box Stack",
"path": "res://tests/functional/test_stack.tscn",
},
{
"id": "Functional Tests/Box Pyramid",
"path": "res://tests/functional/test_pyramid.tscn",
},
{
"id": "Functional Tests/Raycasting",
"path": "res://tests/functional/test_raycasting.tscn",
},
{
"id": "Performance Tests/Broadphase",
"path": "res://tests/performance/test_perf_broadphase.tscn",
},
{
"id": "Performance Tests/Contacts",
"path": "res://tests/performance/test_perf_contacts.tscn",
},
]
func _ready():
var test_menu = $TestsMenu
for test in _tests:
test_menu.add_test(test.id, test.path)

View File

@@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=2]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 20, 20 )
[node name="StackBox" type="RigidBody2D"]
position = Vector2( -180, -20 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )

View File

@@ -0,0 +1,41 @@
extends Test
export(int, 1, 100) var height = 10
export(Vector2) var box_size = Vector2(40.0, 40.0)
export(Vector2) var box_spacing = Vector2(0.0, 0.0)
func _ready():
_create_pyramid()
func _create_pyramid():
var root_node = $Pyramid
var template_body = create_rigidbody_box(box_size)
var pos_y = -0.5 * box_size.y - box_spacing.y
for level in height:
var level_index = height - level - 1
var num_boxes = 2 * level_index + 1
var row_node = Node2D.new()
row_node.position = Vector2(0.0, pos_y)
row_node.name = "Row%02d" % (level + 1)
root_node.add_child(row_node)
var pos_x = -0.5 * (num_boxes - 1) * (box_size.x + box_spacing.x)
for box_index in num_boxes:
var box = template_body.duplicate()
box.position = Vector2(pos_x, 0.0)
box.name = "Box%02d" % (box_index + 1)
row_node.add_child(box)
pos_x += box_size.x + box_spacing.x
pos_y -= box_size.y + box_spacing.y
template_body.queue_free()

View File

@@ -0,0 +1,12 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://tests/functional/test_pyramid.gd" type="Script" id=1]
[ext_resource path="res://tests/static_scene_flat.tscn" type="PackedScene" id=2]
[node name="Test" type="Node2D"]
script = ExtResource( 1 )
[node name="Pyramid" type="Node2D" parent="."]
position = Vector2( 512, 500 )
[node name="StaticSceneFlat" parent="." instance=ExtResource( 2 )]

View File

@@ -0,0 +1,69 @@
extends Test
var _do_raycasts = false
func _ready():
yield(start_timer(0.5), "timeout")
if is_timer_canceled():
return
_do_raycasts = true
func _physics_process(_delta):
if !_do_raycasts:
return
_do_raycasts = false
Log.print_log("* Start Raycasting...")
clear_lines()
for shape in $Shapes.get_children():
var body = shape as PhysicsBody2D
var space_state = body.get_world_2d().direct_space_state
Log.print_log("* Testing: %s" % body.name)
var center = body.global_transform.origin
# Raycast entering from the top.
var res = _add_raycast(space_state, center - Vector2(0, 100), center)
Log.print_log("Raycast in: %s" % ("HIT" if res else "NO HIT"))
# Raycast exiting from inside.
center.x -= 20
res = _add_raycast(space_state, center, center + Vector2(0, 200))
Log.print_log("Raycast out: %s" % ("HIT" if res else "NO HIT"))
# Raycast all inside.
center.x += 40
res = _add_raycast(space_state, center, center + Vector2(0, 40))
Log.print_log("Raycast inside: %s" % ("HIT" if res else "NO HIT"))
if body.name.ends_with("ConcavePolygon"):
# Raycast inside an internal face.
center.x += 20
res = _add_raycast(space_state, center, center + Vector2(0, 40))
Log.print_log("Raycast inside face: %s" % ("HIT" if res else "NO HIT"))
func _add_raycast(space_state, pos_start, pos_end):
var result = space_state.intersect_ray(pos_start, pos_end)
var color
if result:
color = Color.green
else:
color = Color.red.darkened(0.5)
# Draw raycast line.
add_line(pos_start, pos_end, color)
# Draw raycast arrow.
add_line(pos_end, pos_end + Vector2(-5, -10), color)
add_line(pos_end, pos_end + Vector2(5, -10), color)
return result

View File

@@ -0,0 +1,68 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://assets/godot-head.png" type="Texture" id=1]
[ext_resource path="res://tests/functional/test_raycasting.gd" type="Script" id=2]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 40, 60 )
[sub_resource type="CapsuleShape2D" id=2]
radius = 30.0
height = 50.0
[sub_resource type="CircleShape2D" id=3]
radius = 60.0
[node name="Test" type="Node2D"]
script = ExtResource( 2 )
[node name="Shapes" type="Node2D" parent="."]
z_index = -1
z_as_relative = false
[node name="RigidBodyRectangle" type="RigidBody2D" parent="Shapes"]
position = Vector2( 114.877, 248.76 )
mode = 3
[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodyRectangle"]
rotation = -1.19206
scale = Vector2( 1.5, 1.5 )
shape = SubResource( 1 )
[node name="RigidBodyCapsule" type="RigidBody2D" parent="Shapes"]
position = Vector2( 313.583, 261.204 )
mode = 3
[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodyCapsule"]
rotation = -0.202458
scale = Vector2( 1.5, 1.5 )
shape = SubResource( 2 )
[node name="RigidBodyConcavePolygon" type="RigidBody2D" parent="Shapes"]
position = Vector2( 514.899, 252.771 )
mode = 3
[node name="GodotIcon" type="Sprite" parent="Shapes/RigidBodyConcavePolygon"]
modulate = Color( 1, 1, 1, 0.392157 )
texture = ExtResource( 1 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConcavePolygon"]
polygon = PoolVector2Array( -5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332 )
[node name="RigidBodyConvexPolygon" type="RigidBody2D" parent="Shapes"]
position = Vector2( 738.975, 252.771 )
mode = 3
[node name="GodotIcon" type="Sprite" parent="Shapes/RigidBodyConvexPolygon"]
modulate = Color( 1, 1, 1, 0.392157 )
texture = ExtResource( 1 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Shapes/RigidBodyConvexPolygon"]
polygon = PoolVector2Array( 10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332 )
[node name="RigidBodySphere" type="RigidBody2D" parent="Shapes"]
position = Vector2( 917.136, 270.868 )
mode = 3
[node name="CollisionShape2D" type="CollisionShape2D" parent="Shapes/RigidBodySphere"]
shape = SubResource( 3 )

View File

@@ -0,0 +1,64 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://assets/godot-head.png" type="Texture" id=1]
[ext_resource path="res://test.gd" type="Script" id=2]
[ext_resource path="res://tests/static_scene.tscn" type="PackedScene" id=6]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 20, 30 )
[sub_resource type="CapsuleShape2D" id=2]
radius = 20.0
height = 30.0
[sub_resource type="CircleShape2D" id=3]
radius = 30.0
[node name="Test" type="Node2D"]
script = ExtResource( 2 )
[node name="DynamicShapes" type="Node2D" parent="."]
[node name="RigidBodyRectangle" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 96, 127 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyRectangle"]
rotation = 0.675442
shape = SubResource( 1 )
[node name="RigidBodyCapsule" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 270.165, 139.444 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyCapsule"]
rotation = -0.202458
shape = SubResource( 2 )
[node name="RigidBodyConcavePolygon" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 683.614, 132.749 )
[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConcavePolygon"]
scale = Vector2( 0.5, 0.5 )
texture = ExtResource( 1 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConcavePolygon"]
scale = Vector2( 0.5, 0.5 )
polygon = PoolVector2Array( -5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332 )
[node name="RigidBodyConvexPolygon" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 473.536, 134.336 )
[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConvexPolygon"]
scale = Vector2( 0.5, 0.5 )
texture = ExtResource( 1 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConvexPolygon"]
scale = Vector2( 0.5, 0.5 )
polygon = PoolVector2Array( 10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332 )
[node name="RigidBodySphere" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 919.968, 115.129 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodySphere"]
shape = SubResource( 3 )
[node name="StaticScene" parent="." instance=ExtResource( 6 )]

View File

@@ -0,0 +1,39 @@
extends Test
export(int) var height = 10
export(int) var width = 1
export(Vector2) var box_size = Vector2(40.0, 40.0)
export(Vector2) var box_spacing = Vector2(0.0, 0.0)
func _ready():
_create_stack()
func _create_stack():
var root_node = $Stack
var template_body = create_rigidbody_box(box_size)
var pos_y = -0.5 * box_size.y - box_spacing.y
for level in height:
var row_node = Node2D.new()
row_node.position = Vector2(0.0, pos_y)
row_node.name = "Row%02d" % (level + 1)
root_node.add_child(row_node)
var pos_x = -0.5 * (width - 1) * (box_size.x + box_spacing.x)
for box_index in width:
var box = template_body.duplicate()
box.position = Vector2(pos_x, 0.0)
box.name = "Box%02d" % (box_index + 1)
row_node.add_child(box)
pos_x += box_size.x + box_spacing.x
pos_y -= box_size.y + box_spacing.y
template_body.queue_free()

View File

@@ -0,0 +1,12 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://tests/functional/test_stack.gd" type="Script" id=1]
[ext_resource path="res://tests/static_scene_flat.tscn" type="PackedScene" id=2]
[node name="Test" type="Node2D"]
script = ExtResource( 1 )
[node name="Stack" type="Node2D" parent="."]
position = Vector2( 512, 500 )
[node name="StaticSceneFlat" parent="." instance=ExtResource( 2 )]

View File

@@ -0,0 +1,149 @@
extends Test
const BOX_SIZE = Vector2(40, 40)
const BOX_SPACE = Vector2(50, 50)
export(int, 1, 1000) var row_size = 100
export(int, 1, 1000) var column_size = 100
var _objects = []
var _log_physics = false
var _log_physics_time = 0
var _log_physics_time_start = 0
func _ready():
_create_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_add_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_move_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_remove_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
Log.print_log("* Done.")
func _exit_tree():
for object in _objects:
object.free()
func _physics_process(_delta):
if _log_physics:
var time = OS.get_ticks_usec()
var time_delta = time - _log_physics_time
var time_total = time - _log_physics_time_start
_log_physics_time = time
Log.print_log(" Physics Tick: %.3f ms (total = %.3f ms)" % [0.001 * time_delta, 0.001 * time_total])
func _log_physics_start():
_log_physics = true
_log_physics_time_start = OS.get_ticks_usec()
_log_physics_time = _log_physics_time_start
func _log_physics_stop():
_log_physics = false
func _create_objects():
_objects.clear()
var template_body = create_rigidbody_box(BOX_SIZE)
template_body.gravity_scale = 0.0
Log.print_log("* Creating objects...")
var timer = OS.get_ticks_usec()
var pos_x = -0.5 * (row_size - 1) * BOX_SPACE.x
for row in row_size:
var pos_y = -0.5 * (column_size - 1) * BOX_SPACE.y
for column in column_size:
var box = template_body.duplicate()
box.position = Vector2(pos_x, pos_y)
box.name = "Box%03d" % (row * column + 1)
_objects.push_back(box)
pos_y += BOX_SPACE.y
pos_x += BOX_SPACE.x
timer = OS.get_ticks_usec() - timer
Log.print_log(" Create Time: %.3f ms" % (0.001 * timer))
template_body.queue_free()
func _add_objects():
var root_node = $Objects
Log.print_log("* Adding objects...")
var timer = OS.get_ticks_usec()
for object in _objects:
root_node.add_child(object)
timer = OS.get_ticks_usec() - timer
Log.print_log(" Add Time: %.3f ms" % (0.001 * timer))
func _move_objects():
Log.print_log("* Moving objects...")
var timer = OS.get_ticks_usec()
for object in _objects:
object.position += BOX_SPACE
timer = OS.get_ticks_usec() - timer
Log.print_log(" Move Time: %.3f ms" % (0.001 * timer))
func _remove_objects():
var root_node = $Objects
Log.print_log("* Removing objects...")
var timer = OS.get_ticks_usec()
for object in _objects:
root_node.remove_child(object)
timer = OS.get_ticks_usec() - timer
Log.print_log(" Remove Time: %.3f ms" % (0.001 * timer))

View File

@@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://tests/performance/test_perf_broadphase.gd" type="Script" id=1]
[node name="Test" type="Node2D"]
script = ExtResource( 1 )
[node name="Objects" type="Node2D" parent="."]
position = Vector2( 512, 300 )

View File

@@ -0,0 +1,161 @@
extends Test
const OPTION_TYPE_ALL = "Shape type/All"
const OPTION_TYPE_RECTANGLE = "Shape type/Rectangle"
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(int) var spawn_count = 100
export(int, 1, 10) var spawn_multiplier = 5
var _object_templates = []
func _ready():
yield(start_timer(0.5), "timeout")
if is_timer_canceled():
return
while $DynamicShapes.get_child_count():
var type_node = $DynamicShapes.get_child(0)
type_node.position = Vector2.ZERO
_object_templates.push_back(type_node)
$DynamicShapes.remove_child(type_node)
$Options.add_menu_item(OPTION_TYPE_ALL)
$Options.add_menu_item(OPTION_TYPE_RECTANGLE)
$Options.add_menu_item(OPTION_TYPE_SPHERE)
$Options.add_menu_item(OPTION_TYPE_CAPSULE)
$Options.add_menu_item(OPTION_TYPE_CONVEX_POLYGON)
$Options.add_menu_item(OPTION_TYPE_CONCAVE_POLYGON)
$Options.connect("option_selected", self, "_on_option_selected")
_start_all_types()
func _exit_tree():
for object_template in _object_templates:
object_template.free()
func _on_option_selected(option):
cancel_timer()
_despawn_objects()
match option:
OPTION_TYPE_ALL:
_start_all_types()
OPTION_TYPE_RECTANGLE:
_start_type(_find_type_index("Rectangle"))
OPTION_TYPE_SPHERE:
_start_type(_find_type_index("Sphere"))
OPTION_TYPE_CAPSULE:
_start_type(_find_type_index("Capsule"))
OPTION_TYPE_CONVEX_POLYGON:
_start_type(_find_type_index("ConvexPolygon"))
OPTION_TYPE_CONCAVE_POLYGON:
_start_type(_find_type_index("ConcavePolygon"))
func _find_type_index(type_name):
for type_index in _object_templates.size():
var type_node = _object_templates[type_index]
if type_node.name.find(type_name) > -1:
return type_index
Log.print_error("Invalid shape type: " + type_name)
return -1
func _start_type(type_index):
if type_index < 0:
return
if type_index >= _object_templates.size():
return
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_spawn_objects(type_index)
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_activate_objects()
yield(start_timer(5.0), "timeout")
if is_timer_canceled():
return
_despawn_objects()
Log.print_log("* Done.")
func _start_all_types():
for type_index in _object_templates.size():
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_spawn_objects(type_index)
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_activate_objects()
yield(start_timer(5.0), "timeout")
if is_timer_canceled():
return
_despawn_objects()
Log.print_log("* Done.")
func _spawn_objects(type_index):
var template_node = _object_templates[type_index]
for spawn in spawns:
var spawn_parent = get_node(spawn)
Log.print_log("* Spawning: " + template_node.name)
for _index in range(spawn_multiplier):
for _node_index in spawn_count / spawn_multiplier:
var node = template_node.duplicate() as Node2D
spawn_parent.add_child(node)
func _activate_objects():
var spawn_parent = $SpawnTarget1
Log.print_log("* Activating")
for node_index in spawn_parent.get_child_count():
var node = spawn_parent.get_child(node_index) as RigidBody2D
node.set_sleeping(false)
func _despawn_objects():
for spawn in spawns:
var spawn_parent = get_node(spawn)
if spawn_parent.get_child_count() == 0:
return
Log.print_log("* Despawning")
while spawn_parent.get_child_count():
var node_index = spawn_parent.get_child_count() - 1
var node = spawn_parent.get_child(node_index)
spawn_parent.remove_child(node)
node.queue_free()

View File

@@ -0,0 +1,70 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://tests/static_scene.tscn" type="PackedScene" id=1]
[ext_resource path="res://tests/performance/test_perf_contacts.gd" type="Script" id=2]
[ext_resource path="res://assets/godot-head.png" type="Texture" id=3]
[ext_resource path="res://tests/test_options.tscn" type="PackedScene" id=4]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 20, 30 )
[sub_resource type="CircleShape2D" id=2]
radius = 30.0
[sub_resource type="CapsuleShape2D" id=3]
radius = 20.0
height = 30.0
[node name="Test" type="Node2D"]
script = ExtResource( 2 )
spawns = [ NodePath("SpawnTarget1") ]
[node name="Options" parent="." instance=ExtResource( 4 )]
[node name="SpawnTarget1" type="Node2D" parent="."]
position = Vector2( 512, 400 )
[node name="StaticScene" parent="." instance=ExtResource( 1 )]
position = Vector2( 0, 125.017 )
[node name="DynamicShapes" type="Node2D" parent="."]
[node name="RigidBodyRectangle" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 0, 1024 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyRectangle"]
shape = SubResource( 1 )
[node name="RigidBodySphere" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 100, 1024 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodySphere"]
shape = SubResource( 2 )
[node name="RigidBodyCapsule" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 200, 1024 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="DynamicShapes/RigidBodyCapsule"]
shape = SubResource( 3 )
[node name="RigidBodyConvexPolygon" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 300, 1024 )
[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConvexPolygon"]
scale = Vector2( 0.5, 0.5 )
texture = ExtResource( 3 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConvexPolygon"]
scale = Vector2( 0.5, 0.5 )
polygon = PoolVector2Array( 10.7, -54.5, 28.3596, -49.4067, 47.6282, -34.3806, 57.9717, -20.9447, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -58.0115, -20.515, -46.9473, -34.7342, -26.0876, -50.1138, -11.4152, -54.5332 )
[node name="RigidBodyConcavePolygon" type="RigidBody2D" parent="DynamicShapes"]
position = Vector2( 400, 1024 )
[node name="GodotIcon" type="Sprite" parent="DynamicShapes/RigidBodyConcavePolygon"]
scale = Vector2( 0.5, 0.5 )
texture = ExtResource( 3 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="DynamicShapes/RigidBodyConcavePolygon"]
scale = Vector2( 0.5, 0.5 )
polygon = PoolVector2Array( -5.93512, -43.2195, 6.44476, -42.9695, 11.127, -54.3941, 26.9528, -49.4309, 26.2037, -36.508, 37.5346, -28.1737, 47.6282, -34.3806, 58.0427, -20.9631, 51.113, -10.2876, 50.9869, 35.2694, 38.8, 47.5, 15.9852, 54.3613, -14.9507, 54.1845, -36.5, 48.1, -50.4828, 36.33, -51.3668, -9.98545, -57.8889, -20.5885, -46.9473, -34.7342, -37.4014, -28.547, -26.0876, -37.0323, -26.9862, -49.15, -11.4152, -54.5332 )

View File

@@ -0,0 +1,10 @@
[gd_scene format=2]
[node name="StaticScene" type="Node2D"]
[node name="StaticBodyPolygon" type="StaticBody2D" parent="."]
position = Vector2( -7.85718, 399.596 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBodyPolygon"]
build_mode = 1
polygon = PoolVector2Array( 16.3331, -129.432, 154.006, -20.0078, 292.354, 30.3943, 447.054, 33.9161, 584.899, -14.7955, 751.156, -15.5179, 894.098, -65.4518, 1000.73, -209.127, 1037.77, -398.823, 1029.92, 253.327, 6.2309, 261.185, 7.35339, -398.823 )

View File

@@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=2]
[sub_resource type="RectangleShape2D" id=1]
extents = Vector2( 512, 50 )
[node name="StaticSceneFlat" type="Node2D"]
[node name="StaticBodyPolygon" type="StaticBody2D" parent="."]
position = Vector2( 512, 550 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBodyPolygon"]
shape = SubResource( 1 )

View File

@@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://utils/option_menu.gd" type="Script" id=1]
[node name="Options" type="MenuButton"]
margin_left = 10.0
margin_top = 106.719
margin_right = 125.0
margin_bottom = 126.719
text = "TEST OPTIONS"
flat = false
align = 0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}

View File

@@ -0,0 +1,54 @@
extends OptionMenu
class TestData:
var id
var scene_path
var _test_list = []
var _current_test = null
var _current_test_scene = null
func _ready():
connect("option_selected", self, "_on_option_selected")
func _process(_delta):
if Input.is_action_just_pressed("restart_test"):
if _current_test:
_start_test(_current_test)
func add_test(id, scene_path):
var test_data = TestData.new()
test_data.id = id
test_data.scene_path = scene_path
_test_list.append(test_data)
add_menu_item(id)
func _on_option_selected(item_path):
for test in _test_list:
if test.id == item_path:
_start_test(test)
func _start_test(test):
_current_test = test
if _current_test_scene:
_current_test_scene.queue_free()
_current_test_scene = null
Log.print_log("*** STARTING TEST: " + test.id)
var scene = load(test.scene_path)
_current_test_scene = scene.instance()
get_tree().root.add_child(_current_test_scene)
get_tree().root.move_child(_current_test_scene, 0)
var label_test = get_node("../LabelTest")
label_test.test_name = test.id

View File

@@ -0,0 +1,41 @@
extends Control
const MAX_ENTRIES = 100
var _entry_template
func _enter_tree():
Log.connect("entry_logged", self, "_on_log_entry")
_entry_template = get_child(0) as Label
remove_child(_entry_template)
func _exit_tree():
_entry_template.free()
func clear():
while get_child_count():
var entry = get_child(get_child_count() - 1)
remove_child(entry)
entry.queue_free()
func _on_log_entry(message, type):
var new_entry = _entry_template.duplicate() as Label
new_entry.set_text(message)
if type == Log.LogType.ERROR:
new_entry.modulate = Color.red
else:
new_entry.modulate = Color.white
if get_child_count() >= MAX_ENTRIES:
var first_entry = get_child(0) as Label
remove_child(first_entry)
first_entry.queue_free()
add_child(new_entry)

View File

@@ -0,0 +1,12 @@
extends Label
func _process(_delta):
var engine_name = ""
match System.get_physics_engine():
System.PhysicsEngine.GODOT_PHYSICS:
engine_name = "Godot Physics"
System.PhysicsEngine.OTHER:
var engine_setting = ProjectSettings.get_setting("physics/2d/physics_engine")
engine_name = "Other (%s)" % engine_setting
set_text("Physics engine: %s" % engine_name)

View File

@@ -0,0 +1,5 @@
extends Label
func _process(_delta):
set_text("FPS: %d" % Engine.get_frames_per_second())

View File

@@ -0,0 +1,13 @@
extends Label
var test_name setget _set_test_name
func _ready():
set_text("Select a test from the menu to start it")
func _set_test_name(value):
test_name = value
set_text("Test: %s" % test_name)

View File

@@ -0,0 +1,5 @@
extends Label
func _process(_delta):
set_text("Godot Version: %s" % Engine.get_version_info().string)

View File

@@ -0,0 +1,47 @@
class_name OptionMenu
extends MenuButton
signal option_selected(item_path)
func add_menu_item(item_path):
var path_elements = item_path.split("/", false)
var path_element_count = path_elements.size()
assert(path_element_count > 0)
var path = ""
var popup = get_popup()
for element_index in path_element_count - 1:
var popup_label = path_elements[element_index]
path += popup_label + "/"
popup = _add_popup(popup, path, popup_label)
_add_item(popup, path_elements[path_element_count - 1])
func _add_item(parent_popup, label):
parent_popup.add_item(label)
func _add_popup(parent_popup, path, label):
if parent_popup.has_node(label):
var popup_node = parent_popup.get_node(label)
var popup_menu = popup_node as PopupMenu
assert(popup_menu)
return popup_menu
var popup_menu = PopupMenu.new()
popup_menu.name = label
parent_popup.add_child(popup_menu)
parent_popup.add_submenu_item(label, label)
popup_menu.connect("index_pressed", self, "_on_item_pressed", [popup_menu, path])
return popup_menu
func _on_item_pressed(item_index, popup_menu, path):
var item_path = path + popup_menu.get_item_text(item_index)
emit_signal("option_selected", item_path)

View File

@@ -0,0 +1,14 @@
extends ScrollContainer
export(bool) var auto_scroll = false setget set_auto_scroll
func _process(_delta):
if auto_scroll:
var scrollbar = get_v_scrollbar()
scrollbar.value = scrollbar.max_value
func set_auto_scroll(value):
auto_scroll = value

View File

@@ -0,0 +1,50 @@
extends Node
enum PhysicsEngine {
GODOT_PHYSICS,
OTHER,
}
var _engine = PhysicsEngine.OTHER
func _enter_tree():
get_tree().debug_collisions_hint = true
var engine_string = ProjectSettings.get_setting("physics/2d/physics_engine")
match engine_string:
"DEFAULT":
_engine = PhysicsEngine.GODOT_PHYSICS
"GodotPhysics":
_engine = PhysicsEngine.GODOT_PHYSICS
_:
_engine = PhysicsEngine.OTHER
func _process(_delta):
if Input.is_action_just_pressed("toggle_full_screen"):
OS.window_fullscreen = not OS.window_fullscreen
if Input.is_action_just_pressed("toggle_debug_collision"):
var debug_collision_enabled = not _is_debug_collision_enabled()
_set_debug_collision_enabled(debug_collision_enabled)
if debug_collision_enabled:
Log.print_log("Debug Collision ON")
else:
Log.print_log("Debug Collision OFF")
if Input.is_action_just_pressed("exit"):
get_tree().quit()
func get_physics_engine():
return _engine
func _set_debug_collision_enabled(enabled):
get_tree().debug_collisions_hint = enabled
func _is_debug_collision_enabled():
return get_tree().debug_collisions_hint

View File

@@ -0,0 +1,20 @@
extends Node
enum LogType {
LOG,
ERROR,
}
signal entry_logged(message, type)
func print_log(message):
print(message)
emit_signal("entry_logged", message, LogType.LOG)
func print_error(message):
push_error(message)
printerr(message)
emit_signal("entry_logged", message, LogType.ERROR)