Files
5.24BuYu/Assets/MyGame/Scripts/Turret/FlyTurretData.cs
T
2026-09-02 16:27:35 +08:00

278 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using UnityEngine;
/// <summary>
/// 起飞炮台(TurretData 子类):
/// 平时是一门正常炮台(照常手动瞄准开火/自动开火/锁定瞄准);
/// 额外支持起飞模式:轻点空白处 → 炮台飞到该点(点到哪飞到哪)→ 到达后持续向上发射子弹,
/// upFireDuration 到时自动降落回原位恢复正常开火;起飞时播放特殊 spine 动画
/// (takeoffAnim 播一次,flyLoopAnim 循环衔接)。
/// 交互判定在 GameMainPanel_FishBase.HandleInput(轻点=按下短+几乎没移动;按住扫射不触发起飞)。
/// 挂在 XJMH 座位炮台(xjmhTurretList 中的子对象)上即可,无需改预制体结构。
/// 注意:不要在本类定义 Awake/OnEnable,会隐藏基类的同名私有初始化逻辑。
/// </summary>
public class FlyTurretData : TurretData
{
[Header("起飞(点哪飞哪)")]
[Tooltip("起飞特殊 spine 动画名(起飞时播一次;为空则直接播飞行循环动画)")]
public string takeoffAnim;
[Tooltip("飞行/悬停循环 spine 动画名(为空则起飞动画播完后循环自身)")]
public string flyLoopAnim;
[Tooltip("飞行到目标点的时长(秒)")]
public float flyDuration = 0.6f;
[Tooltip("飞行插值:0=匀速 1=平滑缓入缓出")]
[Range(0f, 1f)]
public float flySmooth = 1f;
[Tooltip("目标点距画布边缘的最小内边距(画布本地单位),防止炮台飞出屏幕")]
public float edgePadding = 60f;
[Header("向上发射子弹")]
[Tooltip("向上开火间隔(秒)")]
public float upFireInterval = 0.4f;
[Tooltip("向上开火总时长(秒),<=0 表示一直开火(手动点击别处可改变目标)")]
public float upFireDuration = 0f;
[Tooltip("到达目标点后延迟多久开始向上开火(秒),通常等起飞动画收尾")]
public float upFireDelay = 0f;
/// <summary>向上开火回调:由 GameMainPanel_FishBase 注入(走服务端同步链路 SyncFire)</summary>
[NonSerialized] public Action onUpwardFire;
/// <summary>是否处于飞行/悬停状态(含飞回降落途中)</summary>
public bool IsFlying { get; private set; }
private RectTransform _rect;
private RectTransform _parentRect;
private RectTransform _canvasRect;
private Camera _uiCam;
private Vector2 _originAnchoredPos; // 首次起飞前的原位(降落飞回这里)
private Vector2 _flyFrom;
private Vector2 _flyTo;
private float _flyTimer;
private bool _returning; // true=正在飞回原位降落
private float _fireDelayTimer;
private float _fireTimer;
private float _fireTotalTimer;
private bool _firing;
/// <summary>懒加载引用(不用 Awake,避免隐藏基类私有 Awake)</summary>
private void EnsureRefs()
{
if (_rect != null) return;
_rect = GetComponent<RectTransform>();
_parentRect = transform.parent as RectTransform;
Canvas canvas = null;
if (UIManager.Instance != null && UIManager.Instance.uiRoot != null)
canvas = UIManager.Instance.uiRoot.GetComponent<Canvas>();
if (canvas != null)
{
_canvasRect = canvas.GetComponent<RectTransform>();
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay)
_uiCam = canvas.worldCamera;
}
}
private void Update()
{
if (!IsFlying) return;
UpdateFly();
UpdateUpFire();
}
/// <summary>
/// 起飞/移动到目标屏幕点(点到哪飞到哪)。
/// 首次调用=起飞;飞行中再次调用=改目标点;降落中调用=取消降落改飞新目标。
/// 返回 false 表示未起飞(节点不是 UGUI 或画布引用缺失)。
/// </summary>
public bool TakeoffToScreenPos(Vector2 screenPos)
{
EnsureRefs();
if (_rect == null || _parentRect == null || _canvasRect == null) return false;
if (!IsFlying)
{
_originAnchoredPos = _rect.anchoredPosition;
IsFlying = true;
PlayTakeoffAnim();
PointUp();
}
_returning = false;
_flyFrom = _rect.anchoredPosition;
_flyTo = ScreenPosToAnchoredPos(screenPos);
_flyTimer = 0f;
// 改目标瞬间先停火,到达新目标后按延迟恢复
_firing = false;
_fireDelayTimer = 0f;
return true;
}
/// <summary>降落:停火并飞回起飞前的原位</summary>
public void Land()
{
if (!IsFlying || _returning) return;
_firing = false;
_returning = true;
_flyFrom = _rect.anchoredPosition;
_flyTo = _originAnchoredPos;
_flyTimer = 0f;
}
// ==================== 内部逻辑 ====================
private void UpdateFly()
{
_flyTimer += Time.deltaTime;
float t = flyDuration > 0f ? Mathf.Clamp01(_flyTimer / flyDuration) : 1f;
if (flySmooth > 0f)
t = Mathf.Lerp(t, t * t * (3f - 2f * t), flySmooth);
_rect.anchoredPosition = Vector2.LerpUnclamped(_flyFrom, _flyTo, t);
if (_flyTimer < flyDuration) return;
_rect.anchoredPosition = _flyTo;
if (_returning)
{
// 落地:恢复待机动画,退出飞行状态
IsFlying = false;
_returning = false;
RestoreIdleAnim();
}
else
{
StartUpFire();
}
}
private void StartUpFire()
{
_firing = true;
_fireDelayTimer = upFireDelay;
_fireTimer = upFireInterval; // 延迟结束后立即打第一发
_fireTotalTimer = 0f;
}
private void UpdateUpFire()
{
if (!_firing) return;
// 到达后延迟(等起飞动画收尾)
if (_fireDelayTimer > 0f)
{
_fireDelayTimer -= Time.deltaTime;
return;
}
// 总时长限制(<=0 = 一直开火)
if (upFireDuration > 0f)
{
_fireTotalTimer += Time.deltaTime;
if (_fireTotalTimer >= upFireDuration)
{
Land();
return;
}
}
_fireTimer += Time.deltaTime;
if (_fireTimer < upFireInterval) return;
_fireTimer = 0f;
onUpwardFire?.Invoke();
}
/// <summary>屏幕坐标 → 炮台父节点下的 anchoredPosition(限制在画布范围内)</summary>
private Vector2 ScreenPosToAnchoredPos(Vector2 screenPos)
{
// 屏幕坐标 → 画布根节点本地坐标(相机解析模式与扇形切换一致)
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_canvasRect, screenPos, _uiCam, out Vector2 canvasLocal);
// 限制在画布内(留边距),防止炮台飞出屏幕
Rect r = _canvasRect.rect;
float xMin = r.xMin + edgePadding;
float xMax = r.xMax - edgePadding;
float yMin = r.yMin + edgePadding;
float yMax = r.yMax - edgePadding;
if (xMax < xMin) { xMin = xMax = r.center.x; }
if (yMax < yMin) { yMin = yMax = r.center.y; }
canvasLocal = new Vector2(
Mathf.Clamp(canvasLocal.x, xMin, xMax),
Mathf.Clamp(canvasLocal.y, yMin, yMax));
// 画布本地 → 世界 → 用“世界增量”换算成父节点下的 anchoredPosition。
// 增量法兼容父节点的翻转/旋转/缩放(上方座位 180° 翻转也能正确落点),
// 且不依赖炮台自身的锚点设置。
Vector3 targetWorld = _canvasRect.TransformPoint(canvasLocal);
Vector3 deltaLocal = _parentRect.InverseTransformVector(targetWorld - _rect.position);
return _rect.anchoredPosition + (Vector2)deltaLocal;
}
/// <summary>炮口固定朝上:SetRotation 的 angle 约定 0=正上方(aim-90)</summary>
private void PointUp()
{
if (setPosData != null)
{
setPosData.SetRotation(0f);
return;
}
Transform pivot = tra_RotationPivot != null ? tra_RotationPivot : transform.parent;
if (pivot != null)
pivot.rotation = Quaternion.Euler(0f, 0f, 0f);
}
/// <summary>起飞动画:takeoffAnim 播一次,flyLoopAnim 循环衔接(为空则循环自身兜底)</summary>
private void PlayTakeoffAnim()
{
if (skeletonAnimation == null || skeletonAnimation.AnimationState == null) return;
if (!string.IsNullOrEmpty(takeoffAnim))
{
skeletonAnimation.AnimationState.SetAnimation(0, takeoffAnim, false);
if (!string.IsNullOrEmpty(flyLoopAnim))
skeletonAnimation.AnimationState.AddAnimation(0, flyLoopAnim, true, 0f);
else
skeletonAnimation.AnimationState.AddAnimation(0, takeoffAnim, true, 0f);
}
else if (!string.IsNullOrEmpty(flyLoopAnim))
{
skeletonAnimation.AnimationState.SetAnimation(0, flyLoopAnim, true);
}
}
/// <summary>落地后恢复待机动画(先 None 再 Idle,避免 currState 相同被基类短路)</summary>
private void RestoreIdleAnim()
{
base.ChangeState(TurretState.None);
base.ChangeState(TurretState.Idle);
}
public override void ChangeState(TurretState newState)
{
// 飞行/悬停期间拦截状态动画(Attack/Idle 切换不打断起飞动画)
if (IsFlying) return;
base.ChangeState(newState);
}
private void OnDisable()
{
// 切炮台/座位被禁用时兜底复位(基类无 OnDisable,不会冲突)
if (!IsFlying) return;
IsFlying = false;
_returning = false;
_firing = false;
if (_rect != null)
_rect.anchoredPosition = _originAnchoredPos;
}
}