Skip to content

Commit 027f51e

Browse files
authored
Merge pull request #1257 from endlessm/grappling-camera
Grappling hook: Fancier camera
2 parents 8476a26 + a56a318 commit 027f51e

8 files changed

Lines changed: 192 additions & 3 deletions

File tree

scenes/game_elements/characters/player/components/player_hook.gd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ var hook_string: Line2D
8282
## It is set to aiming when there is no [member hook_string].
8383
@onready var hook_control: HookControl = $HookControl
8484

85+
## Marker which position is set to the ending of the hook.
86+
## [br][br]
87+
## This can be used to pan or zoom the camera to frame the ending of the grappling hook.
88+
@onready var hook_ending: Marker2D = $HookEnding
89+
8590

8691
func _enter_tree() -> void:
8792
if not character and get_parent() is CharacterBody2D:
@@ -126,6 +131,7 @@ func hooked(_new_hooked_to: HookableArea, is_loop: bool) -> void:
126131
if not hook_string:
127132
hook_string = _new_hook_string()
128133
hook_string.add_point(p, 0)
134+
hook_ending.global_position = p
129135
areas_hooked.append(_new_hooked_to)
130136
if not _new_hooked_to.hook_control:
131137
pull_string()
@@ -190,6 +196,8 @@ func remove_string() -> void:
190196
hook_control.release()
191197
hook_control.state = HookControl.State.AIMING
192198

199+
hook_ending.global_position = global_position
200+
193201

194202
## Start pulling.
195203
## [br][br]

scenes/game_elements/characters/player/player.tscn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ script = ExtResource("11_nrnyj")
616616
[node name="HookControl" parent="PlayerHook" instance=ExtResource("13_ecbbk")]
617617
state = 1
618618

619+
[node name="HookEnding" type="Marker2D" parent="PlayerHook"]
620+
619621
[node name="LightOccluder2D" type="LightOccluder2D" parent="."]
620622
visible = false
621623
occluder = SubResource("OccluderPolygon2D_75vfm")
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dagrhfrj0f33i

scenes/quests/lore_quests/quest_002/2_grappling_hook/components/grappling_hook_needles.gd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ extends Node2D
55

66
@onready var door: Node2D = %Door
77
@onready var award_buttons: Node2D = %AwardButtons
8+
@onready var player: Player = %Player
9+
@onready var frame_camera_behavior: FrameCameraBehavior = %FrameCameraBehavior
810

911

1012
func _ready() -> void:
13+
frame_camera_behavior.frame_target = player.get_node("PlayerHook/HookEnding")
14+
1115
# When starting, hide and disable the button items to award so the player can't pick them:
1216
for c in award_buttons.get_children():
1317
c.process_mode = Node.PROCESS_MODE_DISABLED

scenes/quests/lore_quests/quest_002/2_grappling_hook/components/grappling_hook_powerup.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ const LongerHook = preload(
66
"res://scenes/quests/lore_quests/quest_002/2_grappling_hook/components/longer_hook.gd"
77
)
88

9+
@onready var player: Player = %Player
10+
@onready var frame_camera_behavior: FrameCameraBehavior = %FrameCameraBehavior
11+
12+
13+
func _ready() -> void:
14+
frame_camera_behavior.frame_target = player.get_node("PlayerHook/HookEnding")
15+
916

1017
func _on_button_item_collected() -> void:
1118
var player: Player = get_tree().get_first_node_in_group("player")

scenes/quests/lore_quests/quest_002/2_grappling_hook/grappling_hook_needles.tscn

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[gd_scene load_steps=16 format=4 uid="uid://c3iv8nbog410p"]
1+
[gd_scene load_steps=17 format=4 uid="uid://c3iv8nbog410p"]
22

33
[ext_resource type="Script" uid="uid://3eynuk3w4m4a" path="res://scenes/quests/lore_quests/quest_002/2_grappling_hook/components/grappling_hook_needles.gd" id="1_omyxq"]
44
[ext_resource type="TileSet" uid="uid://07fq3rspk8ia" path="res://scenes/tileset.tres" id="2_dwcg4"]
@@ -14,6 +14,7 @@
1414
[ext_resource type="PackedScene" uid="uid://4u7dloafx55f" path="res://scenes/game_elements/props/teleporter/teleporter/teleporter.tscn" id="13_p8sis"]
1515
[ext_resource type="PackedScene" uid="uid://cfcgrfvtn04yp" path="res://scenes/ui_elements/hud/hud.tscn" id="14_bxmjx"]
1616
[ext_resource type="PackedScene" uid="uid://covsdqqsd6rsy" path="res://scenes/game_elements/props/sign/sign.tscn" id="14_omyxq"]
17+
[ext_resource type="Script" uid="uid://dagrhfrj0f33i" path="res://scenes/game_logic/camera_behaviors/frame_camera_behavior.gd" id="15_dwcg4"]
1718

1819
[sub_resource type="RectangleShape2D" id="RectangleShape2D_41yfr"]
1920
size = Vector2(140.5, 194.5)
@@ -72,6 +73,13 @@ limit_bottom = 1728
7273
position_smoothing_enabled = true
7374
editor_draw_limits = true
7475

76+
[node name="FrameCameraBehavior" type="Node2D" parent="OnTheGround/Player/Camera2D" node_paths=PackedStringArray("camera", "main_target", "frame_target")]
77+
unique_name_in_owner = true
78+
script = ExtResource("15_dwcg4")
79+
camera = NodePath("..")
80+
main_target = NodePath("../..")
81+
frame_target = NodePath("../../PlayerHook/HookEnding")
82+
7583
[node name="ButtonsA" type="Node2D" parent="OnTheGround"]
7684
y_sort_enabled = true
7785
position = Vector2(1185, 973)

scenes/quests/lore_quests/quest_002/2_grappling_hook/grappling_hook_powerup.tscn

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
[gd_scene load_steps=19 format=4 uid="uid://bk8rjvfmm20b7"]
1+
[gd_scene load_steps=20 format=4 uid="uid://bk8rjvfmm20b7"]
22

33
[ext_resource type="TileSet" uid="uid://07fq3rspk8ia" path="res://scenes/tileset.tres" id="1_efavn"]
44
[ext_resource type="Script" uid="uid://ca7w1ib458a7x" path="res://scenes/quests/lore_quests/quest_002/2_grappling_hook/components/grappling_hook_powerup.gd" id="1_hx14g"]
55
[ext_resource type="Material" uid="uid://64aeyjitacv3" path="res://scenes/game_elements/props/void/void_chromakey_material.tres" id="2_hx14g"]
66
[ext_resource type="PackedScene" uid="uid://dohb701lxbe5s" path="res://scenes/game_elements/props/hookable_needle/hookable_needle.tscn" id="4_7gbyj"]
77
[ext_resource type="PackedScene" uid="uid://iu2q66clupc6" path="res://scenes/game_elements/characters/player/player.tscn" id="4_rup4f"]
88
[ext_resource type="SpriteFrames" uid="uid://07di3updrwh0" path="res://scenes/game_elements/characters/components/sprite_frames/storyweaver_purple.tres" id="5_ptg16"]
9+
[ext_resource type="Script" uid="uid://dagrhfrj0f33i" path="res://scenes/game_logic/camera_behaviors/frame_camera_behavior.gd" id="6_7gbyj"]
910
[ext_resource type="PackedScene" uid="uid://evb46lm6ssu2" path="res://scenes/game_elements/props/hookable_pin/hookable_pin.tscn" id="6_vodlh"]
1011
[ext_resource type="PackedScene" uid="uid://dvj15pnuqr2ua" path="res://scenes/game_elements/props/hookable_button_item/hookable_button_item.tscn" id="7_rlnni"]
1112
[ext_resource type="PackedScene" uid="uid://covsdqqsd6rsy" path="res://scenes/game_elements/props/sign/sign.tscn" id="8_g2ubw"]
@@ -90,6 +91,14 @@ limit_bottom = 2368
9091
position_smoothing_enabled = true
9192
editor_draw_limits = true
9293

94+
[node name="FrameCameraBehavior" type="Node2D" parent="OnTheGround/Player/Camera2D" node_paths=PackedStringArray("camera", "main_target", "frame_target")]
95+
unique_name_in_owner = true
96+
script = ExtResource("6_7gbyj")
97+
camera = NodePath("..")
98+
main_target = NodePath("../..")
99+
frame_target = NodePath("../../PlayerHook/HookEnding")
100+
metadata/_custom_type_script = "uid://dagrhfrj0f33i"
101+
93102
[node name="ButtonsA" type="Node2D" parent="OnTheGround"]
94103
y_sort_enabled = true
95104
position = Vector2(835, 1729)
@@ -231,7 +240,8 @@ scale = Vector2(2, 2)
231240
[node name="ButtonSprite" parent="OnTheGround/LongerStringPowerUp/ButtonItem" index="0"]
232241
sprite_frames = SubResource("SpriteFrames_g2ubw")
233242

234-
[node name="HookableArea" parent="OnTheGround/LongerStringPowerUp" index="2"]
243+
[node name="HookableArea" parent="OnTheGround/LongerStringPowerUp" index="2" node_paths=PackedStringArray("controlled_entity")]
244+
controlled_entity = NodePath("..")
235245
weight = 0.3
236246

237247
[node name="Teleporter" parent="." instance=ExtResource("9_4a5bj")]

0 commit comments

Comments
 (0)