815 lines
27 KiB
C#
815 lines
27 KiB
C#
using Assets.Scripts;
|
||
using DG.Tweening;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.InputSystem;
|
||
|
||
|
||
/// <summary>
|
||
/// 炮弹,子弹发射类
|
||
/// </summary>
|
||
public class Shot : MonoBehaviour
|
||
{
|
||
public RingManager ringManager;
|
||
public Transform impact;
|
||
public Transform muzzleFlare;
|
||
public Transform bullet;
|
||
|
||
[Tooltip("炮弹连续穿透并自动射击附近的敌人的次数")]
|
||
public int continueShotCount = 3;
|
||
|
||
public Transform turret;
|
||
public Transform gun;
|
||
[Tooltip("发射音效")]
|
||
public AudioClip shotAudioClip;
|
||
[Tooltip("击中音效")]
|
||
public AudioClip hitAudioClip;
|
||
[Tooltip("发射时间间隔")]
|
||
public float shotDuration = 0.2f;
|
||
[Tooltip("连发发射时间间隔")]
|
||
public float shotNextDuration = 0.1f;
|
||
[Tooltip("发射距离")]
|
||
public float shotDistance = 50f;
|
||
[Tooltip("炮弹速度")]
|
||
public float shotSpeed = 1;
|
||
[Tooltip("初始化炮弹数量")]
|
||
public int initBulletCount = 300; // 修改为int类型
|
||
[Tooltip("发射Z坐标偏移前移动倍数,相当于乘以gun.forward")]
|
||
public float shotOffsetZMul = 0;
|
||
|
||
[Tooltip("发射X坐标角度修正")]
|
||
public float shotFixAngleX = 0;
|
||
|
||
[Tooltip("炮台转向,炮管锁定速度")]
|
||
public float lerpSpeed = 5f;
|
||
[Tooltip("是否自动跟踪锁定最近的敌人")]
|
||
public bool isAutoLock = false;
|
||
[Tooltip("是否显示发射时候炮口的火焰")]
|
||
public bool isShowMuzzle = false;
|
||
//[Tooltip("子弹最多多少秒后自动销毁")]
|
||
//public float bulletMaxDuration = 3;
|
||
|
||
[Header("输入绑定")]
|
||
[SerializeField] private InputActionReference pressAction;
|
||
|
||
private bool isShoting = false;
|
||
//private Coroutine shotCoroutine = null;
|
||
//private bool isInitLookedEnemy = false;
|
||
|
||
//// 跟踪正在使用的对象,防止重复分配
|
||
//private HashSet<Transform> activeBullets = new HashSet<Transform>();
|
||
//private HashSet<Transform> activeMuzzles = new HashSet<Transform>();
|
||
private Vector3 turretLerpEuler = Vector3.zero;
|
||
private float smoothXAngle = 0f;
|
||
private float targetXAngle = 0f;
|
||
private float hitDistance = 0.2f; // 命中检测距离阈值
|
||
private bool isCanShot = false;
|
||
private bool isStartAttack = false;
|
||
private ConcurrentQueue<NextShotInfo> nextShotInfos = new();
|
||
private QiuObject[] _qiuObjects = new QiuObject[] { };
|
||
|
||
|
||
|
||
// 弹仓List,避免重复分配对象
|
||
private ConcurrentQueue<Transform> bulletPool = new();
|
||
private ConcurrentQueue<Transform> muzzlePool = new();
|
||
private ConcurrentQueue<Transform> impactPool = new();
|
||
private List<Transform> activeBulletPool = new();
|
||
private List<Transform> activeImpactPool = new();
|
||
|
||
/// <summary>
|
||
/// 上一次发射的时间
|
||
/// </summary>
|
||
private static DateTime beforeShotTime = DateTime.Now;
|
||
|
||
|
||
private Camera _cam = null;
|
||
void Awake()
|
||
{
|
||
if (turret == null)
|
||
{
|
||
turret = new GameObject("Turret").transform;
|
||
turret.SetParent(transform);
|
||
turret.localPosition = Vector3.zero;
|
||
turret.localEulerAngles = Vector3.zero;
|
||
turret.localScale = Vector3.one;
|
||
}
|
||
|
||
if (gun == null)
|
||
{
|
||
gun = new GameObject("Gun").transform;
|
||
gun.SetParent(turret);
|
||
gun.localPosition = Vector3.zero;
|
||
gun.localScale = Vector3.one;
|
||
gun.forward = turret.forward;
|
||
}
|
||
|
||
turretLerpEuler = turret.eulerAngles;
|
||
smoothXAngle = gun.localEulerAngles.x;
|
||
isCanShot = true;// !isAutoLock;//如果不是自动锁定,开火按钮设成True
|
||
_cam = Camera.main;
|
||
|
||
if (transform.parent != null)
|
||
{
|
||
_qiuObjects = transform.parent.GetComponentsInChildren<QiuObject>();
|
||
}
|
||
// 初始化对象池(使用Queue而非全局List)
|
||
InitializePool();
|
||
|
||
}
|
||
|
||
void Start()
|
||
{
|
||
StartCoroutine(ContinueShot());
|
||
StartCoroutine(QueueNextShot());
|
||
}
|
||
|
||
private void ClearCachePool()
|
||
{
|
||
foreach (var bullet in this.activeBulletPool)
|
||
{
|
||
var bulletComp = bullet.GetComponent<Bullet>();
|
||
bulletComp.IsNeedDestroyInRecycle = true;
|
||
}
|
||
// 清理所有对象
|
||
foreach (var bullet in this.bulletPool)
|
||
{
|
||
if (bullet != null)
|
||
{
|
||
bullet.DOKill(true);
|
||
Destroy(bullet.gameObject);
|
||
}
|
||
}
|
||
|
||
foreach (var muzzle in this.muzzlePool)
|
||
{
|
||
if (muzzle != null)
|
||
{
|
||
//muzzle.DOKill();
|
||
Destroy(muzzle.gameObject);
|
||
}
|
||
}
|
||
|
||
foreach (var impact in this.activeImpactPool)
|
||
{
|
||
if (impact != null)
|
||
{
|
||
//impact.DOKill();
|
||
var recycle = impact.GetComponent<RecycleImpact>();
|
||
recycle.IsNeedDestroyInRecycle = true;
|
||
}
|
||
}
|
||
foreach (var impact in this.impactPool)
|
||
{
|
||
if (impact != null)
|
||
{
|
||
//impact.DOKill(true);
|
||
Destroy(impact.gameObject);
|
||
}
|
||
}
|
||
|
||
|
||
//foreach (var bullet in activeBullets)
|
||
//{
|
||
// if (bullet != null)
|
||
// {
|
||
// bullet.DOKill();
|
||
// Destroy(bullet.gameObject);
|
||
// }
|
||
//}
|
||
|
||
//foreach (var muzzle in activeMuzzles)
|
||
//{
|
||
// if (muzzle != null)
|
||
// {
|
||
// muzzle.DOKill();
|
||
// Destroy(muzzle.gameObject);
|
||
// }
|
||
//}
|
||
|
||
// 清空旧数据
|
||
this.activeImpactPool.Clear();
|
||
this.activeBulletPool.Clear();
|
||
this.bulletPool.Clear();
|
||
this.muzzlePool.Clear();
|
||
this.impactPool.Clear();
|
||
}
|
||
|
||
private void RecycleImpactToPool(Transform impactTransform)
|
||
{
|
||
PoolTool.RecycleImpactToPool(impactTransform, impactPool);
|
||
}
|
||
|
||
public void InitializePool()
|
||
{
|
||
//activeBullets.Clear();
|
||
//activeMuzzles.Clear();
|
||
|
||
ClearCachePool();
|
||
// 创建子弹池
|
||
for (int i = 0; i < initBulletCount; i++)
|
||
{
|
||
// 子弹对象
|
||
var bulletObj = Instantiate(bullet);
|
||
bulletObj.gameObject.SetActive(false);
|
||
bulletObj.SetParent(null); // 强制设为世界对象
|
||
bulletObj.name = $"Bullet_{i}";
|
||
this.bulletPool.Enqueue(bulletObj);
|
||
|
||
if (isShowMuzzle)
|
||
{
|
||
// 枪口火焰对象
|
||
var muzzleObj = Instantiate(muzzleFlare);
|
||
muzzleObj.gameObject.SetActive(false);
|
||
muzzleObj.SetParent(null);
|
||
muzzleObj.name = $"Muzzle_{i}";
|
||
this.muzzlePool.Enqueue(muzzleObj);
|
||
}
|
||
|
||
//for (int j = 0; j < Mathf.Max(1, continueShotCount / 3); j++)
|
||
//{
|
||
// 爆炸火焰对象
|
||
var impactObj = Instantiate(impact);
|
||
impactObj.gameObject.SetActive(false);
|
||
impactObj.SetParent(null);
|
||
impactObj.name = $"Impact_{i}";
|
||
impactObj.GetComponent<RecycleImpact>().RecycleAction = this.RecycleImpactToPool;
|
||
this.impactPool.Enqueue(impactObj);
|
||
//}
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
// 停止协程
|
||
//if (shotCoroutine != null)
|
||
//{
|
||
// StopCoroutine(shotCoroutine);
|
||
//}
|
||
StopAllCoroutines();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
pressAction.action.performed += PressAction_performed;
|
||
pressAction.action.canceled += PressAction_canceled;
|
||
pressAction.action.Enable();
|
||
}
|
||
|
||
private void PressAction_performed(InputAction.CallbackContext ctx)
|
||
{
|
||
isShoting = true;
|
||
}
|
||
|
||
private void PressAction_canceled(InputAction.CallbackContext ctx)
|
||
{
|
||
isShoting = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连续发射炮弹的协程
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private IEnumerator ContinueShot()
|
||
{
|
||
//float duration = shotDistance / shotSpeed; // 正确的飞行时间计算:距离/速度
|
||
|
||
while (true)
|
||
{
|
||
if (isCanShot)
|
||
{
|
||
SingleShot(GetBullet(), lockEnemy, false);
|
||
//isCanShot = !isAutoLock;
|
||
}
|
||
|
||
yield return new WaitForSeconds(shotDuration);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 弹射炮弹的协程
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private IEnumerator QueueNextShot()
|
||
{
|
||
//float duration = shotDistance / shotSpeed; // 正确的飞行时间计算:距离/速度
|
||
|
||
while (true)
|
||
{
|
||
if (shotNextDuration == 0)
|
||
{
|
||
yield return new WaitForEndOfFrame();
|
||
}
|
||
else
|
||
{
|
||
yield return new WaitForSeconds(shotNextDuration);
|
||
}
|
||
if (nextShotInfos.TryDequeue(out NextShotInfo info))
|
||
{
|
||
SingleShot(info.BulletObj, info.LockEnemy, true, info.CurrentShotCount, info.InitBulletPos, info.BeforeLockTarget);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从炮弹池中会去对象
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private Transform GetBullet()
|
||
{
|
||
Transform bulletObj = null;
|
||
if (bulletObj == null || bulletObj.IsDestroyed())
|
||
{
|
||
// 从池中获取对象
|
||
bulletObj = PoolTool.GetFromPool(this.bulletPool, this.activeBulletPool, this.activeImpactPool);
|
||
// 激活对象
|
||
bulletObj.gameObject.SetActive(true);
|
||
}
|
||
return bulletObj;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新炮弹对象状态
|
||
/// </summary>
|
||
/// <param name="bulletComp">炮弹对象</param>
|
||
/// <param name="beforeHitTargetTransform">之前集中的目标,可为空</param>
|
||
private void BulletUpdate(Bullet bulletComp, Transform beforeHitTargetTransform)
|
||
{
|
||
|
||
if (isAutoLock)
|
||
{
|
||
if (bulletComp.LockTarget == null)
|
||
{
|
||
bulletComp.LockTarget = GetNearestEnemyFromTransform(bulletComp.transform, beforeHitTargetTransform);
|
||
//bulletComp.IsLockedTarget = true;
|
||
}
|
||
}
|
||
|
||
|
||
//if (bulletComp.ForwardTarget == null)
|
||
//{
|
||
// bulletComp.ForwardTarget = GetNearestEnemyFromTransform(bulletComp.transform, beforeHitTargetTransform);
|
||
//}
|
||
|
||
//if (bulletComp.ForwardTarget != null)
|
||
//{
|
||
// var pos = bulletComp.transform.position;
|
||
// pos.y = bulletComp.ForwardTarget.transform.position.y;
|
||
// bulletComp.transform.position = pos;
|
||
//}
|
||
|
||
// 修正弹体朝向(对准目标,更真实)
|
||
if (bulletComp.LockTarget != null && bulletComp.transform != null)
|
||
{
|
||
bulletComp.ShotTargetPosition = bulletComp.LockTarget.transform.position;
|
||
}
|
||
|
||
//if (isAutoLock)
|
||
//{
|
||
float x = bulletComp.transform.eulerAngles.x;
|
||
|
||
bulletComp.transform.LookAt(bulletComp.ShotTargetPosition);
|
||
|
||
var eulerAngles = bulletComp.transform.eulerAngles;
|
||
eulerAngles.Set(x, eulerAngles.y, eulerAngles.z);
|
||
bulletComp.transform.eulerAngles = eulerAngles;
|
||
//}
|
||
|
||
if (CalcBulletBoom(bulletComp))
|
||
{
|
||
bulletComp.transform.DOComplete(true);
|
||
bulletComp.transform.DOKill();
|
||
}
|
||
}
|
||
|
||
private void ShotBulletComplete(Bullet bulletComp, int currentShotCount, Transform muzzleObj)
|
||
{
|
||
bool isBulletHitted = CalcBulletBoom(bulletComp);
|
||
|
||
if (bulletComp.HitTarget != null)
|
||
{
|
||
if (hitAudioClip != null)
|
||
{
|
||
AudioTool.AudioSource.PlayOneShot(hitAudioClip, 0.5f);
|
||
}
|
||
|
||
currentShotCount++;
|
||
if (currentShotCount <= continueShotCount)
|
||
{
|
||
PoolTool.RecycleMuzzleToPool(muzzleObj, this.muzzlePool, this.activeImpactPool);
|
||
var nextLockTarget = GetNearestEnemyFromTransform(bulletComp.transform, bulletComp.HitTarget == null ? bulletComp.transform : bulletComp.HitTarget.transform);
|
||
if (nextLockTarget != null)
|
||
{
|
||
Vector3 initBulletPos = bulletComp.transform.position;
|
||
Transform hitTransform = null;
|
||
if (bulletComp.HitTarget != null)
|
||
{
|
||
hitTransform = bulletComp.HitTarget.transform;
|
||
initBulletPos = hitTransform.position;
|
||
}
|
||
|
||
CheckBulletBoom(isBulletHitted, bulletComp, false);
|
||
bulletComp.ResetEnemyTargets();
|
||
|
||
var beforeTarget = bulletComp.BeforeHitTarget;
|
||
PoolTool.RecycleBulletToPool(bulletComp.transform, this.bulletPool, this.activeBulletPool);
|
||
SingleShot(GetBullet(), nextLockTarget, true, currentShotCount, initBulletPos, beforeTarget);//连线射击
|
||
//nextShotInfos.Enqueue(new NextShotInfo { CurrentShotCount = currentShotCount, LockEnemy = nextLockTarget, InitBulletPos = initBulletPos, BulletObj = null, BeforeLockTarget = bulletComp.BeforeHitTarget });
|
||
}
|
||
else
|
||
{
|
||
CheckBulletBoom(isBulletHitted, bulletComp);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
CheckBulletBoom(isBulletHitted, bulletComp);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
CheckBulletBoom(isBulletHitted, bulletComp);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 单次发射方法
|
||
/// </summary>
|
||
/// <param name="lockEnemy"></param>
|
||
/// <param name="isNextLineShot">是否弹射</param>
|
||
/// <param name="initBulletPos"></param>
|
||
/// <param name="bulletObj"></param>
|
||
private void SingleShot(Transform bulletObj, Enemy lockEnemy, bool isNextLineShot, int currentShotCount = 0, Vector3? initBulletPos = null, Enemy beforeLockTarget = null)
|
||
{
|
||
Transform muzzleObj = null;
|
||
if (isShowMuzzle && !isNextLineShot)
|
||
{
|
||
muzzleObj = PoolTool.GetFromPool(this.muzzlePool, this.activeBulletPool, this.activeImpactPool);
|
||
}
|
||
if (bulletObj != null)
|
||
{
|
||
// 1. 播放音效
|
||
if (!isNextLineShot && shotAudioClip != null)
|
||
{
|
||
if(DateTime.Now - beforeShotTime > TimeSpan.FromSeconds(shotDuration))
|
||
{
|
||
beforeShotTime = DateTime.Now;
|
||
AudioTool.AudioSource.PlayOneShot(shotAudioClip);
|
||
}
|
||
}
|
||
|
||
// 2. 计算发射位置(确保使用世界坐标)
|
||
Vector3 firePos = gun.position + gun.forward * shotOffsetZMul;
|
||
|
||
// 3. 设置初始位置(强制世界坐标)
|
||
if (initBulletPos == null)
|
||
{
|
||
bulletObj.position = gun.position;
|
||
}
|
||
else
|
||
{
|
||
bulletObj.position = initBulletPos.Value;
|
||
}
|
||
|
||
// 4. 获取子弹组件并重置状态
|
||
var bulletComp = bulletObj.GetComponent<Bullet>();
|
||
bulletComp.BeforeHitTarget = beforeLockTarget;
|
||
bulletComp.IsAutoLock = isAutoLock;
|
||
bulletComp.IsShoting = true;
|
||
var beforeHitTargetTransform = bulletComp.BeforeHitTarget == null ? null : bulletComp.BeforeHitTarget.transform;
|
||
|
||
// 5. 设置炮弹发射方向
|
||
if (isNextLineShot)
|
||
{
|
||
bulletComp.LockTarget = GetNearestEnemyFromTransform(bulletObj, beforeHitTargetTransform);
|
||
if (bulletComp.LockTarget != null)
|
||
{
|
||
bulletComp.ShotTargetPosition = bulletComp.LockTarget.transform.position;
|
||
}
|
||
else
|
||
{
|
||
var pos = gun.position + gun.forward * shotDistance;
|
||
pos.y = gun.position.y;
|
||
bulletComp.ShotTargetPosition = pos;
|
||
}
|
||
}
|
||
else if (isAutoLock && lockEnemy != null && !lockEnemy.IsDestroyed())
|
||
{
|
||
bulletComp.LockTarget = lockEnemy;
|
||
bulletComp.ShotTargetPosition = lockEnemy.transform.position;
|
||
// 6. 设置炮弹角度
|
||
bulletObj.transform.eulerAngles = Vector3.zero;
|
||
}
|
||
else
|
||
{
|
||
var pos = gun.position + gun.forward * shotDistance;
|
||
pos.y = gun.position.y;
|
||
bulletComp.ShotTargetPosition = pos;
|
||
// 6. 设置炮弹角度
|
||
bulletObj.transform.eulerAngles = Vector3.zero;
|
||
bulletObj.transform.LookAt(bulletComp.ShotTargetPosition);
|
||
}
|
||
|
||
|
||
// 7. 击中爆炸效果对象不为空,则设置相关参数
|
||
if (muzzleObj != null)
|
||
{
|
||
//设置炮口火焰父对象(可选)
|
||
muzzleObj.position = firePos;
|
||
muzzleObj.SetParent(gun);
|
||
muzzleObj.gameObject.SetActive(true);
|
||
|
||
muzzleObj.transform.LookAt(bulletComp.ShotTargetPosition);
|
||
}
|
||
|
||
// 8. 延迟一帧,等待下一帧状态更新后,开始炮弹动画和相关逻辑执行
|
||
DOVirtual.DelayedCall(Time.fixedDeltaTime, () =>
|
||
{
|
||
bulletComp.Tweener = DOTween.To(
|
||
() =>
|
||
{
|
||
return bulletObj == null ? transform.position : bulletObj.position; // 当前位置(Getter)
|
||
},
|
||
(Vector3 pos) =>
|
||
{
|
||
if (bulletObj != null)
|
||
{
|
||
bulletObj.position = Vector3.MoveTowards(bulletObj.position, bulletComp.ShotTargetPosition, Time.deltaTime * shotSpeed);
|
||
} // 更新位置(Setter)
|
||
},
|
||
bulletComp.ShotTargetPosition,//目标位置
|
||
shotSpeed
|
||
)
|
||
.SetSpeedBased()
|
||
.SetEase(Ease.Linear)
|
||
.SetUpdate(UpdateType.Manual)
|
||
.OnUpdate(() =>
|
||
{
|
||
//每帧更新炮弹状态参数
|
||
BulletUpdate(bulletComp, beforeHitTargetTransform);
|
||
})
|
||
.OnComplete(() =>
|
||
{
|
||
//动画完成处理(超出视距范围外,或者击中爆炸)
|
||
ShotBulletComplete(bulletComp, currentShotCount, muzzleObj);
|
||
});
|
||
|
||
if (muzzleObj != null)
|
||
{
|
||
// 9. 爆炸火焰自动隐藏(0.2秒后)
|
||
DOVirtual.DelayedCall(0.2f, () =>
|
||
{
|
||
PoolTool.RecycleMuzzleToPool(muzzleObj, this.muzzlePool, this.activeImpactPool);
|
||
});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
private void CheckBulletBoom(bool isBulletHitted, Bullet bulletComp, bool recycleToPool = true)
|
||
{
|
||
if (isBulletHitted)
|
||
{
|
||
CheckBulletBoom(bulletComp);
|
||
}
|
||
if (recycleToPool)
|
||
{
|
||
PoolTool.RecycleBulletToPool(bulletComp.transform, this.bulletPool, this.activeBulletPool);
|
||
}
|
||
}
|
||
|
||
private Vector3? CheckBulletBoom(Bullet bulletComp)
|
||
{
|
||
Vector3? hitPosition = null;
|
||
if (bulletComp.HitTarget != null)// && bulletComp.HitTarget != bulletComp.BeforeHitTarget)
|
||
{
|
||
//Calc is Boom
|
||
hitPosition = bulletComp.HitTarget.transform.position;
|
||
bool isDestroyed = bulletComp.HitTarget.TakeDamage(bulletComp.power);//对敌方造成伤害
|
||
Vector3 targetDir = (bulletComp.transform.position - hitPosition.Value).normalized;
|
||
|
||
var impact = PoolTool.GetFromPool(this.impactPool, this.activeBulletPool, this.activeImpactPool); //Instantiate(impactPrefab, point, rot);
|
||
bulletComp.InitImpact(hitPosition.Value, targetDir, impact);//爆炸效果
|
||
//if (bulletComp.Tweener != null)
|
||
//{
|
||
// bulletComp.Tweener.Complete(withCallback);
|
||
//}
|
||
//bulletComp.transform.DOKill();
|
||
if (isDestroyed)
|
||
{
|
||
lockEnemy = null;
|
||
}
|
||
}
|
||
return hitPosition;
|
||
}
|
||
|
||
|
||
private bool CalcBulletBoom(Bullet bulletComp)
|
||
{
|
||
bool isBulletHitted = false;
|
||
// 命中检测(距离达标则触发效果)
|
||
|
||
if (bulletComp.HitTarget != null)
|
||
{
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
//在摄像机看到的屏幕范围之外,销毁子弹
|
||
isBulletHitted = !VisibilityTool.IsObjectInFrustum(bulletComp.gameObject, _cam);
|
||
}
|
||
//if (!isBulletHitted && bulletComp.LockTarget != null)
|
||
//{
|
||
// if (bulletComp.LockTarget != bulletComp.BeforeHitTarget)
|
||
// {
|
||
// float currentDistance = Vector3.Distance(bulletComp.transform.position, bulletComp.LockTarget.transform.position);
|
||
|
||
// if (currentDistance < hitDistance)
|
||
// {
|
||
// isBulletHitted = true;
|
||
// bulletComp.currentHitPosition = bulletComp.LockTarget.transform.position;
|
||
// bulletComp.HitTarget = bulletComp.LockTarget;
|
||
// bulletComp.BeforeHitTarget = bulletComp.HitTarget;
|
||
// bulletComp.IsShoting = false;
|
||
// }
|
||
// }
|
||
//}
|
||
//if (isDestroyed)
|
||
//{
|
||
// bulletComp.Tweener.Kill(true);
|
||
// //bulletObj.DOKill(true); // 停止Tween
|
||
//}
|
||
// HitTarget(bulletObj, muzzleObj);
|
||
//}
|
||
return isBulletHitted;
|
||
}
|
||
|
||
// 计算弹体到目标的距离
|
||
private float GetDistanceToTarget(Transform bullet, Transform target)
|
||
{
|
||
if (target == null || target.IsDestroyed()) return float.MaxValue;
|
||
return Vector3.Distance(bullet.position, target.position);
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
pressAction.action.performed -= PressAction_performed;
|
||
pressAction.action.canceled -= PressAction_canceled;
|
||
pressAction.action.Disable();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 此发炮弹锁定的对象
|
||
/// </summary>
|
||
private Enemy lockEnemy = null;
|
||
|
||
private void Update()
|
||
{
|
||
//if (isAutoLock)
|
||
//{
|
||
// LockEnemy();
|
||
//}
|
||
LockEnemy();
|
||
LerpTurretAngle();
|
||
LerpGunsAngle();
|
||
}
|
||
|
||
private void LockEnemy()
|
||
{
|
||
if (lockEnemy == null || lockEnemy.IsDestroyed())
|
||
{
|
||
isStartAttack = false;
|
||
lockEnemy = GetNearestEnemyFromTransform(transform);
|
||
}
|
||
}
|
||
|
||
|
||
private Enemy GetNearestEnemyFromTransform(Transform bullet, Transform currentHitTarget = null)
|
||
{
|
||
Enemy result = null;
|
||
float minDistance = float.MaxValue;
|
||
for (int i = 0; i < Global.Enemies.Count; i++)
|
||
{
|
||
var enemy = Global.Enemies[i];
|
||
if (enemy != null)
|
||
{
|
||
if (enemy.transform == currentHitTarget)
|
||
{
|
||
continue;
|
||
}
|
||
float distance = GetDistanceToTarget(bullet, enemy.transform);
|
||
if (distance < minDistance && distance < shotDistance)// && (ringManager == null || distance > ringManager.radius * 1.2f))// && !Global.LockedEnemies.ContainsKey(enemy.GetHashCode()))
|
||
{
|
||
if (VisibilityTool.IsObjectInFrustum(enemy.gameObject, _cam))
|
||
{
|
||
minDistance = distance;
|
||
result = enemy;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
private void LerpTurretAngle()
|
||
{
|
||
if (isAutoLock && lockEnemy != null && !lockEnemy.IsDestroyed())
|
||
{
|
||
float speed = 1f;
|
||
float distance = Vector3.Distance(lockEnemy.transform.position, gun.position);
|
||
if (distance > shotDistance)
|
||
{
|
||
//isCanShot = false;
|
||
lockEnemy = null;
|
||
turretLerpEuler.Set(turret.eulerAngles.x, turret.eulerAngles.y, turret.eulerAngles.z);
|
||
//ResetTurretEuler();
|
||
}
|
||
else
|
||
{
|
||
if (!Global.LockedEnemies.ContainsKey(lockEnemy.GetHashCode()))
|
||
{
|
||
Global.LockedEnemies.Add(lockEnemy.GetHashCode(), lockEnemy);
|
||
}
|
||
Vector3 enemyPos = lockEnemy.transform.position;
|
||
turretLerpEuler.Set(turret.eulerAngles.x, turret.eulerAngles.y, turret.eulerAngles.z);
|
||
speed = this.lerpSpeed;
|
||
|
||
|
||
// 1. 计算炮塔到敌人的方向向量(仅考虑水平平面,忽略y轴高度差)
|
||
Vector3 directionToEnemy = enemyPos - turret.transform.position;
|
||
directionToEnemy.y = 0; // 只保留水平方向(x-z平面)
|
||
|
||
// 2. 计算方向向量对应的y轴欧拉角
|
||
float targetYAngle = Mathf.Atan2(directionToEnemy.x, directionToEnemy.z) * Mathf.Rad2Deg;
|
||
|
||
// 3. 赋值给turretLerpEuler的y轴
|
||
turretLerpEuler.y = targetYAngle;
|
||
turretLerpEuler.y = CalcTool.FixEulerAngle(turretLerpEuler.y, turret.eulerAngles.y);
|
||
}
|
||
turret.eulerAngles = Vector3.Lerp(turret.eulerAngles, turretLerpEuler, Time.deltaTime * speed);
|
||
}
|
||
else
|
||
{
|
||
turret.forward = transform.forward;
|
||
//isCanShot = isStartAttack && Global.Enemies.Any();
|
||
//ResetTurretEuler();
|
||
}
|
||
}
|
||
//private void ResetTurretEuler()
|
||
//{
|
||
// turretLerpEuler.Set(turret.eulerAngles.x, turret.eulerAngles.y + Time.deltaTime * this.lerpSpeed * 2, turret.eulerAngles.z);
|
||
// turret.eulerAngles = turretLerpEuler;
|
||
//}
|
||
|
||
private void LerpGunsAngle()
|
||
{
|
||
if (lockEnemy == null)
|
||
{
|
||
targetXAngle = 0;
|
||
}
|
||
else
|
||
{
|
||
Vector3 enemyPos = lockEnemy.transform.position;
|
||
// ========== 垂直索敌(炮管x轴旋转) ==========
|
||
// 1. 计算从炮管到敌人的方向向量(保留y轴高度差)
|
||
Vector3 gunToEnemy = gun.transform.position - enemyPos;
|
||
|
||
// 2. 将方向向量转换为炮管的局部坐标系(避免炮塔旋转影响计算)
|
||
Vector3 localDir = gun.transform.InverseTransformDirection(gunToEnemy);
|
||
|
||
// 3. 计算垂直旋转角度(x轴)
|
||
targetXAngle = Mathf.Atan2(localDir.y, Mathf.Sqrt(localDir.x * localDir.x + localDir.z * localDir.z)) * Mathf.Rad2Deg;
|
||
|
||
//// 4. 限制俯仰角度范围(防止炮管旋转过度)
|
||
//targetXAngle = Mathf.Clamp(targetXAngle, minXAngle, maxXAngle);
|
||
}
|
||
// 处理360度环绕问题
|
||
smoothXAngle = Mathf.LerpAngle(smoothXAngle, targetXAngle, Time.deltaTime * this.lerpSpeed);
|
||
gun.localEulerAngles = new Vector3(smoothXAngle, gun.localEulerAngles.y, gun.localEulerAngles.z);
|
||
}
|
||
|
||
|
||
//// 调试:在Scene视图中绘制发射位置
|
||
//private void OnDrawGizmosSelected()
|
||
//{
|
||
// if (gun != null)
|
||
// {
|
||
// Vector3 firePos = gun.TransformPoint(new Vector3(0, shotOffsetZ, 0));
|
||
// firePos.z = 0;
|
||
// Vector3 targetPos = new Vector3(firePos.x, firePos.y + shotDistance, 0);
|
||
|
||
// Gizmos.color = Color.green;
|
||
// Gizmos.DrawSphere(firePos, 0.1f);
|
||
|
||
// Gizmos.color = Color.red;
|
||
// Gizmos.DrawSphere(targetPos, 0.1f);
|
||
|
||
// Gizmos.color = Color.yellow;
|
||
// Gizmos.DrawLine(firePos, targetPos);
|
||
// }
|
||
//}
|
||
} |