172 lines
8.7 KiB
C#
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();
|
|
}
|
|
}
|