using UnityEngine; public class Projectile : MonoBehaviour { [Header("爆炸效果(可选)")] public GameObject explosionPrefab; // 爆炸特效预制体,留空则没有爆炸效果 [Header("抛物线飞行(可选)")] public bool useParabola = false; // 是否启用小抛物线飞行 public float parabolaHeight = 1.0f; // 抛物线最高拱起高度 (不太高的抛物线) private GameObject target; private GameObject owner; private float damage; private float speed; private float lifetime; private bool registerEnergyOnHit; private bool initialized; private bool hasHit; private Vector3 startPosition; private float totalFlyDistance; private float horizontalTraveled; public void Initialize( GameObject targetObject, float damageAmount, GameObject ownerObject, float moveSpeed, float lifeTime, bool registerEnergy = true) { target = targetObject; damage = damageAmount; owner = ownerObject; speed = moveSpeed; lifetime = lifeTime; registerEnergyOnHit = registerEnergy; initialized = true; hasHit = false; startPosition = transform.position; horizontalTraveled = 0f; if (targetObject != null) { Vector3 diff = targetObject.transform.position - startPosition; diff.y = 0f; totalFlyDistance = Mathf.Max(0.1f, diff.magnitude); } else { totalFlyDistance = 10f; } // The projectile uses its own raycast hit check. Disable the collider so // it cannot be destroyed or disturbed by the owner's launch socket. foreach (var collider in GetComponentsInChildren(true)) { collider.enabled = false; } if (lifetime > 0f) { Destroy(gameObject, lifetime); } } private void Update() { if (!initialized) { return; } if (target == null) { Destroy(gameObject); return; } Vector3 targetCurrentPos = target.transform.position; Vector3 offset = targetCurrentPos - transform.position; Vector3 flatOffset = new Vector3(offset.x, 0f, offset.z); if (flatOffset.sqrMagnitude <= 0.04f) { HitTarget(target); return; } Vector3 direction = flatOffset.normalized; float step = speed * Time.deltaTime; horizontalTraveled += step; if (step >= flatOffset.magnitude) { HitTarget(target); return; } Vector3 nextPos; if (useParabola && totalFlyDistance > 0.1f) { // 水平线性推进 float t = Mathf.Clamp01(horizontalTraveled / totalFlyDistance); Vector3 groundPos = Vector3.Lerp(new Vector3(startPosition.x, 0.1f, startPosition.z), new Vector3(targetCurrentPos.x, 0.1f, targetCurrentPos.z), t); // 抛物线高度:基础发射高度渐变 + 4 * h * t * (1 - t) 抛物线弧度 float startY = startPosition.y; float targetY = targetCurrentPos.y + 0.8f; float baseY = Mathf.Lerp(startY, targetY, t); float arcY = 4f * parabolaHeight * t * (1f - t); nextPos = new Vector3(groundPos.x, baseY + arcY, groundPos.z); // 矛身旋转:朝向移动切线方向,矛头顺着抛物线弧度自然俯仰 Vector3 velocity = (nextPos - transform.position); if (velocity.sqrMagnitude > 1e-6f) { transform.rotation = Quaternion.LookRotation(velocity.normalized, Vector3.up); } transform.position = nextPos; } else { // 水平直线飞行 transform.rotation = Quaternion.LookRotation(direction, Vector3.up); transform.position += direction * step; } if (Physics.Raycast(transform.position, direction, out RaycastHit hit, step)) { TryHit(hit.collider.gameObject); if (hit.collider.transform.root == target.transform.root) { return; } } } private void OnTriggerEnter(Collider other) { TryHit(other.gameObject); } private void OnCollisionEnter(Collision collision) { TryHit(collision.gameObject); } private void TryHit(GameObject hitObject) { if (!initialized || hitObject == null || target == null || IsOwner(hitObject)) { return; } if (hitObject.transform.root == target.transform.root) { HitTarget(hitObject); } } private void HitTarget(GameObject hitObject) { if (hasHit) { return; } UnitBase targetUnit = hitObject.GetComponentInParent(); WorldHealthBar targetHealthBar = hitObject.GetComponentInParent(); if (targetUnit == null && targetHealthBar == null) { return; } hasHit = true; if (targetUnit != null) { targetUnit.ReceiveDamage(damage, owner); } else if (targetHealthBar != null) { // 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase) targetHealthBar.TakeDamage(damage, owner); } if (registerEnergyOnHit && owner != null) { UnitBase unit = owner.GetComponent(); if (unit != null) { unit.RegisterAttackHit(); // 远程投射物通用吸血 if (unit.lifestealRate > 0f) { WorldHealthBar ownerBar = unit.GetComponent(); if (ownerBar != null) { ownerBar.Heal(damage * unit.lifestealRate); } } } } // 生成爆炸效果 if (explosionPrefab != null) { Instantiate(explosionPrefab, transform.position, Quaternion.identity); } Destroy(gameObject); } private bool IsOwner(GameObject hitObject) { return owner != null && (hitObject == owner || hitObject.transform.root == owner.transform.root); } }