Files
godot-demo-projects/viewport/dynamic_split_screen/camera_controller.gd
2023-01-17 21:41:02 +01:00

110 lines
4.1 KiB
GDScript

extends Node3D
# Handle the motion of both player cameras as well as communication with the
# SplitScreen shader to achieve the dynamic split screen effet
#
# Cameras are place on the segment joining the two players, either in the middle
# if players are close enough or at a fixed distance if they are not.
# In the first case, both cameras being at the same location, only the view of
# the first one is used for the entire screen thus allowing the players to play
# on a unsplit screen.
# In the second case, the screen is split in two with a line perpendicular to the
# segement joining the two players.
#
# The points of customization are:
# max_separation: the distance between players at which the view starts to split
# split_line_thickness: the thickness of the split line in pixels
# split_line_color: color of the split line
# adaptive_split_line_thickness: if true, the split line thickness will vary
# depending on the distance between players. If false, the thickness will
# be constant and equal to split_line_thickness
@export var max_separation: float = 20.0
@export var split_line_thickness: float = 3.0
@export var split_line_color: Color = Color.BLACK
@export var adaptive_split_line_thickness: bool = true
@onready var player1 = $"../Player1"
@onready var player2 = $"../Player2"
@onready var view = $View
@onready var viewport1 = $Viewport1
@onready var viewport2 = $Viewport2
@onready var camera1 = viewport1.get_node(^"Camera1")
@onready var camera2 = viewport2.get_node(^"Camera2")
var viewport_base_height = ProjectSettings.get_setting("display/window/size/viewport_height")
func _ready():
_on_size_changed()
_update_splitscreen()
get_viewport().size_changed.connect(self._on_size_changed)
view.material.set_shader_parameter("viewport1", viewport1.get_texture())
view.material.set_shader_parameter("viewport2", viewport2.get_texture())
func _process(_delta):
_move_cameras()
_update_splitscreen()
func _move_cameras():
var position_difference = _compute_position_difference_in_world()
var distance = clamp(_compute_horizontal_length(position_difference), 0, max_separation)
position_difference = position_difference.normalized() * distance
camera1.position.x = player1.position.x + position_difference.x / 2.0
camera1.position.z = player1.position.z + position_difference.z / 2.0
camera2.position.x = player2.position.x - position_difference.x / 2.0
camera2.position.z = player2.position.z - position_difference.z / 2.0
func _update_splitscreen():
var screen_size = get_viewport().get_visible_rect().size
var player1_position = camera1.unproject_position(player1.position) / screen_size
var player2_position = camera2.unproject_position(player2.position) / screen_size
var thickness
if adaptive_split_line_thickness:
var position_difference = _compute_position_difference_in_world()
var distance = _compute_horizontal_length(position_difference)
thickness = lerpf(0, split_line_thickness, (distance - max_separation) / max_separation)
thickness = clampf(thickness, 0, split_line_thickness)
else:
thickness = split_line_thickness
view.material.set_shader_parameter("split_active", _get_split_state())
view.material.set_shader_parameter("player1_position", player1_position)
view.material.set_shader_parameter("player2_position", player2_position)
view.material.set_shader_parameter("split_line_thickness", thickness)
view.material.set_shader_parameter("split_line_color", split_line_color)
# Split screen is active if players are too far apart from each other.
# Only the horizontal components (x, z) are used for distance computation
func _get_split_state():
var position_difference = _compute_position_difference_in_world()
var separation_distance = _compute_horizontal_length(position_difference)
return separation_distance > max_separation
func _on_size_changed():
var screen_size = get_viewport().get_visible_rect().size
$Viewport1.size = screen_size
$Viewport2.size = screen_size
view.material.set_shader_parameter("viewport_size", screen_size)
func _compute_position_difference_in_world():
return player2.position - player1.position
func _compute_horizontal_length(vec):
return Vector2(vec.x, vec.z).length()