mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-22 10:33:47 +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://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"]
|
[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="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="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://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"]
|
[sub_resource type="Resource" id="Resource_id3mo"]
|
||||||
script = ExtResource("2_kler0")
|
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)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.84862, 0, -4.8932)
|
||||||
|
|
||||||
[node name="StartPosition" type="Marker3D" parent="."]
|
[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="."]
|
[node name="CameraTarget" type="Marker3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.0389, 2.33215, 3.16925)
|
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")]
|
[node name="TestLevel2" parent="." instance=ExtResource("18_e2nai")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 69.0028, 0, -23.3622)
|
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,17 +8,13 @@ public partial class Alert : EnemyStateBase
|
||||||
{
|
{
|
||||||
public override EnemyState StateId => EnemyState.Alert;
|
public override EnemyState StateId => EnemyState.Alert;
|
||||||
|
|
||||||
[Export]
|
[Export] public EnemyStorageModule StorageModule { get; private set; }
|
||||||
public EnemyStorageModule StorageModule { get; private set; }
|
|
||||||
|
|
||||||
[Export]
|
[Export] public PlayerDetectionModule PlayerDetection { get; private set; }
|
||||||
public PlayerDetectionModule PlayerDetection { get; private set; }
|
|
||||||
|
|
||||||
[Export]
|
[Export] public GenericDamageReceiver DamageReceiver { get; private set; }
|
||||||
public GenericDamageReceiver DamageReceiver { get; private set; }
|
|
||||||
|
|
||||||
[Export]
|
[Export] public NavigationMovementModule NavigationModule { get; private set; }
|
||||||
public NavigationMovementModule NavigationModule { get; private set; }
|
|
||||||
|
|
||||||
private bool _isPlayerInRange = false;
|
private bool _isPlayerInRange = false;
|
||||||
|
|
||||||
|
|
@ -71,7 +67,8 @@ public partial class Alert : EnemyStateBase
|
||||||
public override void PhysicsProcessState(double delta)
|
public override void PhysicsProcessState(double delta)
|
||||||
{
|
{
|
||||||
base.PhysicsProcessState(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);
|
StateMachine.SetState(EnemyState.Shooting);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ dest_files=["res://.godot/imported/FairyGuard.png-c13f7bb7cfab625be756e4c8ce7276
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
compress/mode=0
|
compress/mode=3
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=false
|
mipmaps/generate=true
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
@ -31,4 +31,4 @@ process/normal_map_invert_y=false
|
||||||
process/hdr_as_srgb=false
|
process/hdr_as_srgb=false
|
||||||
process/hdr_clamp_exposure=false
|
process/hdr_clamp_exposure=false
|
||||||
process/size_limit=0
|
process/size_limit=0
|
||||||
detect_3d/compress_to=1
|
detect_3d/compress_to=0
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,7 @@ Solid=""
|
||||||
Acid=""
|
Acid=""
|
||||||
Destroyable=""
|
Destroyable=""
|
||||||
navigation_polygon_source_geometry_group=""
|
navigation_polygon_source_geometry_group=""
|
||||||
|
navigation_mesh_source_group=""
|
||||||
|
|
||||||
[importer_defaults]
|
[importer_defaults]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue