camera_controller.gd 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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: float = 20.0
  21. @export var split_line_thickness: float = 3.0
  22. @export var split_line_color: Color = Color.BLACK
  23. @export var adaptive_split_line_thickness: bool = true
  24. @onready var player1 = $"../Player1"
  25. @onready var player2 = $"../Player2"
  26. @onready var view = $View
  27. @onready var viewport1 = $Viewport1
  28. @onready var viewport2 = $Viewport2
  29. @onready var camera1 = viewport1.get_node(^"Camera1")
  30. @onready var camera2 = viewport2.get_node(^"Camera2")
  31. var viewport_base_height = ProjectSettings.get_setting("display/window/size/viewport_height")
  32. func _ready():
  33. _on_size_changed()
  34. _update_splitscreen()
  35. get_viewport().size_changed.connect(self._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):
  39. _move_cameras()
  40. _update_splitscreen()
  41. func _move_cameras():
  42. var position_difference = _compute_position_difference_in_world()
  43. var distance = clamp(_compute_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():
  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
  54. if adaptive_split_line_thickness:
  55. var position_difference = _compute_position_difference_in_world()
  56. var distance = _compute_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", _get_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. # Split screen is active if players are too far apart from each other.
  67. # Only the horizontal components (x, z) are used for distance computation
  68. func _get_split_state():
  69. var position_difference = _compute_position_difference_in_world()
  70. var separation_distance = _compute_horizontal_length(position_difference)
  71. return separation_distance > max_separation
  72. func _on_size_changed():
  73. var screen_size = get_viewport().get_visible_rect().size
  74. $Viewport1.size = screen_size
  75. $Viewport2.size = screen_size
  76. view.material.set_shader_parameter("viewport_size", screen_size)
  77. func _compute_position_difference_in_world():
  78. return player2.position - player1.position
  79. func _compute_horizontal_length(vec):
  80. return Vector2(vec.x, vec.z).length()