diff --git a/Scenes/Actors/ActorEnemyTest.tscn b/Scenes/Actors/ActorEnemyTest.tscn index 007b5170..ae6af5da 100644 --- a/Scenes/Actors/ActorEnemyTest.tscn +++ b/Scenes/Actors/ActorEnemyTest.tscn @@ -1,10 +1,13 @@ -[gd_scene load_steps=32 format=3 uid="uid://bqjcwxene73l2"] +[gd_scene load_steps=36 format=3 uid="uid://bqjcwxene73l2"] [ext_resource type="Script" path="res://Scripts/Components/Actors/Actor.cs" id="1_k5cyk"] [ext_resource type="Texture2D" uid="uid://hukxr2e63gky" path="res://Sprites/Actors/Robot3.png" id="2_wt8wl"] -[ext_resource type="Script" path="res://Scripts/Components/Actors/ActorFreeMovement.cs" id="3_4dv5b"] +[ext_resource type="Script" path="res://Scripts/Components/Actors/EnemyPossessionMovement.cs" id="3_a5upk"] [ext_resource type="Script" path="res://Scripts/Components/Actors/KeyboardInputProvider.cs" id="4_8bcq6"] [ext_resource type="Script" path="res://Scripts/Components/Actors/AnimationHandler.cs" id="5_c7ovk"] +[ext_resource type="Script" path="res://Scripts/Components/Actors/ActorAi.cs" id="6_jlcsg"] +[ext_resource type="Script" path="res://Scripts/Components/Actors/EnemyNavigationMovement.cs" id="7_fvl12"] +[ext_resource type="Script" path="res://Scripts/Components/ProximityPlayerDetection.cs" id="8_62r5q"] [sub_resource type="AtlasTexture" id="AtlasTexture_spe0p"] atlas = ExtResource("2_wt8wl") @@ -220,6 +223,9 @@ animations = [{ [sub_resource type="CircleShape2D" id="CircleShape2D_2b36v"] radius = 5.0 +[sub_resource type="CircleShape2D" id="CircleShape2D_sthwe"] +radius = 85.0529 + [node name="ActorEnemyTest" type="CharacterBody2D"] collision_layer = 16 collision_mask = 115 @@ -232,7 +238,7 @@ sprite_frames = SubResource("SpriteFrames_ix17a") animation = &"walk_down" [node name="MovementProvider" type="Node2D" parent="."] -script = ExtResource("3_4dv5b") +script = ExtResource("3_a5upk") [node name="InputProvider" type="Node2D" parent="MovementProvider"] script = ExtResource("4_8bcq6") @@ -243,3 +249,33 @@ shape = SubResource("CircleShape2D_2b36v") [node name="AnimationHandler" type="Node2D" parent="." node_paths=PackedStringArray("_animatedSprite")] script = ExtResource("5_c7ovk") _animatedSprite = NodePath("../AnimatedSprite2D") + +[node name="ActorAi" type="Node2D" parent="."] +script = ExtResource("6_jlcsg") +Ai = 0 + +[node name="NavigationMovementProvider" type="Node2D" parent="." node_paths=PackedStringArray("_playerDetection")] +script = ExtResource("7_fvl12") +_navigationEnabled = true +_playerDetection = NodePath("../PlayerDetection") + +[node name="PlayerDetection" type="Area2D" parent="."] +visible = false +collision_layer = 16 +collision_mask = 2 +script = ExtResource("8_62r5q") + +[node name="PlayerDetectionArea" type="CollisionShape2D" parent="PlayerDetection"] +shape = SubResource("CircleShape2D_sthwe") + +[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."] +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) + +[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="velocity_computed" from="NavigationAgent2D" to="NavigationMovementProvider" method="_on_navigation_agent_2d_velocity_computed"] diff --git a/Scripts/Components/Actors/ActorAi.cs b/Scripts/Components/Actors/ActorAi.cs new file mode 100644 index 00000000..2759d713 --- /dev/null +++ b/Scripts/Components/Actors/ActorAi.cs @@ -0,0 +1,12 @@ +using Godot; + +public partial class ActorAi : Node2D +{ + + public EnemyState State { get; set; } = EnemyState.Idle; + + [Export] // Temp until the special actor tha sets it + public AiState Ai { get; set; } = AiState.Disabled; + + +} \ No newline at end of file diff --git a/Scripts/Components/Actors/EnemyNavigationMovement.cs b/Scripts/Components/Actors/EnemyNavigationMovement.cs new file mode 100644 index 00000000..f45fc3be --- /dev/null +++ b/Scripts/Components/Actors/EnemyNavigationMovement.cs @@ -0,0 +1,157 @@ +using Godot; +using Cirno.Scripts; +using Cirno.Scripts.Components; +using System.Collections.Generic; +using System.Linq; +using Cirno.Scripts.Components.Actors; + +public partial class EnemyNavigationMovement : MovementHandler +{ + public override Vector2 FacingDirection + { + get => _parent.FacingDirection; + set => _parent.FacingDirection = value; + } + + public override Vector2 MovementDirection + { + get => _parent.MovementDirection; + set => _parent.MovementDirection = value; + } + + [Export] + private bool _navigationEnabled = false; + + [Export] public float AlarmReactRange = 200f; + + [Export] public float PlayerDisengageRange = 500f; + + public bool NavigationEnabled + { + get => _actorAi is not null && _actorAi.Ai is AiState.Enabled && _navigationEnabled && _navigationAgent != null; + set => _navigationEnabled = value; + } + + [Export] + private PlayerDetection _playerDetection; + + private ActorAi _actorAi; + private NavigationAgent2D _navigationAgent; + private AlarmManager _alarmManager; + + private Vector2? _lastPlayerPosition = null; + + private bool IsPlayerInRange => _playerDetection is { IsPlayerInRange: true }; + + private bool IsPlayerInSight => _playerDetection is not null && _playerDetection.IsPlayerInSight(_parent.CollisionMask); + + public override void Init(Actor parent) + { + base.Init(parent); + + MovementDirection = Vector2.Zero; + FacingDirection = Vector2.Down; + + _actorAi = parent.GetNode("ActorAi"); + _navigationAgent = GetNodeOrNull("NavigationAgent2D"); + _alarmManager = this.GetAlarmManager(); + + if (_alarmManager != null) + { + _alarmManager.AlarmEnabled += AlarmManagerOnAlarmEnabled; + } + + } + + private void AlarmManagerOnAlarmEnabled(Vector2 location) + { + if (NavigationEnabled && location.DistanceTo(this.GlobalPosition) <= AlarmReactRange) + { + GD.Print($"Enemy {Name} alerted"); + _actorAi.State = EnemyState.Alert; + _lastPlayerPosition = location; + } + } + + public override void Move(double delta) + { + if (_actorAi.Ai is not AiState.Enabled) + return; + + + switch (_actorAi.State) + { + case EnemyState.Idle: + if (_playerDetection != null && _playerDetection.IsPlayerInSight(_parent.CollisionMask)) + { + _actorAi.State = EnemyState.Alert; + } + break; + case EnemyState.Alert: + + // Update last known player position if it's in range + if (IsPlayerInRange) + { + _lastPlayerPosition = _playerDetection.CachedPlayer.GlobalPosition; + } + + if (NavigationEnabled) + { + if (_lastPlayerPosition.HasValue) + { + _navigationAgent.SetTargetPosition(_lastPlayerPosition.Value); + } + + var currentAgentPosition = GlobalPosition; + + var nextPathPosition = _navigationAgent.GetNextPathPosition(); + + var newVelocity = currentAgentPosition.DirectionTo(nextPathPosition) * _parent.MovementSpeed; + + + // Navigation is over, can do other things like shooting + if (_navigationAgent.IsNavigationFinished()) + { + // Shoot player + if (IsPlayerInSight) + { + //Shoot(); + } + + // TODO: If player totally left the max range it should stop shooting and go back to idle + + return; + } + + if (_navigationAgent.AvoidanceEnabled) + { + _navigationAgent.SetVelocity(newVelocity); + + } + else + { + _on_navigation_agent_2d_velocity_computed(newVelocity); + } + + _parent.MoveAndSlide(); + } + else + { + if (IsPlayerInSight) + { + //Shoot(); + } + } + + break; + } + + } + + public void _on_navigation_agent_2d_velocity_computed(Vector2 safeVelocity) + { + _parent.Velocity = safeVelocity; + + } + +} \ No newline at end of file diff --git a/Scripts/Components/Actors/EnemyPossessionMovement.cs b/Scripts/Components/Actors/EnemyPossessionMovement.cs new file mode 100644 index 00000000..ae19dbbd --- /dev/null +++ b/Scripts/Components/Actors/EnemyPossessionMovement.cs @@ -0,0 +1,23 @@ +using Godot; + +public partial class EnemyPossessionMovement : ActorFreeMovement +{ + + private ActorAi _actorAi; + + // State accessor + + public override void Init(Actor parent) + { + base.Init(parent); + + _actorAi = parent.GetNode("ActorAi"); + } + + public override void Move(double delta) + { + if (_actorAi.Ai is AiState.Controlled) + base.Move(delta); + } + +} \ No newline at end of file diff --git a/Scripts/Enemy.cs b/Scripts/Enemy.cs index f2445d3f..e0c576c7 100644 --- a/Scripts/Enemy.cs +++ b/Scripts/Enemy.cs @@ -308,17 +308,21 @@ public partial class Enemy : CharacterBody2D Ai = AiState.Controlled; } - protected enum EnemyState - { - Idle, - Alert, - Patrolling - } + - public enum AiState - { - Enabled, - Disabled, - Controlled - } + +} + +public enum EnemyState +{ + Idle, + Alert, + Patrolling +} + +public enum AiState +{ + Enabled, + Disabled, + Controlled }