Skip to content

Commit c7729e3

Browse files
authored
Make Repel action a bit more generic (#2088)
The repel mechanic was initially intended for the ink blobs, then to any projectile. But we can now consider a repel action that affects more game elements. ---- ### Physics layers: Rename projectile to repellable Similar to the "hookable" layer. To make the repel mechanic a bit more generic. Update the enum and the references in eldrune StoryQuest. ---- ### Project: Set default gravity to zero Otherwise adding a RigidBody2D to the scene, the bodies go down until they hit a wall. Setting the default gravity to zero, the object looks like it's on the ground. ---- ### Repel mechanic: Make it work with any Node2D That has a got_repelled() method. So far we've been assuming a Projectile node for the on_body_entered handler of the Repel area. But the Area2D.body_entered signal is sent with the body: Node2D parameter (which in reality can be a PhysicsBody2D or a TileMap). So use duck typing and also rename the expected method to "got_repelled". Also, call it with the direction of the repel. Previously the Player was passed and the repel direction was calculated on the called node. This makes the repel ability more generic. Also adapt the eldrune_projectile.gd script. ---- ### Player repel: Split to its own scene So the action can be used in other entities of the game, like in NPCs. Export a player controlled boolean for that same reason. Move all animation tracks related to the repel action, except the player animation, to its own AnimationPlayer. These two animations (the player moving the button and the repel air stream growing) should be kept in sync. ---- ### Add examples of custom repellable objects Add a test gym scene with multiple things to repel: - A box that can be pushed along a grid. - Bouncy ball. - A lever that can be swithed in the repelled direction. ---- Resolve #2059
1 parent 566117a commit c7729e3

27 files changed

Lines changed: 869 additions & 211 deletions

project.godot

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,14 @@ locale/translations_pot_files=PackedStringArray("res://scenes/menus/title/compon
294294
2d_physics/layer_6="interactable"
295295
2d_physics/layer_7="players hitbox"
296296
2d_physics/layer_8="enemies hitbox"
297-
2d_physics/layer_9="projectiles"
297+
2d_physics/layer_9="repellable"
298298
2d_physics/layer_10="non_walkable_floor"
299299
2d_physics/layer_13="hookable"
300300

301+
[physics]
302+
303+
2d/default_gravity=0.0
304+
301305
[rendering]
302306

303307
textures/canvas_textures/default_texture_filter=0

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
# SPDX-License-Identifier: MPL-2.0
33
extends AnimationPlayer
44

5-
const REPEL_ANTICIPATION_TIME: float = 0.3
6-
75
var _is_player_running: bool
86

97
@onready var player: Player = owner
@@ -62,7 +60,7 @@ func _on_player_repel_repelling_changed(repelling: bool) -> void:
6260
# directly to the action.
6361
speed_scale = original_speed_scale
6462
play(&"repel")
65-
seek(REPEL_ANTICIPATION_TIME, false, false)
63+
seek(player_repel.REPEL_ANTICIPATION_TIME, false, false)
6664

6765

6866
func _on_player_hook_string_thrown() -> void:

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

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,62 @@ extends Node2D
66
## Emitted when the repel starts or stops.
77
signal repelling_changed(repelling: bool)
88

9+
const REPEL_ANTICIPATION_TIME: float = 0.3
10+
11+
## If false, [member repelling] should be changed by other means.
12+
@export var player_controlled: bool = true
13+
14+
## If controlled by the player, which input action triggers the repel.
915
@export var input_action: StringName = &"repel"
1016

1117
## Current state of the repel.
12-
var repelling: bool = false:
18+
@export var repelling: bool = false:
1319
set = _set_repelling
1420

1521
@onready var air_stream: Area2D = %AirStream
16-
17-
@onready var player: Player = self.owner as Player
22+
@onready var repel_animation: AnimationPlayer = %RepelAnimation
1823

1924

2025
func _set_repelling(new_repelling: bool) -> void:
2126
repelling = new_repelling
27+
if not is_node_ready():
28+
return
2229
repelling_changed.emit(repelling)
30+
if repelling:
31+
_animate()
2332

2433

2534
func _unhandled_input(_event: InputEvent) -> void:
35+
if not player_controlled:
36+
return
2637
if Input.is_action_just_pressed(input_action):
2738
repelling = true
2839
elif Input.is_action_just_released(input_action):
2940
repelling = false
3041

3142

32-
func _on_air_stream_body_entered(body: Projectile) -> void:
33-
body.got_hit(owner)
43+
func repel_once() -> void:
44+
repelling = true
45+
repelling = false
46+
47+
48+
func _on_air_stream_body_entered(body: Node2D) -> void:
49+
if body.has_method("got_repelled"):
50+
var direction := global_position.direction_to(body.global_position)
51+
body.got_repelled(direction)
52+
53+
54+
func _animate() -> void:
55+
# The repel animation is already ongoing. Prevent starting it again by smashing the buttons.
56+
if repel_animation.current_animation == &"repel":
57+
return
58+
59+
# Repel animation is being played for the first time. So skip the anticipation and go
60+
# directly to the action.
61+
repel_animation.play(&"repel")
62+
repel_animation.seek(REPEL_ANTICIPATION_TIME, false, false)
63+
64+
65+
func _on_repel_animation_animation_finished(anim_name: StringName) -> void:
66+
if anim_name == &"repel" and repelling:
67+
repel_animation.play(&"repel")
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
[gd_scene format=3 uid="uid://cl06pid826dtg"]
2+
3+
[ext_resource type="Script" uid="uid://kni2yl26matc" path="res://scenes/game_elements/characters/player/components/player_repel.gd" id="1_y1fpu"]
4+
[ext_resource type="Texture2D" uid="uid://dda0lxfswrncy" path="res://scenes/game_elements/characters/player/components/blow.png" id="2_ochor"]
5+
[ext_resource type="AudioStream" uid="uid://crfylo055wa8e" path="res://scenes/game_elements/characters/player/components/blow.wav" id="3_bvany"]
6+
7+
[sub_resource type="CircleShape2D" id="CircleShape2D_h17s1"]
8+
radius = 20.0
9+
10+
[sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_je7p5"]
11+
blend_mode = 1
12+
13+
[sub_resource type="Animation" id="Animation_5lbww"]
14+
resource_name = "RESET"
15+
tracks/0/type = "value"
16+
tracks/0/imported = false
17+
tracks/0/enabled = true
18+
tracks/0/path = NodePath("AirStream/AirStreamCollision:disabled")
19+
tracks/0/interp = 1
20+
tracks/0/loop_wrap = true
21+
tracks/0/keys = {
22+
"times": PackedFloat32Array(0),
23+
"transitions": PackedFloat32Array(1),
24+
"update": 1,
25+
"values": [true]
26+
}
27+
tracks/1/type = "value"
28+
tracks/1/imported = false
29+
tracks/1/enabled = true
30+
tracks/1/path = NodePath("AirStream/AirStreamCollision:shape:radius")
31+
tracks/1/interp = 1
32+
tracks/1/loop_wrap = true
33+
tracks/1/keys = {
34+
"times": PackedFloat32Array(0),
35+
"transitions": PackedFloat32Array(1),
36+
"update": 0,
37+
"values": [20.0]
38+
}
39+
tracks/2/type = "value"
40+
tracks/2/imported = false
41+
tracks/2/enabled = true
42+
tracks/2/path = NodePath("AirStream/Sprite2D:visible")
43+
tracks/2/interp = 1
44+
tracks/2/loop_wrap = true
45+
tracks/2/keys = {
46+
"times": PackedFloat32Array(0),
47+
"transitions": PackedFloat32Array(1),
48+
"update": 1,
49+
"values": [false]
50+
}
51+
tracks/3/type = "value"
52+
tracks/3/imported = false
53+
tracks/3/enabled = true
54+
tracks/3/path = NodePath("AirStream/Sprite2D:scale")
55+
tracks/3/interp = 1
56+
tracks/3/loop_wrap = true
57+
tracks/3/keys = {
58+
"times": PackedFloat32Array(0),
59+
"transitions": PackedFloat32Array(1),
60+
"update": 0,
61+
"values": [Vector2(0.12577, 0.12577)]
62+
}
63+
tracks/4/type = "value"
64+
tracks/4/imported = false
65+
tracks/4/enabled = true
66+
tracks/4/path = NodePath("AirStream/Sprite2D:modulate")
67+
tracks/4/interp = 1
68+
tracks/4/loop_wrap = true
69+
tracks/4/keys = {
70+
"times": PackedFloat32Array(0),
71+
"transitions": PackedFloat32Array(1),
72+
"update": 0,
73+
"values": [Color(1, 1, 1, 1)]
74+
}
75+
76+
[sub_resource type="Animation" id="Animation_cvb4d"]
77+
resource_name = "repel"
78+
length = 0.6
79+
tracks/0/type = "value"
80+
tracks/0/imported = false
81+
tracks/0/enabled = true
82+
tracks/0/path = NodePath("AirStream/AirStreamCollision:disabled")
83+
tracks/0/interp = 1
84+
tracks/0/loop_wrap = true
85+
tracks/0/keys = {
86+
"times": PackedFloat32Array(0, 0.3, 0.6),
87+
"transitions": PackedFloat32Array(1, 1, 1),
88+
"update": 1,
89+
"values": [true, false, true]
90+
}
91+
tracks/1/type = "value"
92+
tracks/1/imported = false
93+
tracks/1/enabled = true
94+
tracks/1/path = NodePath("AirStream/AirStreamCollision:shape:radius")
95+
tracks/1/interp = 1
96+
tracks/1/loop_wrap = true
97+
tracks/1/keys = {
98+
"times": PackedFloat32Array(0.3, 0.6),
99+
"transitions": PackedFloat32Array(1, 1),
100+
"update": 0,
101+
"values": [20.0, 150.0]
102+
}
103+
tracks/2/type = "value"
104+
tracks/2/imported = false
105+
tracks/2/enabled = true
106+
tracks/2/path = NodePath("AirStream/Sprite2D:visible")
107+
tracks/2/interp = 1
108+
tracks/2/loop_wrap = true
109+
tracks/2/keys = {
110+
"times": PackedFloat32Array(0, 0.3, 0.6),
111+
"transitions": PackedFloat32Array(1, 1, 1),
112+
"update": 1,
113+
"values": [false, true, false]
114+
}
115+
tracks/3/type = "value"
116+
tracks/3/imported = false
117+
tracks/3/enabled = true
118+
tracks/3/path = NodePath("AirStream/Sprite2D:scale")
119+
tracks/3/interp = 1
120+
tracks/3/loop_wrap = true
121+
tracks/3/keys = {
122+
"times": PackedFloat32Array(0.3, 0.6),
123+
"transitions": PackedFloat32Array(1, 1),
124+
"update": 0,
125+
"values": [Vector2(0.12577, 0.12577), Vector2(1, 1)]
126+
}
127+
tracks/4/type = "value"
128+
tracks/4/imported = false
129+
tracks/4/enabled = true
130+
tracks/4/path = NodePath("AirStream/Sprite2D:modulate")
131+
tracks/4/interp = 1
132+
tracks/4/loop_wrap = true
133+
tracks/4/keys = {
134+
"times": PackedFloat32Array(0.3, 0.5, 0.6),
135+
"transitions": PackedFloat32Array(2, 0.5, 0.5),
136+
"update": 0,
137+
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0.456), Color(1, 1, 1, 0)]
138+
}
139+
tracks/5/type = "audio"
140+
tracks/5/imported = false
141+
tracks/5/enabled = true
142+
tracks/5/path = NodePath("AirStream/AudioStreamPlayer2D")
143+
tracks/5/interp = 1
144+
tracks/5/loop_wrap = true
145+
tracks/5/keys = {
146+
"clips": [{
147+
"end_offset": 0.0,
148+
"start_offset": 0.0,
149+
"stream": ExtResource("3_bvany")
150+
}],
151+
"times": PackedFloat32Array(0.3)
152+
}
153+
tracks/5/use_blend = true
154+
155+
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ubfef"]
156+
_data = {
157+
&"RESET": SubResource("Animation_5lbww"),
158+
&"repel": SubResource("Animation_cvb4d")
159+
}
160+
161+
[node name="PlayerRepel" type="Node2D" unique_id=1324972992]
162+
script = ExtResource("1_y1fpu")
163+
164+
[node name="AirStream" type="Area2D" parent="." unique_id=1185536811]
165+
unique_name_in_owner = true
166+
collision_layer = 0
167+
collision_mask = 256
168+
169+
[node name="AirStreamCollision" type="CollisionShape2D" parent="AirStream" unique_id=482921981]
170+
unique_name_in_owner = true
171+
shape = SubResource("CircleShape2D_h17s1")
172+
disabled = true
173+
174+
[node name="Sprite2D" type="Sprite2D" parent="AirStream" unique_id=1723853226]
175+
visible = false
176+
material = SubResource("CanvasItemMaterial_je7p5")
177+
scale = Vector2(0.12577, 0.12577)
178+
texture = ExtResource("2_ochor")
179+
180+
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="AirStream" unique_id=1428086503]
181+
bus = &"SFX"
182+
183+
[node name="RepelAnimation" type="AnimationPlayer" parent="." unique_id=2103763625]
184+
unique_name_in_owner = true
185+
libraries/ = SubResource("AnimationLibrary_ubfef")
186+
187+
[connection signal="body_entered" from="AirStream" to="." method="_on_air_stream_body_entered"]
188+
[connection signal="animation_finished" from="RepelAnimation" to="." method="_on_repel_animation_animation_finished"]

0 commit comments

Comments
 (0)