using System; using Framework.Timer; using Framework.Utils.Extend; using Pathfinding; using UnityEngine; using UnityEngine.Serialization; using XFFSM; namespace Game.Component { public class EnemyEntity : MonoBehaviour { [Serializable] public class FsmData { public bool HasAttack; public bool HasSafeArea; public bool HasRunaway; public bool HasActive = true; } [SerializeField] protected FSMController _fsmController; [SerializeField] protected AILerp _aiLerp; public EnemyAnimState AnimState; [Header ("最大活动区域")] public Vector2 ActiveArea = new Vector2 (10 , 5); [Header ("随机巡逻区域")] public Vector2 WalkArea = new Vector2 (5 , 5); [Header ("攻击检测半径")] public float TriggerDistance = -1f; [Header ("追逐检测半径")] public float FollowDistance = -1f; [Header ("攻击最小持续时间")] public float AttackStateDuration = 5f; //攻击状态最小持续时间 [Header ("仇恨丢失冷却时间")] public float AttackCDDuration = 2f; [Header ("单位移动速度")] public float MaxMoveSpeed = 3; [Header ("常规移动速度系数"), Range (0, 1)] public float WalkSpeedOffset = 1; [Header ("攻击移动速度系数"), Range (0, 1)] public float AttackSpeedOffset = 1; [Header ("速度变化系数&插值t")] public float SpeedChangeT = 0.5f; private float _expSpeedOffset; //额外速度系数 private TimeHandler _expTimeHandler; //额外速度计时器 private FsmData _fsmData; private float _curAttackingTime; private float _curAttackCDTime; private float _curMoveSpeedOffset; private TimeHandler _speedTimeHandler; private bool _hasAtkState; //是否为攻击状态 //出生点 [HideInInspector] public Vector2 CreatePos; #if UNITY_EDITOR public bool IsDebugDraw = true; private void OnDrawGizmos () { this._aiLerp.speed = this.CurMoveSpeed; if (!IsDebugDraw) { return; } var enemyTransform = this.transform.position; //基于CreatePos为中心点,绘制一个矩形,宽高为ActiveArea, 用于表示此单位的活动区域 if (!UnityEditor.EditorApplication.isPlaying) { this.CreatePos = enemyTransform; } if (this.ActiveArea.y != 0 && this.ActiveArea.x != 0) { UnityEditor.Handles.color = new Color (0f, 1f, 0f, 0.35f); UnityEditor.Handles.DrawSolidRectangleWithOutline ( new Rect (CreatePos.x - ActiveArea.x / 2, CreatePos.y - ActiveArea.y / 2, ActiveArea.x, ActiveArea.y), new Color (0f, 1f, 0f, 0.25f), Color.white); // 在圆顶部绘制文本“活动范围” UnityEditor.Handles.Label (CreatePos + new Vector2 (0f, ActiveArea.y / 2), "活动范围"); } if ( this.WalkArea.y != 0 && this.WalkArea.x != 0) { UnityEditor.Handles.color = new Color (0f, 0f, 1f, 0.35f); UnityEditor.Handles.DrawSolidRectangleWithOutline ( new Rect (CreatePos.x - this.WalkArea.x / 2, CreatePos.y - WalkArea.y / 2, WalkArea.x, WalkArea.y), new Color (0f, 0f, 1f, 0.25f), Color.white); // 在圆顶部绘制文本“巡逻范围” UnityEditor.Handles.Label (CreatePos + new Vector2 (0f, WalkArea.y / 2), "巡逻范围"); } if (TriggerDistance > 0) { //绘制一个半径为TriggerDistance的圆形 UnityEditor.Handles.color = new Color (1f, 0f, 0f, 0.35f); UnityEditor.Handles.DrawSolidDisc (enemyTransform, Vector3.back, TriggerDistance); //在圆顶部绘制文本“攻击范围” UnityEditor.Handles.Label (enemyTransform + new Vector3 (0f, TriggerDistance, 0f), "攻击范围"); } if (this.FollowDistance > 0 && this._curAttackingTime > 0) { //绘制一个半径为FollowDistance的圆形 UnityEditor.Handles.color = new Color (0.62f, 0f, 1f, 0.25f); UnityEditor.Handles.DrawSolidDisc (enemyTransform, Vector3.back, FollowDistance); //在圆顶部绘制文本“追逐范围” UnityEditor.Handles.Label (enemyTransform + new Vector3 (0f, FollowDistance, 0f), "追逐范围"); } if (UnityEditor.EditorApplication.isPlaying && this._fsmController.Initialized) { var currentStateInfo = this._fsmController.GetCurrentStateInfo (0); //绘制文本 var text = "行动状态: " + currentStateInfo.data.DisplayName; UnityEditor.Handles.Label (enemyTransform, text); } } #endif public AILerp AiLerp => this._aiLerp; public bool HasAttack => this._curAttackingTime > 0; public bool HasSafeArea { get { if (this.ActiveArea.x.Compare (-1) && this.ActiveArea.y.Compare (-1)) { return true; } //特殊情况 ,如果值为-1 ,表示不限制 //x如果为-1 if (this.ActiveArea.x.Compare (-1)) { if (transform.position.y < this.CreatePos.y - this.ActiveArea.y / 2 || transform.position.y > this.CreatePos.y + this.ActiveArea.y / 2) { return false; } return true; } //y如果为-1 if (this.ActiveArea.y.Compare (-1)) { if (transform.position.x < this.CreatePos.x - this.ActiveArea.x / 2 || transform.position.x > this.CreatePos.x + this.ActiveArea.x / 2) { return false; } return true; } //检测当前位置是否在活动范围内, 如果不在活动范围内,直接返回false if (transform.position.x < this.CreatePos.x - this.ActiveArea.x / 2 || transform.position.x > this.CreatePos.x + this.ActiveArea.x / 2 || transform.position.y < this.CreatePos.y - this.ActiveArea.y / 2 || transform.position.y > this.CreatePos.y + this.ActiveArea.y / 2) { return false; } return true; } } public bool HasRunaway { get; private set; } = false; public float CurMoveSpeed => this._curMoveSpeedOffset * (this.MaxMoveSpeed * (1 + ( this._expTimeHandler?.IsPlaying ?? false ? (1 - this._expTimeHandler.CurProgress) * this._expSpeedOffset : 0f) ) ); public void ResetState () { this.AnimState.SetState (EnemyAnimStateType.Idle); this.UpdateSpeedState (false , false); _curAttackingTime = 0; _curAttackCDTime = 0; this._aiLerp.speed = this.CurMoveSpeed; } public void UpdateSpeedState (bool hasAtk , bool anim = true) { this._hasAtkState = hasAtk; var targetSpeedOffset = this._hasAtkState ? this.AttackSpeedOffset : this.WalkSpeedOffset; if (anim) { this._curMoveSpeedOffset = Mathf.Lerp (this._curMoveSpeedOffset , targetSpeedOffset , this.SpeedChangeT); } else { this._curMoveSpeedOffset = targetSpeedOffset; } } #region 生命周期 private void OnDisable () { this._expTimeHandler?.Kill (); GameUpdateMgr.Instance.RemoveUpdater (DoUpdate); } private void OnEnable () { if (!this._fsmController.Initialized) { Init (); } GameUpdateMgr.Instance.AddUpdater (DoUpdate); ResetState (); } private void DoUpdate () { UpdateSpeedState (this._hasAtkState); if (_curAttackingTime > 0) { this._curAttackingTime -= Time.deltaTime; if (this._curAttackingTime <= 0) { EndAttack (); } } if (this._curAttackCDTime > 0) { _curAttackCDTime -= Time.deltaTime; } AttackCheck (); CheckUpdateFsmData (); } private void AttackCheck () { //处于攻击cd时不作攻击检测 if (_curAttackCDTime > 0) { return; } var hasAttack = false; if (this._curAttackingTime > 0) { //追逐中使用追逐半径检测 hasAttack = this.FollowDistance < 0 || Vector2.Distance ( MapContent.Instance.PlayerPosition , this.transform.position) < this.FollowDistance; } else { hasAttack = this.TriggerDistance < 0 || Vector2.Distance ( MapContent.Instance.PlayerPosition , this.transform.position) < this.TriggerDistance; } if (hasAttack) { this._curAttackingTime = this.AttackStateDuration; } } public void CheckUpdateFsmData (bool isUpdate = false) { isUpdate |= _fsmData.HasAttack != this.HasAttack; isUpdate |= _fsmData.HasSafeArea != this.HasSafeArea; isUpdate |= _fsmData.HasRunaway != this.HasRunaway; isUpdate |= _fsmData.HasActive != MapContent.Instance.IsActiveGame; this._fsmData.HasAttack = HasAttack; this._fsmData.HasSafeArea = HasSafeArea; this._fsmData.HasRunaway = HasRunaway; this._fsmData.HasActive = MapContent.Instance.IsActiveGame; if (isUpdate) { this._fsmController.SetBool ("hasAttack", this._fsmData.HasAttack); this._fsmController.SetBool ("hasSafeArea", this._fsmData.HasSafeArea); this._fsmController.SetBool ("hasRunaway", this._fsmData.HasRunaway); this._fsmController.SetBool ("hasActive", this._fsmData.HasActive); } } #endregion public void AddSpeedBuff (float duration , float offset) { this._expSpeedOffset = offset; this._expTimeHandler?.Kill (); this._expTimeHandler = GameUpdateMgr.Instance.CreateTimer (duration , null); } public void EndAttack () { this._hasAtkState = false; this._curAttackingTime = 0; this._curAttackCDTime = this.AttackCDDuration; } private void Init () { this._fsmData = new FsmData () { HasActive = true }; this.CreatePos = this.transform.position; this._fsmController.Init (this); } } }