446 lines
14 KiB
C#
446 lines
14 KiB
C#
using UnityEngine;
|
|
using DG.Tweening;
|
|
using AibisDream.Kit;
|
|
using AibisDream.Framework;
|
|
|
|
namespace AibisDream.FixSystem
|
|
{
|
|
public class CablePanel : MonoBehaviour, IOperatorPanel
|
|
{
|
|
private const int SortingOrderRetracted = 41;
|
|
private const float RetractDuration = 0.18f; // 强制收起:插头 DOMove 与绳收拢共用时长
|
|
|
|
#region 组件索引
|
|
|
|
[Header("插线组件索引")]
|
|
[SerializeField] private Plug plugRef;
|
|
[SerializeField] private Cable cableRef; // 旧视觉线缆,已由 PhysicCable 统一取代,Start 时停用
|
|
[SerializeField] private PhysicCable physicCableRef;
|
|
[SerializeField] private Transform cableRootPos;
|
|
[SerializeField] private CableReel cableReelRef;
|
|
|
|
[Header("灯光系统")]
|
|
[SerializeField] private CablePanelLight panelLight;
|
|
|
|
public Plug PlugRef => plugRef;
|
|
public Cable CableRef => cableRef;
|
|
public PhysicCable PhysicCableRef => physicCableRef;
|
|
public Transform CableRootPos => cableRootPos;
|
|
public CableReel CableReelRef => cableReelRef;
|
|
public CablePanelLight PanelLight => panelLight;
|
|
|
|
public BodyModuleSystem BodyModuleSystem { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region 系统状态
|
|
public bool isCableRetracted = true; // 线缆是否收起
|
|
|
|
[SerializeField] private Transform retractedPos; // 收起位置
|
|
|
|
private ISocket curSocket;
|
|
|
|
// 收线动画期间插头由 DOTween 驱动,暂停"插头跟随绳末端"
|
|
private bool suppressPlugFollow;
|
|
|
|
#endregion
|
|
|
|
private void Start()
|
|
{
|
|
InitComponentRefs();
|
|
InitSystem();
|
|
|
|
// 单线方案:旧视觉线缆整体停用,所有状态都走 PhysicCable
|
|
if (cableRef != null)
|
|
{
|
|
cableRef.gameObject.SetActive(false);
|
|
}
|
|
|
|
InitCableVisualFromPrefab();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
plugRef.OnInsertSocket -= OnInsertSocket;
|
|
plugRef.OnPullUpSocket -= OnPullUpSocket;
|
|
plugRef.OnPlugPointerDown -= OnPlugPoinerDown;
|
|
}
|
|
|
|
private void InitComponentRefs()
|
|
{
|
|
BodyModuleSystem = FixSystemCenter.SystemDic.Get<BodyModuleSystem>();
|
|
}
|
|
|
|
|
|
public void InitLight(bool isLightOn)
|
|
{
|
|
panelLight?.Initialize(isLightOn);
|
|
}
|
|
|
|
private void InitSystem()
|
|
{
|
|
plugRef.OnInsertSocket += OnInsertSocket;
|
|
plugRef.OnPullUpSocket += OnPullUpSocket;
|
|
plugRef.OnPlugPointerDown += OnPlugPoinerDown;
|
|
}
|
|
|
|
private void InitCableVisualFromPrefab()
|
|
{
|
|
if (isCableRetracted)
|
|
{
|
|
SetRetractCable();
|
|
}
|
|
else
|
|
{
|
|
DropDownCableImmediate();
|
|
}
|
|
}
|
|
|
|
public bool TryGetPluggedModuleName(out string moduleName)
|
|
{
|
|
if (curSocket is BodyModule bodyModule)
|
|
{
|
|
moduleName = bodyModule.data.moduleName;
|
|
return !string.IsNullOrEmpty(moduleName);
|
|
}
|
|
|
|
moduleName = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>读档:同步写回线缆与插头终态,无动画、无 Yarn、无 PlugIn 事件。</summary>
|
|
public void ApplyCableSnapshot(bool retracted, string pluggedModuleName)
|
|
{
|
|
SilentPullUpSocket();
|
|
|
|
if (retracted)
|
|
{
|
|
if (ShouldSkipRetractOnRestore())
|
|
{
|
|
if (isCableRetracted)
|
|
{
|
|
DropDownCableImmediate();
|
|
}
|
|
|
|
curSocket = null;
|
|
return;
|
|
}
|
|
|
|
SetRetractCable();
|
|
curSocket = null;
|
|
return;
|
|
}
|
|
|
|
DropDownCableImmediate();
|
|
|
|
if (!string.IsNullOrEmpty(pluggedModuleName))
|
|
{
|
|
RestorePlugToModule(pluggedModuleName);
|
|
}
|
|
else
|
|
{
|
|
curSocket = null;
|
|
}
|
|
}
|
|
|
|
public void DropDownCableImmediate()
|
|
{
|
|
PlugRef.PullUpSocketSilent();
|
|
CableReelRef.ResetRotation();
|
|
PlugRef.RestoreAtStartPositionImmediate();
|
|
physicCableRef.ShowFree(true);
|
|
isCableRetracted = false;
|
|
}
|
|
|
|
private void RestorePlugToModule(string moduleName)
|
|
{
|
|
var module = BodyModuleSystem?.FindBodyModuleByName(moduleName);
|
|
if (module == null)
|
|
{
|
|
Debug.LogWarning($"[CablePanel] RestorePlugToModule: 未找到模块 {moduleName},插头留在初始位。");
|
|
curSocket = null;
|
|
physicCableRef.ShowFree(true);
|
|
return;
|
|
}
|
|
|
|
curSocket = module;
|
|
var socketPos = module.GetSocketPos();
|
|
PlugRef.RestoreInsertedAt(socketPos);
|
|
physicCableRef.PlugInto(socketPos, true);
|
|
BodyModuleSystem.CloseModuleSlots(null);
|
|
}
|
|
|
|
private void SilentPullUpSocket()
|
|
{
|
|
if (curSocket == null) return;
|
|
|
|
curSocket.PlugOut();
|
|
curSocket = null;
|
|
PlugRef.SetSpriteVisible(true);
|
|
}
|
|
|
|
private static bool ShouldSkipRetractOnRestore()
|
|
{
|
|
return FixSystemCenter.Instance != null
|
|
&& FixSystemCenter.Director.HasMode
|
|
&& FixSystemCenter.Director.CurrentMode == FixSceneMode.BodyModule;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// 拖拽/插接时转盘朝拉力方向转动,闲置时缓动回正
|
|
if (CableReelRef != null && physicCableRef != null
|
|
&& physicCableRef.State != PhysicCable.CableState.Hidden)
|
|
{
|
|
bool active = physicCableRef.State == PhysicCable.CableState.Dragging
|
|
|| physicCableRef.State == PhysicCable.CableState.Plugged;
|
|
float tautness = active ? physicCableRef.GetTautness() : 0f;
|
|
CableReelRef.UpdateRotation(active, tautness);
|
|
}
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (suppressPlugFollow || physicCableRef == null) return;
|
|
|
|
// 闲置/拖拽时插头挂在绳末端,位置与朝向都由绳给出
|
|
var state = physicCableRef.State;
|
|
if (state == PhysicCable.CableState.Free || state == PhysicCable.CableState.Dragging)
|
|
{
|
|
physicCableRef.ApplyPlugPose(plugRef.transform, plugRef.PlugRoot);
|
|
plugRef.ApplyFeedbackOffset();
|
|
}
|
|
}
|
|
|
|
public void ResetPlug()
|
|
{
|
|
if (IsPlugInSocket())
|
|
{
|
|
PlugRef.PullUpSocket();
|
|
}
|
|
|
|
BodyModuleSystem.CloseModuleSlots(null);
|
|
DropDownCableImmediate();
|
|
|
|
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
|
|
AudioManager.Instance.PlaySfx("event:/FollowInput/plugdrop");
|
|
}
|
|
|
|
// ------------------------------ 插头事件 ------------------------------
|
|
private void OnInsertSocket(ISocket socket)
|
|
{
|
|
var isSameSocket = curSocket == socket;
|
|
curSocket = socket;
|
|
physicCableRef.PlugInto(socket.GetSocketPos());
|
|
AudioManager.Instance.PlaySfx("event:/FollowInput/plugin");
|
|
|
|
if (!isSameSocket
|
|
&& !EventSystemEx.Instance.isLocked
|
|
&& socket is BodyModule bodyModule)
|
|
{
|
|
// 插入模块时触发插入事件
|
|
EnumEventSystem.Global.Send(EventEnum.PlugIn);
|
|
}
|
|
}
|
|
|
|
private void OnPullUpSocket()
|
|
{
|
|
bool wasPlugged = curSocket != null;
|
|
curSocket?.PlugOut();
|
|
AudioManager.Instance.PlaySfx("event:/FollowInput/plugout");
|
|
|
|
// 只要插头确实从 Socket 拔出,就通知 Screen 清空。
|
|
// 空闲状态下的防御性 PullUpSocket 调用不会误触发该事件。
|
|
if (wasPlugged)
|
|
{
|
|
EnumEventSystem.Global.Send(EventEnum.PlugOut);
|
|
}
|
|
|
|
curSocket = null;
|
|
}
|
|
|
|
private void OnPlugPoinerDown()
|
|
{
|
|
Transform unplugSocket = curSocket?.GetSocketPos();
|
|
bool wasPlugged = IsPlugInSocket();
|
|
if (wasPlugged) PlugRef.PullUpSocket();
|
|
|
|
// 末端钉到拖拽目标,按需送线;指针位置随 OnDrag 持续更新。
|
|
// 排序不随拖拽切换:线缆与插头常驻 FixTop 高位(原型画序固定不变)
|
|
physicCableRef.BeginDrag(PlugRef.transform.position);
|
|
if (wasPlugged)
|
|
{
|
|
physicCableRef.StartUnplugResistance(unplugSocket);
|
|
}
|
|
|
|
// 打开插孔
|
|
BodyModuleSystem.OpenModuleSlots();
|
|
}
|
|
|
|
/// <summary>拖拽中更新指针位置(世界坐标),由 Plug.OnDrag 调用。</summary>
|
|
public void UpdateDragPointer(Vector3 pointerWorldPos)
|
|
{
|
|
physicCableRef.SetPointer(pointerWorldPos);
|
|
}
|
|
|
|
/// <summary>结束拖拽:关闭所有插孔盖。</summary>
|
|
public void ClearDragHighlight()
|
|
{
|
|
BodyModuleSystem?.CloseModuleSlots(null);
|
|
}
|
|
|
|
/// <summary>松手且未插中插孔:解除钉住,线缆自动回收,插头挂回绳末端。</summary>
|
|
public void ReleasePlug()
|
|
{
|
|
ClearDragHighlight();
|
|
physicCableRef.EndDrag();
|
|
}
|
|
|
|
// ------------------------------ 系统状态 ------------------------------
|
|
public bool IsPlugInSocket()
|
|
{
|
|
return curSocket != null;
|
|
}
|
|
|
|
// ------------------------------ 线缆操作 ------------------------------
|
|
|
|
/// <summary>
|
|
/// 寻找最接近的Module
|
|
/// </summary>
|
|
/// <param name="sourcePos">源位置</param>
|
|
/// <param name="targetModule">最近的Module</param>
|
|
/// <returns>是否能找到目标范围内的Module</returns>
|
|
public bool TryFindClosestModule(Vector3 sourcePos, out BodyModule targetModule)
|
|
{
|
|
return BodyModuleSystem.TryFindClosestModule(sourcePos, out targetModule);
|
|
}
|
|
|
|
public void PullUpCable()
|
|
{
|
|
if (isCableRetracted) return;
|
|
|
|
if (IsPlugInSocket())
|
|
{
|
|
PlugRef.PullUpSocket();
|
|
}
|
|
|
|
BodyModuleSystem.CloseModuleSlots(null);
|
|
|
|
// 收线动画:绳身按纯插值收拢到收纳位(不走拖拽物理,避免其阻尼参数在极短时长内来不及收敛而甩鞭),
|
|
// 插头 DOMove 与绳收拢共用同一时长,动画结束时绳已真正收完,再切换到收起态不会有可见的切割感
|
|
suppressPlugFollow = true;
|
|
physicCableRef.CollapseTo(retractedPos.position, RetractDuration);
|
|
PlugRef.transform.DOMove(retractedPos.position, RetractDuration)
|
|
.OnComplete(() =>
|
|
{
|
|
ApplyRetractedState();
|
|
isCableRetracted = true;
|
|
});
|
|
|
|
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
|
|
AudioManager.Instance.PlaySfx("event:/FollowInput/plugdrop");
|
|
}
|
|
|
|
public void DropDownCable()
|
|
{
|
|
if (!isCableRetracted) return;
|
|
// 确保 Plug 已脱离 Socket(收起状态下通常已脱离,此处为防御性调用)
|
|
PlugRef.PullUpSocket();
|
|
CableReelRef.ResetRotation();
|
|
suppressPlugFollow = true;
|
|
PlugRef.transform.DOMove(PlugRef.GetStartPos(), 0.1f).OnComplete(() =>
|
|
{
|
|
suppressPlugFollow = false;
|
|
physicCableRef.ShowFree(true);
|
|
isCableRetracted = false;
|
|
});
|
|
}
|
|
|
|
/// <summary>应用收起状态的视觉与组件设置(不含动画、不含 isCableRetracted 更新)</summary>
|
|
private void ApplyRetractedState()
|
|
{
|
|
physicCableRef.Hide();
|
|
suppressPlugFollow = false;
|
|
|
|
// 强制收起时长可能短于拔出反馈动画(挤压 punch scale),未播完就会被打断,
|
|
// 收起完成后强制清掉,避免插头带着没播完的形变停在收纳位
|
|
PlugRef.CancelFeedbackTween();
|
|
|
|
PlugRef.transform.position = retractedPos.position;
|
|
PlugRef.transform.rotation = retractedPos.rotation;
|
|
|
|
var plugSprite = PlugRef.GetComponent<SpriteRenderer>();
|
|
plugSprite.sortingLayerName = CableReelRef.GetComponent<SpriteRenderer>().sortingLayerName;
|
|
plugSprite.sortingOrder = SortingOrderRetracted;
|
|
}
|
|
|
|
public void SetRetractCable()
|
|
{
|
|
ApplyRetractedState();
|
|
isCableRetracted = true;
|
|
}
|
|
|
|
#region 灯光系统 - 供外部调用
|
|
|
|
/// <summary>
|
|
/// 启动系统
|
|
/// </summary>
|
|
public System.Collections.IEnumerator StartSystem()
|
|
{
|
|
if (panelLight != null)
|
|
{
|
|
yield return panelLight.StartSystem();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新呼吸灯
|
|
/// </summary>
|
|
public void UpdateBreathingLight(LightState state)
|
|
{
|
|
panelLight?.UpdateBreathingLight(state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新ECG波形
|
|
/// </summary>
|
|
public void UpdateECGWave(LightState state)
|
|
{
|
|
panelLight?.UpdateECGWave(state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取当前波形类型
|
|
/// </summary>
|
|
public RobotECGWave.WaveType GetCurrentWaveType()
|
|
{
|
|
return panelLight != null ? panelLight.GetCurrentWaveType() : RobotECGWave.WaveType.RobotECG;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置波形类型
|
|
/// </summary>
|
|
public void SetWaveType(RobotECGWave.WaveType waveType)
|
|
{
|
|
panelLight?.SetWaveType(waveType);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void Init()
|
|
{
|
|
|
|
}
|
|
|
|
public void Open()
|
|
{
|
|
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
|
|
}
|
|
}
|
|
}
|