Files

510 lines
16 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.22f;
#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; // 线缆是否收起
// 与 isCableRetracted(当前可交互状态)分开记录最后一次指令的目标,
// 防止相反方向或外部 DOKill 触发旧动画回调,把线缆恢复到错误状态。
private bool targetCableRetracted = true;
[SerializeField] private Transform retractedPos; // 收起位置
private ISocket curSocket;
// 放线动画期间插头由 DOTween 驱动,暂停"插头跟随绳末端"
private bool suppressPlugFollow;
private Tween retractTween;
#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()
{
targetCableRetracted = isCableRetracted;
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()
{
targetCableRetracted = false;
retractTween?.Kill(false);
retractTween = null;
PlugRef.transform.DOKill(false);
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()
{
// 仅拖拽时由拉力驱动出线口倾斜;插接/松弛不预判朝向插头。
// GetTautness 已扣除送线设计余量,绷紧时才会转。
if (CableReelRef != null && physicCableRef != null
&& physicCableRef.State != PhysicCable.CableState.Hidden)
{
bool active = physicCableRef.State == PhysicCable.CableState.Dragging;
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
|| state == PhysicCable.CableState.Collapsing)
{
physicCableRef.ApplyPlugPose(plugRef.transform, plugRef.PlugRoot);
plugRef.ApplyFeedbackOffset();
}
}
/// <summary>
/// 从插孔拔出(若已插入),放回初始可插线位:线缆放下、可拖拽,不收起。
/// </summary>
public void ResetPlug()
{
bool wasPlugged = IsPlugInSocket();
if (wasPlugged)
{
PlugRef.PullUpSocket();
}
BodyModuleSystem.CloseModuleSlots(null);
// 已在初始可插线位且未收起:只关槽,不重复放线/音效
if (!wasPlugged && !isCableRetracted && !targetCableRetracted)
{
return;
}
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(BodyModule preservedModule = null)
{
BodyModuleSystem?.CloseModuleSlots(preservedModule);
}
/// <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);
}
/// <summary>收起线缆:无论是否插在模块上,统一收到线盘位。</summary>
public void PullUpCable()
{
bool wasPlugged = IsPlugInSocket();
if (wasPlugged)
{
PlugRef.PullUpSocket();
}
BodyModuleSystem.CloseModuleSlots(null);
// 已在收起或正在收起,且本次无需拔插:不重复收线动画/音效
if (targetCableRetracted && !wasPlugged)
{
isCableRetracted = true;
return;
}
targetCableRetracted = true;
isCableRetracted = true;
retractTween?.Kill(false);
retractTween = null;
PlugRef.transform.DOKill(false);
PlugRef.CancelFeedbackTween();
if (physicCableRef.State == PhysicCable.CableState.Hidden)
{
ApplyRetractedState();
return;
}
suppressPlugFollow = false;
physicCableRef.CollapseTo(physicCableRef.startTransform.position, RetractDuration);
retractTween = DOVirtual.DelayedCall(RetractDuration, CompleteRetractIfCurrent)
.OnKill(() =>
{
// DelayedCall 正常完成后会自动 Kill;此时回调已经把字段清空,不重复结算。
if (retractTween != null)
{
CompleteRetractIfCurrent();
}
});
AudioManager.Instance.PlaySfx("event:/ActionFB/mods_close");
AudioManager.Instance.PlaySfx("event:/FollowInput/plugdrop");
}
private void CompleteRetractIfCurrent()
{
if (!targetCableRetracted) return;
retractTween = null;
ApplyRetractedState();
isCableRetracted = true;
}
public void DropDownCable()
{
if (!isCableRetracted && !targetCableRetracted) return;
targetCableRetracted = false;
retractTween?.Kill(false);
retractTween = null;
PlugRef.PullUpSocket();
PlugRef.transform.DOKill(false);
CableReelRef.ResetRotation();
suppressPlugFollow = true;
PlugRef.transform.DOMove(PlugRef.GetStartPos(), 0.1f).OnComplete(CompleteDropIfCurrent)
.OnKill(CompleteDropIfCurrent);
}
private void CompleteDropIfCurrent()
{
if (targetCableRetracted) return;
suppressPlugFollow = false;
physicCableRef.ShowFree(true);
isCableRetracted = false;
}
/// <summary>应用收起状态的视觉与组件设置(不含动画、不含 isCableRetracted 更新)</summary>
private void ApplyRetractedState()
{
physicCableRef.Hide();
suppressPlugFollow = false;
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()
{
targetCableRetracted = true;
isCableRetracted = true;
retractTween?.Kill(false);
retractTween = null;
PlugRef.transform.DOKill(false);
ApplyRetractedState();
}
#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()
{
}
}
}