diff --git a/Resources/Enemies/Base_Fairy.tres b/Resources/Enemies/Base_Fairy.tres index bc16c649..6fbc7dd1 100644 --- a/Resources/Enemies/Base_Fairy.tres +++ b/Resources/Enemies/Base_Fairy.tres @@ -45,8 +45,10 @@ EnemyName = &"Fairy" EnemyKey = &"FAIRY_BASE" PrefabPath = &"res://Scenes/Actors/Fairy_New.tscn" MaxHealth = 2.0 +MovementSpeed = 20.0 Weapon = ExtResource("7_xkg5o") LootDrops = Array[Object]([SubResource("Resource_c8nix"), SubResource("Resource_gs2l3"), SubResource("Resource_sqnvg"), SubResource("Resource_5tyar"), SubResource("Resource_48xq6")]) -AlarmReactRange = 200.0 -PlayerDisengageRange = 500.0 +PlayerDetectionRange = 90.0 +AlarmReactRange = 300.0 +PlayerDisengageRange = 200.0 metadata/_custom_type_script = "uid://cd5o0ceb50jki" diff --git a/Scenes/Actors/Fairy_FSM.tscn b/Scenes/Actors/Fairy_FSM.tscn index 0572e5d6..d897c046 100644 --- a/Scenes/Actors/Fairy_FSM.tscn +++ b/Scenes/Actors/Fairy_FSM.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=16 format=3 uid="uid://clieeuln36a7a"] +[gd_scene load_steps=20 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"] @@ -8,10 +8,14 @@ [ext_resource type="Script" uid="uid://bjrh5q24nuoec" path="res://Scripts/Components/FSM/Enemy/Idle.cs" id="4_kjmts"] [ext_resource type="Script" uid="uid://dbmc3klko5x18" path="res://Scripts/Components/FSM/Enemy/Alert.cs" id="5_f112g"] [ext_resource type="Script" uid="uid://mb4ugq74a17c" path="res://Scripts/Components/FSM/Enemy/PlayerDetectionModule.cs" id="5_rkav6"] +[ext_resource type="Script" uid="uid://7mig30eneu8x" path="res://Scripts/Components/FSM/Enemy/Shooting.cs" id="7_br0mr"] [ext_resource type="Script" uid="uid://bflvr26h52c55" path="res://Scripts/Components/FSM/Enemy/EnemyStorageModule.cs" id="8_fu65u"] [ext_resource type="Script" uid="uid://cq3hkweplldbr" path="res://Scripts/Components/Actors/GenericDamageReceiver.cs" id="10_l7aey"] [ext_resource type="PackedScene" uid="uid://dmumxecckh42r" path="res://Scenes/Activable/BrokenFloorEmitter.tscn" id="11_br0mr"] [ext_resource type="Script" uid="uid://cqwvssstkrdmw" path="res://Scripts/Components/Actors/ActorResourceProvider.cs" id="12_w08b8"] +[ext_resource type="Script" uid="uid://ik7s65de723k" path="res://Scripts/Components/FSM/Enemy/NavigationMovementModule.cs" id="14_w08b8"] +[ext_resource type="PackedScene" uid="uid://cj63k0dmk7tl1" path="res://Scenes/Weapons/enemy_weapon_base.tscn" id="15_ydpwc"] +[ext_resource type="Resource" uid="uid://csdlihliv4cr8" path="res://Resources/Weapons/EnemyWeapon_simple.tres" id="16_pi7ab"] [sub_resource type="CircleShape2D" id="CircleShape2D_pnkma"] @@ -45,8 +49,19 @@ StorageModule = NodePath("../../Storage") PlayerDetection = NodePath("../../PlayerDetection") DamageReceiver = NodePath("../../DamageReceiver") -[node name="Alert" type="Node2D" parent="StateMachine"] +[node name="Alert" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "NavigationModule")] script = ExtResource("5_f112g") +StorageModule = NodePath("../../Storage") +PlayerDetection = NodePath("../../PlayerDetection") +DamageReceiver = NodePath("../../DamageReceiver") +NavigationModule = NodePath("../../NavigationModule") + +[node name="Shooting" type="Node2D" parent="StateMachine" node_paths=PackedStringArray("StorageModule", "PlayerDetection", "DamageReceiver", "EquippedWeapon")] +script = ExtResource("7_br0mr") +StorageModule = NodePath("../../Storage") +PlayerDetection = NodePath("../../PlayerDetection") +DamageReceiver = NodePath("../../DamageReceiver") +EquippedWeapon = NodePath("../../EnemyWeapon") [node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] sprite_frames = ExtResource("1_ho0th") @@ -82,6 +97,22 @@ shape = SubResource("CircleShape2D_6x22m") script = ExtResource("12_w08b8") ResourceName = "Health" +[node name="NavigationModule" type="Node2D" parent="." node_paths=PackedStringArray("StorageModule")] +script = ExtResource("14_w08b8") +StorageModule = NodePath("../Storage") + +[node name="NavigationAgent2D" type="NavigationAgent2D" parent="NavigationModule"] +target_desired_distance = 64.0 +path_max_distance = 800.0 +path_postprocessing = 1 +avoidance_enabled = true +debug_enabled = true +debug_path_custom_color = Color(1, 0, 0, 1) + +[node name="EnemyWeapon" parent="." instance=ExtResource("15_ydpwc")] +WeaponData = ExtResource("16_pi7ab") + [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"] +[connection signal="velocity_computed" from="NavigationModule/NavigationAgent2D" to="NavigationModule" method="_on_navigation_agent_2d_velocity_computed"] diff --git a/Scripts/Components/FSM/Enemy/Alert.cs b/Scripts/Components/FSM/Enemy/Alert.cs index 043476ad..3bfd7037 100644 --- a/Scripts/Components/FSM/Enemy/Alert.cs +++ b/Scripts/Components/FSM/Enemy/Alert.cs @@ -17,12 +17,16 @@ public partial class Alert : EnemyStateBase [Export] public GenericDamageReceiver DamageReceiver { get; private set; } + [Export] + public NavigationMovementModule NavigationModule { get; private set; } + private bool _isPlayerInRange = false; public override void EnterState() { base.EnterState(); - GD.Print("Entered Idle"); + GD.Print($"Entered {Name}"); + NavigationModule.Init(MainObject); PlayerDetection.SetRange(StorageModule.Root.EnemyResource.PlayerDetectionRange); _isPlayerInRange = PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.PlayerDetectionRange); @@ -52,14 +56,14 @@ public partial class Alert : EnemyStateBase public override void ExitState() { base.ExitState(); - GD.Print("Exited Idle"); + GD.Print($"Exited {Name}"); PlayerDetection.PlayerInRange -= PlayerDetectionOnPlayerInRange; PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange; DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted; DamageReceiver.ChangeState(false); - + NavigationModule.SetTarget(null); } private void PlayerDetectionOnPlayerInRange() @@ -73,6 +77,7 @@ public partial class Alert : EnemyStateBase base.PhysicsProcessState(delta); if (_isPlayerInRange && PlayerDetection.IsPlayerInSight()) { + GD.Print("Player is in sight, shooting"); StateMachine.SetState(EnemyState.Shooting); return; } @@ -81,7 +86,9 @@ public partial class Alert : EnemyStateBase if (this.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) >= StorageModule.Root.EnemyResource.PlayerDisengageRange) { + GD.Print("Player is out of sight, idling"); StateMachine.SetState(EnemyState.Idle); + return; } // Move towards last known position @@ -90,12 +97,12 @@ public partial class Alert : EnemyStateBase MoveTowardsPosition(PlayerDetection.LastKnownPlayerPosition.Value); } - + NavigationModule.Move(); } private void MoveTowardsPosition(Vector2 position) { - + NavigationModule.SetTarget(position); } public override void ProcessState(double delta) diff --git a/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs b/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs new file mode 100644 index 00000000..72980a6f --- /dev/null +++ b/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs @@ -0,0 +1,69 @@ +using Godot; + +namespace Cirno.Scripts.Components.FSM.Enemy; + +public partial class NavigationMovementModule : Node2D +{ + + private Vector2? _lastTargetPosition; + private CharacterBody2D _characterBody; + private NavigationAgent2D _navigationAgent; + + [Export] + public EnemyStorageModule StorageModule { get; private set; } + + public void Init(CharacterBody2D characterBody) + { + _characterBody = characterBody; + + if (_navigationAgent is not null) return; + _navigationAgent = this.GetNode("NavigationAgent2D"); + } + + public void SetTarget(Vector2? target) + { + _lastTargetPosition = target; + } + + public void Move() + { + if (!_lastTargetPosition.HasValue) + { + return; + } + + _navigationAgent.SetTargetPosition(_lastTargetPosition.Value); + + var currentAgentPosition = _characterBody.GlobalPosition; + + if (_navigationAgent.IsNavigationFinished()) + { + return; + } + + var nextPathPosition = _navigationAgent.GetNextPathPosition(); + + var newVelocity = currentAgentPosition.DirectionTo(nextPathPosition) * StorageModule.MovementSpeed; + + if (_navigationAgent.AvoidanceEnabled) + { + _navigationAgent.SetVelocity(newVelocity); + } + else + { + _on_navigation_agent_2d_velocity_computed(newVelocity); + } + + _characterBody.MoveAndSlide(); + } + + public void _on_navigation_agent_2d_velocity_computed(Vector2 safeVelocity) + { + if (_characterBody is null) return; + _characterBody.Velocity = safeVelocity; + if (_characterBody.Velocity.Length() > 0) + { + StorageModule.FacingDirection = _characterBody.Velocity.Normalized(); + } + } +} \ No newline at end of file diff --git a/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs.uid b/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs.uid new file mode 100644 index 00000000..6cf89900 --- /dev/null +++ b/Scripts/Components/FSM/Enemy/NavigationMovementModule.cs.uid @@ -0,0 +1 @@ +uid://ik7s65de723k diff --git a/Scripts/Components/FSM/Enemy/PlayerDetectionModule.cs b/Scripts/Components/FSM/Enemy/PlayerDetectionModule.cs index 9f2c141d..18e8c468 100644 --- a/Scripts/Components/FSM/Enemy/PlayerDetectionModule.cs +++ b/Scripts/Components/FSM/Enemy/PlayerDetectionModule.cs @@ -35,7 +35,9 @@ public partial class PlayerDetectionModule : Area2D public bool IsPlayerInRange(float range) { - if (!GameManager.Instance?.PlayerPosition.HasValue ?? false) + if (GameManager.Instance is null) return false; + + if (!GameManager.Instance.PlayerPosition.HasValue) { return false; } @@ -45,8 +47,9 @@ public partial class PlayerDetectionModule : Area2D public bool IsPlayerInSight() { + if (GameManager.Instance is null) return false; //if (_cachedPlayer == null) return false; - if (!GameManager.Instance?.PlayerPosition.HasValue ?? false) return false; + if (!GameManager.Instance.PlayerPosition.HasValue) return false; var spaceState = GetWorld2D().DirectSpaceState; diff --git a/Scripts/Components/FSM/Enemy/Shooting.cs b/Scripts/Components/FSM/Enemy/Shooting.cs new file mode 100644 index 00000000..5823aa35 --- /dev/null +++ b/Scripts/Components/FSM/Enemy/Shooting.cs @@ -0,0 +1,104 @@ +using Cirno.Scripts.Components.Actors; +using Cirno.Scripts.Enums; +using Godot; + +namespace Cirno.Scripts.Components.FSM.Enemy; + +public partial class Shooting : EnemyStateBase +{ + public override EnemyState StateId => EnemyState.Shooting; + + [Export] + public EnemyStorageModule StorageModule { get; private set; } + + [Export] + public PlayerDetectionModule PlayerDetection { get; private set; } + + [Export] + public GenericDamageReceiver DamageReceiver { get; private set; } + + [Export] public Weapon EquippedWeapon; + + private bool _isPlayerInRange = false; + + public override void EnterState() + { + base.EnterState(); + GD.Print($"Entered {Name}"); + + _isPlayerInRange = PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.PlayerDetectionRange); + + PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange; + + DamageReceiver.ChangeState(true); + + DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted; + + // PlayerDetection.SetRange(StorageModule.Root.EnemyResource.PlayerDetectionRange); + // + // _isPlayerInRange = PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.PlayerDetectionRange); + // + // PlayerDetection.PlayerInRange += PlayerDetectionOnPlayerInRange; + // + // PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange; + // + // DamageReceiver.ChangeState(true); + // + // DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted; + + } + + private void HealthProviderOnResourceDepleted() + { + ChangeState(EnemyState.Dead); + } + + private void PlayerDetectionOnPlayerOutOfRange() + { + _isPlayerInRange = false; + StateMachine.SetState(EnemyState.Alert); + } + + public override void ExitState() + { + base.ExitState(); + GD.Print($"Exited {Name}"); + + PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange; + + DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted; + DamageReceiver.ChangeState(false); + + } + + public override void PhysicsProcessState(double delta) + { + base.PhysicsProcessState(delta); + + if (_isPlayerInRange && PlayerDetection.IsPlayerInSight()) + { + // SHOOT + Shoot(); + } + else + { + StateMachine.SetState(EnemyState.Alert); + } + + } + + private void Shoot() + { + if (EquippedWeapon == null) return; + if (!PlayerDetection.LastKnownPlayerPosition.HasValue) return; + + + var direction = ( PlayerDetection.LastKnownPlayerPosition.Value - MainObject.GlobalPosition).Normalized(); + + // Shoot at the player's last known position + EquippedWeapon.ShootDirection = direction; + StorageModule.FacingDirection = direction; + + EquippedWeapon.Shoot(); + } +} \ No newline at end of file diff --git a/Scripts/Components/FSM/Enemy/Shooting.cs.uid b/Scripts/Components/FSM/Enemy/Shooting.cs.uid new file mode 100644 index 00000000..33cf7ee5 --- /dev/null +++ b/Scripts/Components/FSM/Enemy/Shooting.cs.uid @@ -0,0 +1 @@ +uid://7mig30eneu8x