test_one_way_collision.gd 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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: OptionMenu = $Options
  38. var _rigid_body_template: RigidBody2D = null
  39. var _character_body_template: CharacterBody2D = null
  40. var _moving_body: PhysicsBody2D = null
  41. var _platform_template: StaticBody2D = null
  42. var _platform_body: PhysicsBody2D = null
  43. var _platform_velocity := Vector2.ZERO
  44. @onready var _target_area: Area2D = $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() -> void:
  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(_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(_on_target_entered)
  69. $Timer.timeout.connect(_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: float) -> void:
  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: float) -> void:
  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: InputEvent) -> void:
  94. if event is InputEventKey and not event.pressed:
  95. if event.keycode == KEY_0:
  96. await _on_option_selected(OPTION_TEST_CASE_ALL)
  97. if event.keycode == KEY_1:
  98. await _on_option_selected(OPTION_OBJECT_TYPE_RIGIDBODY)
  99. elif event.keycode == KEY_2:
  100. await _on_option_selected(OPTION_OBJECT_TYPE_CHARACTER)
  101. func _exit_tree() -> void:
  102. if not Engine.is_editor_hint():
  103. _rigid_body_template.free()
  104. _character_body_template.free()
  105. _platform_template.free()
  106. func _update_platform_size(value: float, reset: bool = true) -> void:
  107. if _lock_controls:
  108. return
  109. if value == _platform_size:
  110. return
  111. _platform_size = value
  112. if is_inside_tree():
  113. if Engine.is_editor_hint():
  114. $OneWayStaticBody2D/CollisionShape2D.shape.size.x = value
  115. else:
  116. var platform_collision := _platform_template.get_child(0)
  117. platform_collision.shape.size.x = value
  118. if _platform_body:
  119. # BUG: Need to re-add when changing shape.
  120. var child_index := _platform_body.get_index()
  121. remove_child(_platform_body)
  122. add_child(_platform_body)
  123. move_child(_platform_body, child_index)
  124. if reset:
  125. await _reset_test()
  126. func _update_platform_angle(value: float, reset: bool = true) -> void:
  127. if _lock_controls:
  128. return
  129. if value == _platform_angle:
  130. return
  131. _platform_angle = value
  132. if is_inside_tree():
  133. if Engine.is_editor_hint():
  134. $OneWayStaticBody2D.rotation = deg_to_rad(value)
  135. else:
  136. if _platform_body:
  137. _platform_body.rotation = deg_to_rad(value)
  138. _platform_template.rotation = deg_to_rad(value)
  139. if reset:
  140. await _reset_test()
  141. func _update_rigidbody_angle(value: float, reset: bool = true) -> void:
  142. if _lock_controls:
  143. return
  144. if value == _body_angle:
  145. return
  146. _body_angle = value
  147. if is_inside_tree():
  148. if Engine.is_editor_hint():
  149. $RigidBody2D.rotation = deg_to_rad(value)
  150. $CharacterBody2D.rotation = deg_to_rad(value)
  151. else:
  152. if _moving_body:
  153. _moving_body.rotation = deg_to_rad(value)
  154. _rigid_body_template.rotation = deg_to_rad(value)
  155. _character_body_template.rotation = deg_to_rad(value)
  156. if reset:
  157. await _reset_test()
  158. func _on_option_selected(option: String) -> void:
  159. match option:
  160. OPTION_OBJECT_TYPE_CHARACTER:
  161. _use_character_body = true
  162. await _reset_test()
  163. OPTION_OBJECT_TYPE_RIGIDBODY:
  164. _use_character_body = false
  165. await _reset_test()
  166. OPTION_TEST_CASE_ALL:
  167. await _test_all()
  168. OPTION_TEST_CASE_ALL_RIGID:
  169. await _test_all_rigid_body()
  170. OPTION_TEST_CASE_ALL_CHARACTER:
  171. await _test_all_character_body()
  172. OPTION_TEST_CASE_ALL_ANGLES_RIGID:
  173. _use_character_body = false
  174. _test_all_angles = true
  175. await _reset_test(false)
  176. OPTION_TEST_CASE_ALL_ANGLES_CHARACTER:
  177. _use_character_body = true
  178. _test_all_angles = true
  179. await _reset_test(false)
  180. OPTION_TEST_CASE_MOVING_PLATFORM_RIGID:
  181. _use_character_body = false
  182. await _test_moving_platform()
  183. OPTION_TEST_CASE_MOVING_PLATFORM_CHARACTER:
  184. _use_character_body = true
  185. await _test_moving_platform()
  186. func _start_test_case(option: String) -> void:
  187. Log.print_log("* Starting " + option)
  188. await _on_option_selected(option)
  189. await all_tests_done
  190. func _wait_for_test() -> void:
  191. await _reset_test()
  192. await test_done
  193. func _test_all_rigid_body() -> void:
  194. Log.print_log("* All RigidBody test cases...")
  195. await _update_platform_size(128.0, false)
  196. await _update_rigidbody_angle(0.0, false)
  197. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  198. if _test_canceled:
  199. return
  200. await _update_platform_size(128.0, false)
  201. await _update_rigidbody_angle(45.0, false)
  202. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  203. if _test_canceled:
  204. return
  205. await _update_platform_size(64.0, false)
  206. await _update_rigidbody_angle(45.0, false)
  207. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_RIGID)
  208. if _test_canceled:
  209. return
  210. await _start_test_case(OPTION_TEST_CASE_MOVING_PLATFORM_RIGID)
  211. if _test_canceled:
  212. return
  213. func _test_all_character_body() -> void:
  214. Log.print_log("* All CharacterBody test cases...")
  215. await _update_platform_size(128.0, false)
  216. await _update_rigidbody_angle(0.0, false)
  217. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  218. if _test_canceled:
  219. return
  220. await _update_platform_size(128.0, false)
  221. await _update_rigidbody_angle(45.0, false)
  222. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  223. if _test_canceled:
  224. return
  225. await _update_platform_size(64.0, false)
  226. await _update_rigidbody_angle(45.0, false)
  227. await _start_test_case(OPTION_TEST_CASE_ALL_ANGLES_CHARACTER)
  228. if _test_canceled:
  229. return
  230. await _start_test_case(OPTION_TEST_CASE_MOVING_PLATFORM_CHARACTER)
  231. if _test_canceled:
  232. return
  233. func _test_moving_platform() -> void:
  234. Log.print_log("* Start moving platform tests")
  235. Log.print_log("* Platform moving away from body...")
  236. await _update_platform_size(128.0, false)
  237. await _update_rigidbody_angle(0.0, false)
  238. _platform_speed = 50.0
  239. await _update_platform_angle(90.0, false)
  240. await _wait_for_test()
  241. if _test_canceled:
  242. return
  243. await _update_platform_angle(-90.0, false)
  244. await _wait_for_test()
  245. if _test_canceled:
  246. return
  247. Log.print_log("* Platform moving towards body...")
  248. await _update_platform_size(128.0, false)
  249. await _update_rigidbody_angle(0.0, false)
  250. _platform_speed = -50.0
  251. await _update_platform_angle(90.0, false)
  252. await _wait_for_test()
  253. if _test_canceled:
  254. return
  255. await _update_platform_angle(-90.0, false)
  256. await _wait_for_test()
  257. if _test_canceled:
  258. return
  259. _platform_speed = 0.0
  260. all_tests_done.emit()
  261. func _test_all() -> void:
  262. Log.print_log("* TESTING ALL...")
  263. await _test_all_rigid_body()
  264. if _test_canceled:
  265. return
  266. await _test_all_character_body()
  267. if _test_canceled:
  268. return
  269. Log.print_log("* Done.")
  270. func _start_test() -> void:
  271. var test_label := "Testing: "
  272. var platform_angle := _platform_template.rotation
  273. if _platform_body:
  274. platform_angle = _platform_body.rotation
  275. _platform_body.remove_child(_target_area)
  276. remove_child(_platform_body)
  277. _platform_body.queue_free()
  278. _platform_body = null
  279. _platform_body = _platform_template.duplicate()
  280. _platform_body.rotation = platform_angle
  281. add_child(_platform_body)
  282. _platform_body.add_child(_target_area)
  283. _target_area.position = Vector2()
  284. if _use_character_body:
  285. test_label += String(_character_body_template.name)
  286. _moving_body = _character_body_template.duplicate()
  287. else:
  288. test_label += String(_rigid_body_template.name)
  289. _moving_body = _rigid_body_template.duplicate()
  290. _moving_body.linear_velocity = _body_velocity
  291. _moving_body.body_entered.connect(_on_contact_detected)
  292. add_child(_moving_body)
  293. if _platform_speed != 0.0:
  294. var platform_pos := _platform_body.global_position
  295. var body_pos := _moving_body.global_position
  296. var dir := (platform_pos - body_pos).normalized()
  297. _platform_velocity = dir * _platform_speed
  298. else:
  299. _platform_velocity = Vector2.ZERO
  300. if _test_all_angles:
  301. test_label += " - All angles"
  302. $LabelTestType.text = test_label
  303. _contact_detected = false
  304. _target_entered = false
  305. _test_passed = false
  306. _test_step += 1
  307. $Timer.start()
  308. $LabelResult.text = "..."
  309. $LabelResult.self_modulate = Color.WHITE
  310. func _reset_test(cancel_test: bool = true) -> void:
  311. _test_canceled = true
  312. await _on_timeout()
  313. _test_canceled = false
  314. _test_step = 0
  315. if _test_all_angles:
  316. if cancel_test:
  317. Log.print_log("*** Stop around the clock tests")
  318. _test_all_angles = false
  319. all_tests_done.emit()
  320. else:
  321. Log.print_log("*** Start around the clock tests")
  322. _platform_body.rotation = deg_to_rad(_platform_angle)
  323. _lock_controls = true
  324. $Controls/PlatformAngle/HSlider.value = _platform_angle
  325. _lock_controls = false
  326. _next_test(true)
  327. func _next_test(force_start: bool = false) -> void:
  328. if _moving_body:
  329. remove_child(_moving_body)
  330. _moving_body.queue_free()
  331. _moving_body = null
  332. if _test_all_angles:
  333. var angle := rad_to_deg(_platform_body.rotation)
  334. if angle >= _platform_angle + TEST_ALL_ANGLES_MAX:
  335. _platform_body.rotation = deg_to_rad(_platform_angle)
  336. _lock_controls = true
  337. $Controls/PlatformAngle/HSlider.value = _platform_angle
  338. _lock_controls = false
  339. _test_all_angles = false
  340. Log.print_log("*** Done all angles")
  341. else:
  342. angle = _platform_angle + _test_step * TEST_ALL_ANGLES_STEP
  343. _platform_body.rotation = deg_to_rad(angle)
  344. _lock_controls = true
  345. $Controls/PlatformAngle/HSlider.value = angle
  346. _lock_controls = false
  347. _start_test()
  348. elif force_start:
  349. _start_test()
  350. func _on_contact_detected(_body: PhysicsBody2D) -> void:
  351. if _contact_detected or _target_entered:
  352. return
  353. _contact_detected = true
  354. _test_passed = _should_collide()
  355. _set_result()
  356. await _on_timeout()
  357. func _on_target_entered(_body: PhysicsBody2D) -> void:
  358. if _body != _moving_body:
  359. return
  360. if _contact_detected or _target_entered:
  361. return
  362. _target_entered = true
  363. _test_passed = not _should_collide()
  364. _set_result()
  365. await _on_timeout()
  366. func _should_collide() -> bool:
  367. var platform_rotation := roundf(rad_to_deg(_platform_body.rotation))
  368. var angle := fposmod(platform_rotation, 360)
  369. return angle > 180
  370. func _on_timeout() -> void:
  371. cancel_timer()
  372. if $Timer.is_stopped():
  373. return
  374. $Timer.stop()
  375. if _test_canceled:
  376. test_done.emit()
  377. all_tests_done.emit()
  378. return
  379. if not _contact_detected and not _target_entered:
  380. Log.print_log("Test TIMEOUT")
  381. _set_result()
  382. await start_timer(0.5).timeout
  383. if _test_canceled:
  384. test_done.emit()
  385. all_tests_done.emit()
  386. return
  387. var was_all_angles := _test_all_angles
  388. _next_test()
  389. test_done.emit()
  390. if was_all_angles and not _test_all_angles:
  391. all_tests_done.emit()
  392. func _set_result() -> void:
  393. var result := ""
  394. if _test_passed:
  395. result = "PASSED"
  396. $LabelResult.self_modulate = Color.GREEN
  397. else:
  398. result = "FAILED"
  399. $LabelResult.self_modulate = Color.RED
  400. $LabelResult.text = result
  401. var platform_angle := rad_to_deg(_platform_body.rotation)
  402. result += ": size=%.1f, angle=%.1f, body angle=%.1f" % [_platform_size, platform_angle, _body_angle]
  403. Log.print_log("Test %s" % result)