Installed SmartCamera2D plugin.
This commit is contained in:
17
Scenes/Main/camera_controller.gd
Normal file
17
Scenes/Main/camera_controller.gd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready() -> void:
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if Input.is_action_pressed("MoveUp"):
|
||||||
|
global_position += Vector2.UP * 2
|
||||||
|
if Input.is_action_pressed("MoveDown"):
|
||||||
|
global_position += Vector2.DOWN * 2
|
||||||
|
if Input.is_action_pressed("MoveLeft"):
|
||||||
|
global_position += Vector2.LEFT * 2
|
||||||
|
if Input.is_action_pressed("MoveRight"):
|
||||||
|
global_position += Vector2.RIGHT * 2
|
||||||
1
Scenes/Main/camera_controller.gd.uid
Normal file
1
Scenes/Main/camera_controller.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://brublmhrsdc7l
|
||||||
1
addons/smartcamera2D/Camera2D.svg
Normal file
1
addons/smartcamera2D/Camera2D.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#8da5f3" d="M9 2a3 3 0 0 0-3 2.777 3 3 0 1 0-3 5.047V12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-1l3 2V7l-3 2V7.23A3 3 0 0 0 9 2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 203 B |
37
addons/smartcamera2D/Camera2D.svg.import
Normal file
37
addons/smartcamera2D/Camera2D.svg.import
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bq6ud1dmn8fds"
|
||||||
|
path="res://.godot/imported/Camera2D.svg-e2316bbab95f65a3786cbb6cb8741380.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/smartcamera2D/Camera2D.svg"
|
||||||
|
dest_files=["res://.godot/imported/Camera2D.svg-e2316bbab95f65a3786cbb6cb8741380.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
14
addons/smartcamera2D/CameraControl.gd
Normal file
14
addons/smartcamera2D/CameraControl.gd
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
signal apply_flash
|
||||||
|
signal apply_shake
|
||||||
|
|
||||||
|
var active = true
|
||||||
|
|
||||||
|
func apply_camera_flash(color: Color, duration = 0.3):
|
||||||
|
if not active: return
|
||||||
|
apply_flash.emit(color, duration)
|
||||||
|
|
||||||
|
func apply_camera_shake(force: float = 2.0, duration = 0.4):
|
||||||
|
if not active: return
|
||||||
|
apply_shake.emit(force, duration)
|
||||||
1
addons/smartcamera2D/CameraControl.gd.uid
Normal file
1
addons/smartcamera2D/CameraControl.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cl0y12rxbo7ub
|
||||||
21
addons/smartcamera2D/LICENSE
Normal file
21
addons/smartcamera2D/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 AndreMicheletti
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
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
|
||||||
1
addons/smartcamera2D/SmartCamera2D.gd.uid
Normal file
1
addons/smartcamera2D/SmartCamera2D.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://xrddv2epi3ty
|
||||||
7
addons/smartcamera2D/plugin.cfg
Normal file
7
addons/smartcamera2D/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="smartcamera2D"
|
||||||
|
description="A plug-and-play camera controller with smooth follow, screen shake, zoom"
|
||||||
|
author="Async Studio"
|
||||||
|
version="1.0"
|
||||||
|
script="plugin.gd"
|
||||||
14
addons/smartcamera2D/plugin.gd
Normal file
14
addons/smartcamera2D/plugin.gd
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
# Initialization of the plugin goes here.
|
||||||
|
# Add the new type with a name, a parent type, a script and an icon.
|
||||||
|
add_custom_type("SmartCamera2D", "Camera2D", preload("SmartCamera2D.gd"), preload("Camera2D.svg"))
|
||||||
|
add_autoload_singleton("CameraControl", "CameraControl.gd")
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
# Clean-up of the plugin goes here.
|
||||||
|
# Always remember to remove it from the engine when deactivated.
|
||||||
|
remove_custom_type("SmartCamera2D")
|
||||||
|
remove_autoload_singleton("CameraControl")
|
||||||
1
addons/smartcamera2D/plugin.gd.uid
Normal file
1
addons/smartcamera2D/plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b6pcau1vjhp20
|
||||||
Reference in New Issue
Block a user