From 03abf91f59fe699bf0960dbaff06ac08822e0c1b Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 13 Sep 2025 14:18:43 +0200 Subject: [PATCH] Add RecoilComponent and SquashAndStretchComponent; implement recoil and animation effects on shooting --- objects/entities/cannon.tscn | 18 ++++- resources/tilesets/village/terain.tres | 2 +- .../components/PeriodicShootingComponent.cs | 3 + scripts/components/RecoilComponent.cs | 51 +++++++++++++ scripts/components/RecoilComponent.cs.uid | 1 + .../components/SquashAndStretchComponent.cs | 71 +++++++++++++++++++ .../SquashAndStretchComponent.cs.uid | 1 + 7 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 scripts/components/RecoilComponent.cs create mode 100644 scripts/components/RecoilComponent.cs.uid create mode 100644 scripts/components/SquashAndStretchComponent.cs create mode 100644 scripts/components/SquashAndStretchComponent.cs.uid diff --git a/objects/entities/cannon.tscn b/objects/entities/cannon.tscn index a400e2c..0cf8114 100644 --- a/objects/entities/cannon.tscn +++ b/objects/entities/cannon.tscn @@ -1,8 +1,10 @@ -[gd_scene load_steps=5 format=3 uid="uid://dstko446qydsc"] +[gd_scene load_steps=7 format=3 uid="uid://dstko446qydsc"] [ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_6gptm"] [ext_resource type="Script" uid="uid://bnaxy8cw3wrko" path="res://scripts/components/PeriodicShootingComponent.cs" id="2_q37h7"] [ext_resource type="PackedScene" uid="uid://bhc7y4xugu4q7" path="res://objects/entities/bullet.tscn" id="3_bhwy3"] +[ext_resource type="Script" uid="uid://b3j23e7b7x8ro" path="res://scripts/components/RecoilComponent.cs" id="4_bhwy3"] +[ext_resource type="Script" uid="uid://c707c53k7c5ae" path="res://scripts/components/SquashAndStretchComponent.cs" id="5_ww0hb"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_j5sus"] size = Vector2(16, 16) @@ -23,7 +25,7 @@ shape = SubResource("RectangleShape2D_j5sus") [node name="PeriodicShootingComponent" type="Node" parent="." node_paths=PackedStringArray("BulletSpawnPointRight")] script = ExtResource("2_q37h7") BulletScene = ExtResource("3_bhwy3") -ShootInterval = 3.5 +ShootInterval = 0.2 ShootDirection = Vector2(0, -1) BulletSpawnPointRight = NodePath("../Bullet spawn") ShootingIntervalVariation = 0.3 @@ -31,3 +33,15 @@ metadata/_custom_type_script = "uid://bnaxy8cw3wrko" [node name="Bullet spawn" type="Marker2D" parent="."] position = Vector2(0, -16) + +[node name="RecoilComponent" type="Node" parent="." node_paths=PackedStringArray("RecoilTarget")] +script = ExtResource("4_bhwy3") +RecoilTarget = NodePath("../Sprite2D") +RecoilDistance = 4.0 +RecoilDuration = 0.12 +metadata/_custom_type_script = "uid://b3j23e7b7x8ro" + +[node name="SquashAndStretchComponent" type="Node" parent="." node_paths=PackedStringArray("TargetNode")] +script = ExtResource("5_ww0hb") +TargetNode = NodePath("../Sprite2D") +metadata/_custom_type_script = "uid://c707c53k7c5ae" diff --git a/resources/tilesets/village/terain.tres b/resources/tilesets/village/terain.tres index b783f51..07370ce 100644 --- a/resources/tilesets/village/terain.tres +++ b/resources/tilesets/village/terain.tres @@ -1056,7 +1056,7 @@ texture = ExtResource("2_43n76") [resource] physics_layer_0/collision_layer = 1 -physics_layer_0/collision_mask = 29 +physics_layer_0/collision_mask = 93 terrain_set_0/mode = 0 terrain_set_0/terrain_0/name = "Village" terrain_set_0/terrain_0/color = Color(1, 1, 1, 1) diff --git a/scripts/components/PeriodicShootingComponent.cs b/scripts/components/PeriodicShootingComponent.cs index 08a3cc1..71a37cc 100644 --- a/scripts/components/PeriodicShootingComponent.cs +++ b/scripts/components/PeriodicShootingComponent.cs @@ -12,6 +12,8 @@ public partial class PeriodicShootingComponent : Node [Export] public Node2D BulletSpawnPointRight { get; set; } [Export] public Node2D BulletSpawnPointLeft { get; set; } [Export] public float ShootingIntervalVariation { get; set; } = 0.0f; + + [Signal] public delegate void ShotFiredEventHandler(Vector2 shootDirection); private Timer _timer; private RandomNumberGenerator _rng; @@ -84,5 +86,6 @@ public partial class PeriodicShootingComponent : Node bulletInstance.GlobalPosition = spawnPosition; GetTree().CurrentScene.AddChild(bulletInstance); + EmitSignalShotFired(ShootDirection); } } \ No newline at end of file diff --git a/scripts/components/RecoilComponent.cs b/scripts/components/RecoilComponent.cs new file mode 100644 index 0000000..11fd31a --- /dev/null +++ b/scripts/components/RecoilComponent.cs @@ -0,0 +1,51 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +[GlobalClass] +public partial class RecoilComponent : Node +{ + [Export] public Node2D RecoilTarget { get; set; } + [Export] public float RecoilDistance { get; set; } = 8f; + [Export] public float RecoilDuration { get; set; } = 0.1f; + + private Vector2 _originalPosition; + private Tween _recoilTween; + private PeriodicShootingComponent _shootingComponent; + + public override void _Ready() + { + RecoilTarget ??= Owner as Node2D; + if (RecoilTarget == null) + { + GD.PushError("RecoilComponent: RecoilTarget is null"); + SetProcess(false); + return; + } + + _originalPosition = RecoilTarget.Position; + + _shootingComponent = Owner.GetNodeOrNull("PeriodicShootingComponent"); + if (_shootingComponent != null) + { + _shootingComponent.ShotFired += TriggerRecoil; + } + } + + public void TriggerRecoil(Vector2 shootDirection) + { + if (RecoilTarget == null) return; + + _recoilTween?.Kill(); + + var recoilDirection = -shootDirection.Normalized(); + var recoilPosition = _originalPosition + recoilDirection * RecoilDistance; + + _recoilTween = CreateTween(); + _recoilTween.SetEase(Tween.EaseType.Out); + _recoilTween.SetTrans(Tween.TransitionType.Cubic); + + _recoilTween.TweenProperty(RecoilTarget, "position", recoilPosition, RecoilDuration / 2); + _recoilTween.TweenProperty(RecoilTarget, "position", _originalPosition, RecoilDuration / 2).SetDelay(RecoilDuration / 2); + } +} \ No newline at end of file diff --git a/scripts/components/RecoilComponent.cs.uid b/scripts/components/RecoilComponent.cs.uid new file mode 100644 index 0000000..d772653 --- /dev/null +++ b/scripts/components/RecoilComponent.cs.uid @@ -0,0 +1 @@ +uid://b3j23e7b7x8ro diff --git a/scripts/components/SquashAndStretchComponent.cs b/scripts/components/SquashAndStretchComponent.cs new file mode 100644 index 0000000..f96ce9a --- /dev/null +++ b/scripts/components/SquashAndStretchComponent.cs @@ -0,0 +1,71 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +[GlobalClass] +public partial class SquashAndStretchComponent : Node +{ + [Export] public Node2D TargetNode { get; set; } + [Export(PropertyHint.Range, "0.1, 1.0, 0.01")] public float AnimationDuration { get; set; } = 0.25f; + + [ExportGroup("Effect Strength")] + [Export(PropertyHint.Range, "1.0, 2.0, 0.05")] public float SquashFactor { get; set; } = 1.2f; + [Export(PropertyHint.Range, "0.5, 1.0, 0.05")] public float StretchFactor { get; set; } = 0.8f; + + private Vector2 _originalScale; + private Tween _tween; + + public override void _Ready() + { + TargetNode ??= Owner as Node2D; + + if (TargetNode == null) + { + GD.PrintErr("SquashAndStretchComponent: No valid TargetNode found. Disabling component."); + SetProcess(false); + return; + } + + _originalScale = TargetNode.Scale; + + var shootingComponent = Owner.GetNodeOrNull("PeriodicShootingComponent"); + if (shootingComponent != null) + { + shootingComponent.ShotFired += OnShotFired; + } + else + { + GD.PrintErr("SquashAndStretchComponent requires a PeriodicShootingComponent on the same owner to function."); + } + } + + private void OnShotFired(Vector2 shootDirection) + { + if (TargetNode == null) return; + + _tween?.Kill(); + + Vector2 squashScale; + Vector2 stretchScale; + + if (Mathf.Abs(shootDirection.X) > Mathf.Abs(shootDirection.Y)) + { + squashScale = new Vector2(StretchFactor, SquashFactor) * _originalScale; + stretchScale = new Vector2(SquashFactor, StretchFactor) * _originalScale; + } + else + { + squashScale = new Vector2(SquashFactor, StretchFactor) * _originalScale; + stretchScale = new Vector2(StretchFactor, SquashFactor) * _originalScale; + } + + _tween = CreateTween(); + _tween.SetTrans(Tween.TransitionType.Elastic).SetEase(Tween.EaseType.Out); + + var partDuration = AnimationDuration / 3.0f; + + _tween.TweenProperty(TargetNode, "scale", squashScale, partDuration); + _tween.TweenProperty(TargetNode, "scale", stretchScale, partDuration); + _tween.TweenProperty(TargetNode, "scale", _originalScale, partDuration); + } +} \ No newline at end of file diff --git a/scripts/components/SquashAndStretchComponent.cs.uid b/scripts/components/SquashAndStretchComponent.cs.uid new file mode 100644 index 0000000..35e23d2 --- /dev/null +++ b/scripts/components/SquashAndStretchComponent.cs.uid @@ -0,0 +1 @@ +uid://c707c53k7c5ae