Installed SmartCamera2D plugin.
This commit is contained in:
205
addons/smartcamera2D/SmartCamera2D.gd
Normal file
205
addons/smartcamera2D/SmartCamera2D.gd
Normal 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
|
||||
Reference in New Issue
Block a user