266 lines
7.0 KiB
C#
266 lines
7.0 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Data.Odbc;
|
|
using System.Linq;
|
|
using BITKit;
|
|
using BITKit.AI.States;
|
|
using BITKit.Entities;
|
|
using BITKit.Sensors;
|
|
using BITKit.StateMachine;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
namespace BITFALL.AI.States
|
|
{
|
|
public abstract class AIRuntimeState : AIState
|
|
{
|
|
[Inject,HideInInspector] public AIService Service;
|
|
private readonly ConcurrentDictionary<int, bool> _friends = new();
|
|
private readonly ConcurrentDictionary<int, bool> _enemies = new();
|
|
protected readonly HashSet<int> ignoreNoises = new();
|
|
protected readonly CacheList<IEntity> detectedEnemies = new();
|
|
protected readonly CacheList<IEntity> hearEnemies = new();
|
|
public bool IsFriend(IEntity target)
|
|
{
|
|
return _friends.GetOrAdd(target.Id, IsFriendInternal);
|
|
}
|
|
public bool IsEnemy(IEntity target)
|
|
{
|
|
return _enemies.GetOrAdd(target.Id, IsEnemyInternal);
|
|
}
|
|
public override void OnStateUpdate(float deltaTime)
|
|
{
|
|
base.OnStateUpdate(deltaTime);
|
|
detectedEnemies.Clear();
|
|
foreach (var x in Service.RangeSensor.Get())
|
|
{
|
|
if (x.TryGetComponent<IEntity>(out var entity) is false) continue;
|
|
if (IsEnemy(entity) is false)
|
|
{
|
|
foreach (var _collider in entity.As<MonoBehaviour>().GetComponents<Collider>())
|
|
{
|
|
Service.RangeSensor.Ignores.Add(_collider.GetInstanceID());
|
|
; }
|
|
continue;
|
|
}
|
|
detectedEnemies.Add(entity);
|
|
}
|
|
hearEnemies.Clear();
|
|
foreach (var x in Service.AudioSensor.Get())
|
|
{
|
|
if (x.TryGetComponent<IEntity>(out var entity) is false) continue;
|
|
if (IsEnemy(entity) is false)
|
|
{
|
|
foreach (var _collider in entity.As<MonoBehaviour>().GetComponents<Collider>())
|
|
{
|
|
Service.AudioSensor.Ignores.Add(_collider.GetInstanceID());
|
|
}
|
|
continue;
|
|
}
|
|
hearEnemies.Add(entity);
|
|
}
|
|
}
|
|
|
|
private bool IsEnemyInternal(int id)
|
|
{
|
|
var target = UnityEntitiesService.Get(id);
|
|
var tags = target.As<MonoBehaviour>().GetComponent<ITag>().GetTags();
|
|
foreach (var x in Service.EnemyTag.GetTags())
|
|
{
|
|
if (tags.Contains(x)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsFriendInternal(int id)
|
|
{
|
|
var target = UnityEntitiesService.Get(id);
|
|
var tags = target.As<MonoBehaviour>().GetComponent<ITag>().GetTags();
|
|
foreach (var x in Service.FriendTag.GetTags())
|
|
{
|
|
if (tags.Contains(x)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
[CustomType(typeof(AI_Idle))]
|
|
public sealed class Idle : AIRuntimeState, AI_Idle
|
|
{
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
}
|
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
{
|
|
base.OnStateUpdate(deltaTime);
|
|
//foreach (var x in Service.AudioSensor.Get().Union(Service.RangeSensor.Get()))
|
|
foreach (var entity in detectedEnemies)
|
|
{
|
|
Service.CombatTarget = entity;
|
|
Service.TransitionState<Alert>();
|
|
return;
|
|
}
|
|
foreach (var entity in hearEnemies)
|
|
{
|
|
Service.CombatTarget = entity;
|
|
Service.TransitionState<Alert>();
|
|
return;
|
|
}
|
|
foreach (var x in Service.AudioSensor.Noises)
|
|
{
|
|
if(ignoreNoises.Add(x.Id) is false)continue;
|
|
var distance = Vector3.Distance(Service.transform.position, x.Position);
|
|
//Debug.Log($"{x.Radius}:{distance}");
|
|
if (distance <= x.Radius && NavMesh.SamplePosition(x.Position,out var hit,1,NavMesh.AllAreas))
|
|
{
|
|
Service.Agent.SetDestination(hit.position);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
[Serializable]
|
|
[CustomType(typeof(AI_Idle))]
|
|
public sealed class Alert : AIRuntimeState, AI_Alert
|
|
{
|
|
public int AlertLevel { get; set; }
|
|
public float RemainingAlertTime { get; set; }
|
|
[SerializeField] private int initialAlertTime;
|
|
[SerializeField] private int immediatelyDetectDistance = 5;
|
|
[SerializeField,ReadOnly]private float detectedWeight;
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
RemainingAlertTime = initialAlertTime;
|
|
detectedWeight = old switch
|
|
{
|
|
AI_Combat=>0.5f,
|
|
_ => 0
|
|
};
|
|
}
|
|
public override void OnStateUpdate(float deltaTime)
|
|
{
|
|
base.OnStateUpdate(deltaTime);
|
|
|
|
if (detectedEnemies.Count is 0)
|
|
{
|
|
foreach (var entity in hearEnemies)
|
|
{
|
|
Service.Agent.SetDestination(entity.As<MonoBehaviour>().transform.position);
|
|
return;
|
|
}
|
|
detectedWeight -= deltaTime;
|
|
}
|
|
else
|
|
{
|
|
foreach (var entity in detectedEnemies)
|
|
{
|
|
var distance= Vector3.Distance(Service.transform.position, entity.As<MonoBehaviour>().transform.position);
|
|
entity.TryGetComponent<ISensorTarget>(out var sensorTarget);
|
|
if(distance<=immediatelyDetectDistance)
|
|
{
|
|
Service.CombatTarget = entity;
|
|
Service.TransitionState<Combat>();
|
|
sensorTarget?.Detected(1,Service.RangeSensor,Service.transform);
|
|
return;
|
|
}
|
|
detectedWeight += deltaTime;
|
|
sensorTarget?.Detected(detectedWeight,Service.RangeSensor,Service.transform);
|
|
if (detectedWeight >= 1)
|
|
{
|
|
Service.CombatTarget = entity;
|
|
Service.TransitionState<Combat>();
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
RemainingAlertTime -= deltaTime;
|
|
|
|
if(RemainingAlertTime<=0)
|
|
{
|
|
Service.TransitionState<Idle>();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
[CustomType(typeof(AI_Idle))]
|
|
public sealed class Combat : AIRuntimeState, AI_Combat
|
|
{
|
|
public AITarget CurrentTarget => _currentTarget;
|
|
private AITarget _currentTarget;
|
|
[SerializeField] private float lostTargetTime;
|
|
private readonly IntervalUpdate reTargetInterval = new(2);
|
|
[SerializeField, ReadOnly] private float currentLostTargetTime;
|
|
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
Service.RangeSensor.DetectedHeightWeight = 1;
|
|
currentLostTargetTime = lostTargetTime;
|
|
reTargetInterval.Reset();
|
|
Service.CombatTarget.TryGetComponent<AITarget>(out _currentTarget);
|
|
Service.Agent.isStopped = false;
|
|
}
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
base.OnStateExit(old, newState);
|
|
Service.RangeSensor.DetectedHeightWeight = 0.5f;
|
|
Service.RangeSensor.transform.localRotation = Quaternion.identity;
|
|
}
|
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
{
|
|
base.OnStateUpdate(deltaTime);
|
|
|
|
var position = Service.CombatTarget.As<MonoBehaviour>().transform.position;
|
|
var direction = position - Service.transform.position;
|
|
direction = Vector3.ProjectOnPlane(direction, Vector3.up);
|
|
if (direction.magnitude > 0.01)
|
|
{
|
|
Service.RangeSensor.transform.rotation = Quaternion.LookRotation(direction);
|
|
}
|
|
|
|
if (NavMesh.SamplePosition(position, out var hit, 1, NavMesh.AllAreas))
|
|
{
|
|
Service.Agent.SetDestination(hit.position);
|
|
}
|
|
|
|
foreach (var entity in detectedEnemies)
|
|
{
|
|
currentLostTargetTime = lostTargetTime;
|
|
if (reTargetInterval.AllowUpdate)
|
|
{
|
|
Service.CombatTarget = entity;
|
|
Service.CombatTarget.TryGetComponent<AITarget>(out _currentTarget);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
foreach (var entity in hearEnemies)
|
|
{
|
|
currentLostTargetTime = lostTargetTime;
|
|
}
|
|
|
|
currentLostTargetTime -= deltaTime;
|
|
if (currentLostTargetTime <= 0)
|
|
{
|
|
Service.TransitionState<Alert>();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|