camera_controller.gd 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. extends Node3D
  2. # Handle the motion of both player cameras as well as communication with the
  3. # SplitScreen shader to achieve the dynamic split screen effet
  4. #
  5. # Cameras are place on the segment joining the two players, either in the middle
  6. # if players are close enough or at a fixed distance if they are not.
  7. # In the first case, both cameras being at the same location, only the view of
  8. # the first one is used for the entire screen thus allowing the players to play
  9. # on a unsplit screen.
  10. # In the second case, the screen is split in two with a line perpendicular to the
  11. # segement joining the two players.
  12. #
  13. # The points of customization are:
  14. # max_separation: the distance between players at which the view starts to split
  15. # split_line_thickness: the thickness of the split line in pixels
  16. # split_line_color: color of the split line
  17. # adaptive_split_line_thickness: if true, the split line thickness will vary
  18. # depending on the distance between players. If false, the thickness will
  19. # be constant and equal to split_line_thickness
  20. @export var max_separation := 20.0
  21. @export var split_line_thickness := 3.0
  22. @export var split_line_color := Color.BLACK
  23. @export var adaptive_split_line_thickness := true
  24. @onready var player1: CharacterBody3D = $"../Player1"
  25. @onready var player2: CharacterBody3D = $"../Player2"
  26. @onready var view: TextureRect = $View
  27. @onready var viewport1: SubViewport = $Viewport1
  28. @onready var viewport2: SubViewport = $Viewport2
  29. @onready var camera1: Camera3D = viewport1.get_node(^"Camera1")
  30. @onready var camera2: Camera3D = viewport2.get_node(^"Camera2")
  31. var viewport_base_height := int(ProjectSettings.get_setting("display/window/size/viewport_height"))
  32. func _ready() -> void:
  33. _on_size_changed()
  34. _update_splitscreen()
  35. get_viewport().size_changed.connect(_on_size_changed)
  36. view.material.set_shader_parameter("viewport1", viewport1.get_texture())
  37. view.material.set_shader_parameter("viewport2", viewport2.get_texture())
  38. func _process(_delta: float) -> void:
  39. _move_cameras()
  40. _update_splitscreen()
  41. func _move_cameras() -> void:
  42. var position_difference := _get_position_difference_in_world()
  43. var distance := clampf(_get_horizontal_length(position_difference), 0, max_separation)
  44. position_difference = position_difference.normalized() * distance
  45. camera1.position.x = player1.position.x + position_difference.x / 2.0
  46. camera1.position.z = player1.position.z + position_difference.z / 2.0
  47. camera2.position.x = player2.position.x - position_difference.x / 2.0
  48. camera2.position.z = player2.position.z - position_difference.z / 2.0
  49. func _update_splitscreen() -> void:
  50. var screen_size := get_viewport().get_visible_rect().size
  51. var player1_position := camera1.unproject_position(player1.position) / screen_size
  52. var player2_position := camera2.unproject_position(player2.position) / screen_size
  53. var thickness := 0.0
  54. if adaptive_split_line_thickness:
  55. var position_difference := _get_position_difference_in_world()
  56. var distance := _get_horizontal_length(position_difference)
  57. thickness = lerpf(0, split_line_thickness, (distance - max_separation) / max_separation)
  58. thickness = clampf(thickness, 0, split_line_thickness)
  59. else:
  60. thickness = split_line_thickness
  61. view.material.set_shader_parameter("split_active", _is_split_state())
  62. view.material.set_shader_parameter("player1_position", player1_position)
  63. view.material.set_shader_parameter("player2_position", player2_position)
  64. view.material.set_shader_parameter("split_line_thickness", thickness)
  65. view.material.set_shader_parameter("split_line_color", split_line_color)
  66. ## Returns `true` if split screen is active (which occurs when players are
  67. ## too far apart from each other), `false` otherwise.
  68. ## Only the horizontal components (x, z) are used for distance computation.
  69. func _is_split_state() -> bool:
  70. var position_difference := _get_position_difference_in_world()
  71. var separation_distance := _get_horizontal_length(position_difference)
  72. return separation_distance > max_separation
  73. func _on_size_changed() -> void:
  74. var screen_size := get_viewport().get_visible_rect().size
  75. $Viewport1.size = screen_size
  76. $Viewport2.size = screen_size
  77. view.material.set_shader_parameter("viewport_size", screen_size)
  78. func _get_position_difference_in_world() -> Vector3:
  79. return player2.position - player1.position
  80. func _get_horizontal_length(vec: Vector3) -> float:
  81. return Vector2(vec.x, vec.z).length()