mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 11:05:34 +00:00
Enemy AI
This commit is contained in:
parent
383fc740df
commit
ede8f2028a
34 changed files with 1418 additions and 417 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=59 format=3 uid="uid://ec4m3geediis"]
|
||||
[gd_scene load_steps=60 format=3 uid="uid://ec4m3geediis"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cvisn0b641od4" path="res://addons/cyclops_level_builder/nodes/cyclops_block.gd" id="1_18fbr"]
|
||||
[ext_resource type="Script" uid="uid://ba0tf7ihw4hpp" path="res://Scripts/Misc/CameraController3D.cs" id="1_g4gcm"]
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
[ext_resource type="PackedScene" uid="uid://c8gtrjf2xeue7" path="res://3D/MapScenes/TestLevel.tscn" id="12_g83w3"]
|
||||
[ext_resource type="Script" uid="uid://dnslcy71dgea" path="res://Scripts/Misc/CameraTarget3D.cs" id="16_e2nai"]
|
||||
[ext_resource type="PackedScene" uid="uid://cupulrjeeivxm" path="res://3D/MapScenes/TestLevel2.tscn" id="18_e2nai"]
|
||||
[ext_resource type="PackedScene" uid="uid://bh3vxmqflijgj" path="res://Scenes/Actors/Generic_Enemy_FSM_3D.tscn" id="20_1dvih"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_id3mo"]
|
||||
script = ExtResource("2_kler0")
|
||||
|
|
@ -483,7 +484,7 @@ hframes = 4
|
|||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.84862, 0, -4.8932)
|
||||
|
||||
[node name="StartPosition" type="Marker3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.5972, 1.57535, 17.7437)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 22.8199, 1.57535, 10.1004)
|
||||
|
||||
[node name="CameraTarget" type="Marker3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.0389, 2.33215, 3.16925)
|
||||
|
|
@ -500,3 +501,6 @@ TargetPath = NodePath("../CameraTarget")
|
|||
|
||||
[node name="TestLevel2" parent="." instance=ExtResource("18_e2nai")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 69.0028, 0, -23.3622)
|
||||
|
||||
[node name="FairyGuardFsm" parent="." instance=ExtResource("20_1dvih")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 20.7257, 1.4, 18.9936)
|
||||
|
|
|
|||
69
Resources/Enemies/Fairy_Guard_3D.tres
Normal file
69
Resources/Enemies/Fairy_Guard_3D.tres
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
[gd_resource type="Resource" script_class="EnemyResource" load_steps=17 format=3 uid="uid://ccym6mcq4fbul"]
|
||||
|
||||
[ext_resource type="SpriteFrames" uid="uid://ch2ll1on8im2p" path="res://Resources/Sprites/FairyGuard.tres" id="1_b2551"]
|
||||
[ext_resource type="Texture2D" uid="uid://xhwfgbv0fjbr" path="res://Sprites/Actors/FairyGuard.png" id="2_c6xyh"]
|
||||
[ext_resource type="Script" uid="uid://cq65aed620ijo" path="res://Scripts/Resources/Loot/LootDrop.cs" id="3_juf1x"]
|
||||
[ext_resource type="Resource" uid="uid://ct1fa2huvy34n" path="res://Resources/Items/Ammo1.tres" id="4_m2lqx"]
|
||||
[ext_resource type="Resource" uid="uid://dy53gia1tmkah" path="res://Resources/Items/Points_Pickup.tres" id="5_80clb"]
|
||||
[ext_resource type="Resource" uid="uid://bhbufxodybsw4" path="res://Resources/Items/Shield_Pickup.tres" id="6_ili73"]
|
||||
[ext_resource type="Resource" uid="uid://dodwpect0ldjf" path="res://Resources/Items/Heart_Pickup.tres" id="7_7ibiq"]
|
||||
[ext_resource type="Resource" uid="uid://clr1gln7nxa1o" path="res://Resources/Items/Power_Pickup.tres" id="8_lij2i"]
|
||||
[ext_resource type="Resource" uid="uid://c6ywv08e6is5o" path="res://Resources/Weapons/EnemyWeapon_Big_3D.tres" id="9_b2551"]
|
||||
[ext_resource type="Script" uid="uid://cd5o0ceb50jki" path="res://Scripts/Resources/EnemyResource.cs" id="10_by7h3"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_n54y5"]
|
||||
atlas = ExtResource("2_c6xyh")
|
||||
region = Rect2(0, 0, 16, 16)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_c8nix"]
|
||||
script = ExtResource("3_juf1x")
|
||||
Item = ExtResource("4_m2lqx")
|
||||
Chance = 40.0
|
||||
metadata/_custom_type_script = "uid://cq65aed620ijo"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_gs2l3"]
|
||||
script = ExtResource("3_juf1x")
|
||||
Item = ExtResource("5_80clb")
|
||||
Chance = 10.0
|
||||
metadata/_custom_type_script = "uid://cq65aed620ijo"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_sqnvg"]
|
||||
script = ExtResource("3_juf1x")
|
||||
Item = ExtResource("6_ili73")
|
||||
Chance = 5.0
|
||||
metadata/_custom_type_script = "uid://cq65aed620ijo"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_5tyar"]
|
||||
script = ExtResource("3_juf1x")
|
||||
Item = ExtResource("7_7ibiq")
|
||||
Chance = 5.0
|
||||
metadata/_custom_type_script = "uid://cq65aed620ijo"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_48xq6"]
|
||||
script = ExtResource("3_juf1x")
|
||||
Item = ExtResource("8_lij2i")
|
||||
Chance = 6.0
|
||||
metadata/_custom_type_script = "uid://cq65aed620ijo"
|
||||
|
||||
[resource]
|
||||
script = ExtResource("10_by7h3")
|
||||
EnemyName = &"Fairy Guard"
|
||||
EnemyKey = &"FAIRY_GUARD"
|
||||
PrefabPath = &"uid://bh3vxmqflijgj"
|
||||
MaxHealth = 10.0
|
||||
MovementSpeed = 2.0
|
||||
Weapon = ExtResource("9_b2551")
|
||||
LootDrops = Array[ExtResource("3_juf1x")]([SubResource("Resource_c8nix"), SubResource("Resource_gs2l3"), SubResource("Resource_sqnvg"), SubResource("Resource_5tyar"), SubResource("Resource_48xq6")])
|
||||
MotivationReward = 4.0
|
||||
PredictPlayer = false
|
||||
PlayerDetectionRange = 4.0
|
||||
ViewRange = 5.0
|
||||
AlarmReactRange = 8.0
|
||||
PlayerDisengageRange = 10.0
|
||||
StrafeSpeed = 1.5
|
||||
MaxStrafeDistance = 1.0
|
||||
MinStrafeDistance = 0.2
|
||||
ResponseTime = 0.5
|
||||
IconSprite = SubResource("AtlasTexture_n54y5")
|
||||
AnimationFrames = ExtResource("1_b2551")
|
||||
metadata/_custom_type_script = "uid://cd5o0ceb50jki"
|
||||
22
Resources/Weapons/EnemyWeapon_Big_3D.tres
Normal file
22
Resources/Weapons/EnemyWeapon_Big_3D.tres
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[gd_resource type="Resource" script_class="WeaponResource" load_steps=3 format=3 uid="uid://c6ywv08e6is5o"]
|
||||
|
||||
[ext_resource type="Resource" uid="uid://wbdspte0ch33" path="res://Resources/Bullets/simple_enemy_bullet_3D.tres" id="1_itg3a"]
|
||||
[ext_resource type="Script" uid="uid://b6fmrnipv88bk" path="res://Scripts/Resources/WeaponResource.cs" id="2_6m4qy"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_6m4qy")
|
||||
Name = &"Enemy weapon with big bullets"
|
||||
BulletData = ExtResource("1_itg3a")
|
||||
Priority = 0
|
||||
AmmoPerShot = 1
|
||||
RateOfFire = 0.6
|
||||
BulletCapacity = 4
|
||||
ReloadTime = 1.0
|
||||
AutoReload = true
|
||||
InfiniteAmmo = true
|
||||
ItemKey = &""
|
||||
AmmoKey = &""
|
||||
BulletsPerShot = 1
|
||||
SpreadAngle = 0.0
|
||||
RandomSpread = 0.0
|
||||
_rotationOffset = 0.0
|
||||
112
Scenes/Actors/Generic_Enemy_FSM_3D.tscn
Normal file
112
Scenes/Actors/Generic_Enemy_FSM_3D.tscn
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
[gd_scene load_steps=18 format=3 uid="uid://bh3vxmqflijgj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dwregubt4iila" path="res://Scripts/Components/FSM/Enemy/3D/EnemyProxy3D.cs" id="1_a3crc"]
|
||||
[ext_resource type="Resource" uid="uid://ccym6mcq4fbul" path="res://Resources/Enemies/Fairy_Guard_3D.tres" id="2_jgarc"]
|
||||
[ext_resource type="Script" uid="uid://c651imhj6rjsh" path="res://Scripts/Components/FSM/Enemy/3D/EnemyStateMachine3D.cs" id="2_xne4s"]
|
||||
[ext_resource type="Script" uid="uid://cy34e3htvbvnl" path="res://Scripts/Components/FSM/Enemy/3D/Init.cs" id="4_jgarc"]
|
||||
[ext_resource type="Script" uid="uid://jpdgfn701crh" path="res://Scripts/Components/FSM/Enemy/3D/Idle.cs" id="5_rg1hb"]
|
||||
[ext_resource type="Script" uid="uid://dvtdw2hcp4rm2" path="res://Scripts/Components/FSM/Enemy/3D/Alert.cs" id="6_jgarc"]
|
||||
[ext_resource type="Script" uid="uid://crahxykgis2bp" path="res://Scripts/Components/FSM/Enemy/3D/Shooting.cs" id="7_rg1hb"]
|
||||
[ext_resource type="Script" uid="uid://3irm5sccr2fc" path="res://Scripts/Components/FSM/Enemy/3D/Dead.cs" id="8_5j04l"]
|
||||
[ext_resource type="Script" uid="uid://mpws3eyrmx0q" path="res://Scripts/Components/FSM/Enemy/3D/Controlled.cs" id="9_dm2sd"]
|
||||
[ext_resource type="SpriteFrames" uid="uid://ch2ll1on8im2p" path="res://Resources/Sprites/FairyGuard.tres" id="10_hew1j"]
|
||||
[ext_resource type="Script" uid="uid://de31afbiua8xu" path="res://Scripts/Components/FSM/Enemy/3D/EnemyFSMAnimatedSprite3D.cs" id="11_jgarc"]
|
||||
[ext_resource type="Script" uid="uid://chq5a73kw0c0m" path="res://Scripts/Components/FSM/Enemy/3D/EnemyStorage3D.cs" id="11_xne4s"]
|
||||
[ext_resource type="Script" uid="uid://extjdng8nk6r" path="res://Scripts/Components/FSM/Enemy/3D/PlayerDetection3D.cs" id="13_rg1hb"]
|
||||
[ext_resource type="Script" uid="uid://k5k8wf821ytg" path="res://Scripts/Components/FSM/Enemy/3D/NavigationProvider3D.cs" id="14_dm2sd"]
|
||||
[ext_resource type="PackedScene" uid="uid://cfgc6ik8vb08c" path="res://Scenes/Weapons/BaseWeapon_3D.tscn" id="15_27vgy"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_jgarc"]
|
||||
radius = 0.264547
|
||||
height = 0.935884
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_5j04l"]
|
||||
height = 1.91858
|
||||
radius = 3.04834
|
||||
|
||||
[node name="FairyGuardFsm" type="CharacterBody3D" node_paths=PackedStringArray("EnemyFSM")]
|
||||
collision_layer = 64
|
||||
collision_mask = 17
|
||||
script = ExtResource("1_a3crc")
|
||||
EnemyFSM = NodePath("StateMachine")
|
||||
EnemyResource = ExtResource("2_jgarc")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("CapsuleShape3D_jgarc")
|
||||
|
||||
[node name="StateMachine" type="Node" parent="."]
|
||||
script = ExtResource("2_xne4s")
|
||||
|
||||
[node name="Init" type="Node" parent="StateMachine" node_paths=PackedStringArray("Storage", "DetectionProvider")]
|
||||
script = ExtResource("4_jgarc")
|
||||
Storage = NodePath("../../Storage")
|
||||
DetectionProvider = NodePath("../../PlayerDetectionProvider")
|
||||
|
||||
[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("Storage", "PlayerDetection", "_moduleNodes")]
|
||||
script = ExtResource("5_rg1hb")
|
||||
Storage = NodePath("../../Storage")
|
||||
PlayerDetection = NodePath("../../PlayerDetectionProvider")
|
||||
DebugEnabled = true
|
||||
_moduleNodes = [NodePath(""), NodePath(""), NodePath("")]
|
||||
|
||||
[node name="Alert" type="Node" parent="StateMachine" node_paths=PackedStringArray("Storage", "PlayerDetection", "NavigationModule", "_moduleNodes")]
|
||||
script = ExtResource("6_jgarc")
|
||||
Storage = NodePath("../../Storage")
|
||||
PlayerDetection = NodePath("../../PlayerDetectionProvider")
|
||||
NavigationModule = NodePath("../../NavigationProvider")
|
||||
DebugEnabled = true
|
||||
_moduleNodes = [NodePath(""), NodePath("")]
|
||||
|
||||
[node name="Shooting" type="Node" parent="StateMachine" node_paths=PackedStringArray("Storage", "PlayerDetection", "EquippedWeapon", "NavigationModule", "_moduleNodes")]
|
||||
script = ExtResource("7_rg1hb")
|
||||
Storage = NodePath("../../Storage")
|
||||
PlayerDetection = NodePath("../../PlayerDetectionProvider")
|
||||
EquippedWeapon = NodePath("../../Weapon")
|
||||
NavigationModule = NodePath("../../NavigationProvider")
|
||||
_moduleNodes = [NodePath(""), NodePath("")]
|
||||
|
||||
[node name="Dead" type="Node" parent="StateMachine" node_paths=PackedStringArray("Storage")]
|
||||
script = ExtResource("8_5j04l")
|
||||
Storage = NodePath("../../Storage")
|
||||
|
||||
[node name="Controlled" type="Node" parent="StateMachine" node_paths=PackedStringArray("Storage", "_moduleNodes")]
|
||||
script = ExtResource("9_dm2sd")
|
||||
Storage = NodePath("../../Storage")
|
||||
_moduleNodes = [NodePath("")]
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite3D" parent="."]
|
||||
pixel_size = 0.05
|
||||
billboard = 1
|
||||
texture_filter = 0
|
||||
sprite_frames = ExtResource("10_hew1j")
|
||||
animation = &"down"
|
||||
script = ExtResource("11_jgarc")
|
||||
|
||||
[node name="Storage" type="Node" parent="." node_paths=PackedStringArray("Root")]
|
||||
script = ExtResource("11_xne4s")
|
||||
Root = NodePath("..")
|
||||
|
||||
[node name="PlayerDetectionProvider" type="Area3D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
script = ExtResource("13_rg1hb")
|
||||
ObstaclesCollisionMask = 17
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerDetectionProvider"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.557434, 0)
|
||||
shape = SubResource("CylinderShape3D_5j04l")
|
||||
|
||||
[node name="NavigationProvider" type="Node" parent="." node_paths=PackedStringArray("NavigationAgent", "StorageModule")]
|
||||
script = ExtResource("14_dm2sd")
|
||||
NavigationAgent = NodePath("../NavigationAgent3D")
|
||||
StorageModule = NodePath("../Storage")
|
||||
|
||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
||||
path_postprocessing = 1
|
||||
debug_enabled = true
|
||||
|
||||
[node name="Weapon" parent="." instance=ExtResource("15_27vgy")]
|
||||
|
||||
[connection signal="body_entered" from="PlayerDetectionProvider" to="PlayerDetectionProvider" method="_on_body_entered"]
|
||||
[connection signal="body_exited" from="PlayerDetectionProvider" to="PlayerDetectionProvider" method="_on_body_exited"]
|
||||
[connection signal="velocity_computed" from="NavigationAgent3D" to="NavigationProvider" method="_on_navigation_agent_3d_velocity_computed"]
|
||||
96
Scripts/Components/FSM/Enemy/3D/Alert.cs
Normal file
96
Scripts/Components/FSM/Enemy/3D/Alert.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Cirno.Scripts.Utils;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class Alert : EnemyStateBase3D
|
||||
{
|
||||
public override EnemyState StateId => EnemyState.Alert;
|
||||
|
||||
[Export] public EnemyStorage3D Storage { get; private set; }
|
||||
[Export] public PlayerDetection3D PlayerDetection { get; private set; }
|
||||
[Export] public NavigationProvider3D NavigationModule { get; private set; }
|
||||
[Export] public bool DebugEnabled { get; set; } = false;
|
||||
|
||||
private bool _isPlayerInRange = false;
|
||||
|
||||
public override void EnterState()
|
||||
{
|
||||
base.EnterState();
|
||||
// player detection
|
||||
// damage receiver will be a module
|
||||
|
||||
NavigationModule.Init(MainObject);
|
||||
|
||||
PlayerDetection.SetRange(Storage.Root.EnemyResource.PlayerDetectionRange);
|
||||
|
||||
PlayerDetection.PlayerInRange += PlayerDetectionOnPlayerInRange;
|
||||
|
||||
PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
GD.Print("Entered alert");
|
||||
}
|
||||
|
||||
private void PlayerDetectionOnPlayerOutOfRange()
|
||||
{
|
||||
_isPlayerInRange = false;
|
||||
}
|
||||
|
||||
public override void ExitState()
|
||||
{
|
||||
base.ExitState();
|
||||
PlayerDetection.PlayerInRange -= PlayerDetectionOnPlayerInRange;
|
||||
|
||||
PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange;
|
||||
NavigationModule.SetTarget(null);
|
||||
// DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
|
||||
// DamageReceiver.ChangeState(false);
|
||||
// NavigationModule.SetTarget(null);
|
||||
}
|
||||
|
||||
private void PlayerDetectionOnPlayerInRange()
|
||||
{
|
||||
//_isPlayerInRange = true;
|
||||
}
|
||||
|
||||
public override void PhysicsProcessState(double delta)
|
||||
{
|
||||
base.PhysicsProcessState(delta);
|
||||
if (PlayerDetection.IsPlayerInRange(Storage.Root.EnemyResource.ViewRange) && PlayerDetection.IsPlayerInSight())
|
||||
{
|
||||
StateMachine.SetState(EnemyState.Shooting);
|
||||
return;
|
||||
}
|
||||
|
||||
// if player is outside disengage range, change to idle (later on, search)
|
||||
if (MainObject.GlobalPosition.DistanceTo(GameController.Instance.PlayerPosition.Value) >=
|
||||
Storage.Root.EnemyResource.PlayerDisengageRange)
|
||||
{
|
||||
StateMachine.SetState(EnemyState.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move towards last known position
|
||||
if (PlayerDetection.LastKnownPlayerPosition.HasValue)
|
||||
{
|
||||
MoveTowardsPosition(PlayerDetection.LastKnownPlayerPosition.Value);
|
||||
}
|
||||
|
||||
NavigationModule.Move(Storage.EnemyData.MovementSpeed);
|
||||
|
||||
//Storage.FacingDirection = MainObject.Velocity.SnapToCardinal().Normalized();
|
||||
Storage.FacingDirection = MainObject.Velocity.ToVector2().Normalized();
|
||||
Storage.AimingDirection = Storage.FacingDirection;
|
||||
}
|
||||
|
||||
private void MoveTowardsPosition(Vector3 position)
|
||||
{
|
||||
NavigationModule.SetTarget(position);
|
||||
}
|
||||
|
||||
public override void ProcessState(double delta)
|
||||
{
|
||||
base.ProcessState(delta);
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/Alert.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/Alert.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dvtdw2hcp4rm2
|
||||
19
Scripts/Components/FSM/Enemy/3D/Controlled.cs
Normal file
19
Scripts/Components/FSM/Enemy/3D/Controlled.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class Controlled : EnemyStateBase3D
|
||||
{
|
||||
public override EnemyState StateId => EnemyState.Controlled;
|
||||
|
||||
[Export] public EnemyStorage3D Storage { get; private set; }
|
||||
|
||||
public override void EnterState()
|
||||
{
|
||||
base.EnterState();
|
||||
// player detection
|
||||
// damage receiver will be a module
|
||||
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/Controlled.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/Controlled.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://mpws3eyrmx0q
|
||||
19
Scripts/Components/FSM/Enemy/3D/Dead.cs
Normal file
19
Scripts/Components/FSM/Enemy/3D/Dead.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class Dead : EnemyStateBase3D
|
||||
{
|
||||
public override EnemyState StateId => EnemyState.Dead;
|
||||
|
||||
[Export] public EnemyStorage3D Storage { get; private set; }
|
||||
|
||||
public override void EnterState()
|
||||
{
|
||||
base.EnterState();
|
||||
// player detection
|
||||
// damage receiver will be a module
|
||||
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/Dead.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/Dead.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://3irm5sccr2fc
|
||||
16
Scripts/Components/FSM/Enemy/3D/EnemyFSMAnimatedSprite3D.cs
Normal file
16
Scripts/Components/FSM/Enemy/3D/EnemyFSMAnimatedSprite3D.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class EnemyFSMAnimatedSprite3D : AnimatedSprite3D
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var enemyFsmProxy = this.GetParentOrNull<EnemyProxy3D>();
|
||||
|
||||
if (enemyFsmProxy?.EnemyResource?.AnimationFrames != null)
|
||||
{
|
||||
this.SpriteFrames = enemyFsmProxy.EnemyResource.AnimationFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://de31afbiua8xu
|
||||
78
Scripts/Components/FSM/Enemy/3D/EnemyProxy3D.cs
Normal file
78
Scripts/Components/FSM/Enemy/3D/EnemyProxy3D.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Cirno.Scripts.Resources;
|
||||
using Cirno.Scripts.Resources.Loot;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class EnemyProxy3D : CharacterBody3D, IActivable
|
||||
{
|
||||
[Export] public EnemyStateMachine3D EnemyFSM { get; private set; }
|
||||
|
||||
[Export] public EnemyResource EnemyResource { get; private set; }
|
||||
|
||||
[Export] public Array<LootDrop> ExtraLoot { get; private set; } = [];
|
||||
|
||||
[Export] public bool OverrideLoot { get; set; } = false;
|
||||
|
||||
[Export]
|
||||
public AiState StartingAiState { get; private set; }
|
||||
|
||||
[ExportCategory("Defeat Script")]
|
||||
[Export] public Node DefeatScript { get; set; }
|
||||
|
||||
[Export] public ActivationType ActivationType { get; private set; } = ActivationType.Toggle;
|
||||
|
||||
[Signal] public delegate void DeathEventHandler(EnemyProxy3D enemy);
|
||||
|
||||
public void Init(EnemyResource enemyResource)
|
||||
{
|
||||
this.EnemyResource = enemyResource;
|
||||
|
||||
}
|
||||
|
||||
public void TriggerDeath()
|
||||
{
|
||||
EmitSignalDeath(this);
|
||||
}
|
||||
|
||||
public void RequestAlert(Vector3 destination)
|
||||
{
|
||||
//EnemyFSM.SetState(EnemyState.Alert);
|
||||
}
|
||||
|
||||
public bool Activate(ActivationType activationType = ActivationType.Toggle)
|
||||
{
|
||||
switch (activationType)
|
||||
{
|
||||
case ActivationType.Toggle:
|
||||
EnemyFSM.SetState(EnemyState.Controlled);
|
||||
break;
|
||||
case ActivationType.Enable:
|
||||
// Enable or disable AI
|
||||
EnemyFSM.SetState(EnemyState.Shooting);
|
||||
break;
|
||||
case ActivationType.Disable:
|
||||
EnemyFSM.SetState(EnemyState.Idle);
|
||||
// Enable or disable AI
|
||||
break;
|
||||
case ActivationType.Use:
|
||||
break;
|
||||
case ActivationType.Destroy:
|
||||
EnemyFSM.SetState(EnemyState.Dead);
|
||||
break;
|
||||
case ActivationType.Open:
|
||||
break;
|
||||
case ActivationType.Close:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
this.Activate();
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/EnemyProxy3D.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/EnemyProxy3D.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dwregubt4iila
|
||||
9
Scripts/Components/FSM/Enemy/3D/EnemyStateBase3D.cs
Normal file
9
Scripts/Components/FSM/Enemy/3D/EnemyStateBase3D.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public abstract partial class EnemyStateBase3D : BaseState<EnemyState, CharacterBody3D>
|
||||
{
|
||||
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/EnemyStateBase3D.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/EnemyStateBase3D.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://62p771loh356
|
||||
10
Scripts/Components/FSM/Enemy/3D/EnemyStateMachine3D.cs
Normal file
10
Scripts/Components/FSM/Enemy/3D/EnemyStateMachine3D.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class EnemyStateMachine3D : StateMachineBase<EnemyState, CharacterBody3D>
|
||||
{
|
||||
[Export] public override EnemyState InitialState { get; protected set; } = EnemyState.Init;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://c651imhj6rjsh
|
||||
31
Scripts/Components/FSM/Enemy/3D/EnemyStorage3D.cs
Normal file
31
Scripts/Components/FSM/Enemy/3D/EnemyStorage3D.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cirno.Scripts.Resources;
|
||||
using Cirno.Scripts.Resources.Loot;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class EnemyStorage3D : Node
|
||||
{
|
||||
[Export]
|
||||
public EnemyProxy3D Root { get; private set; }
|
||||
|
||||
public Node3D RootAsNode => Root;
|
||||
|
||||
public EnemyResource EnemyData => Root.EnemyResource;
|
||||
|
||||
public Vector3 HomePosition { get; set; }
|
||||
|
||||
public Vector2 MovementDirection { get; set; }
|
||||
public Vector2 FacingDirection { get; set; }
|
||||
|
||||
public Vector2 AimingDirection { get; set; }
|
||||
public Vector3 KnockbackVelocity { get; set; } = Vector3.Zero;
|
||||
|
||||
public float MovementSpeed => Root.EnemyResource.MovementSpeed;
|
||||
|
||||
public IEnumerable<LootDrop> LootDrops => Root.OverrideLoot ? Root.ExtraLoot : Root.EnemyResource.LootDrops.Concat(Root.ExtraLoot);
|
||||
|
||||
public AiState AiState { get; set; }
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/EnemyStorage3D.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/EnemyStorage3D.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://chq5a73kw0c0m
|
||||
93
Scripts/Components/FSM/Enemy/3D/Idle.cs
Normal file
93
Scripts/Components/FSM/Enemy/3D/Idle.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class Idle : EnemyStateBase3D
|
||||
{
|
||||
public override EnemyState StateId => EnemyState.Idle;
|
||||
|
||||
[Export] public EnemyStorage3D Storage { get; private set; }
|
||||
[Export] public PlayerDetection3D PlayerDetection { get; private set; }
|
||||
|
||||
[Export] public bool DebugEnabled { get; set; } = false;
|
||||
|
||||
private bool _isPlayerInRange = false;
|
||||
|
||||
public override void EnterState()
|
||||
{
|
||||
base.EnterState();
|
||||
PlayerDetection.SetRange(Storage.Root.EnemyResource.PlayerDetectionRange);
|
||||
|
||||
_isPlayerInRange = PlayerDetection.IsPlayerInRange(Storage.Root.EnemyResource.ViewRange);
|
||||
|
||||
PlayerDetection.PlayerInRange += PlayerDetectionOnPlayerInRange;
|
||||
|
||||
PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
// player detection
|
||||
// damage receiver will be a module
|
||||
GD.Print("Entered Idle");
|
||||
}
|
||||
|
||||
public override void ExitState()
|
||||
{
|
||||
base.ExitState();
|
||||
|
||||
PlayerDetection.PlayerInRange -= PlayerDetectionOnPlayerInRange;
|
||||
|
||||
PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
// DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
|
||||
//
|
||||
// DamageReceiver.HealthProvider.ResourceDecreased -= HealthProviderOnResourceDecreased;
|
||||
// DamageReceiver.ChangeState(false);
|
||||
|
||||
}
|
||||
|
||||
private void HealthProviderOnResourceDepleted()
|
||||
{
|
||||
ChangeState(EnemyState.Dead);
|
||||
}
|
||||
|
||||
private void HealthProviderOnResourceDecreased(float oldvalue, float newvalue, float maxvalue)
|
||||
{
|
||||
Storage.AiState = AiState.Enabled;
|
||||
ChangeState(EnemyState.Alert);
|
||||
}
|
||||
|
||||
private void PlayerDetectionOnPlayerInRange()
|
||||
{
|
||||
_isPlayerInRange = true;
|
||||
GD.Print("Player In Range");
|
||||
}
|
||||
|
||||
private void PlayerDetectionOnPlayerOutOfRange()
|
||||
{
|
||||
_isPlayerInRange = false;
|
||||
}
|
||||
|
||||
public override void PhysicsProcessState(double delta)
|
||||
{
|
||||
base.PhysicsProcessState(delta);
|
||||
if (Storage.AiState is AiState.Enabled && _isPlayerInRange)
|
||||
{
|
||||
if (PlayerDetection.IsPlayerInSight())
|
||||
{
|
||||
GD.Print("Moving to alert");
|
||||
StateMachine.SetState(EnemyState.Alert);
|
||||
}
|
||||
}
|
||||
|
||||
if (DebugEnabled)
|
||||
{
|
||||
DebugDraw3D.DrawText(MainObject.GlobalPosition - new Vector3(0,16,0), "Idle");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessState(double delta)
|
||||
{
|
||||
base.ProcessState(delta);
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/Idle.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/Idle.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://jpdgfn701crh
|
||||
25
Scripts/Components/FSM/Enemy/3D/Init.cs
Normal file
25
Scripts/Components/FSM/Enemy/3D/Init.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class Init : EnemyStateBase3D
|
||||
{
|
||||
public override EnemyState StateId => EnemyState.Init;
|
||||
[Export] public EnemyStorage3D Storage {get; private set;}
|
||||
|
||||
[Export] public PlayerDetection3D DetectionProvider { get; private set; }
|
||||
|
||||
public override void EnterState()
|
||||
{
|
||||
//DamageReceiver.HealthProvider.MaxResource = StorageModule.Root.EnemyResource.MaxHealth;
|
||||
DetectionProvider.Initialize(MainObject);
|
||||
Storage.AiState = Storage.Root.StartingAiState;
|
||||
|
||||
Storage.HomePosition = MainObject.GlobalPosition;
|
||||
// TODO: Hide wings
|
||||
// TODO: Hide aiming reticule
|
||||
|
||||
StateMachine.SetState(EnemyState.Idle);
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/Init.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/Init.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cy34e3htvbvnl
|
||||
84
Scripts/Components/FSM/Enemy/3D/NavigationProvider3D.cs
Normal file
84
Scripts/Components/FSM/Enemy/3D/NavigationProvider3D.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using Cirno.Scripts.Utils;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class NavigationProvider3D : Node
|
||||
{
|
||||
private Vector3? _lastTargetPosition;
|
||||
private CharacterBody3D _characterBody;
|
||||
//private NavigationAgent3D _navigationAgent;
|
||||
|
||||
[Export] public NavigationAgent3D NavigationAgent { get; private set; }
|
||||
|
||||
[Export]
|
||||
public EnemyStorage3D StorageModule { get; private set; }
|
||||
|
||||
public void Init(CharacterBody3D characterBody)
|
||||
{
|
||||
_characterBody = characterBody;
|
||||
|
||||
if (NavigationAgent is not null) return;
|
||||
//_navigationAgent = this.GetNode<NavigationAgent3D>("NavigationAgent");
|
||||
}
|
||||
|
||||
public void SetTarget(Vector3? target)
|
||||
{
|
||||
_lastTargetPosition = target;
|
||||
}
|
||||
|
||||
public void Move(float movementSpeed)
|
||||
{
|
||||
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) * movementSpeed;
|
||||
|
||||
newVelocity += StorageModule.KnockbackVelocity;
|
||||
|
||||
if (NavigationAgent.AvoidanceEnabled)
|
||||
{
|
||||
NavigationAgent.SetVelocity(newVelocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_on_navigation_agent_3d_velocity_computed(newVelocity);
|
||||
}
|
||||
|
||||
_characterBody.MoveAndSlide();
|
||||
}
|
||||
|
||||
public void _on_navigation_agent_3d_velocity_computed(Vector3 safeVelocity)
|
||||
{
|
||||
if (_characterBody is null) return;
|
||||
_characterBody.Velocity = safeVelocity;
|
||||
if (_characterBody.Velocity.Length() > 0)
|
||||
{
|
||||
StorageModule.FacingDirection = _characterBody.Velocity.Normalized().ToVector2();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNavigable(Vector3 newPos)
|
||||
{
|
||||
NavigationAgent.SetTargetPosition(newPos);
|
||||
return NavigationAgent.IsTargetReachable();
|
||||
}
|
||||
|
||||
public bool IsNavigationFinished()
|
||||
{
|
||||
return NavigationAgent.IsNavigationFinished();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://k5k8wf821ytg
|
||||
120
Scripts/Components/FSM/Enemy/3D/PlayerDetection3D.cs
Normal file
120
Scripts/Components/FSM/Enemy/3D/PlayerDetection3D.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using Cirno.Scripts.Components.FSM._3DPlayer;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class PlayerDetection3D : Area3D
|
||||
{
|
||||
[Signal]
|
||||
public delegate void PlayerInRangeEventHandler();
|
||||
|
||||
[Signal]
|
||||
public delegate void PlayerOutOfRangeEventHandler();
|
||||
|
||||
[Export(PropertyHint.Layers3DPhysics)]
|
||||
public uint ObstaclesCollisionMask { get; private set; }
|
||||
|
||||
public Vector3? LastKnownPlayerPosition { get; set; }
|
||||
public Vector3? LastKnowPlayerVelocity { get; set; }
|
||||
|
||||
//public bool PlayerInActiveArea { get; private set; }
|
||||
private CollisionShape3D _collisionShape;
|
||||
|
||||
private bool _initialized = false;
|
||||
// public override void _Ready()
|
||||
// {
|
||||
// CallDeferred(MethodName.Initialize);
|
||||
// }
|
||||
|
||||
private CharacterBody3D _mainBody;
|
||||
|
||||
public void Initialize(CharacterBody3D mainBody)
|
||||
{
|
||||
_mainBody = mainBody;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void SetRange(float range)
|
||||
{
|
||||
_collisionShape ??= this.GetNode<CollisionShape3D>("CollisionShape3D");
|
||||
if (_collisionShape.Shape is CylinderShape3D shape3D)
|
||||
{
|
||||
shape3D.Radius = range;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPlayerInRange(float range)
|
||||
{
|
||||
if (!_initialized) return false;
|
||||
if (GameController.Instance is null) return false;
|
||||
|
||||
if (!GameController.Instance.PlayerPosition.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.GlobalPosition.DistanceTo(GameController.Instance.PlayerPosition.Value) <= range;
|
||||
}
|
||||
|
||||
public bool IsPlayerInSight()
|
||||
{
|
||||
if (!_initialized) return false;
|
||||
if (GameController.Instance is null) return false;
|
||||
//if (_cachedPlayer == null) return false;
|
||||
if (!GameController.Instance.PlayerPosition.HasValue) return false;
|
||||
|
||||
var found = HasLineOfSight(this.GlobalPosition, GameController.Instance.PlayerPosition.Value);
|
||||
|
||||
// var spaceState = GetWorld2D().DirectSpaceState;
|
||||
//
|
||||
// // It needs to use its own collision mask because it's detecting obstacles rather than the player
|
||||
// var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, GameController.Instance.PlayerPosition.Value, ObstaclesCollisionMask,
|
||||
// [GetRid()]);
|
||||
// //query.CollideWithBodies = true;
|
||||
// //query.CollideWithAreas = true;
|
||||
// // var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
|
||||
// var result = spaceState.IntersectRay(query);
|
||||
//
|
||||
// // If count is 0 then the player is in sight, otherwise there is level geometry in the way
|
||||
// var found = result.Count == 0;
|
||||
if (found)
|
||||
{
|
||||
LastKnownPlayerPosition = GameController.Instance.PlayerPosition;
|
||||
LastKnowPlayerVelocity = GameController.Instance.PlayerVelocity;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private void _on_body_entered(Node3D area)
|
||||
{
|
||||
if (area is not IsoPlayerFSMProxy player) return;
|
||||
EmitSignal(SignalName.PlayerInRange);
|
||||
//PlayerInActiveArea = true;
|
||||
}
|
||||
|
||||
private void _on_body_exited(Node3D area)
|
||||
{
|
||||
if (area is not IsoPlayerFSMProxy player) return;
|
||||
EmitSignal(SignalName.PlayerOutOfRange);
|
||||
//PlayerInActiveArea = false;
|
||||
}
|
||||
|
||||
public bool HasLineOfSight(Vector3 startPos, Vector3 endPos)
|
||||
{
|
||||
var spaceState = GetWorld3D().DirectSpaceState;
|
||||
|
||||
// It needs to use its own collision mask because it's detecting obstacles rather than the player
|
||||
var query = PhysicsRayQueryParameters3D.Create(startPos, endPos, ObstaclesCollisionMask,
|
||||
[GetRid(), _mainBody.GetRid()]);
|
||||
query.CollideWithBodies = true;
|
||||
|
||||
//query.CollideWithAreas = true;
|
||||
// var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
|
||||
var result = spaceState.IntersectRay(query);
|
||||
|
||||
// If count is 0 then the player is in sight, otherwise there is level geometry in the way
|
||||
var found = result.Count == 0;
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/PlayerDetection3D.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/PlayerDetection3D.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://extjdng8nk6r
|
||||
171
Scripts/Components/FSM/Enemy/3D/Shooting.cs
Normal file
171
Scripts/Components/FSM/Enemy/3D/Shooting.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
using Cirno.Scripts.Enums;
|
||||
using Cirno.Scripts.Utils;
|
||||
using Cirno.Scripts.Weapons;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM.Enemy._3D;
|
||||
|
||||
public partial class Shooting : EnemyStateBase3D
|
||||
{
|
||||
public override EnemyState StateId => EnemyState.Shooting;
|
||||
|
||||
[Export] public EnemyStorage3D Storage { get; private set; }
|
||||
|
||||
[Export] public PlayerDetection3D PlayerDetection { get; private set; }
|
||||
|
||||
[Export] public Weapon3D EquippedWeapon;
|
||||
[Export] public NavigationProvider3D NavigationModule { get; private set; }
|
||||
|
||||
private bool _isPlayerInRange = false;
|
||||
private Vector3? _currentStrafeTarget = null;
|
||||
private float _strafeSpeed => Storage.EnemyData.StrafeSpeed;
|
||||
|
||||
private double _responseTimer = 0;
|
||||
public override void EnterState()
|
||||
{
|
||||
base.EnterState();
|
||||
|
||||
GD.Print("Entering Shooting");
|
||||
|
||||
PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
//DamageReceiver.ChangeState(true);
|
||||
|
||||
//DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted;
|
||||
|
||||
EquippedWeapon.WeaponData = Storage.Root.EnemyResource.Weapon;
|
||||
|
||||
_currentStrafeTarget = null;
|
||||
|
||||
_responseTimer = 0;
|
||||
|
||||
}
|
||||
|
||||
private void PlayerDetectionOnPlayerOutOfRange()
|
||||
{
|
||||
GD.Print("Player out of range, returning to alert");
|
||||
StateMachine.SetState(EnemyState.Alert);
|
||||
}
|
||||
|
||||
public override void ExitState()
|
||||
{
|
||||
base.ExitState();
|
||||
|
||||
PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
//DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
|
||||
|
||||
_currentStrafeTarget = null;
|
||||
|
||||
//DamageReceiver.ChangeState(false);
|
||||
|
||||
}
|
||||
|
||||
public override void PhysicsProcessState(double delta)
|
||||
{
|
||||
base.PhysicsProcessState(delta);
|
||||
|
||||
if (PlayerDetection.IsPlayerInRange(Storage.Root.EnemyResource.ViewRange) && PlayerDetection.IsPlayerInSight())
|
||||
{
|
||||
// SHOOT
|
||||
Shoot();
|
||||
|
||||
if (_strafeSpeed > 0)
|
||||
{
|
||||
// Check if a strafe position is needed
|
||||
if (!_currentStrafeTarget.HasValue || NavigationModule.IsNavigationFinished())
|
||||
{
|
||||
if (_responseTimer < Storage.EnemyData.ResponseTime)
|
||||
{
|
||||
_responseTimer += delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentStrafeTarget = CalculateStrafePosition();
|
||||
_responseTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Back to alert because the player could not be detected anymore");
|
||||
StateMachine.SetState(EnemyState.Alert);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentStrafeTarget.HasValue)
|
||||
{
|
||||
NavigationModule.SetTarget(_currentStrafeTarget.Value);
|
||||
NavigationModule.Move(_strafeSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3? CalculateStrafePosition()
|
||||
{
|
||||
var playerPos = PlayerDetection.LastKnownPlayerPosition.Value;
|
||||
var enemyPos = MainObject.GlobalPosition;
|
||||
|
||||
// Calculate direction to player
|
||||
var directionToPlayer = (playerPos - enemyPos).Normalized();
|
||||
|
||||
// Get perpendicular vectors (left and right strafing directions)
|
||||
var leftStrafe = directionToPlayer.Rotated(Vector3.Up, -Mathf.Pi / 2);
|
||||
var rightStrafe = directionToPlayer.Rotated(Vector3.Up,Mathf.Pi / 2);
|
||||
|
||||
// Randomly decide left or right first
|
||||
bool tryLeftFirst = GD.Randf() < 0.5f;
|
||||
|
||||
for (float factor = 1f; factor > 0.25f; factor *= 0.75f)
|
||||
{
|
||||
float strafeDistance = Mathf.Lerp(Storage.EnemyData.MinStrafeDistance, Storage.EnemyData.MaxStrafeDistance, factor);
|
||||
var newPos = enemyPos + (tryLeftFirst ? leftStrafe : rightStrafe) * strafeDistance;
|
||||
if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos))
|
||||
{
|
||||
return newPos;
|
||||
}
|
||||
}
|
||||
|
||||
for (float factor = 1f; factor > 0.25f; factor *= 0.75f)
|
||||
{
|
||||
float strafeDistance = Mathf.Lerp(Storage.EnemyData.MinStrafeDistance, Storage.EnemyData.MaxStrafeDistance, factor);
|
||||
var newPos = enemyPos + (tryLeftFirst ? rightStrafe : leftStrafe) * strafeDistance;
|
||||
if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos))
|
||||
{
|
||||
return newPos;
|
||||
}
|
||||
}
|
||||
|
||||
return null; // No valid strafe position found
|
||||
}
|
||||
|
||||
private Vector2 GetShootDirection()
|
||||
{
|
||||
// if (Storage.EnemyData.PredictPlayer && PlayerDetection.LastKnowPlayerVelocity.HasValue)
|
||||
// {
|
||||
// var predictedDirection = MathFunctions.PredictInterceptPosition(MainObject.GlobalPosition,
|
||||
// PlayerDetection.LastKnownPlayerPosition.Value, PlayerDetection.LastKnowPlayerVelocity.Value,
|
||||
// EquippedWeapon.WeaponData.BulletData.BulletSpeed);
|
||||
// if (predictedDirection.HasValue) return (predictedDirection.Value - MainObject.GlobalPosition).Normalized();
|
||||
//
|
||||
// }
|
||||
|
||||
return ( PlayerDetection.LastKnownPlayerPosition.Value - MainObject.GlobalPosition).ToVector2().Normalized();
|
||||
}
|
||||
|
||||
private void Shoot()
|
||||
{
|
||||
if (EquippedWeapon == null) return;
|
||||
if (!PlayerDetection.LastKnownPlayerPosition.HasValue) return;
|
||||
|
||||
var direction = GetShootDirection();
|
||||
|
||||
// Shoot at the player's last known position
|
||||
EquippedWeapon.ShootDirection = direction;
|
||||
//StorageModule.FacingDirection = direction;
|
||||
Storage.FacingDirection = direction.SnapToCardinal().Normalized();
|
||||
Storage.AimingDirection = Storage.FacingDirection;
|
||||
|
||||
EquippedWeapon.Shoot();
|
||||
}
|
||||
}
|
||||
1
Scripts/Components/FSM/Enemy/3D/Shooting.cs.uid
Normal file
1
Scripts/Components/FSM/Enemy/3D/Shooting.cs.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://crahxykgis2bp
|
||||
|
|
@ -8,39 +8,35 @@ public partial class Alert : EnemyStateBase
|
|||
{
|
||||
public override EnemyState StateId => EnemyState.Alert;
|
||||
|
||||
[Export]
|
||||
public EnemyStorageModule StorageModule { get; private set; }
|
||||
|
||||
[Export]
|
||||
public PlayerDetectionModule PlayerDetection { get; private set; }
|
||||
|
||||
[Export]
|
||||
public GenericDamageReceiver DamageReceiver { get; private set; }
|
||||
|
||||
[Export]
|
||||
public NavigationMovementModule NavigationModule { get; private set; }
|
||||
|
||||
[Export] public EnemyStorageModule StorageModule { get; private set; }
|
||||
|
||||
[Export] public PlayerDetectionModule PlayerDetection { get; private set; }
|
||||
|
||||
[Export] public GenericDamageReceiver DamageReceiver { get; private set; }
|
||||
|
||||
[Export] public NavigationMovementModule NavigationModule { get; private set; }
|
||||
|
||||
private bool _isPlayerInRange = false;
|
||||
|
||||
|
||||
public override void EnterState()
|
||||
{
|
||||
base.EnterState();
|
||||
NavigationModule.Init(MainObject);
|
||||
|
||||
|
||||
PlayerDetection.SetRange(StorageModule.Root.EnemyResource.PlayerDetectionRange);
|
||||
|
||||
|
||||
//_isPlayerInRange = PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.ViewRange);
|
||||
//GD.Print($"Player In Range: {_isPlayerInRange}");
|
||||
|
||||
|
||||
PlayerDetection.PlayerInRange += PlayerDetectionOnPlayerInRange;
|
||||
|
||||
|
||||
PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
DamageReceiver.ChangeState(true);
|
||||
|
||||
|
||||
DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted;
|
||||
}
|
||||
|
||||
|
||||
private void HealthProviderOnResourceDepleted()
|
||||
{
|
||||
ChangeState(EnemyState.Dead);
|
||||
|
|
@ -55,12 +51,12 @@ public partial class Alert : EnemyStateBase
|
|||
{
|
||||
base.ExitState();
|
||||
PlayerDetection.PlayerInRange -= PlayerDetectionOnPlayerInRange;
|
||||
|
||||
|
||||
PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange;
|
||||
|
||||
|
||||
DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
|
||||
DamageReceiver.ChangeState(false);
|
||||
NavigationModule.SetTarget(null);
|
||||
NavigationModule.SetTarget(null);
|
||||
}
|
||||
|
||||
private void PlayerDetectionOnPlayerInRange()
|
||||
|
|
@ -71,12 +67,13 @@ public partial class Alert : EnemyStateBase
|
|||
public override void PhysicsProcessState(double delta)
|
||||
{
|
||||
base.PhysicsProcessState(delta);
|
||||
if (PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.ViewRange) && PlayerDetection.IsPlayerInSight())
|
||||
if (PlayerDetection.IsPlayerInRange(StorageModule.Root.EnemyResource.ViewRange) &&
|
||||
PlayerDetection.IsPlayerInSight())
|
||||
{
|
||||
StateMachine.SetState(EnemyState.Shooting);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if player is outside disengage range, change to idle (later on, search)
|
||||
if (MainObject.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) >=
|
||||
StorageModule.Root.EnemyResource.PlayerDisengageRange)
|
||||
|
|
@ -84,13 +81,13 @@ public partial class Alert : EnemyStateBase
|
|||
StateMachine.SetState(EnemyState.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Move towards last known position
|
||||
if (PlayerDetection.LastKnownPlayerPosition.HasValue)
|
||||
{
|
||||
MoveTowardsPosition(PlayerDetection.LastKnownPlayerPosition.Value);
|
||||
}
|
||||
|
||||
|
||||
NavigationModule.Move(StorageModule.EnemyData.MovementSpeed);
|
||||
|
||||
StorageModule.FacingDirection = MainObject.Velocity.SnapToCardinal().Normalized();
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ dest_files=["res://.godot/imported/FairyGuard.png-c13f7bb7cfab625be756e4c8ce7276
|
|||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/mode=3
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
@ -31,4 +31,4 @@ process/normal_map_invert_y=false
|
|||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
detect_3d/compress_to=0
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ Solid=""
|
|||
Acid=""
|
||||
Destroyable=""
|
||||
navigation_polygon_source_geometry_group=""
|
||||
navigation_mesh_source_group=""
|
||||
|
||||
[importer_defaults]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue