1
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2fbda49303fb1543a68b553ec721872
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -31,7 +31,7 @@ public class TurretData : MonoBehaviour
|
||||
public Transform tra_BulletStartPosition;
|
||||
[Tooltip("炮台旋转节点。拖入后 SitPosData.tra_CurrTurret 用此节点旋转;不拖则用炮台自身的父节点(兼容旧逻辑)")]
|
||||
public Transform tra_RotationPivot;
|
||||
public GameObject bulletPrefab_Two;
|
||||
public GameObject bulletPrefab_Two;
|
||||
public GameObject bulletPrefab;
|
||||
|
||||
[Header("战神激光(锁定时显示,普通炮台不配置即无此效果)")]
|
||||
@@ -244,7 +244,7 @@ public class TurretData : MonoBehaviour
|
||||
|
||||
|
||||
|
||||
public void ChangeState(TurretState newState)
|
||||
public virtual void ChangeState(TurretState newState)
|
||||
{
|
||||
if (currState == newState) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user