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)