Installed SmartCamera2D plugin.

This commit is contained in:
gdz
2025-12-07 21:32:24 +01:00
parent 269f2daf6c
commit 9429d48e32
13 changed files with 321 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
@tool
extends Camera2D
class_name SmartCamera2D
enum TARGET_MODE {
PARENT,
SINGLE,
MULTIPLE,
GROUP
}
@export_category("GENERAL CONFIG")
@export var default_zoom: Vector2 = Vector2.ONE
@export_category("TARGET CONFIG")
@export var target_mode: TARGET_MODE:
set(value):
target_mode = value
notify_property_list_changed()
@export var target: NodePath
@export var targets: Array[NodePath]
@export var group_name: String
@export var adjust_zoom: bool = true
@export_range(1.0, 999.9) var adjust_zoom_margin: float
@export_range(0.001, 999.9) var adjust_zoom_min: float = 0.1
@export_range(0.001, 999.9) var adjust_zoom_max: float = 1.0
@export_range(-1, 999, 1) var adjust_zoom_priority_node_index: int = -1
@export_category("EFFECTS CONFIG")
@export var effects_layer: int = 2
var target_node: Node2D
var target_nodes: Array[Node]
var extra_position = Vector2.ZERO
@onready var refresh_target_timer = Timer.new()
@onready var canvas_layer = CanvasLayer.new()
@onready var camera_flash: ColorRect
func _ready():
if Engine.is_editor_hint():
return
CameraControl.apply_flash.connect(apply_camera_flash)
CameraControl.apply_shake.connect(apply_camera_shake)
zoom = default_zoom
_make_refresh_timer()
_make_effects_layers()
if target_mode == TARGET_MODE.SINGLE:
ensure_target_node()
elif target_mode == TARGET_MODE.MULTIPLE:
ensure_target_nodes()
call_deferred("_refresh_targets")
func _make_refresh_timer():
refresh_target_timer.connect("timeout", _refresh_targets)
refresh_target_timer.wait_time = 0.3
add_child(refresh_target_timer)
refresh_target_timer.start()
func _make_effects_layers():
canvas_layer.layer = effects_layer
add_child(canvas_layer)
camera_flash = create_color_rect()
func apply_camera_shake(force: float = 2.0, duration = 0.8):
var tween = create_tween()
var step_dur = duration / 5.0
tween.tween_property(self, "extra_position", Vector2(-force, force), step_dur)
tween.tween_property(self, "extra_position", Vector2(force, force), step_dur)
tween.tween_property(self, "extra_position", Vector2(force, -force), step_dur)
tween.tween_property(self, "extra_position", Vector2(-force, -force), step_dur)
tween.tween_property(self, "extra_position", Vector2.ZERO, step_dur)
func apply_camera_flash(color: Color, duration = 0.3):
var screen_size = get_viewport_rect().size
camera_flash.size = screen_size
var tween = create_tween()
var final_color = Color(color, 0.0)
var initial_color = Color(color, 0.2)
camera_flash.visible = true
tween.tween_property(camera_flash, "color", final_color, duration) \
.from(initial_color)
await tween.finished
camera_flash.visible = false
func _process(delta):
if not Engine.is_editor_hint():
_sanitize_targets()
if target_mode == TARGET_MODE.PARENT:
position = Vector2.ZERO
elif target_mode == TARGET_MODE.SINGLE:
_process_target_single(delta)
elif target_mode == TARGET_MODE.MULTIPLE:
_process_target_multiple(delta)
elif target_mode == TARGET_MODE.GROUP:
_process_target_group(delta)
position = position + extra_position
func _process_target_single(delta):
if not target_node:
printerr("[SmartCamera2D] TARGET NODE IS NULL")
return
global_position = target_node.global_position
func _process_target_multiple(delta):
if target_nodes.size() < 0:
printerr("[SmartCamera2D] NOT ENOUGH TARGET NODES")
return
adjust_camera_zoom()
func _process_target_group(delta):
if target_nodes.size() < 0:
printerr("[SmartCamera2D] NOT ENOUGH TARGET NODES")
return
adjust_camera_zoom()
func ensure_target_node():
if target_node: return
target_node = get_node(target)
func ensure_target_nodes():
target_nodes.clear()
for path in targets:
target_nodes.append(get_node(path))
func adjust_camera_zoom():
if target_nodes.is_empty():
return
if target_nodes.size() == 1:
zoom = default_zoom
global_position = target_nodes[0].global_position
return
var screen_size = get_viewport_rect().size
var min_pos = target_nodes[0].global_position
var max_pos = target_nodes[0].global_position
for node in target_nodes:
min_pos = min_pos.min(node.global_position)
max_pos = max_pos.max(node.global_position)
var size = max_pos - min_pos
var zoom_x = screen_size.x / size.x if size.x != 0 else 1.0
var zoom_y = screen_size.y / size.y if size.y != 0 else 1.0
var zoom_safe = clamp(min(zoom_x, zoom_y) / adjust_zoom_margin, adjust_zoom_min, adjust_zoom_max)
zoom = Vector2(zoom_safe, zoom_safe)
var center_position = (min_pos + max_pos) / 2.0
global_position = center_position
var priority_i = adjust_zoom_priority_node_index
if priority_i > -1:
if not is_point_visible(target_nodes[priority_i].global_position, screen_size):
center_position = target_nodes[priority_i].global_position
func is_point_visible(point: Vector2, screen_size: Vector2) -> bool:
var half_screen = (screen_size / 2.0) / zoom
var camera_min = global_position - half_screen
var camera_max = global_position + half_screen
return point.x >= camera_min.x and point.x <= camera_max.x and point.y >= camera_min.y and point.y <= camera_max.y
func create_color_rect():
var color_rect = ColorRect.new()
color_rect.visible = false
color_rect.color = Color.TRANSPARENT
color_rect.size_flags_horizontal = Control.SIZE_EXPAND_FILL
color_rect.size_flags_vertical = Control.SIZE_EXPAND_FILL
color_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
canvas_layer.add_child(color_rect)
return color_rect
func _refresh_targets():
if target_mode == TARGET_MODE.SINGLE:
if not target_node:
target_node = get_node(target)
elif target_mode == TARGET_MODE.GROUP:
target_nodes = get_tree().get_nodes_in_group(group_name)
func _sanitize_targets():
if target_node and target_node.is_queued_for_deletion():
target_node = null
if not target_nodes.is_empty():
target_nodes = target_nodes.filter(_filter_existing_node)
func _filter_existing_node(variant) -> bool:
if variant == null:
return false
if not variant is Node2D:
return false
if variant is Node2D and variant.is_queued_for_deletion():
return false
return true
func _validate_property(property: Dictionary):
var multiple_properties = [
"adjust_zoom", "zoom_margin", "adjust_zoom_margin",
"adjust_zoom_min", "adjust_zoom_max", "adjust_zoom_priority_node_index"
]
if property.name == "target" and target_mode != TARGET_MODE.SINGLE:
property.usage = PROPERTY_USAGE_NO_EDITOR
if property.name == "group_name" and target_mode != TARGET_MODE.GROUP:
property.usage = PROPERTY_USAGE_NO_EDITOR
if property.name == "targets" and target_mode != TARGET_MODE.MULTIPLE:
property.usage = PROPERTY_USAGE_NO_EDITOR
if property.name in multiple_properties and target_mode not in [TARGET_MODE.MULTIPLE, TARGET_MODE.GROUP]:
property.usage = PROPERTY_USAGE_NO_EDITOR