using UnityEngine; public class Projectile : MonoBehaviour { [Header("爆炸效果(可选)")] public GameObject explosionPrefab; // 爆炸特效预制体,留空则没有爆炸效果 private GameObject target; private GameObject owner; private float damage; private float speed; private float lifetime; private bool registerEnergyOnHit; private bool initialized; private bool hasHit; 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; // 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 offset = target.transform.position - transform.position; offset.y = 0f; if (offset.sqrMagnitude <= 0.01f) { HitTarget(target); return; } Vector3 direction = offset.normalized; float distance = speed * Time.deltaTime; if (distance >= offset.magnitude) { HitTarget(target); return; } // 投射物保持水平飞行,并让根节点前方始终指向当前目标。 transform.rotation = Quaternion.LookRotation(direction, Vector3.up); if (Physics.Raycast(transform.position, direction, out RaycastHit hit, distance)) { TryHit(hit.collider.gameObject); if (hit.collider.transform.root == target.transform.root) { return; } } // 火球保持 Prefab 固定角度,不随飞行方向旋转 // transform.forward = direction; transform.position += direction * distance; } 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(); CrystalMineCapture crystalMine = hitObject.GetComponentInParent(); BuildingHealth targetBuilding = hitObject.GetComponentInParent(); WorldHealthBar targetHealthBar = hitObject.GetComponentInParent(); if (targetUnit == null && crystalMine == null && targetBuilding == null && targetHealthBar == null) { return; } hasHit = true; if (targetUnit != null) { targetUnit.ReceiveDamage(damage, owner); } else if (crystalMine != null) { crystalMine.ReceiveCaptureDamage(damage, owner); } else if (targetBuilding != null) { targetBuilding.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 (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); } }