Files
5.24BuYu/Assets/MyGame/aa/Game/ZhanShen/ZhanShenTurretData.cs
T
2026-10-14 00:29:07 +08:00

172 lines
8.7 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem; // Pointer.current:鼠标拖动 / 屏幕滑动统一采样
/// <summary>战神跟手旋转的旋转轴(3D 模型按轴转身,各战神模型朝向不同,面板可选)</summary>
public enum ZhanShenRotateAxis
{
X = 0,
Y = 1,
Z = 2
}
public class ZhanShenTurretData : TurretData
{
[Tooltip("散射激光起点球预制体:出场/换战神时克隆到座位的 JGFXLeftParent/JGFXRightParent 下")]
public GameObject JGQiuStartPrefab;
[Tooltip("激光起点特效预制体(UI 层):0=克隆到 turretRotate 下 selfOffsetJGStart 处(跟随炮台旋转),1/2=克隆到 JGFX 左右父对象下(跟随父节点);为空不生成")]
public GameObject JGStartFXPrefab;
[Tooltip("激光命中特效预制体:三发+锁定激活时克隆到每条激光终点(锁定目标身上),跟随目标移动;为空不生成")]
public GameObject JGHitFXPrefab;
[Tooltip("锁定后激光及特效的事件等待兜底秒数。⭐ 正门=Animator loop 状态上的 ZhanShenLaserLoopSMB(或剪辑事件 ZhanShenLaserLoopBegin)→ 过渡播完后立即显示;此值=事件超时兜底(忘配事件时到期强制显示)。0=未配置,用代码默认兜底 2s(特效不再锁定立即显示,必须等动画事件开门)")]
public float laserFXDelay = 0f;
[Tooltip("主激光起点偏移:turretRotate 的本地偏移(子对象语义),⭐ 跟随炮台旋转(炮口转向时起点绕炮台中心转)。编辑器运行时用 Gizmo 可视化")]
public Vector2 selfOffsetJGStart;
[Tooltip("下方座位战神初始旋转(欧拉角,直接设置,覆盖该战神预制体原始旋转)。全0=保持默认朝向")]
public Vector3 buttomOffsetZSRotateInit;
[Tooltip("上方座位战神初始旋转(欧拉角,直接设置,覆盖该战神预制体原始旋转)。上下视角不一样分开设置。全0=保持默认朝向")]
public Vector3 topOffsetZSRotateInit;
[Tooltip("战神激光音效(锁定中):声音较长,锁定时循环播放,松开锁定/激光消失即停止;为空则逐发回退 bulletAudio/bulletClip")]
public AudioClip laserAudio;
[Tooltip("战神普通开火音效(未锁定时):不锁定开炮时逐发播放;为空则回退普通炮台音效 bulletClip")]
public AudioClip bulletAudio;
[Tooltip("旋转目标对象:跟手旋转时被旋转的节点(只转它 rotateAxis 那一个轴,其他轴和位移都不动)。为空回退战神实例自身")]
public Transform rotateTargetNode;
[Tooltip("跟手旋转绕哪个轴:3D 模型转身一般是 Y,部分模型/特效用 X 或 Z")]
public ZhanShenRotateAxis rotateAxis = ZhanShenRotateAxis.Y;
[Tooltip("跟手灵敏度:指针每滑动 1 像素累积多少度旋转。正数=向右划模型向右转、向左划向左转;想反方向就填负数")]
public float rotateSensitivity = 0.4f;
[Tooltip("跟手平滑系数:越大越跟手(每帧按 系数×帧间隔 插值到目标角度)。0=立即到位,不平滑")]
public float rotateLerpSpeed = 14f;
[Tooltip("跟手旋转是否由 Timeline 事件触发:\n" +
"勾上(默认)= 等 Timeline 上的事件调 RotateZhanShen 才开始跟手;\n" +
"取消 = 战神一出现就自动开始跟手(特效简单的战神没有 Timeline,用这个直接展示)")]
public bool rotateByTimeline = true;
/// <summary>是否正在跟手旋转(由 Timeline 事件 RotateZhanShen 开始、StopRotateZhanShen 结束)</summary>
private bool _rotateFollowing;
private Coroutine _rotateRoutine;
/// <summary>累积的目标角度(原始值,可超过 360°,保证连续拖动不绕圈回跳)</summary>
private float _rotateTargetAngle;
/// <summary>平滑显示用的角度(原始值,与 _rotateTargetAngle 同坐标系)</summary>
private float _rotateShownAngle;
/// <summary>上一帧指针位置:按帧间位移量累加角度,所以按下时也要先把位置记准</summary>
private Vector2 _rotateLastPointerPos;
/// <summary>
/// ⭐ 旋转战神(Timeline 事件调用="开始点"):开始持续跟手旋转。
/// 调用后每帧按指针(鼠标点击拖动 / 屏幕滑动)的横向位移量累加到目标角度,
/// 节点再按 rotateLerpSpeed 平滑绕 rotateAxis 转到该角度;累加从节点当前角度起步,不会跳变。
/// 重复触发(Timeline 上多个事件)不会叠加协程。
/// </summary>
public void RotateZhanShen()
{
if (_rotateFollowing) return;
// 战神实例被隐藏时(特殊鱼炮台期间 SetActive(false))不能起协程,Unity 会直接报错
if (!isActiveAndEnabled) return;
var node = ResolveRotateTargetNode();
if (node == null) return;
_rotateFollowing = true;
_rotateTargetAngle = ReadAxisAngle(node.localEulerAngles); // 从当前角度开始累加
_rotateShownAngle = _rotateTargetAngle;
_rotateLastPointerPos = ReadPointerPos();
_rotateRoutine = StartCoroutine(FollowRotateRoutine());
}
/// <summary>停止跟手旋转(Timeline 结束事件调用;不调用则由 OnDisable 兜底停止)</summary>
public void StopRotateZhanShen()
{
_rotateFollowing = false;
if (_rotateRoutine != null)
{
StopCoroutine(_rotateRoutine);
_rotateRoutine = null;
}
}
private IEnumerator FollowRotateRoutine()
{
while (_rotateFollowing)
{
yield return null;
var node = ResolveRotateTargetNode();
if (node == null) break;
var pointer = Pointer.current;
if (pointer != null)
{
Vector2 cur = pointer.position.ReadValue();
// 按住(拖动中)才累加:松手保持当前角度
// ⚠ 这里用 -= 而不是 +=:Unity 绕 Y 轴的正角度在屏幕上看是"向左转"(相机朝 -Z 看时
// +θ 把模型的正面转向 -X),所以"向右划=向右转"必须把右移量反号。改回 += 方向就会反。
if (pointer.press.isPressed)
_rotateTargetAngle -= (cur.x - _rotateLastPointerPos.x) * rotateSensitivity;
// ⚠ 不管有没有按住都记录位置:否则按下那一帧会用"开始跟手时的旧位置"算出巨大跳变
_rotateLastPointerPos = cur;
}
_rotateShownAngle = rotateLerpSpeed > 0f
? Mathf.Lerp(_rotateShownAngle, _rotateTargetAngle, Mathf.Clamp01(rotateLerpSpeed * Time.deltaTime))
: _rotateTargetAngle;
// 只改 rotateAxis 那一个轴:其余两轴沿用节点当前值(模型只绕该轴转身)
Vector3 e = node.localEulerAngles;
switch (rotateAxis)
{
case ZhanShenRotateAxis.X: e.x = _rotateShownAngle; break;
case ZhanShenRotateAxis.Z: e.z = _rotateShownAngle; break;
default: e.y = _rotateShownAngle; break;
}
node.localEulerAngles = e;
}
_rotateRoutine = null;
}
/// <summary>取当前旋转轴上的角度(跟手累加的起点)</summary>
private float ReadAxisAngle(Vector3 euler)
{
switch (rotateAxis)
{
case ZhanShenRotateAxis.X: return euler.x;
case ZhanShenRotateAxis.Z: return euler.z;
default: return euler.y;
}
}
private Transform ResolveRotateTargetNode()
{
return rotateTargetNode != null ? rotateTargetNode : transform;
}
private static Vector2 ReadPointerPos()
{
var pointer = Pointer.current;
return pointer != null ? pointer.position.ReadValue() : Vector2.zero;
}
private void OnDisable()
{
// 战神实例被回收/隐藏时停止跟手,避免协程挂在已停用的对象上继续跑
StopRotateZhanShen();
}
/// <summary>
/// 战神出现/重新激活:不走 Timeline 的简单战神按面板开关直接开始跟手。
/// ⭐ 必须挂基类 OnEnable 的扩展点,不能自己写 OnEnable——Unity 只调最派生的那一个,
/// 自建会把基类的"状态复位 + 播 Idle + 绑定按钮"整段顶掉。
/// 走这里(而不是 Start)是因为战神实例会被反复 SetActive(false)/(true)(特殊鱼炮台期间),
/// Start 一生只跑一次,重新显示时不会再触发。
/// </summary>
protected override void OnTurretEnable()
{
if (!rotateByTimeline) RotateZhanShen();
}
}