Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,26 @@ func shatter_string() -> void:
## While pulling, the player is allowed to go through non-walkable floor.
func pull_string() -> void:
pulling = true

# While pulling, this class takes control over the player movement.
if character.has_method("take_control"):
character.take_control(self)
character.set_collision_mask_value(Enums.CollisionLayers.NON_WALKABLE_FLOOR, false)

# If the entity has a got_pulled handler, call it and connect to the pull_released signal
# of the HookableArea. The entity is responsible to call it.
var ending_area := get_ending_area()
if ending_area.controlled_entity.has_method("got_pulled"):
ending_area.pull_released.connect(_on_pull_released, CONNECT_ONE_SHOT)
var direction := hook_string.points[0].direction_to(hook_string.points[1])
ending_area.controlled_entity.got_pulled(direction)


func _on_pull_released(cancelled: bool) -> void:
if cancelled and hook_string:
shatter_string()
stop_pulling()


## Stop pulling and remove the [member hook_string].
## [br][br]
Expand Down Expand Up @@ -332,7 +347,7 @@ func _process_pulling(_delta: float) -> void:
return

var target := ending_area.controlled_entity
var weight := ending_area.weight if target is CharacterBody2D else 1.0
var weight := ending_area.weight

# Vector from player to first point:
var player_distance: Vector2 = hook_string.points[-2] - hook_string.points[-1]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
[ext_resource type="Script" uid="uid://b7704hdflt5t8" path="res://scenes/game_elements/characters/player/components/player_hook.gd" id="1_40sye"]
[ext_resource type="PackedScene" uid="uid://b0dehcnfo68j1" path="res://scenes/game_elements/props/hook_control/hook_control.tscn" id="2_5svv0"]

[node name="PlayerHook" type="Node2D" unique_id=1972626703 node_paths=PackedStringArray("character") groups=["hook_listener"]]
[node name="PlayerHook" type="Node2D" unique_id=1972626703 groups=["hook_listener"]]
script = ExtResource("1_40sye")
character = NodePath("")

[node name="HookControl" parent="." unique_id=1022482491 instance=ExtResource("2_5svv0")]
state = 1
Expand Down
284 changes: 284 additions & 0 deletions scenes/game_elements/components/custom_hookable_objects_test.tscn

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ extends Area2D
## This script automatically configures the correct [member collision_layer] and
## [member collision_mask] values to enable interaction with the grappling hook.

## Use this signal to release itself from a grappling hook pull.
signal pull_released(cancelled: bool)

## The game entity that becomes hookable.
## [br][br]
## [b]Note:[/b] If the parent node is a Node2D and this isn't set,
Expand Down Expand Up @@ -70,6 +73,11 @@ func get_anchor_position() -> Vector2:
return anchor_point.global_position if anchor_point else global_position


## Emit the [signal pull_released] signal.
func release_from_pull(cancelled: bool = false) -> void:
pull_released.emit(cancelled)


func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray
if not controlled_entity:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends AnimatableBody2D

const NEIGHBORS_FOR_AXIS: Dictionary[Vector2i, TileSet.CellNeighbor] = {
Vector2i.DOWN: TileSet.CELL_NEIGHBOR_BOTTOM_SIDE,
Vector2i.LEFT: TileSet.CELL_NEIGHBOR_LEFT_SIDE,
Vector2i.UP: TileSet.CELL_NEIGHBOR_TOP_SIDE,
Vector2i.RIGHT: TileSet.CELL_NEIGHBOR_RIGHT_SIDE,
}

@export var constrain_layer: TileMapLayer

var tween: Tween

@onready var hookable_area: HookableArea = %HookableArea
@onready var shaker: Shaker = %Shaker


func global_position_to_tile_coordinate(global_pos: Vector2) -> Vector2i:
return constrain_layer.local_to_map(constrain_layer.to_local(global_pos))


func tile_coordinate_to_global_position(coord: Vector2i) -> Vector2:
return constrain_layer.to_global(constrain_layer.map_to_local(coord))


func _ready() -> void:
# Put this object on the grid:
var coord := global_position_to_tile_coordinate(global_position)
global_position = tile_coordinate_to_global_position(coord)


func get_closest_axis(vector: Vector2) -> Vector2i:
if abs(vector.x) > abs(vector.y):
# Closer to Horizontal (X-axis)
return Vector2i(sign(vector.x), 0)

# Closer to Vertical (Y-axis)
return Vector2i(0, sign(vector.y))


func got_pulled(direction: Vector2) -> void:
var axis := get_closest_axis(direction)
if axis == Vector2i.ZERO:
shaker.shake()
await Engine.get_main_loop().create_timer(0.1).timeout
hookable_area.release_from_pull(true)
return

var neighbor := NEIGHBORS_FOR_AXIS[axis]
var coord := global_position_to_tile_coordinate(global_position)
assert(constrain_layer.get_cell_tile_data(coord) != null)
var new_coord := constrain_layer.get_neighbor_cell(coord, neighbor)
var data := constrain_layer.get_cell_tile_data(new_coord)

if not data:
shaker.shake()
await Engine.get_main_loop().create_timer(0.1).timeout
hookable_area.release_from_pull(true)
return

if tween:
if tween.is_running():
return
tween.kill()

tween = create_tween()
tween.set_ease(Tween.EASE_OUT)
# Assuming that the tile size is square:
var new_position := position + Vector2(axis) * constrain_layer.tile_set.tile_size.x
tween.tween_property(self, "position", new_position, .2)
await tween.finished
hookable_area.release_from_pull()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://gvjhy1uvsqpc
69 changes: 69 additions & 0 deletions scenes/game_elements/props/hookable_box/hookable_box.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[gd_scene format=3 uid="uid://dkpelgxwtvcxd"]

[ext_resource type="Script" uid="uid://gvjhy1uvsqpc" path="res://scenes/game_elements/props/hookable_box/components/hookable_box.gd" id="1_34j61"]
[ext_resource type="Texture2D" uid="uid://dslom0xbe1if7" path="res://assets/third_party/tiny-swords/Terrain/Ground/Shadows.png" id="2_f8qwn"]
[ext_resource type="Texture2D" uid="uid://c7oht7wudd8wa" path="res://assets/first_party/tiles/Cliff_Tiles.png" id="3_u0gye"]
[ext_resource type="Script" uid="uid://dabvr3pqmyya4" path="res://scenes/game_elements/props/hookable_area/components/hookable_area.gd" id="4_ml4a5"]
[ext_resource type="Script" uid="uid://dunsvrhq42214" path="res://scenes/game_elements/fx/shaker/components/shaker.gd" id="4_to7a4"]

[sub_resource type="AtlasTexture" id="AtlasTexture_bu3x1"]
atlas = ExtResource("3_u0gye")
region = Rect2(192, 256, 64, 128)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_8dti7"]
size = Vector2(64, 64)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_ml4a5"]
size = Vector2(96, 160)

[sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_34j61"]
polygon = PackedVector2Array(-32, 32, 32, 32, 32, -64, -32, -64)

[node name="HookableBox" type="AnimatableBody2D" unique_id=1805651676]
editor_description = "A hookable box that moves in a fixed grid.

Almost the same as the repellable box example, but instead has a HookableArea and defines a got_hooked() handler method."
collision_layer = 768
collision_mask = 531
script = ExtResource("1_34j61")

[node name="Sprite2D2" type="Sprite2D" parent="." unique_id=1393129317]
texture = ExtResource("2_f8qwn")

[node name="Sprite2D" type="Sprite2D" parent="." unique_id=898668959]
position = Vector2(0, -32)
texture = SubResource("AtlasTexture_bu3x1")

[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=363689712]
rotation = -1.5707964
shape = SubResource("RectangleShape2D_8dti7")

[node name="Shaker" type="Node2D" parent="." unique_id=103380831 node_paths=PackedStringArray("target")]
unique_name_in_owner = true
script = ExtResource("4_to7a4")
target = NodePath("..")
shake_intensity = 60.0
duration = 0.5
frequency = 30.0
metadata/_custom_type_script = "uid://dunsvrhq42214"

[node name="HookableArea" type="Area2D" parent="." unique_id=1417858664 node_paths=PackedStringArray("controlled_entity", "anchor_point")]
unique_name_in_owner = true
collision_layer = 4096
collision_mask = 0
script = ExtResource("4_ml4a5")
controlled_entity = NodePath("..")
anchor_point = NodePath("Marker2D")
weight = 0.0
metadata/_custom_type_script = "uid://dabvr3pqmyya4"

[node name="CollisionShape2D" type="CollisionShape2D" parent="HookableArea" unique_id=68720889]
position = Vector2(0, -32)
shape = SubResource("RectangleShape2D_ml4a5")
debug_color = Color(0.689707, 0.288376, 1, 0.42)

[node name="Marker2D" type="Marker2D" parent="HookableArea" unique_id=1758696959]
position = Vector2(0, -16)

[node name="LightOccluder2D" type="LightOccluder2D" parent="." unique_id=1375993843]
occluder = SubResource("OccluderPolygon2D_34j61")
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends StaticBody2D

## Emitted when the scene starts, indicating the initial state of this lever.
signal initialized(satisfied: bool)

## Emitted when the lever state changes.
signal toggled(is_on: bool)

# Note: Changing the value of "is_on" won't emit a signal. To do that, use "toggle"
@export var is_on: bool = false:
set(new_val):
is_on = new_val
update_appearance()

# Toggles can be connected via targets (simple) or via signal (using the toggled signal)
@export var targets: Array[Toggleable]

@onready var hookable_area: HookableArea = %HookableArea
@onready var lever_sprite: Sprite2D = %LeverSprite
@onready var shaker: Shaker = %Shaker


func update_appearance() -> void:
if not is_node_ready():
return
lever_sprite.frame = 1 if is_on else 0


func _ready() -> void:
_connect_targets()

# To ensure the targets are ready, we do a "call_deferred"
_initialize_toggle_state.call_deferred()


func _initialize_toggle_state() -> void:
initialized.emit(is_on)


func _connect_targets() -> void:
for target: Toggleable in targets:
initialized.connect(target.initialize_with_value)
toggled.connect(target.set_toggled)


func got_pulled(direction: Vector2) -> void:
var sign_x := signf(direction.x)
if sign_x == 1 and not is_on:
toggle()
await get_tree().create_timer(0.2).timeout
hookable_area.release_from_pull()
elif sign_x == -1 and is_on:
toggle()
await get_tree().create_timer(0.2).timeout
hookable_area.release_from_pull()
else:
shaker.shake()
hookable_area.release_from_pull(true)


func toggle(new_val: bool = not is_on) -> void:
is_on = new_val
toggled.emit(is_on)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://d3iwkqlwrjrg0
51 changes: 51 additions & 0 deletions scenes/game_elements/props/hookable_lever/hookable_lever.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[gd_scene format=3 uid="uid://c47lkkvljrsg"]

[ext_resource type="Script" uid="uid://d3iwkqlwrjrg0" path="res://scenes/game_elements/props/hookable_lever/components/hookable_lever.gd" id="1_mrig6"]
[ext_resource type="Texture2D" uid="uid://uy2acspf6apo" path="res://scenes/game_elements/props/lever/components/Lever.png" id="2_ved61"]
[ext_resource type="Script" uid="uid://dunsvrhq42214" path="res://scenes/game_elements/fx/shaker/components/shaker.gd" id="3_082ac"]
[ext_resource type="Script" uid="uid://dabvr3pqmyya4" path="res://scenes/game_elements/props/hookable_area/components/hookable_area.gd" id="4_l44sk"]

[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_p2k53"]
height = 58.0

[sub_resource type="RectangleShape2D" id="RectangleShape2D_h3468"]
size = Vector2(96, 96)

[node name="HookableLever" type="StaticBody2D" unique_id=37452001]
editor_description = "A repellable lever.

It can be switched in the horizontal direction that's being repelled. It will shake when the direction is the same as the lever, indicating that it couldn't be switched from there."
collision_layer = 768
collision_mask = 0
script = ExtResource("1_mrig6")

[node name="LeverSprite" type="Sprite2D" parent="." unique_id=2043405312]
unique_name_in_owner = true
position = Vector2(0, -10)
texture = ExtResource("2_ved61")
hframes = 2

[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1984459816]
rotation = -1.5707964
shape = SubResource("CapsuleShape2D_p2k53")

[node name="Shaker" type="Node2D" parent="." unique_id=1105059859 node_paths=PackedStringArray("target")]
unique_name_in_owner = true
script = ExtResource("3_082ac")
target = NodePath("..")
duration = 0.5
frequency = 30.0
metadata/_custom_type_script = "uid://dunsvrhq42214"

[node name="HookableArea" type="Area2D" parent="." unique_id=72195883 node_paths=PackedStringArray("controlled_entity")]
unique_name_in_owner = true
collision_layer = 4096
collision_mask = 0
script = ExtResource("4_l44sk")
controlled_entity = NodePath("..")
weight = 0.0
metadata/_custom_type_script = "uid://dabvr3pqmyya4"

[node name="CollisionShape2D" type="CollisionShape2D" parent="HookableArea" unique_id=503665020]
shape = SubResource("RectangleShape2D_h3468")
debug_color = Color(0.689707, 0.288376, 1, 0.42)
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ region = Rect2(192, 256, 64, 128)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_8dti7"]
size = Vector2(64, 64)

[sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_sbite"]
polygon = PackedVector2Array(-32, 32, 32, 32, 32, -64, -32, -64)

[node name="RepellableBox" type="AnimatableBody2D" unique_id=1805651676]
editor_description = "A repellable box that moves in a fixed grid.

Expand Down Expand Up @@ -43,3 +46,6 @@ shake_intensity = 60.0
duration = 0.5
frequency = 30.0
metadata/_custom_type_script = "uid://dunsvrhq42214"

[node name="LightOccluder2D" type="LightOccluder2D" parent="." unique_id=2018120731]
occluder = SubResource("OccluderPolygon2D_sbite")
Loading