|
| 1 | +# SPDX-FileCopyrightText: The Threadbare Authors |
| 2 | +# SPDX-License-Identifier: MPL-2.0 |
| 3 | +class_name FrameCameraBehavior |
| 4 | +extends Node2D |
| 5 | +## Pan a camera to frame a target. |
| 6 | +## |
| 7 | +## If the target is within a safe region, make the camera work as usual, |
| 8 | +## centered in [member main_target], which is usually the camera parent node. |
| 9 | +## [br][br] |
| 10 | +## The safe region is defined by the [member target_drag_left_margin], |
| 11 | +## [member target_drag_top_margin], [member target_drag_right_margin] and |
| 12 | +## [member target_drag_bottom_margin] margins. |
| 13 | +## [br][br] |
| 14 | + |
| 15 | +## The controlled camera. |
| 16 | +@export var camera: Camera2D: |
| 17 | + set = _set_camera |
| 18 | + |
| 19 | +## The camera will be centered in this node if [member frame_target] is on frame. |
| 20 | +## This will be set to the camera parent automatically, if not set. |
| 21 | +@export var main_target: Node2D: |
| 22 | + set = _set_main_target |
| 23 | + |
| 24 | +## The target to frame. |
| 25 | +@export var frame_target: Node2D |
| 26 | + |
| 27 | +## The [member Camera2D.drag_horizontal_enabled] when [member frame_target] is off frame. |
| 28 | +@export var target_drag_horizontal_enabled := true |
| 29 | + |
| 30 | +## The [member Camera2D.target_drag_vertical_enabled] when [member frame_target] is off frame. |
| 31 | +@export var target_drag_vertical_enabled := true |
| 32 | + |
| 33 | +## The [member Camera2D.target_drag_left_margin] when [member frame_target] is off frame. |
| 34 | +## [br] Also defines the safe region. |
| 35 | +@export var target_drag_left_margin := 0.5 |
| 36 | + |
| 37 | +## The [member Camera2D.target_drag_top_margin] when [member frame_target] is off frame. |
| 38 | +## [br] Also defines the safe region. |
| 39 | +@export var target_drag_top_margin := 0.5 |
| 40 | + |
| 41 | +## The [member Camera2D.target_drag_right_margin] when [member frame_target] is off frame. |
| 42 | +## [br] Also defines the safe region. |
| 43 | +@export var target_drag_right_margin := 0.5 |
| 44 | + |
| 45 | +## The [member Camera2D.target_drag_bottom_margin] when [member frame_target] is off frame. |
| 46 | +## [br] Also defines the safe region. |
| 47 | +@export var target_drag_bottom_margin := 0.5 |
| 48 | + |
| 49 | +## The safe region. |
| 50 | +var safe_region_rect: Rect2 |
| 51 | + |
| 52 | +var _initial_drag_horizontal_enabled: bool |
| 53 | +var _initial_drag_vertical_enabled: bool |
| 54 | +var _initial_drag_left_margin: float |
| 55 | +var _initial_drag_top_margin: float |
| 56 | +var _initial_drag_right_margin: float |
| 57 | +var _initial_drag_bottom_margin: float |
| 58 | + |
| 59 | + |
| 60 | +func _enter_tree() -> void: |
| 61 | + if not camera and get_parent() is Camera2D: |
| 62 | + camera = get_parent() |
| 63 | + if not main_target and get_parent() and get_parent().get_parent() is Node2D: |
| 64 | + main_target = get_parent().get_parent() |
| 65 | + |
| 66 | + |
| 67 | +func _set_camera(new_camera: Camera2D) -> void: |
| 68 | + camera = new_camera |
| 69 | + update_configuration_warnings() |
| 70 | + |
| 71 | + |
| 72 | +func _set_main_target(new_main_target: Node2D) -> void: |
| 73 | + main_target = new_main_target |
| 74 | + update_configuration_warnings() |
| 75 | + |
| 76 | + |
| 77 | +func _get_configuration_warnings() -> PackedStringArray: |
| 78 | + var warnings: PackedStringArray |
| 79 | + if not camera: |
| 80 | + warnings.append("Camera must be set.") |
| 81 | + if not main_target: |
| 82 | + warnings.append("Consider setting Main Target.") |
| 83 | + if not frame_target: |
| 84 | + warnings.append("Consider setting Frame Target.") |
| 85 | + return warnings |
| 86 | + |
| 87 | + |
| 88 | +func _ready() -> void: |
| 89 | + if Engine.is_editor_hint() or not camera.enabled or not camera.is_current(): |
| 90 | + set_process(false) |
| 91 | + |
| 92 | + if not Engine.is_editor_hint(): |
| 93 | + _initial_drag_horizontal_enabled = camera.drag_horizontal_enabled |
| 94 | + _initial_drag_vertical_enabled = camera.drag_vertical_enabled |
| 95 | + _initial_drag_left_margin = camera.drag_left_margin |
| 96 | + _initial_drag_top_margin = camera.drag_top_margin |
| 97 | + _initial_drag_right_margin = camera.drag_right_margin |
| 98 | + _initial_drag_bottom_margin = camera.drag_bottom_margin |
| 99 | + |
| 100 | + safe_region_rect = _calculate_safe_region() |
| 101 | + |
| 102 | + |
| 103 | +func _calculate_safe_region() -> Rect2: |
| 104 | + var camera_rect := Rect2( |
| 105 | + Vector2.ZERO, |
| 106 | + Vector2( |
| 107 | + ProjectSettings.get_setting("display/window/size/viewport_width"), |
| 108 | + ProjectSettings.get_setting("display/window/size/viewport_height"), |
| 109 | + ) |
| 110 | + ) |
| 111 | + # Shrink the camera rect according to the margins defined. |
| 112 | + # A margin of 1 doesn't shrink, leaves the side in the same place. |
| 113 | + return ( |
| 114 | + camera_rect |
| 115 | + . grow_individual( |
| 116 | + -1 * (1.0 - target_drag_left_margin) * camera_rect.size.x / 2, |
| 117 | + -1 * (1.0 - target_drag_top_margin) * camera_rect.size.y / 2, |
| 118 | + -1 * (1.0 - target_drag_right_margin) * camera_rect.size.x / 2, |
| 119 | + -1 * (1.0 - target_drag_bottom_margin) * camera_rect.size.y / 2, |
| 120 | + ) |
| 121 | + ) |
| 122 | + |
| 123 | + |
| 124 | +func _process(_delta: float) -> void: |
| 125 | + if not frame_target: |
| 126 | + return |
| 127 | + |
| 128 | + var rect := Rect2( |
| 129 | + main_target.global_position - safe_region_rect.size / 2, safe_region_rect.size |
| 130 | + ) |
| 131 | + |
| 132 | + if rect.has_point(frame_target.global_position): |
| 133 | + # The target is on frame, so set the camera initial settings |
| 134 | + # and the initial position. |
| 135 | + camera.global_position = main_target.global_position + position |
| 136 | + camera.drag_horizontal_enabled = _initial_drag_horizontal_enabled |
| 137 | + camera.drag_vertical_enabled = _initial_drag_vertical_enabled |
| 138 | + camera.drag_left_margin = _initial_drag_left_margin |
| 139 | + camera.drag_top_margin = _initial_drag_top_margin |
| 140 | + camera.drag_right_margin = _initial_drag_right_margin |
| 141 | + camera.drag_bottom_margin = _initial_drag_bottom_margin |
| 142 | + else: |
| 143 | + camera.global_position = frame_target.global_position |
| 144 | + camera.drag_horizontal_enabled = target_drag_horizontal_enabled |
| 145 | + camera.drag_vertical_enabled = target_drag_vertical_enabled |
| 146 | + camera.drag_left_margin = target_drag_left_margin |
| 147 | + camera.drag_top_margin = target_drag_top_margin |
| 148 | + camera.drag_right_margin = target_drag_right_margin |
| 149 | + camera.drag_bottom_margin = target_drag_bottom_margin |
0 commit comments