using System; using System.Collections.Generic; using System.Net; using ActionGameFramework.Health; using Core.Health; using UnityEngine; using UnityEngine.UIElements; using Random = UnityEngine.Random; namespace TowerDefense.Targetting { /// /// 选择目标Agent的方式 /// public enum EEnemySelFunc { NULL, Nearest, // 离终点最近的. Random, // 随机选择. MaxHp, // 血量最高的. END } /// /// Class used to track targets for an affector /// 这个类比较古怪的地方是他本身并没有显示出来Collider,但却有Collider Component.是在 /// void AttachCollider() 这个函数中(TargetterEditor.cs),动态的添加了不同类型的Collider Component, /// 然后隐藏了这个Collider Component,但是却可以在游戏内当作Collider Component来使用。 /// 在TargetterEditor内,设置isTrigger为true /// /// event 的作用在于声明的对应公有 Action or delegate变量,不能在类的外面执行,只在类的外面 += -= 操作 /// /// public class Targetter : MonoBehaviour { public static bool bSearchTarget = true; /// /// Fires when a targetable enters the target collider /// public event Action targetEntersRange; /// /// Fires when a targetable exits the target collider /// public event Action targetExitsRange; /// /// Fires when an appropriate target is found /// public event Action acquiredTarget; /// /// Fires when the current target was lost /// public event Action lostTarget; /// /// The transform to point at the target /// public Transform turret; /// /// 搜索敌人的方式:默认是最近,加入随机和血量最多,后期再加其它的模式 /// public EEnemySelFunc searchEnemyFunc = EEnemySelFunc.Nearest; /// /// The range of the turret's x rotation /// public Vector2 turretXRotationRange = new Vector2(0, 359); /// /// If m_Turret rotates freely or only on y; /// public bool onlyYTurretRotation; /// /// The search rate in searches per second /// public float searchRate; /// /// Y rotation speed while the turret is idle in degrees per second /// public float idleRotationSpeed = 39f; /// /// The time it takes for the tower to correct its x rotation on idle in seconds /// public float idleCorrectionTime = 2.0f; /// /// The collider attached to the targetter /// public Collider attachedCollider; /// /// How long the turret waits in its idle form before spinning in seconds /// public float idleWaitTime = 2.0f; /// /// The current targetables in the collider /// protected List m_TargetsInRange = new List(); /// /// The seconds until a search is allowed /// protected float m_SearchTimer = 0.0f; /// /// The seconds until the tower starts spinning /// protected float m_WaitTimer = 0.0f; /// /// The current targetable /// protected Targetable m_CurrrentTargetable; /// /// Counter used for x rotation correction /// protected float m_XRotationCorrectionTime; /// /// If there was a targetable in the last frame /// protected bool m_HadTarget; /// /// How fast this turret is spinning /// protected float m_CurrentRotationSpeed; /// /// returns the radius of the collider whether /// its a sphere or capsule /// public float effectRadius { get { var sphere = attachedCollider as SphereCollider; if (sphere != null) { return sphere.radius; } var capsule = attachedCollider as CapsuleCollider; if (capsule != null) { return capsule.radius; } return 0; } } /// /// The alignment of the affector /// public IAlignmentProvider alignment; /// /// Returns the current target /// public Targetable GetTarget(int waveline, bool noPoison = false) { switch (this.searchEnemyFunc) { case EEnemySelFunc.Nearest: case EEnemySelFunc.MaxHp: case EEnemySelFunc.Random: { if (bOpponent) { //return AgentInsManager.instance.oppoMinDisAgent; if (waveline >= 0) return AgentInsManager.instance.GetMinDisAgent(waveline, true, false, noPoison); else { Debug.Log("当前Opponent传入的参数waveLine is:" + waveline); return AgentInsManager.instance.MinDisAgent; } } else { //return AgentInsManager.instance.MinDisAgent; if (waveline >= 0) return AgentInsManager.instance.GetMinDisAgent(waveline, false, false, noPoison); else { Debug.Log("当前传入的参数waveLine is:" + waveline); return AgentInsManager.instance.MinDisAgent; } } } /* case EEnemySelFunc.MaxHp: { return AgentInsManager.instance.getMaxHpAgent(bOpponent); } case EEnemySelFunc.Random: { return AgentInsManager.instance.getRandomAgent(bOpponent); }*/ } return null; } /// /// 是否是对手战斗方. /// public bool bOpponent { set; get; } /// /// Clears the list of current targets and clears all events /// public void ResetTargetter() { m_TargetsInRange.Clear(); m_CurrrentTargetable = null; targetEntersRange = null; targetExitsRange = null; acquiredTarget = null; lostTarget = null; // Reset turret facing if (turret != null) { turret.localRotation = Quaternion.identity; } } /// /// Returns all the targets within the collider. This list must not be changed as it is the working /// list of the targetter. Changing it could break the targetter /// public List GetAllTargets() { switch (searchEnemyFunc) { case EEnemySelFunc.MaxHp: { return AgentInsManager.instance.getAgentListMaxHp(bOpponent); } case EEnemySelFunc.Random: { return AgentInsManager.instance.getAgentListRandom(bOpponent); } } // 最后返回的就是距离计算的模式: if (this.bOpponent) return AgentInsManager.instance.OppoAgentInRange; else return AgentInsManager.instance.AgentInRange; } /// /// Checks if the targetable is a valid target /// /// /// true if targetable is vaild, false if not protected virtual bool IsTargetableValid(Targetable targetable) { if (targetable == null) { return false; } IAlignmentProvider targetAlignment = targetable.configuration.alignmentProvider; bool canDamage = alignment == null || targetAlignment == null || alignment.CanHarm(targetAlignment); return canDamage; } /// /// On exiting the trigger, a valid targetable is removed from the tracking list. /// /// The other collider in the collision protected virtual void OnTriggerExit(Collider other) { var targetable = other.GetComponent(); if (!IsTargetableValid(targetable)) { return; } m_TargetsInRange.Remove(targetable); if (targetExitsRange != null) { targetExitsRange(targetable); } if (targetable == m_CurrrentTargetable) { OnTargetRemoved(targetable); } else { // Only need to remove if we're not our actual target, otherwise OnTargetRemoved will do the work above targetable.removed -= OnTargetRemoved; } } /// /// On entering the trigger, a valid targetable is added to the tracking list. /// /// The other collider in the collision protected virtual void OnTriggerEnter(Collider other) { GameObject tobj = this.transform.parent.gameObject; Collider tcol = GetComponent(); var targetable = other.GetComponent(); if (!IsTargetableValid(targetable)) { return; } targetable.removed += OnTargetRemoved; m_TargetsInRange.Add(targetable); if (targetEntersRange != null) { targetEntersRange(targetable); } } /// /// Returns the nearest targetable within the currently tracked targetables /// /// The nearest targetable if there is one, null otherwise protected virtual Targetable GetNearestTargetable() { // 使用统一管理的管理器接口. if (this.bOpponent) return AgentInsManager.instance.oppoMinDisAgent; else return AgentInsManager.instance.MinDisAgent; } /// /// Starts the search timer /// protected virtual void Start() { m_SearchTimer = searchRate; m_WaitTimer = idleWaitTime; } /// /// Checks if any targets are destroyed and aquires a new targetable if appropriate /// protected virtual void Update() { m_SearchTimer -= Time.deltaTime; if (m_SearchTimer <= 0.0f && m_CurrrentTargetable == null && m_TargetsInRange.Count > 0) { m_CurrrentTargetable = GetNearestTargetable(); if (m_CurrrentTargetable != null) { if (acquiredTarget != null) { acquiredTarget(m_CurrrentTargetable); } m_SearchTimer = searchRate; } } AimTurret(); m_HadTarget = m_CurrrentTargetable != null; } /// /// Fired by the agents died event or when the current target moves out of range, /// Fires the lostTarget event. /// void OnTargetRemoved(DamageableBehaviour target) { target.removed -= OnTargetRemoved; if (m_CurrrentTargetable != null && target.configuration == m_CurrrentTargetable.configuration) { if (lostTarget != null) { lostTarget(); } m_HadTarget = false; m_TargetsInRange.Remove(m_CurrrentTargetable); m_CurrrentTargetable = null; m_XRotationCorrectionTime = 0.0f; } else //wasnt the current target, find and remove from targets list { for (int i = 0; i < m_TargetsInRange.Count; i++) { if (m_TargetsInRange[i].configuration == target.configuration) { m_TargetsInRange.RemoveAt(i); break; } } } } /// /// Aims the turret at the current target /// 将炮塔对准当前目标 /// protected virtual void AimTurret() { if (turret == null) { return; } if (m_CurrrentTargetable == null) // do idle rotation { if (m_WaitTimer > 0) { m_WaitTimer -= Time.deltaTime; if (m_WaitTimer <= 0) { m_CurrentRotationSpeed = (Random.value * 2 - 1) * idleRotationSpeed; } } else { Vector3 euler = turret.rotation.eulerAngles; euler.x = Mathf.Lerp(Wrap180(euler.x), 0, m_XRotationCorrectionTime); m_XRotationCorrectionTime = Mathf.Clamp01((m_XRotationCorrectionTime + Time.deltaTime) / idleCorrectionTime); euler.y += m_CurrentRotationSpeed * Time.deltaTime; turret.eulerAngles = euler; } } else { m_WaitTimer = idleWaitTime; Vector3 targetPosition = m_CurrrentTargetable.position; if (onlyYTurretRotation) { targetPosition.y = turret.position.y; } Vector3 direction = targetPosition - turret.position; Quaternion look = Quaternion.LookRotation(direction, Vector3.up); Vector3 lookEuler = look.eulerAngles; // We need to convert the rotation to a -180/180 wrap so that we can clamp the angle with a min/max float x = Wrap180(lookEuler.x); lookEuler.x = Mathf.Clamp(x, turretXRotationRange.x, turretXRotationRange.y); look.eulerAngles = lookEuler; turret.rotation = look; } } /// /// A simply function to convert an angle to a -180/180 wrap /// static float Wrap180(float angle) { angle %= 360; if (angle < -180) { angle += 360; } else if (angle > 180) { angle -= 360; } return angle; } } }