Camera control

extends Camera2D
class_name LevelCamera

@onready var starting_pos : Vector2
@export var starting_zoom : float = 0.5
@onready var current_zoom : Vector2
@onready var zoom_tween : Tween
@onready var margin = Vector2(700,600)
@onready var screen_size = get_viewport_rect().size

@export var multi_max_zoom = 1
@export var multi_min_zoom = .15
@onready var framed_rect : Rect2
@onready var force_zoom_out : bool = false
@onready var debug_text : String

@onready var can_move : bool = true

@onready var target_player : Node2D
@onready var target : set = set_target
@onready var level_player
@onready var over_target : bool = false

@onready var target_list = []

@onready var recover_control_time = 1.0

@onready var speed_x :float = 2.0
@onready var speed_y : float = 10.0

@export var random_shake_strength : float = 30.0
@export var shake_dekay_rate : float = 5.0
@onready var rand_number = RandomNumberGenerator.new()
var shake_strength : float = 0.0


func _ready():
	rand_number.randomize()
	#without it, the camera lerps pos from the center of scene to the pos (in editor) setted.
	reset_smoothing()
	starting_pos = global_position
	
	#this is just to me, to remember set the camera group in each level.
	if !is_in_group("LevelCamera"):
		queue_free()
		
	zoom = Vector2(starting_zoom, starting_zoom)
	current_zoom = zoom
	make_current()

func _process(_delta: float) -> void:
	queue_redraw()


func _physics_process(delta):
	if can_move:
		if target_list.size() > 1:
			follow_multitarget(target_list, delta)
		
		else:
			follow_target(target, delta)
			if zoom != current_zoom:
				zoom = lerp (zoom, current_zoom, 0.25)

		if shake_strength > 0.1:
			shake_strength = lerp(shake_strength, 0.0 , shake_dekay_rate * delta)
			offset = get_random_offset()

func add_target(new_target):
	if target_list.has(new_target) == false:
		target_list.append(new_target)


func remove_target(r_target):
	target_list.erase(r_target)



func set_target(new_target):
	#set a new and single target
	target = new_target
	target_list.clear()
	target_list.append(target)
	
	if new_target != target_player:
		level_player.freeze_player(true)
		
	if new_target == target_player:
		if level_player:
			if level_player.pause_freeze == true:
				await get_tree().create_timer(recover_control_time).timeout
				level_player.freeze_player(false)


func recover_control():
	level_player.freeze_player(false)


func follow_target(new_target, delta):
	var curr_pos = global_position
	if new_target != null:
		global_position.x = lerp(curr_pos.x, new_target.global_position.x, speed_x * delta)
		global_position.y = lerp(curr_pos.y, new_target.global_position.y, speed_y * delta)
		var distance = global_position.distance_to(new_target.global_position)
		if distance < 20.0:
			over_target = true
		else:
			over_target = false


func follow_multitarget(t_list, delta):
	
	#all the part about getting the position of the targets centering camera on them.
	var pos_now = global_position
	var new_pos = Vector2.ZERO
	
	for t in t_list:
		new_pos += t.global_position
	new_pos /= t_list.size()
	
	global_position.x = lerp(pos_now.x, new_pos.x, speed_x * delta)
	global_position.y = lerp(pos_now.y, new_pos.y, speed_y * delta)
	
	#now is the zoom part
	#current_zoom is the max value of zoom the camera can do at this point and change over level. 
	var _multi_max_zoom = min(multi_max_zoom, current_zoom.x)

	framed_rect = build_rect(new_pos, t_list)

	var _screen_size = ((get_viewport().get_visible_rect().size)/zoom) / 2
	
	var n_zoom
	var dead_zone = 1000
	var zoom_speed = 0.25
	
	#target rect bigger than screen, need to zoom out, decreasing the zoom value
	if framed_rect.size.length() > _screen_size.length():
		debug_text = "bigger than screen "
		var n_size : Vector2 = framed_rect.size / screen_size

		n_zoom = clamp(min(min(n_size.x, n_size.y), multi_min_zoom), multi_min_zoom, _multi_max_zoom)
		zoom = lerp (zoom, Vector2.ONE * n_zoom, zoom_speed)

	#target rect + dead zone is smaller than screen, need to zoom in, increasing the zoom value 
	elif framed_rect.size.length() + dead_zone < _screen_size.length():
		debug_text = "way smaller than the screen"
		n_zoom = max(current_zoom.x, multi_max_zoom)
		zoom = lerp (zoom, Vector2.ONE * n_zoom, zoom_speed)
	
	#smaller but not much so do nothing
	else: 
		debug_text = "smaller but not that much"
		pass

	print(debug_text)
	
func build_rect(_pos_now, _t_list):
	#now make a rect around all targets
	var _rect = Rect2(_pos_now, Vector2.ONE)
	for _t in _t_list:
		_rect = _rect.expand(_t.global_position)
	_rect = _rect.grow_individual(margin.x, margin.y, margin.x, margin.y)

	return _rect


func tween_zoom(new_zoom, speed):
	if current_zoom == new_zoom:
		pass
	else:
		zoom_tween = get_tree().create_tween()
		zoom_tween.tween_property(self, "zoom", new_zoom, speed).set_trans(Tween.TRANS_QUAD)
		current_zoom = new_zoom


func shake_cam(force : float):
	shake_strength = force


func get_random_offset():
	var new_shake = Vector2(rand_number.randf_range(-shake_strength, shake_strength), rand_number.randf_range(-shake_strength, shake_strength))
	return new_shake


func reset_camera():
	global_position = starting_pos
	reset_smoothing()
	zoom = Vector2(starting_zoom, starting_zoom)
	current_zoom = zoom
	make_current()
	

func _draw():
	
	draw_set_transform_matrix(global_transform.affine_inverse())
	
	#draw a white dot and line from targets to camera center
	if target_list.size() > 1:
		for t in target_list:
			draw_circle(t.global_position, 30, Color.WHITE)
			draw_line(t.global_position, global_position, Color.WHITE, 30)
		
		#Draw a red circle on camera center position
		draw_circle(global_position, 30, Color.RED)
		
		#Draw a magenta rect of all targets + an extra marging
		draw_rect(framed_rect, Color.MAGENTA, false, 30)
		
		#draw another rect with  the adiction of the dead zone of 500px bigger than the targets rect
		var dead_zone_rect = framed_rect.grow(-500)
		draw_rect(dead_zone_rect, Color.YELLOW, false, 20)
		
		#this is a try to draw the camera rect, but it getting a different size. why? I don't know
		var camera_view = get_viewport().get_visible_rect().size
		var camera_rect = Rect2(global_position - (camera_view / 2), camera_view)
		var screen_s = ((get_viewport().get_visible_rect().size)/zoom) / 2
		#why two divisions made the number bigger? I also don't know
		var n_x = (screen_s.x - 1920)# / 2
		var n_y = (screen_s.y - 1080)# / 2
		camera_rect = camera_rect.grow_individual(n_x, n_y, n_x, n_y)
		
		draw_rect(camera_rect, Color.BLUE, false, 20)
Powered by Codespace