feat(gameplay): 更新法师主角预制体和法师生怪功能 - 添加了召唤师主角预制体文件 - 更新法师生怪预制体,替换为新的火球弹道效果 - 修复投射物斜向发射问题,统一使用Quaternion.identity旋转 fix(projectile): 修复远程投射物旋转和目标锁定问题 - 修正Projectile组件,让投射物保持水平飞行并始终朝向目标 - 修改UnitBase中投射物实例化逻辑,不再传递spawn point的旋转 - 解决法师/弓箭手attackPos骨骼动画影响导致火球和箭矢斜向发射的问题 refactor(texture): 优化火焰球纹理导入设置 - 禁用mipmap以提升显示效果 - 启用alpha透明度支持 - 将纹理类型改为Sprite模式 - 启用wrap模式改善渲染表现 docs(update): 记录远程弹道视觉方案调整和投射物修复说明 ```
169 lines
4.5 KiB
C#
169 lines
4.5 KiB
C#
using UnityEngine;
|
||
|
||
public class Projectile : MonoBehaviour
|
||
{
|
||
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<Collider>(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<UnitBase>();
|
||
CrystalMineCapture crystalMine = hitObject.GetComponentInParent<CrystalMineCapture>();
|
||
BuildingHealth targetBuilding = hitObject.GetComponentInParent<BuildingHealth>();
|
||
WorldHealthBar targetHealthBar = hitObject.GetComponentInParent<WorldHealthBar>();
|
||
|
||
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<UnitBase>();
|
||
if (unit != null)
|
||
{
|
||
unit.RegisterAttackHit();
|
||
}
|
||
}
|
||
|
||
Destroy(gameObject);
|
||
}
|
||
|
||
private bool IsOwner(GameObject hitObject)
|
||
{
|
||
return owner != null &&
|
||
(hitObject == owner || hitObject.transform.root == owner.transform.root);
|
||
}
|
||
}
|