using System;
|
using System.Collections.Generic;
|
using ActionGameFramework.Audio;
|
using ActionGameFramework.Health;
|
using Core.Health;
|
using TowerDefense.Targetting;
|
using TowerDefense.Towers;
|
using TowerDefense.Towers.Projectiles;
|
using UnityEngine;
|
using KTGMGemClient;
|
using TowerDefense.Agents;
|
using TowerDefense.Level;
|
|
namespace TowerDefense.Affectors
|
{
|
/// <summary>
|
/// The common effect for handling firing projectiles to attack
|
///
|
/// Requires an ILauncher but it is not automatically added
|
/// Add an ILauncher implementation to this GameObject before you add this script
|
/// </summary>
|
[RequireComponent(typeof(ILauncher))]
|
public class AttackAffector : Affector, ITowerRadiusProvider
|
{
|
/// <summary>
|
/// The projectile used to attack
|
/// </summary>
|
public GameObject projectile;
|
|
/// <summary>
|
/// 木塔最后一击是特殊攻击,需要替换projectile
|
/// </summary>
|
public GameObject woodProjectile_SP;
|
//
|
protected GameObject projectile1;
|
protected GameObject projectile2;
|
|
/// <summary>
|
/// The list of points to launch the projectiles from
|
/// </summary>
|
public Transform[] projectilePoints;
|
|
/// <summary>
|
/// The reference to the center point where the tower will search from
|
/// </summary>
|
public Transform epicenter;
|
|
/// <summary>
|
/// Configuration for when the tower does splash damage
|
/// </summary>
|
public bool isMultiAttack;
|
|
/// <summary>
|
/// 如果是多目标攻击,最多攻击目标
|
/// </summary>
|
public int maxAttackNum = 1;
|
|
/// <summary>
|
/// The fire rate in fires-per-second
|
/// </summary>
|
[SerializeField]
|
private float projectileFireRate = 1;
|
|
public float FireRate
|
{
|
get { return 1 / GetFireDuration(); }
|
}
|
|
/// <summary>
|
/// 是否木属性数据
|
/// </summary>
|
public bool bWoodAffector = false;
|
|
/// <summary>
|
/// The audio source to play when firing
|
/// </summary>
|
public RandomAudioSource randomAudioSource;
|
|
/// <summary>
|
/// Gets the targetter
|
/// </summary>
|
public Targetter towerTargetter;
|
|
/// <summary>
|
/// Color of effect radius visualization
|
/// </summary>
|
public Color radiusEffectColor;
|
|
/// <summary>
|
/// Search condition
|
/// </summary>
|
public Filter searchCondition;
|
|
/// <summary>
|
/// Fire condition
|
/// </summary>
|
public Filter fireCondition;
|
|
/// <summary>
|
/// The reference to the attached launcher
|
/// </summary>
|
protected ILauncher m_Launcher;
|
|
/// <summary>
|
/// The time before firing is possible
|
/// </summary>
|
protected float m_FireTimer;
|
|
protected float freezeBreathTimer;
|
|
/// <summary>
|
/// Reference to the current tracked enemy
|
/// </summary>
|
protected Targetable m_TrackingEnemy;
|
|
public TowerLevel towerLevel;
|
|
/// <summary>
|
/// 处理装弹时间.
|
/// </summary>
|
protected float fillBulletTime = 0.0f;
|
|
/// <summary>
|
/// 火精灵充能时间
|
/// </summary>
|
protected float energyCalTime = 0;
|
|
protected float fInEnergy = 0;
|
|
protected float fBackupTimer = 0.0f;
|
|
/// <summary>
|
/// 水精灵的充能时间
|
/// </summary>
|
protected float freezeBreathCallTime = 0;
|
|
protected float inFreezeBreath;
|
|
protected float freezeBreathBackTimer = 0;
|
|
private int towerAttributeId;
|
|
/// <summary>
|
/// 火精灵技能固定攻击倍速
|
/// </summary>
|
/// <value></value>
|
protected float fireSpeed { get; set; } = 5f;
|
|
/// <summary>
|
/// 木属性精灵蓄力时间
|
/// </summary>
|
protected float woodChargeTime { get; set; } = 1.5f;
|
|
protected float woodRemainChargeTime { get; set; }
|
|
/// <summary>
|
/// 蓄力特效时间
|
/// </summary>
|
protected float woodChargeEffectTime { get; set; }
|
|
/// <summary>
|
/// 木属性精灵蓄力特效
|
/// </summary>
|
public GameObject WoodChargeEffect;
|
|
private GameObject woodChargeEffect;
|
|
public Transform WoodChargeTransform;
|
|
/// <summary>
|
/// 木属性正在瞄准的Agent
|
/// </summary>
|
protected Agent woodAimAgent;
|
|
/// <summary>
|
/// 火精灵攻击最终攻击倍速,里面计算了buff增加的倍速
|
/// </summary>
|
/// <value></value>
|
public float finalFireSpeed
|
{
|
get
|
{
|
FireRateAdd fireRateAdd = (FireRateAdd)EndlessBuffManager.instance.GetBuffInstanceByType(EndlessBuffEffectType.FireRateAdd);
|
float rateAdd = 0;
|
|
if (fireRateAdd != null)
|
rateAdd = fireRateAdd.GetFireSpeedAdd(towerPtr.attributeId);
|
return rateAdd > 1 ? rateAdd : fireSpeed;
|
}
|
}
|
|
/// <summary>
|
/// Gets the search rate from the targetter
|
/// </summary>
|
public float searchRate
|
{
|
get { return towerTargetter.searchRate; }
|
set { towerTargetter.searchRate = value; }
|
}
|
|
/// <summary>
|
/// Gets the targetable
|
/// </summary>
|
public Targetable trackingEnemy
|
{
|
get { return m_TrackingEnemy; }
|
}
|
|
/// <summary>
|
/// Gets or sets the attack radius
|
/// </summary>
|
public float effectRadius
|
{
|
get { return towerTargetter.effectRadius; }
|
}
|
|
public Color effectColor
|
{
|
get { return radiusEffectColor; }
|
}
|
|
public Targetter targetter
|
{
|
get { return towerTargetter; }
|
}
|
|
/// <summary>
|
/// Initializes the attack affector
|
/// </summary>
|
public override void Initialize(IAlignmentProvider affectorAlignment)
|
{
|
Initialize(affectorAlignment, -1);
|
}
|
|
/// <summary>
|
/// 返回可能存在的Targetter.
|
/// </summary>
|
/// <returns></returns>
|
public override TowerDefense.Targetting.Targetter GetTargetter()
|
{
|
return targetter;
|
}
|
|
/// <summary>
|
/// Initialises the attack affector with a layer mask
|
/// </summary>
|
public override void Initialize(IAlignmentProvider affectorAlignment, LayerMask mask)
|
{
|
base.Initialize(affectorAlignment, mask);
|
SetUpTimers();
|
|
towerTargetter.ResetTargetter();
|
towerTargetter.alignment = affectorAlignment;
|
towerTargetter.acquiredTarget += OnAcquiredTarget;
|
// towerTargetter.lostTarget += OnLostTarget;
|
GetAudioEnum();
|
|
myTower = transform.parent.GetComponent<TowerLevel>();
|
|
}
|
private AudioEnum audioEnum;//当前音乐的种类
|
|
void GetAudioEnum()
|
{
|
if (transform.parent.name.StartsWith("GrowUpTower"))
|
{
|
//火元素
|
audioEnum = AudioEnum.FireTAttack;
|
}
|
else if (transform.parent.name.StartsWith("BlinkTower"))
|
{
|
//木元素
|
audioEnum = AudioEnum.WoodTAttack;
|
}
|
else if (transform.parent.name.StartsWith("CopyCatTower"))
|
{
|
//水元素
|
audioEnum = AudioEnum.WaterTAttack;
|
}
|
}
|
|
void OnDestroy()
|
{
|
towerTargetter.acquiredTarget -= OnAcquiredTarget;
|
// towerTargetter.lostTarget -= OnLostTarget;
|
}
|
|
void OnAcquiredTarget(Targetable acquiredTarget)
|
{
|
// m_TrackingEnemy = acquiredTarget;
|
}
|
|
public Damager damagerProjectile
|
{
|
get { return projectile == null ? null : projectile.GetComponent<Damager>(); }
|
}
|
|
public Damager damagerProjectile1
|
{
|
get { return projectile == null ? null : projectile.GetComponent<Damager>(); }
|
}
|
|
public Damager damagerProjectile2
|
{
|
get { return projectile == null ? null : projectile.GetComponent<Damager>(); }
|
}
|
|
|
/// <summary>
|
/// Returns the total projectile damage
|
/// </summary>
|
public float GetProjectileDamage()
|
{
|
var splash = projectile.GetComponent<SplashDamager>();
|
float splashDamage = splash != null ? splash.damage : 0;
|
return damagerProjectile.finalDamage + splashDamage;
|
}
|
|
/// <summary>
|
/// Initialise the RepeatingTimer
|
/// </summary>
|
protected virtual void SetUpTimers()
|
{
|
m_Launcher = GetComponent<ILauncher>();
|
}
|
|
TowerLevel myTower;
|
bool fireState = false;
|
protected void updateTowerSkillData()
|
{
|
HandleBullet();
|
HandleEnergy();
|
HandleFreezeBreath();
|
}
|
|
// 处理木精灵装填子弹
|
private void HandleBullet()
|
{
|
if (woodRemainChargeTime > 0f)
|
woodRemainChargeTime -= Time.deltaTime;
|
|
if (woodChargeEffectTime > 0f)
|
{
|
woodChargeEffectTime -= Time.deltaTime;
|
UpdateWoodAim();
|
|
if (woodChargeEffectTime <= 0 && woodChargeEffect != null)
|
{
|
towerPtr.IsWoodCharge = false;
|
CancelWoodAim();
|
Destroy(woodChargeEffect);
|
woodChargeEffect = null;
|
}
|
}
|
|
// 预留出来装填子弹的时间.
|
if (fillBulletTime > 0)
|
{
|
fillBulletTime -= Time.deltaTime;
|
if (fillBulletTime <= 0.3f)
|
{
|
if (towerPtr && towerPtr.bulletCtl)
|
towerPtr.bulletCtl.ResetToMaxBullet();
|
}
|
|
if (fillBulletTime <= 0)
|
{
|
fillBulletTime = 0;
|
}
|
}
|
}
|
|
/// <summary>
|
/// 更新木属性瞄准
|
/// </summary>
|
private void UpdateWoodAim()
|
{
|
// 离得最近的 Agent
|
Agent agent = GetMinDistanceAgent();
|
|
if (agent != null)
|
{
|
// 还没有瞄准目标,直接分配
|
if (woodAimAgent == null)
|
{
|
woodAimAgent = agent;
|
towerPtr.WoodAimAgent = agent;
|
|
if (agent.WoodAimCount == 0)
|
agent.PlayWoodAimEffect();
|
|
++agent.WoodAimCount;
|
}
|
// 有小怪走到之前瞄准目标的前面 或者 之前瞄准的目标死亡,切换瞄准目标
|
else if (woodAimAgent.Id != agent.Id)
|
{
|
if (woodAimAgent.WoodAimCount > 0)
|
{
|
--woodAimAgent.WoodAimCount;
|
|
if (woodAimAgent.WoodAimCount == 0)
|
woodAimAgent.StopWoodAimEffect();
|
}
|
|
woodAimAgent = agent;
|
towerPtr.WoodAimAgent = agent;
|
|
if (agent.WoodAimCount == 0)
|
agent.PlayWoodAimEffect();
|
|
++agent.WoodAimCount;
|
}
|
}
|
}
|
|
/// <summary>
|
/// 获取距离终点最近的Agent
|
/// </summary>
|
/// <returns></returns>
|
private Agent GetMinDistanceAgent()
|
{
|
Agent ret = null;
|
float minDistance = -1f;
|
|
WaveLineAgentInsMgr[] waveLineAgentIns = AgentInsManager.instance.GetWaveLineList();
|
WaveLineAgentInsMgr waveLineAgentInsMgr = waveLineAgentIns[waveLineID];
|
List<Agent> agents = waveLineAgentInsMgr.listAgent;
|
Vector3 endPos = EndlessLevelManager.instance.GetHomeBasePosition(waveLineID + 1);
|
|
for (int i = 0; i < agents.Count; ++i)
|
{
|
float distance = Mathf.Abs(agents[i].transform.position.z - endPos.z);
|
|
if (minDistance < 0 || distance < minDistance)
|
{
|
minDistance = distance;
|
ret = agents[i];
|
}
|
}
|
|
return ret;
|
}
|
|
/// <summary>
|
/// 取消木属性瞄准
|
/// </summary>
|
private void CancelWoodAim()
|
{
|
if (woodAimAgent != null && woodAimAgent.WoodAimCount > 0)
|
{
|
--woodAimAgent.WoodAimCount;
|
|
if (woodAimAgent.WoodAimCount == 0)
|
woodAimAgent.StopWoodAimEffect();
|
}
|
|
woodAimAgent = null;
|
}
|
|
// 处理火精灵充能
|
private void HandleEnergy()
|
{
|
// 充能时间的处理
|
if (towerPtr && towerPtr.energyCtl)
|
{
|
if (fInEnergy <= 0)
|
{
|
energyCalTime += Time.deltaTime;
|
float process = energyCalTime % 11.0f;
|
int proint = (int)Math.Floor(process);
|
proint += towerPtr.uiProOffset;
|
towerPtr.energyCtl.SetEnergyProcessFloat(process);
|
if (proint == 10)
|
{
|
fireState = true;
|
fInEnergy = finalFireSpeed;
|
myTower.SetFireMatSpeed(true);//设置了火宝石快速攻击
|
// 设置多倍攻击速度
|
fBackupTimer = m_FireTimer;
|
m_FireTimer = m_FireTimer / finalFireSpeed;
|
|
towerPtr.uiProOffset = 0;
|
towerPtr.PlayEnergyEffect(true);
|
}
|
}
|
else
|
{
|
fInEnergy -= Time.deltaTime;
|
if (fInEnergy <= 0)
|
{
|
myTower.SetFireMatSpeed(false);//恢复了火宝石攻击速度
|
|
fireState = false;
|
EventCenter.Ins.BroadCast((int)KTGMGemClient.EventType.FireTowerChargeEnd);
|
fInEnergy = 0.0f;
|
energyCalTime = 0.0f;
|
towerPtr.energyCtl.SetEnergyProgress(0);
|
|
// 恢复正常攻击速度
|
m_FireTimer = fBackupTimer;
|
|
towerPtr.PlayEnergyEffect(false);
|
|
}
|
}
|
}
|
}
|
|
// 处理水精灵的充能
|
private void HandleFreezeBreath()
|
{
|
if (towerPtr && towerPtr.FreezeBreathCtrl)
|
{
|
Damager damager = projectile.gameObject.GetComponent<Damager>();
|
float finalDamage = damager.damage;
|
|
List<EndlessBuffConfig> list = EndlessBuffManager.instance.GetBuffListByEffectType(EndlessBuffEffectType.AttackAdd, towerPtr.attributeId);
|
float ratio = 0;
|
float add = 0;
|
|
if (list.Count > 0)
|
{
|
for (int i = 0; i < list.Count; ++i)
|
{
|
ratio += list[i].Config.buff_effect[1];
|
add += list[i].Config.buff_effect[2];
|
}
|
}
|
|
finalDamage += (ratio / 100f) * finalDamage + add;
|
|
if (inFreezeBreath <= 0)
|
{
|
freezeBreathCallTime += Time.deltaTime;
|
float process = freezeBreathCallTime % (FreezeBreath.ChargeTime + 1);
|
int processInt = (int)Mathf.Floor(process);
|
processInt += towerPtr.FreezeBreathProgressOffset;
|
towerPtr.FreezeBreathCtrl.SetProgress(process);
|
|
if (processInt == (int)Mathf.Floor(FreezeBreath.ChargeTime))
|
{
|
inFreezeBreath = towerPtr.FreezeBreathCtrl.SkillTime;
|
towerPtr.FreezeBreathProgressOffset = 0;
|
towerPtr.PlayFreezeBreathEffect(true);
|
towerPtr.FreezeBreathCtrl.ReleaseCount = 1;
|
towerPtr.FreezeBreathCtrl.PlayFreezeEffect(waveLineID);
|
towerPtr.FreezeBreathCtrl.ReleaseFreeze(waveLineID, finalDamage, damager.alignmentProvider);
|
}
|
}
|
else
|
{
|
inFreezeBreath -= Time.deltaTime;
|
int time = Mathf.FloorToInt(towerPtr.FreezeBreathCtrl.EffectTime / (towerPtr.FreezeBreathCtrl.DamageCount - 1) * 10);
|
int interval = Mathf.FloorToInt(inFreezeBreath * 10);
|
int offset = Mathf.FloorToInt(towerPtr.FreezeBreathCtrl.SkillTime * 10) - Mathf.FloorToInt(towerPtr.FreezeBreathCtrl.EffectTime * 10);
|
|
if (interval == time * (towerPtr.FreezeBreathCtrl.DamageCount - towerPtr.FreezeBreathCtrl.ReleaseCount - 1) + offset && towerPtr.FreezeBreathCtrl.ReleaseCount < towerPtr.FreezeBreathCtrl.DamageCount)
|
{
|
++towerPtr.FreezeBreathCtrl.ReleaseCount;
|
towerPtr.FreezeBreathCtrl.ReleaseFreeze(waveLineID, finalDamage, damager.alignmentProvider);
|
}
|
|
if (inFreezeBreath <= 0)
|
{
|
inFreezeBreath = 0;
|
freezeBreathCallTime = 0;
|
towerPtr.FreezeBreathCtrl.SetProgress(0);
|
towerPtr.PlayFreezeBreathEffect(false);
|
}
|
}
|
}
|
}
|
|
/// <summary>
|
/// This function is called when the object becomes enabled and active.
|
/// </summary>
|
void OnEnable()
|
{
|
if (towerPtr && towerPtr.energyCtl)
|
{
|
if (fireState)
|
{
|
myTower.SetFireMatSpeed(true);//设置了火宝石快速攻击
|
|
towerPtr.PlayEnergyEffect(true);
|
}
|
}
|
}
|
|
/// <summary>
|
/// This function is called when the behaviour becomes disabled or inactive.
|
/// </summary>
|
void OnDisable()
|
{
|
if (towerPtr && towerPtr.energyCtl)
|
{
|
towerPtr.PlayEnergyEffect(false, false);
|
}
|
|
if (towerPtr && towerPtr.FreezeBreathCtrl)
|
towerPtr.PlayFreezeBreathEffect(false, false);
|
}
|
|
/// <summary>
|
/// 获取子弹发射时间间隔
|
/// </summary>
|
public float GetFireDuration()
|
{
|
DecreaseTowerAttackCD endlessBuff = (DecreaseTowerAttackCD)EndlessBuffManager.instance.GetBuffInstanceByType(EndlessBuffEffectType.DecreaseTowerAttackCD);
|
|
return endlessBuff != null ? endlessBuff.GetDecreaseCD(towerPtr.attributeId, 1 / projectileFireRate) : 1 / projectileFireRate;
|
}
|
|
/// <summary>
|
/// Update the timers
|
/// </summary>
|
protected virtual void Update()
|
{
|
if (m_Launcher == null) return;
|
|
// 处理当前Affector所在Tower对应的技能
|
updateTowerSkillData();
|
|
m_FireTimer -= Time.deltaTime;
|
m_TrackingEnemy = targetter.GetTarget(waveLineID, bWoodAffector);
|
|
if (m_TrackingEnemy != null && m_FireTimer < 0)
|
{
|
m_FireTimer = GetFireDuration();
|
|
if (fInEnergy > 0)
|
m_FireTimer /= finalFireSpeed;
|
|
towerLevel.FireSpeed = fInEnergy > 0 ? finalFireSpeed : 1f;
|
|
if (towerPtr && towerPtr.bulletCtl != null)
|
{
|
int bnum = towerPtr.bulletCtl.GetCtlProgress();
|
|
// 蓄力时间内不攻击
|
if (bnum == 0 || woodRemainChargeTime > 0f) return;
|
}
|
|
if (towerPtr && towerPtr.FreezeBreathCtrl != null)
|
{
|
// 冷冻气息期间不攻击
|
if (inFreezeBreath > 0.0001f) return;
|
}
|
|
towerLevel.ChangeState(TowerActionState.Attack);
|
}
|
}
|
|
/// <summary>
|
/// Common logic when attacking
|
/// 调用攻击的核心函数,由这个函数发起真正的攻击,多目标或者单目标
|
/// </summary>
|
public virtual void FireProjectile()
|
{
|
// 不再处理多子弹攻击,确保只有一个弹道
|
isMultiAttack = false;
|
m_TrackingEnemy = targetter.GetTarget(waveLineID, bWoodAffector);
|
|
GameObject go = damagerProjectile.gameObject;
|
|
if (m_TrackingEnemy == null || fillBulletTime > 0) return;
|
|
go.GetComponent<Damager>().IsEnhancedBullet = false;
|
|
// 处理子弹充能相关的内容
|
if (towerPtr && towerPtr.bulletCtl != null)
|
{
|
int bnum = towerPtr.bulletCtl.decBullet();
|
// 暴击子弹的数量,如果获得相应buff可能会修改暴击子弹数量
|
int critBulletNum = towerPtr.bulletCtl.CritBulletNum;
|
|
if (bnum < critBulletNum)
|
{
|
if (bnum == 0)
|
// 不需要装填时间
|
fillBulletTime = 0.1f;
|
|
//这里需要替换特效
|
var poolable = Core.Utilities.Poolable.TryGetPoolable<Core.Utilities.Poolable>(woodProjectile_SP);
|
go = poolable.gameObject;
|
Damager tmpDamager = go.GetComponent<Damager>();
|
tmpDamager.damageMulti = 10.0f;
|
tmpDamager.damage = damagerProjectile.damage;
|
tmpDamager.IsEnhancedBullet = true;
|
}
|
|
// 下一颗子弹是强化子弹,然后直接蓄力
|
if (bnum - 1 >= 0 && bnum - 1 < critBulletNum)
|
{
|
woodRemainChargeTime = woodChargeTime;
|
towerPtr.IsWoodCharge = true;
|
DecreaseWoodChargeTime decreaseWoodChargeTime = (DecreaseWoodChargeTime)EndlessBuffManager.instance.GetBuffInstanceByType(EndlessBuffEffectType.DecreaseWoodChargeTime);
|
|
if (decreaseWoodChargeTime != null)
|
woodRemainChargeTime = decreaseWoodChargeTime.GetWoodChargeTime(woodChargeTime);
|
|
woodChargeEffectTime = woodRemainChargeTime + 0.5f / towerLevel.ActionAnimator.speed;
|
woodChargeEffect = Instantiate(WoodChargeEffect);
|
woodChargeEffect.transform.SetParent(gameObject.transform);
|
woodChargeEffect.transform.SetPositionAndRotation(WoodChargeTransform.position, WoodChargeTransform.rotation);
|
ParticleSystem ps = woodChargeEffect.transform.GetChild(0).GetComponent<ParticleSystem>();
|
ps.Play();
|
}
|
}
|
else
|
{
|
if (towerPtr)
|
towerPtr.setTowerState(true);
|
}
|
|
if (isMultiAttack)
|
{
|
List<Targetable> enemies = towerTargetter.GetAllTargets();
|
if ((enemies != null) && (Targetter.bSearchTarget))
|
m_Launcher.Launch(enemies, projectile, projectilePoints, maxAttackNum);
|
}
|
else
|
{
|
if (Targetter.bSearchTarget)
|
{
|
m_Launcher.Launch(m_TrackingEnemy, go, projectilePoints);
|
if (AudioSourceManager.Ins)
|
AudioSourceManager.Ins.Play(audioEnum);
|
}
|
}
|
if (randomAudioSource != null)
|
{
|
if (Targetter.bSearchTarget)
|
randomAudioSource.PlayRandomClip();
|
}
|
}
|
|
/// <summary>
|
/// A delegate to compare distances of components
|
/// </summary>
|
/// <param name="first"></param>
|
/// <param name="second"></param>
|
protected virtual int ByDistance(Targetable first, Targetable second)
|
{
|
float firstSqrMagnitude = Vector3.SqrMagnitude(first.position - epicenter.position);
|
float secondSqrMagnitude = Vector3.SqrMagnitude(second.position - epicenter.position);
|
return firstSqrMagnitude.CompareTo(secondSqrMagnitude);
|
}
|
|
#if UNITY_EDITOR
|
/// <summary>
|
/// Draws the search area
|
/// </summary>
|
void OnDrawGizmosSelected()
|
{
|
Gizmos.DrawWireSphere(epicenter.position, towerTargetter.effectRadius);
|
}
|
#endif
|
}
|
|
/// <summary>
|
/// A delegate for boolean calculation logic
|
/// </summary>
|
public delegate bool Filter();
|
}
|