diff options
Diffstat (limited to 'addons/escoria-core/game/scenes/camera_player')
5 files changed, 576 insertions, 0 deletions
diff --git a/addons/escoria-core/game/scenes/camera_player/camera.tscn b/addons/escoria-core/game/scenes/camera_player/camera.tscn new file mode 100644 index 0000000..4dc2e9c --- /dev/null +++ b/addons/escoria-core/game/scenes/camera_player/camera.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://dmw5gicuenj53"] + +[ext_resource type="Script" uid="uid://b8xyisawuhtw3" path="res://addons/escoria-core/game/scenes/camera_player/esc_camera.gd" id="1"] + +[node name="camera" type="Camera2D"] +current = true +drag_horizontal_enabled = true +drag_vertical_enabled = true +script = ExtResource("1") diff --git a/addons/escoria-core/game/scenes/camera_player/esc_camera.gd b/addons/escoria-core/game/scenes/camera_player/esc_camera.gd new file mode 100644 index 0000000..6473121 --- /dev/null +++ b/addons/escoria-core/game/scenes/camera_player/esc_camera.gd @@ -0,0 +1,525 @@ +## Camera handling for Escoria scenes. +extends Camera2D +class_name ESCCamera + +## Reference to the tween node for animating camera movements. +var _tween: Tween3: + get = get_tween + +## Target position of the camera. +var _target: Vector2 = Vector2() + +## The object to follow. +var _follow_target: Node = null + +## Target zoom of the camera. +var _zoom_target: Vector2 + +## Prepare the tween for camera movement.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func _ready(): + _tween = Tween3.new(self) + _tween.finished.connect(_target_reached) + +## Update the position if the followed target is moving.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |_delta|`Variant`|Frame delta time|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func _process(_delta): + if is_instance_valid(_follow_target) and not _tween.is_running() and _follow_target.has_moved(): + self.global_position = _follow_target.global_position + +## Register this camera with the object manager so it can be used before being made active as part of the current scene tree.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |room|`Variant`|The room with which to register the camera|no|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func register(room = null): + escoria.object_manager.register_object( + ESCObject.new( + escoria.object_manager.CAMERA, + self + ), + room, + true + ) + +## The camera's tween instance.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns the camera's tween instance. (`Tween3`) +func get_tween() -> Tween3: + return _tween + +## Sets camera limits so it doesn't go out of the scene.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |limits|`ESCCameraLimits`|The limits to set|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func set_limits(limits: ESCCameraLimits): + self.limit_left = limits.limit_left + self.limit_right = limits.limit_right + self.limit_top = limits.limit_top + self.limit_bottom = limits.limit_bottom + +## Enable or disable drag margins for the camera.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |p_dm_h_enabled|`Variant`|Enable horizontal drag margin|yes|[br] +## |p_dm_v_enabled|`Variant`|Enable vertical drag margin|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func set_drag_margin_enabled(p_dm_h_enabled, p_dm_v_enabled): + self.drag_horizontal_enabled = p_dm_h_enabled + self.drag_vertical_enabled = p_dm_v_enabled + +## Set the target for the camera to move to.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |p_target|`Variant`|Object to target|yes|[br] +## |p_time|`float`|Number of seconds for the camera to reach the target|no|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func set_target(p_target, p_time : float = 0.0): + _resolve_target_and_zoom(p_target) + + escoria.logger.info( + self, + "Current camera position = %s." % str(self.global_position) + ) + + if p_time == 0.0: + self.global_position = _target + else: + # Need to wait a frame in order to ensure the screen centre position is + # recalculated. Also to allow any close-calls with the tween to finish. + await get_tree().process_frame + + if _tween.is_running(): + escoria.logger.debug( + self, + "set_target tween is still active: %f seconds of %s completed." % [ + _tween.get_total_elapsed_time(), + _tween.get_duration() + ] + ) + _tween.stop() + + set_drag_margin_enabled(false, false) + + _convert_current_global_pos_for_disabled_drag_margin() + _target = _convert_pos_for_disabled_drag_margin(_target) + + _tween.interpolate_property( + self, + "global_position", + self.global_position, + _target, + p_time, + Tween.TRANS_LINEAR, + Tween.EASE_IN_OUT + ) + _tween.play() + +## Set the camera zoom level.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |p_zoom_level|`float`|Zoom level to set|yes|[br] +## |p_time|`float`|Number of seconds for the camera to reach the zoom level|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func set_camera_zoom(p_zoom_level: float, p_time: float): + if p_zoom_level <= 0.0: + escoria.logger.error( + self, + "Tried to set negative or zero zoom level." + ) + + _zoom_target = Vector2(1, 1) * p_zoom_level + + if p_time == 0: + self.zoom = _zoom_target + else: + # Need to wait a frame in order to ensure the screen centre position is + # recalculated. Also to allow any close-calls with the tween to finish. + await get_tree().process_frame + + if _tween.is_running(): + escoria.logger.debug( + self, + "set_camera_zoom tween is still active: %f seconds of %s completed." % [ + _tween.get_total_elapsed_time(), + _tween.get_duration() + ] + ) + _tween.stop() + + set_drag_margin_enabled(false, false) + + _convert_current_global_pos_for_disabled_drag_margin() + + _tween.interpolate_property( + self, + "zoom", + self.zoom, + _zoom_target, + p_time, + Tween.TRANS_LINEAR, + Tween.EASE_IN_OUT + ) + _tween.play() + +## Push the camera towards the target in terms of position and zoom level using a given transition type and time. See https://docs.godotengine.org/en/stable/classes/class_tween.html#enumerations[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |p_target|`Variant`|Target to push to|yes|[br] +## |p_time|`float`|Number of seconds for the transition to take|no|[br] +## |p_type|`int`|Tween transition type|no|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func push(p_target, p_time: float = 0.0, p_type: int = 0): + _resolve_target_and_zoom(p_target) + + var push_target = null + + if _follow_target != null: + push_target = p_target.position + else: + push_target = _target + + if p_time == 0: + self.global_position = push_target + + if _zoom_target != Vector2(): + self.zoom = _zoom_target + else: + # Need to wait a frame in order to ensure the screen centre position is + # recalculated. Also to allow any close-calls with the tween to finish. + await get_tree().process_frame + + if _tween.is_running(): + escoria.logger.debug( + self, + "camera push tween is still active: %f seconds of %f completed." % [ + _tween.tell(), + _tween.get_runtime() + ] + ) + _tween.stop() + + if _zoom_target != Vector2(): + _tween.interpolate_property( + self, + "zoom", + self.zoom, + _zoom_target, + p_time, + p_type, + Tween.EASE_IN_OUT + ) + + set_drag_margin_enabled(false, false) + + _convert_current_global_pos_for_disabled_drag_margin() + + _tween.interpolate_property( + self, + "global_position", + self.global_position, + push_target, + p_time, + p_type, + Tween.EASE_IN_OUT + ) + + _tween.play() + +## Shift the camera by the given vector in a given time and using a specific Tween transition type. See https://docs.godotengine.org/en/stable/classes/class_tween.html#enumerations[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |p_target|`Vector2`|Vector to shift the camera by|yes|[br] +## |p_time|`float`|Number of seconds for the transition to take|yes|[br] +## |p_type|`int`|Tween transition type|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func shift(p_target: Vector2, p_time: float, p_type: int): + _follow_target = null + + var new_pos = self.global_position + p_target + _target = new_pos + + if _tween.is_running(): + # Need to wait a frame in order to ensure the screen centre position is + # recalculated. Also to allow any close-calls with the tween to finish. + await get_tree().process_frame + + escoria.logger.debug( + self, + "camera shift tween is still active: %f seconds of %f completed." % [ + _tween.tell(), + _tween.get_runtime() + ] + ) + _tween.stop() + + set_drag_margin_enabled(false, false) + + _convert_current_global_pos_for_disabled_drag_margin() + + _tween.interpolate_property( + self, + "global_position", + self.global_position, + _target, + p_time, + p_type, + Tween.EASE_IN_OUT + ) + _tween.play() + +## Checks whether the given point is contained within the viewport's limits. Note that this is different from the camera's limits when using anchor mode DRAG_CENTER.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |point|`Vector2`|Point to be tested against viewport limits.|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns a `bool` value. (`bool`) +func check_point_is_inside_viewport_limits(point: Vector2) -> bool: + var viewport_rect: Rect2 = get_viewport_rect() + var screen_half_size: Vector2 = viewport_rect.size * 0.5 + + var limits_to_test: Rect2 = Rect2( + limit_left + screen_half_size.x, + limit_top + screen_half_size.y, + limit_right - limit_left - viewport_rect.size.x + 1, + limit_bottom - limit_top - viewport_rect.size.y + 1 + ) + + return limits_to_test.has_point(point) + +## The inclusive minimum and maximum values for the x-component of the current valid viewport. Mainly used in any logging messages related to same. the current valid viewport.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns the inclusive minimum and maximum values for the x-component of the current valid viewport. Mainly used in any logging messages related to same. the current valid viewport. (`Array`) +func get_current_valid_viewport_values_x() -> Array: + var viewport_rect: Rect2 = get_viewport_rect() + + return [limit_left + viewport_rect.size.x * 0.5, limit_right - viewport_rect.size.x * 0.5] + +## The inclusive minimum and maximum values for the y-component of the current valid viewport. Mainly used in any logging messages related to same. the current valid viewport.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns the inclusive minimum and maximum values for the y-component of the current valid viewport. Mainly used in any logging messages related to same. the current valid viewport. (`Array`) +func get_current_valid_viewport_values_y() -> Array: + var viewport_rect: Rect2 = get_viewport_rect() + + return [limit_top + viewport_rect.size.y * 0.5, limit_bottom - viewport_rect.size.y * 0.5] + +## The camera's current limits as a Rect2. Mainly used in any logging messages related to same.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns the camera's current limits as a Rect2. Mainly used in any logging messages related to same. (`Rect2`) +func get_camera_limit_rect() -> Rect2: + return Rect2(limit_left, limit_top, limit_right - limit_left, limit_bottom - limit_top) + +## Used when drag margins are enabled. Clamps the camera so it respects the viewport limits inside the camera limits.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func clamp_to_viewport_limits() -> void: + var viewport_rect: Rect2 = get_viewport_rect() + + var cur_camera_pos: Vector2 = self.get_screen_center_position() + var ret_position: Vector2 = cur_camera_pos + + if cur_camera_pos.x - viewport_rect.size.x * 0.5 * zoom.x <= limit_left: + ret_position.x = limit_left + viewport_rect.size.x * 0.5 * zoom.x * (1 + drag_left_margin) + elif cur_camera_pos.x + viewport_rect.size.x * 0.5 * zoom.x >= limit_right: + ret_position.x = limit_right - viewport_rect.size.x * 0.5 * zoom.x * (1 + drag_right_margin) + + if cur_camera_pos.y - viewport_rect.size.y * 0.5 * zoom.y <= limit_top: + ret_position.y = limit_top + viewport_rect.size.y * 0.5 * zoom.y * (1 + drag_top_margin) + elif cur_camera_pos.y + viewport_rect.size.y * 0.5 * zoom.y >= limit_bottom: + ret_position.y = limit_bottom - viewport_rect.size.y * 0.5 * zoom.y * (1 + drag_bottom_margin) + + self.global_position = ret_position + +## Called when the camera's target is reached. Stops and resets the tween, and re-enables drag margins.[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func _target_reached(): + _tween.stop() + _tween.reset() + set_drag_margin_enabled(true, true) + +## Compensates the camera's current global_position when disabling drag margins. This helps to ensure that when we disable or enable drag margins that the position on the screen is maintained without the camera "jumping". (See https://github.com/godotengine/godot/blob/3.5/scene/2d/camera_2d.cpp for more details.)[br] +## [br] +## #### Parameters[br] +## [br] +## None. +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func _convert_current_global_pos_for_disabled_drag_margin() -> void: + var cur_camera_pos: Vector2 = self.get_screen_center_position() + var ret_position: Vector2 = _convert_pos_for_disabled_drag_margin(cur_camera_pos) + + self.global_position = ret_position + +## Converts the given position set with drag margins enabled to the same position when calculated with drag margins disabled. This is helpful for preventing the camera from "jumping" when disabling drag margins, e.g. in order to perform some camera translations/tweening. when rendered with drag margins disabled.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |pos|`Vector2`|Position to be converted.|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns a `Vector2` value. (`Vector2`) +func _convert_pos_for_disabled_drag_margin(pos: Vector2) -> Vector2: + var viewport_rect: Rect2 = get_viewport_rect() + var ret_position: Vector2 = pos + + # If the current calculated centre of the camera/viewport is close enough to + # the set camera limits (i.e. the centre is upto and including half the + # viewport's size to the limit being tested), then we make sure the + # global_position is at the same coordinates since Camera2D will recalculate + # that position to the exact same position (i.e. no funny math). + # Otherwise, we set the global_position to be the value that would allow + # Camera2D to convert it to the value of the current calculated centre. This + # compensates for the switch when disabling drag margins. + if ret_position.x - viewport_rect.size.x * 0.5 * zoom.x <= limit_left: + ret_position.x = limit_left + viewport_rect.size.x * 0.5 * zoom.x + elif ret_position.x + viewport_rect.size.x * 0.5 * zoom.x >= limit_right: + ret_position.x = limit_right - viewport_rect.size.x * 0.5 * zoom.x + + if ret_position.y - viewport_rect.size.y * 0.5 * zoom.y <= limit_top: + ret_position.y = limit_top + viewport_rect.size.y * 0.5 * zoom.y + elif ret_position.y + viewport_rect.size.y * 0.5 * zoom.y >= limit_bottom: + ret_position.y = limit_bottom - viewport_rect.size.y * 0.5 * zoom.y + + return ret_position + +## Resolve the correct position and zoom of the target object.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |p_target|`Variant`|The target to resolve|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func _resolve_target_and_zoom(p_target) -> void: + _target = Vector2() + _zoom_target = Vector2() + _follow_target = null + + if p_target is Node and "is_movable" in p_target and p_target.is_movable: + _follow_target = p_target + + if p_target is Vector2: + _target = p_target + elif p_target is Array and p_target.size() > 0: + var target_pos = Vector2() + + for obj in p_target: + target_pos += obj.get_camera_pos() + + _target = target_pos / p_target.size() + elif p_target.has_method("get_camera_node"): + if "global_position" in p_target.get_camera_node(): + _target = p_target.get_camera_node().global_position + if "zoom" in p_target.get_camera_node(): + _zoom_target = p_target.get_camera_node().zoom + else: + _target = p_target.global_position diff --git a/addons/escoria-core/game/scenes/camera_player/esc_camera.gd.uid b/addons/escoria-core/game/scenes/camera_player/esc_camera.gd.uid new file mode 100644 index 0000000..8c1b7c1 --- /dev/null +++ b/addons/escoria-core/game/scenes/camera_player/esc_camera.gd.uid @@ -0,0 +1 @@ +uid://b8xyisawuhtw3 diff --git a/addons/escoria-core/game/scenes/camera_player/esc_camera_limits.gd b/addons/escoria-core/game/scenes/camera_player/esc_camera_limits.gd new file mode 100644 index 0000000..316e392 --- /dev/null +++ b/addons/escoria-core/game/scenes/camera_player/esc_camera_limits.gd @@ -0,0 +1,40 @@ +## Describes a bounding box that limits the camera movement in the scene. +extends RefCounted +class_name ESCCameraLimits + +## The left side of the bounding box. +var limit_left: int = -10000 + +## The right side of the bounding box. +var limit_right: int = 10000 + +## The top side of the bounding box. +var limit_top: int = -10000 + +## The bottom side of the bounding box. +var limit_bottom: int = 10000 + +## Initializes the camera limits with the given bounding box values.[br] +## [br] +## #### Parameters[br] +## [br] +## | Name | Type | Description | Required? |[br] +## |:-----|:-----|:------------|:----------|[br] +## |left|`int`|The left side of the bounding box|yes|[br] +## |right|`int`|The right side of the bounding box|yes|[br] +## |top|`int`|The top side of the bounding box|yes|[br] +## |bottom|`int`|The bottom side of the bounding box|yes|[br] +## [br] +## #### Returns[br] +## [br] +## Returns nothing. +func _init( + left: int, + right: int, + top: int, + bottom: int +): + limit_left = left + limit_right = right + limit_top = top + limit_bottom = bottom diff --git a/addons/escoria-core/game/scenes/camera_player/esc_camera_limits.gd.uid b/addons/escoria-core/game/scenes/camera_player/esc_camera_limits.gd.uid new file mode 100644 index 0000000..0319263 --- /dev/null +++ b/addons/escoria-core/game/scenes/camera_player/esc_camera_limits.gd.uid @@ -0,0 +1 @@ +uid://dwcagpvytibk5 |
