test_one_way_collision.gd 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. @tool
  2. extends Test
  3. signal all_tests_done()
  4. signal test_done()
  5. const OPTION_OBJECT_TYPE_RIGIDBODY = "Object type/Rigid body (1)"
  6. const OPTION_OBJECT_TYPE_CHARACTER = "Object type/Character body (2)"
  7. const OPTION_TEST_CASE_ALL = "Test Cases/TEST ALL (0)"
  8. const OPTION_TEST_CASE_ALL_RIGID = "Test Cases/All Rigid Body tests"
  9. const OPTION_TEST_CASE_ALL_CHARACTER = "Test Cases/All Character Body tests"
  10. const OPTION_TEST_CASE_ALL_ANGLES_RIGID = "Test Cases/Around the clock (Rigid Body)"
  11. const OPTION_TEST_CASE_ALL_ANGLES_CHARACTER = "Test Cases/Around the clock (Character Body)"
  12. const OPTION_TEST_CASE_MOVING_PLATFORM_RIGID = "Test Cases/Moving Platform (Rigid Body)"
  13. const OPTION_TEST_CASE_MOVING_PLATFORM_CHARACTER = "Test Cases/Moving Platform (Character Body)"
  14. const TEST_ALL_ANGLES_STEP = 15.0
  15. const TEST_ALL_ANGLES_MAX = 344.0
  16. @export_range(64, 256, 0.1) var _platform_size = 128.0:
  17. set(value):
  18. if value == _platform_size:
  19. return
  20. _platform_size = value
  21. _update_platform_size(value)
  22. @export_range(0, 360, 0.1) var _platform_angle = 0.0:
  23. set(value):
  24. if value == _platform_angle:
  25. return
  26. _platform_angle = value
  27. _update_platform_angle(value)
  28. @export var _platform_speed = 0.0
  29. @export_range(0, 360, 0.1) var _body_angle = 0.0:
  30. set(value):
  31. if value == _body_angle:
  32. return
  33. _body_angle = value
  34. _update_rigidbody_angle(value)
  35. @export var _body_velocity = Vector2(400.0, 0.0)
  36. @export var _use_character_body = false
  37. @onready var options = $Options
  38. var _rigid_body_template = null
  39. var _character_body_template = null
  40. var _moving_body: PhysicsBody2D = null
  41. var _platform_template = null
  42. var _platform_body: PhysicsBody2D = null
  43. var _platform_velocity = Vector2.ZERO
  44. @onready var _target_area = $TargetArea2D
  45. var _contact_detected = false
  46. var _target_entered = false
  47. var _test_passed = false
  48. var _test_step = 0
  49. var _test_all_angles = false
  50. var _lock_controls = false
  51. var _test_canceled = false
  52. func _ready():
  53. if not Engine.is_editor_hint():
  54. options.add_menu_item(OPTION_OBJECT_TYPE_RIGIDBODY, true, not _use_character_body, true)
  55. options.add_menu_item(OPTION_OBJECT_TYPE_CHARACTER, true, _use_character_body, true)
  56. options.add_menu_item(OPTION_TEST_CASE_ALL)
  57. options.add_menu_item(OPTION_TEST_CASE_ALL_RIGID)
  58. options.add_menu_item(OPTION_TEST_CASE_ALL_CHARACTER)
  59. options.add_menu_item(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  60. options.add_menu_item(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  61. options.add_menu_item(OPTION_TEST_CASE_MOVING_PLATFORM_RIGID)
  62. options.add_menu_item(OPTION_TEST_CASE_MOVING_PLATFORM_CHARACTER)
  63. options.option_selected.connect(self._on_option_selected)
  64. $Controls/PlatformSize/HSlider.value = _platform_size
  65. $Controls/PlatformAngle/HSlider.value = _platform_angle
  66. $Controls/BodyAngle/HSlider.value = _body_angle
  67. remove_child(_target_area)
  68. _target_area.body_entered.connect(self._on_target_entered)
  69. $Timer.timeout.connect(self._on_timeout)
  70. _rigid_body_template = $RigidBody2D
  71. remove_child(_rigid_body_template)
  72. _character_body_template = $CharacterBody2D
  73. remove_child(_character_body_template)
  74. _platform_template = $OneWayStaticBody2D
  75. remove_child(_platform_template)
  76. _start_test()
  77. func _process(_delta):
  78. if not Engine.is_editor_hint():
  79. if Input.is_action_just_pressed(&"ui_accept"):
  80. await _reset_test(false)
  81. func _physics_process(delta):
  82. super._physics_process(delta)
  83. if not Engine.is_editor_hint():
  84. if _moving_body and not _contact_detected:
  85. if _use_character_body:
  86. var collision = _moving_body.move_and_collide(_body_velocity * delta, false)
  87. if collision:
  88. var colliding_body = collision.get_collider()
  89. await _on_contact_detected(colliding_body)
  90. if _platform_body and _platform_velocity != Vector2.ZERO:
  91. var motion = _platform_velocity * delta
  92. _platform_body.global_position += motion
  93. func _input(event):
  94. var key_event = event as InputEventKey
  95. if key_event and not key_event.pressed:
  96. if key_event.keycode == KEY_0:
  97. await _on_option_selected(OPTION_TEST_CASE_ALL)
  98. if key_event.keycode == KEY_1:
  99. await _on_option_selected(OPTION_OBJECT_TYPE_RIGIDBODY)
  100. elif key_event.keycode == KEY_2:
  101. await _on_option_selected(OPTION_OBJECT_TYPE_CHARACTER)
  102. func _exit_tree():
  103. if not Engine.is_editor_hint():
  104. _rigid_body_template.free()
  105. _character_body_template.free()
  106. _platform_template.free()
  107. func _update_platform_size(value, reset = true):
  108. if _lock_controls:
  109. return
  110. if value == _platform_size:
  111. return
  112. _platform_size = value
  113. if is_inside_tree():
  114. if Engine.is_editor_hint():
  115. $OneWayStaticBody2D/CollisionShape2D.shape.size.x = value
  116. else:
  117. var platform_collision = _platform_template.get_child(0)
  118. platform_collision.shape.size.x = value
  119. if _platform_body:
  120. # Bug: need to re-add when changing shape.
  121. var child_index = _platform_body.get_index()
  122. remove_child(_platform_body)
  123. add_child(_platform_body)
  124. move_child(_platform_body, child_index)
  125. if reset:
  126. await _reset_test()
  127. func _update_platform_angle(value, reset = true):
  128. if _lock_controls:
  129. return
  130. if value == _platform_angle:
  131. return
  132. _platform_angle = value
  133. if is_inside_tree():
  134. if Engine.is_editor_hint():
  135. $OneWayStaticBody2D.rotation = deg_to_rad(value)
  136. else:
  137. if _platform_body:
  138. _platform_body.rotation = deg_to_rad(value)
  139. _platform_template.rotation = deg_to_rad(value)
  140. if reset:
  141. await _reset_test()
  142. func _update_rigidbody_angle(value, reset = true):
  143. if _lock_controls:
  144. return
  145. if value == _body_angle:
  146. return
  147. _body_angle = value
  148. if is_inside_tree():
  149. if Engine.is_editor_hint():
  150. $RigidBody2D.rotation = deg_to_rad(value)
  151. $CharacterBody2D.rotation = deg_to_rad(value)
  152. else:
  153. if _moving_body:
  154. _moving_body.rotation = deg_to_rad(value)
  155. _rigid_body_template.rotation = deg_to_rad(value)
  156. _character_body_template.rotation = deg_to_rad(value)
  157. if reset:
  158. await _reset_test()
  159. func _on_option_selected(option):
  160. match option:
  161. OPTION_OBJECT_TYPE_CHARACTER:
  162. _use_character_body = true
  163. await _reset_test()
  164. OPTION_OBJECT_TYPE_RIGIDBODY:
  165. _use_character_body = false
  166. await _reset_test()
  167. OPTION_TEST_CASE_ALL:
  168. await _test_all()
  169. OPTION_TEST_CASE_ALL_RIGID:
  170. await _test_all_rigid_body()
  171. OPTION_TEST_CASE_ALL_CHARACTER:
  172. await _test_all_character_body()
  173. OPTION_TEST_CASE_ALL_ANGLES_RIGID:
  174. _use_character_body = false
  175. _test_all_angles = true
  176. await _reset_test(false)
  177. OPTION_TEST_CASE_ALL_ANGLES_CHARACTER:
  178. _use_character_body = true
  179. _test_all_angles = true
  180. await _reset_test(false)
  181. OPTION_TEST_CASE_MOVING_PLATFORM_RIGID:
  182. _use_character_body = false
  183. await _test_moving_platform()
  184. OPTION_TEST_CASE_MOVING_PLATFORM_CHARACTER:
  185. _use_character_body = true
  186. await _test_moving_platform()
  187. func _start_test_case(option):
  188. Log.print_log("* Starting " + option)
  189. await _on_option_selected(option)
  190. await self.all_tests_done
  191. func _wait_for_test():
  192. await _reset_test()
  193. await self.test_done
  194. func _test_all_rigid_body():
  195. Log.print_log("* All RigidBody test cases...")
  196. await _update_platform_size(128.0, false)
  197. await _update_rigidbody_angle(0.0, false)
  198. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  199. if _test_canceled:
  200. return
  201. await _update_platform_size(128.0, false)
  202. await _update_rigidbody_angle(45.0, false)
  203. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  204. if _test_canceled:
  205. return
  206. await _update_platform_size(64.0, false)
  207. await _update_rigidbody_angle(45.0, false)
  208. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  209. if _test_canceled:
  210. return
  211. await _start_test_case(OPTION_TEST_CASE_MOVING_PLATFORM_RIGID)
  212. if _test_canceled:
  213. return
  214. func _test_all_character_body():
  215. Log.print_log("* All CharacterBody test cases...")
  216. await _update_platform_size(128.0, false)
  217. await _update_rigidbody_angle(0.0, false)
  218. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  219. if _test_canceled:
  220. return
  221. await _update_platform_size(128.0, false)
  222. await _update_rigidbody_angle(45.0, false)
  223. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  224. if _test_canceled:
  225. return
  226. await _update_platform_size(64.0, false)
  227. await _update_rigidbody_angle(45.0, false)
  228. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  229. if _test_canceled:
  230. return
  231. await _start_test_case(OPTION_TEST_CASE_MOVING_PLATFORM_CHARACTER)
  232. if _test_canceled:
  233. return
  234. func _test_moving_platform():
  235. Log.print_log("* Start moving platform tests")
  236. Log.print_log("* Platform moving away from body...")
  237. await _update_platform_size(128.0, false)
  238. await _update_rigidbody_angle(0.0, false)
  239. _platform_speed = 50.0
  240. await _update_platform_angle(90.0, false)
  241. await _wait_for_test()
  242. if _test_canceled:
  243. return
  244. await _update_platform_angle(-90.0, false)
  245. await _wait_for_test()
  246. if _test_canceled:
  247. return
  248. Log.print_log("* Platform moving towards body...")
  249. await _update_platform_size(128.0, false)
  250. await _update_rigidbody_angle(0.0, false)
  251. _platform_speed = -50.0
  252. await _update_platform_angle(90.0, false)
  253. await _wait_for_test()
  254. if _test_canceled:
  255. return
  256. await _update_platform_angle(-90.0, false)
  257. await _wait_for_test()
  258. if _test_canceled:
  259. return
  260. _platform_speed = 0.0
  261. emit_signal("all_tests_done")
  262. func _test_all():
  263. Log.print_log("* TESTING ALL...")
  264. await _test_all_rigid_body()
  265. if _test_canceled:
  266. return
  267. await _test_all_character_body()
  268. if _test_canceled:
  269. return
  270. Log.print_log("* Done.")
  271. func _start_test():
  272. var test_label = "Testing: "
  273. var platform_angle = _platform_template.rotation
  274. if _platform_body:
  275. platform_angle = _platform_body.rotation
  276. _platform_body.remove_child(_target_area)
  277. remove_child(_platform_body)
  278. _platform_body.queue_free()
  279. _platform_body = null
  280. _platform_body = _platform_template.duplicate()
  281. _platform_body.rotation = platform_angle
  282. add_child(_platform_body)
  283. _platform_body.add_child(_target_area)
  284. _target_area.position = Vector2()
  285. if _use_character_body:
  286. test_label += String(_character_body_template.name)
  287. _moving_body = _character_body_template.duplicate()
  288. else:
  289. test_label += String(_rigid_body_template.name)
  290. _moving_body = _rigid_body_template.duplicate()
  291. _moving_body.linear_velocity = _body_velocity
  292. _moving_body.body_entered.connect(self._on_contact_detected)
  293. add_child(_moving_body)
  294. if _platform_speed != 0.0:
  295. var platform_pos = _platform_body.global_position
  296. var body_pos = _moving_body.global_position
  297. var dir = (platform_pos - body_pos).normalized()
  298. _platform_velocity = dir * _platform_speed
  299. else:
  300. _platform_velocity = Vector2.ZERO
  301. if _test_all_angles:
  302. test_label += " - All angles"
  303. $LabelTestType.text = test_label
  304. _contact_detected = false
  305. _target_entered = false
  306. _test_passed = false
  307. _test_step += 1
  308. $Timer.start()
  309. $LabelResult.text = "..."
  310. $LabelResult.self_modulate = Color.WHITE
  311. func _reset_test(cancel_test = true):
  312. _test_canceled = true
  313. await _on_timeout()
  314. _test_canceled = false
  315. _test_step = 0
  316. if _test_all_angles:
  317. if cancel_test:
  318. Log.print_log("*** Stop around the clock tests")
  319. _test_all_angles = false
  320. emit_signal("all_tests_done")
  321. else:
  322. Log.print_log("*** Start around the clock tests")
  323. _platform_body.rotation = deg_to_rad(_platform_angle)
  324. _lock_controls = true
  325. $Controls/PlatformAngle/HSlider.value = _platform_angle
  326. _lock_controls = false
  327. _next_test(true)
  328. func _next_test(force_start = false):
  329. if _moving_body:
  330. remove_child(_moving_body)
  331. _moving_body.queue_free()
  332. _moving_body = null
  333. if _test_all_angles:
  334. var angle = rad_to_deg(_platform_body.rotation)
  335. if angle >= _platform_angle + TEST_ALL_ANGLES_MAX:
  336. _platform_body.rotation = deg_to_rad(_platform_angle)
  337. _lock_controls = true
  338. $Controls/PlatformAngle/HSlider.value = _platform_angle
  339. _lock_controls = false
  340. _test_all_angles = false
  341. Log.print_log("*** Done all angles")
  342. else:
  343. angle = _platform_angle + _test_step * TEST_ALL_ANGLES_STEP
  344. _platform_body.rotation = deg_to_rad(angle)
  345. _lock_controls = true
  346. $Controls/PlatformAngle/HSlider.value = angle
  347. _lock_controls = false
  348. _start_test()
  349. elif force_start:
  350. _start_test()
  351. func _on_contact_detected(_body):
  352. if _contact_detected or _target_entered:
  353. return
  354. _contact_detected = true
  355. _test_passed = _should_collide()
  356. _set_result()
  357. await _on_timeout()
  358. func _on_target_entered(_body):
  359. if _body != _moving_body:
  360. return
  361. if _contact_detected or _target_entered:
  362. return
  363. _target_entered = true
  364. _test_passed = not _should_collide()
  365. _set_result()
  366. await _on_timeout()
  367. func _should_collide():
  368. var platform_rotation = round(rad_to_deg(_platform_body.rotation))
  369. var angle = fposmod(platform_rotation, 360)
  370. return angle > 180
  371. func _on_timeout():
  372. cancel_timer()
  373. if $Timer.is_stopped():
  374. return
  375. $Timer.stop()
  376. if _test_canceled:
  377. emit_signal("test_done")
  378. emit_signal("all_tests_done")
  379. return
  380. if not _contact_detected and not _target_entered:
  381. Log.print_log("Test TIMEOUT")
  382. _set_result()
  383. await start_timer(0.5).timeout
  384. if _test_canceled:
  385. emit_signal("test_done")
  386. emit_signal("all_tests_done")
  387. return
  388. var was_all_angles = _test_all_angles
  389. _next_test()
  390. emit_signal("test_done")
  391. if was_all_angles and not _test_all_angles:
  392. emit_signal("all_tests_done")
  393. func _set_result():
  394. var result = ""
  395. if _test_passed:
  396. result = "PASSED"
  397. $LabelResult.self_modulate = Color.GREEN
  398. else:
  399. result = "FAILED"
  400. $LabelResult.self_modulate = Color.RED
  401. $LabelResult.text = result
  402. var platform_angle = rad_to_deg(_platform_body.rotation)
  403. result += ": size=%.1f, angle=%.1f, body angle=%.1f" % [_platform_size, platform_angle, _body_angle]
  404. Log.print_log("Test %s" % result)