``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
222 lines
6.4 KiB
C#
222 lines
6.4 KiB
C#
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<Collider>(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<UnitBase>();
|
||
WorldHealthBar targetHealthBar = hitObject.GetComponentInParent<WorldHealthBar>();
|
||
|
||
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<UnitBase>();
|
||
if (unit != null)
|
||
{
|
||
unit.RegisterAttackHit();
|
||
// 远程投射物通用吸血
|
||
if (unit.lifestealRate > 0f)
|
||
{
|
||
WorldHealthBar ownerBar = unit.GetComponent<WorldHealthBar>();
|
||
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);
|
||
}
|
||
}
|