From 78fe44e6eca2a9e265ccba5db7eafec76d05564b Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 6 May 2025 16:06:00 +0200 Subject: [PATCH] Simple knockback implementation --- Resources/Bullets/icicle_gun_bullets.tres | 1 + Scenes/Actors/Fairy_FSM.tscn | 14 +++-- Scenes/Actors/Fairy_Guard_FSM.tscn | 14 +++-- .../Actors/GenericDamageReceiver.cs | 8 +++ Scripts/Components/BulletSpawner.cs | 1 + .../FSM/Enemy/EnemyKnockbackProvider.cs | 60 +++++++++++++++++++ .../FSM/Enemy/EnemyKnockbackProvider.cs.uid | 1 + .../FSM/Enemy/EnemyStorageModule.cs | 1 + .../FSM/Enemy/NavigationMovementModule.cs | 2 + Scripts/Resources/BulletResource.cs | 2 + 10 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs create mode 100644 Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs.uid diff --git a/Resources/Bullets/icicle_gun_bullets.tres b/Resources/Bullets/icicle_gun_bullets.tres index 81e46f5e..3f96b9ad 100644 --- a/Resources/Bullets/icicle_gun_bullets.tres +++ b/Resources/Bullets/icicle_gun_bullets.tres @@ -11,6 +11,7 @@ DestructionParticlesScene = ExtResource("2_04tom") BulletSpeed = 300.0 Direction = Vector2(1, 0) BulletDamage = 0.4 +Knockback = 100.0 LifeTime = 10.0 DestroyOnCollision = true Owner = 1 diff --git a/Scenes/Actors/Fairy_FSM.tscn b/Scenes/Actors/Fairy_FSM.tscn index 7e955000..3183350f 100644 --- a/Scenes/Actors/Fairy_FSM.tscn +++ b/Scenes/Actors/Fairy_FSM.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=35 format=3 uid="uid://clieeuln36a7a"] +[gd_scene load_steps=36 format=3 uid="uid://clieeuln36a7a"] [ext_resource type="Script" uid="uid://dn6dbog1s2818" path="res://Scripts/Components/FSM/Enemy/EnemyStateMachine.cs" id="1_27djw"] [ext_resource type="SpriteFrames" uid="uid://bcc5mlwwnkvri" path="res://Resources/Sprites/Fairy.tres" id="1_ho0th"] @@ -25,6 +25,7 @@ [ext_resource type="Script" uid="uid://dq338w2lw5phl" path="res://Scripts/Components/Actors/KeyboardInputProvider.cs" id="24_f3gn5"] [ext_resource type="Texture2D" uid="uid://b2v6j7lsyltrc" path="res://Sprites/Actors/CirnoWings.png" id="25_hnfyq"] [ext_resource type="Script" uid="uid://d208gvthkstvc" path="res://Scripts/Components/Actors/PlayerCrosshairProvider.cs" id="26_hnfyq"] +[ext_resource type="Script" uid="uid://by1w0oo6nguyv" path="res://Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs" id="27_hnfyq"] [ext_resource type="Texture2D" uid="uid://cf2855sd3hqty" path="res://Sprites/Actors/Aiming_Reticule_Small.png" id="27_wafqr"] [sub_resource type="CircleShape2D" id="CircleShape2D_pnkma"] @@ -97,7 +98,7 @@ script = ExtResource("4_kjmts") StorageModule = NodePath("../../Storage") PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") -_moduleNodes = [NodePath("../../AlarmModule"), NodePath("../../AnimationModule")] +_moduleNodes = [NodePath("../../AlarmModule"), NodePath("../../AnimationModule"), NodePath("../../Knockbackprovider")] [node name="Alert" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "_moduleNodes")] script = ExtResource("5_f112g") @@ -105,7 +106,7 @@ StorageModule = NodePath("../../Storage") PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") NavigationModule = NodePath("../../NavigationModule") -_moduleNodes = [NodePath("../../AnimationModule")] +_moduleNodes = [NodePath("../../AnimationModule"), NodePath("../../Knockbackprovider")] [node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "EquippedWeapon", "_moduleNodes")] script = ExtResource("7_br0mr") @@ -114,7 +115,7 @@ PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") NavigationModule = NodePath("../../NavigationModule") EquippedWeapon = NodePath("../../EnemyWeapon") -_moduleNodes = [NodePath("../../AnimationModule")] +_moduleNodes = [NodePath("../../AnimationModule"), NodePath("../../Knockbackprovider")] [node name="Dead" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "DropsProvider")] script = ExtResource("8_pi7ab") @@ -222,6 +223,11 @@ CrosshairDistance = 35.0 [node name="Crosshair" type="AnimatedSprite2D" parent="CrosshairProvider"] sprite_frames = SubResource("SpriteFrames_biwfl") +[node name="Knockbackprovider" type="Node2D" parent="." node_paths=PackedStringArray("DamageReceiver", "StorageModule")] +script = ExtResource("27_hnfyq") +DamageReceiver = NodePath("../DamageReceiver") +StorageModule = NodePath("../Storage") + [connection signal="area_entered" from="PlayerDetection" to="PlayerDetection" method="_on_area_entered"] [connection signal="area_exited" from="PlayerDetection" to="PlayerDetection" method="_on_area_exited"] [connection signal="area_entered" from="DamageReceiver" to="DamageReceiver" method="_on_damage_hitbox_area_entered"] diff --git a/Scenes/Actors/Fairy_Guard_FSM.tscn b/Scenes/Actors/Fairy_Guard_FSM.tscn index ae8afe32..1997416d 100644 --- a/Scenes/Actors/Fairy_Guard_FSM.tscn +++ b/Scenes/Actors/Fairy_Guard_FSM.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=36 format=3 uid="uid://bb32f4p5e671j"] +[gd_scene load_steps=37 format=3 uid="uid://bb32f4p5e671j"] [ext_resource type="Script" uid="uid://bi2edpdosngll" path="res://Scripts/Components/FSM/Enemy/EnemyFSMProxy.cs" id="1_514kd"] [ext_resource type="Resource" uid="uid://qbo6avc7x64b" path="res://Resources/Enemies/Fairy_Guard.tres" id="2_514kd"] @@ -27,6 +27,7 @@ [ext_resource type="Script" uid="uid://dq338w2lw5phl" path="res://Scripts/Components/Actors/KeyboardInputProvider.cs" id="25_5xyu0"] [ext_resource type="Script" uid="uid://d208gvthkstvc" path="res://Scripts/Components/Actors/PlayerCrosshairProvider.cs" id="26_u2sah"] [ext_resource type="Texture2D" uid="uid://cf2855sd3hqty" path="res://Sprites/Actors/Aiming_Reticule_Small.png" id="27_na4im"] +[ext_resource type="Script" uid="uid://by1w0oo6nguyv" path="res://Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs" id="28_5gayb"] [sub_resource type="CircleShape2D" id="CircleShape2D_pnkma"] @@ -98,7 +99,7 @@ script = ExtResource("5_xxkf8") StorageModule = NodePath("../../Storage") PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") -_moduleNodes = [NodePath("../../AlarmModule"), NodePath("../../AnimationModule")] +_moduleNodes = [NodePath("../../AlarmModule"), NodePath("../../AnimationModule"), NodePath("../../Knockbackprovider")] [node name="Alert" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "_moduleNodes")] script = ExtResource("6_kji07") @@ -106,7 +107,7 @@ StorageModule = NodePath("../../Storage") PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") NavigationModule = NodePath("../../NavigationModule") -_moduleNodes = [NodePath("../../AnimationModule")] +_moduleNodes = [NodePath("../../AnimationModule"), NodePath("../../Knockbackprovider")] [node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule", "EquippedWeapon", "_moduleNodes")] script = ExtResource("7_w6ssf") @@ -115,7 +116,7 @@ PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") NavigationModule = NodePath("../../NavigationModule") EquippedWeapon = NodePath("../../EnemyWeapon") -_moduleNodes = [NodePath("../../AnimationModule")] +_moduleNodes = [NodePath("../../AnimationModule"), NodePath("../../Knockbackprovider")] [node name="Dead" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "DropsProvider")] script = ExtResource("8_8jlfa") @@ -224,6 +225,11 @@ CrosshairDistance = 35.0 [node name="Crosshair" type="AnimatedSprite2D" parent="CrosshairProvider"] sprite_frames = SubResource("SpriteFrames_biwfl") +[node name="Knockbackprovider" type="Node2D" parent="." node_paths=PackedStringArray("DamageReceiver", "StorageModule")] +script = ExtResource("28_5gayb") +DamageReceiver = NodePath("../DamageReceiver") +StorageModule = NodePath("../Storage") + [connection signal="area_entered" from="PlayerDetection" to="PlayerDetection" method="_on_area_entered"] [connection signal="area_exited" from="PlayerDetection" to="PlayerDetection" method="_on_area_exited"] [connection signal="area_entered" from="DamageReceiver" to="DamageReceiver" method="_on_damage_hitbox_area_entered"] diff --git a/Scripts/Components/Actors/GenericDamageReceiver.cs b/Scripts/Components/Actors/GenericDamageReceiver.cs index 25577029..47f0d4ae 100644 --- a/Scripts/Components/Actors/GenericDamageReceiver.cs +++ b/Scripts/Components/Actors/GenericDamageReceiver.cs @@ -23,6 +23,8 @@ public partial class GenericDamageReceiver : Area2D, IHittable [Signal] public delegate void ShieldHitEventHandler(); + [Signal] public delegate void BulletHitEventHandler(Bullet bullet, Vector2 position, Vector2 direction); + //[Signal] public delegate void DeathEventHandler(); private Node2D _parent; @@ -54,6 +56,9 @@ public partial class GenericDamageReceiver : Area2D, IHittable if (BulletGroup is BulletOwner.None) { this.Hit(bullet.Damage, bullet.DamageType); + + EmitSignalBulletHit(bullet, area.GlobalPosition, (this.GlobalPosition - area.GlobalPosition).Normalized()); + bullet.RequestCollisionDestruction(); return; } @@ -61,6 +66,9 @@ public partial class GenericDamageReceiver : Area2D, IHittable if (bullet.BulletInfo.Owner == BulletGroup) return; this.Hit(bullet.Damage, bullet.DamageType); + + EmitSignalBulletHit(bullet, area.GlobalPosition, (this.GlobalPosition - area.GlobalPosition).Normalized()); + bullet.RequestCollisionDestruction(); } diff --git a/Scripts/Components/BulletSpawner.cs b/Scripts/Components/BulletSpawner.cs index 87321f52..1d4fa379 100644 --- a/Scripts/Components/BulletSpawner.cs +++ b/Scripts/Components/BulletSpawner.cs @@ -92,6 +92,7 @@ public class BulletInfo public BulletOwner Owner { get; set; } public DamageType DamageType { get; set; } public float Damage { get; set; } + public float Knockback { get; set; } public int BulletCount { get; set; } public float RotationSpeed { get; set; } public float RotationOffset { get; set; } diff --git a/Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs b/Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs new file mode 100644 index 00000000..1480c422 --- /dev/null +++ b/Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs @@ -0,0 +1,60 @@ +using Cirno.Scripts.Components.Actors; +using Cirno.Scripts.Enums; +using Godot; + +namespace Cirno.Scripts.Components.FSM.Enemy; + +public partial class EnemyKnockbackProvider : ModuleBase +{ + [Export] public GenericDamageReceiver DamageReceiver { get; private set; } + [Export] public EnemyStorageModule StorageModule { get; private set; } + + [Export] public float KnockbackDecayRate = 800f; // pixels per second^2, tweak this + + private Vector2 KnockbackVelocity + { + get => StorageModule.KnockbackVelocity; + set => StorageModule.KnockbackVelocity = value; + } + + private bool IsKnockbackActive => KnockbackVelocity.LengthSquared() > 1f; + + private IStateMachine _machine; + private bool _enabled = false; + public override void EnterState(EnemyState state) + { + _enabled = true; + DamageReceiver.BulletHit += DamageReceiverOnBulletHit; + } + + private void DamageReceiverOnBulletHit(Bullet bullet, Vector2 position, Vector2 direction) + { + var knockbackVelocity = direction * bullet.BulletInfo.Knockback; + + KnockbackVelocity = knockbackVelocity; + } + + public override void ExitState(EnemyState state) + { + _enabled = false; + DamageReceiver.BulletHit -= DamageReceiverOnBulletHit; + } + + public override void Init(IStateMachine machine) + { + _machine = machine; + } + + public override void Process(double delta) + { + + } + + public override void PhysicsProcess(double delta) + { + if (IsKnockbackActive) + { + KnockbackVelocity = KnockbackVelocity.MoveToward(Vector2.Zero, KnockbackDecayRate * (float)delta); + } + } +} \ No newline at end of file diff --git a/Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs.uid b/Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs.uid new file mode 100644 index 00000000..042ed194 --- /dev/null +++ b/Scripts/Components/FSM/Enemy/EnemyKnockbackProvider.cs.uid @@ -0,0 +1 @@ +uid://by1w0oo6nguyv diff --git a/Scripts/Components/FSM/Enemy/EnemyStorageModule.cs b/Scripts/Components/FSM/Enemy/EnemyStorageModule.cs index 98f56bff..47c07f0d 100644 --- a/Scripts/Components/FSM/Enemy/EnemyStorageModule.cs +++ b/Scripts/Components/FSM/Enemy/EnemyStorageModule.cs @@ -22,6 +22,7 @@ public partial class EnemyStorageModule : Node2D, IFSMStorage public Vector2 FacingDirection { get; set; } public Vector2 AimingDirection { get; set; } + public Vector2 KnockbackVelocity { get; set; } = Vector2.Zero; public float MovementSpeed => Root.EnemyResource.MovementSpeed; diff --git a/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs b/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs index 5c4edbe8..e5704ca5 100644 --- a/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs +++ b/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs @@ -44,6 +44,8 @@ public partial class NavigationMovementModule : Node2D var nextPathPosition = _navigationAgent.GetNextPathPosition(); var newVelocity = currentAgentPosition.DirectionTo(nextPathPosition) * movementSpeed; + + newVelocity += StorageModule.KnockbackVelocity; if (_navigationAgent.AvoidanceEnabled) { diff --git a/Scripts/Resources/BulletResource.cs b/Scripts/Resources/BulletResource.cs index 7b4b1c93..200b86f3 100644 --- a/Scripts/Resources/BulletResource.cs +++ b/Scripts/Resources/BulletResource.cs @@ -16,6 +16,7 @@ public partial class BulletResource : Resource [Export] public float BulletSpeed = 100f; [Export] public Vector2 Direction = Vector2.Right; [Export] public float BulletDamage = 1; + [Export] public float Knockback = 1; [Export] public float LifeTime = 10f; [Export] public bool DestroyOnCollision = true; [Export] public BulletOwner Owner = BulletOwner.None; @@ -40,6 +41,7 @@ public partial class BulletResource : Resource Owner = Owner, DamageType = DamageType, Damage = BulletDamage, + Knockback = Knockback, BulletCount = count, Spread = spread, BulletScene = BulletScene,