357 lines
11 KiB
C#
357 lines
11 KiB
C#
using System;
|
|
using AibisDream.Framework;
|
|
using Newtonsoft.Json;
|
|
using UnityEngine;
|
|
using System.Collections;
|
|
using DG.Tweening;
|
|
using AibisDream.Kit;
|
|
|
|
namespace AibisDream.FixSystem
|
|
{
|
|
public class BodyModule : MonoBehaviour, ISocket
|
|
{
|
|
#region 组件引用
|
|
|
|
private const string SocketObjName = "Socket";
|
|
|
|
private Transform _socketPos;
|
|
private Transform _socketLeft;
|
|
private Transform _socketRight;
|
|
private Tween _socketTween;
|
|
|
|
private BodyModuleSystem _bodyModuleSystem;
|
|
private Material _highlightMaterial;
|
|
private Material _alarmMaterial;
|
|
private Material _originalMaterial; // 原材质
|
|
|
|
/// <summary>
|
|
/// Yarn 可能在 Start 里异步加载材质完成前就调用 ModuleHightLight,需记下来待加载后再套材质。
|
|
/// </summary>
|
|
private bool _wantsHighlight;
|
|
private bool _wantsAlarm;
|
|
|
|
public bool IsHighlighted => _wantsHighlight;
|
|
public bool IsAlarmed => _wantsAlarm;
|
|
|
|
[SerializeField]
|
|
private SpriteRenderer targetSpriteRenderer; // 目标的 SpriteRenderer
|
|
|
|
[Header("模块运行状态")]
|
|
[SerializeField] private ModuleConditionVisual conditionVisual;
|
|
[SerializeField] private ModuleCondition condition = ModuleCondition.Normal;
|
|
|
|
public ModuleCondition Condition => condition;
|
|
public bool SupportsConditionVisual => conditionVisual != null && conditionVisual.IsConfigured;
|
|
|
|
private SpriteRenderer _cutSpriteRenderer;
|
|
private SpriteRenderer _cutTextRenderer; // 添加cutText的SpriteRenderer引用
|
|
|
|
[Header("CutText闪烁效果")] private float cutTextFlickerThreshold = 0.4f; // 开始闪烁的阈值
|
|
private float cutTextFlickerInterval = 0.1f; // 闪烁间隔
|
|
private bool isCutTextFlickering = false; // 是否正在闪烁
|
|
private Coroutine flickerCoroutine; // 闪烁协程引用
|
|
|
|
#endregion
|
|
|
|
// 模块基本数据
|
|
[SerializeField] public BodyModuleData data;
|
|
public bool available = true;
|
|
private bool _isOpen;
|
|
|
|
private void Awake()
|
|
{
|
|
// 获取组件索引
|
|
InitReference();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
StartCoroutine(LoadMaterial());
|
|
conditionVisual?.ApplyCondition(condition);
|
|
}
|
|
|
|
protected virtual void InitReference()
|
|
{
|
|
_socketPos = transform.Find(SocketObjName);
|
|
_socketLeft = _socketPos.Find("Left");
|
|
_socketRight = _socketPos.Find("Right");
|
|
|
|
_bodyModuleSystem = transform.parent.parent.parent.GetComponent<BodyModuleSystem>();
|
|
|
|
if (targetSpriteRenderer == null)
|
|
{
|
|
targetSpriteRenderer = transform.Find("Module Pic").GetComponent<SpriteRenderer>();
|
|
}
|
|
_originalMaterial = targetSpriteRenderer.GetComponent<SpriteRenderer>().material;
|
|
|
|
_cutSpriteRenderer = transform.Find("Cut Pic")?.GetComponent<SpriteRenderer>();
|
|
if (_cutSpriteRenderer != null)
|
|
{
|
|
// 初始化cutText的SpriteRenderer
|
|
_cutTextRenderer =
|
|
_cutSpriteRenderer.transform.Find("Cut Text")?.GetComponent<SpriteRenderer>();
|
|
}
|
|
}
|
|
|
|
private IEnumerator LoadMaterial()
|
|
{
|
|
// 异步加载材质
|
|
var highlightHandle = ResourceSystem.LoadAsync<Material>(FixAssetRef.HighlightMaterial);
|
|
var alarmHandle = ResourceSystem.LoadAsync<Material>(FixAssetRef.AlarmMaterial);
|
|
yield return highlightHandle;
|
|
yield return alarmHandle;
|
|
|
|
if (highlightHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
|
|
_highlightMaterial = highlightHandle.Result;
|
|
else
|
|
Debug.LogError("[BodyModule] Failed to load HighlightMaterial");
|
|
|
|
if (alarmHandle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
|
|
_alarmMaterial = alarmHandle.Result;
|
|
else
|
|
Debug.LogError("[BodyModule] Failed to load AlarmMaterial");
|
|
|
|
ApplyModuleVisual();
|
|
}
|
|
|
|
#region 插槽功能
|
|
|
|
public bool IsAvailable()
|
|
{
|
|
return available;
|
|
}
|
|
|
|
public void SetSocketAvailable(bool isAvailable)
|
|
{
|
|
available = isAvailable;
|
|
}
|
|
|
|
public virtual void PlugIn()
|
|
{
|
|
if (DialogController.Instance)
|
|
// 然后触发Yarn节点
|
|
DialogController.Instance.StartDialogNode(_bodyModuleSystem.inHotErr ? "系统发热" : data.PlugInNodeName);
|
|
}
|
|
|
|
public virtual void PlugOut()
|
|
{
|
|
}
|
|
|
|
public Transform GetSocketPos()
|
|
{
|
|
return _socketPos;
|
|
}
|
|
|
|
public void OpenPlugSlot()
|
|
{
|
|
if (!_socketLeft) return;
|
|
|
|
if (_isOpen) return;
|
|
_isOpen = true;
|
|
// 打开插槽
|
|
if (_socketTween.IsActive() && _socketTween.IsPlaying()) _socketTween.Kill(true);
|
|
var sequence = DOTween.Sequence();
|
|
sequence.Join(_socketLeft.DOScale(new Vector3(0, 1, 1), 0.2f).SetEase(Ease.InSine));
|
|
sequence.Join(_socketRight.DOScale(new Vector3(0, 1, 1), 0.2f).SetEase(Ease.InSine));
|
|
_socketTween = sequence;
|
|
}
|
|
|
|
public void ClosePlugSlot()
|
|
{
|
|
if (!_socketLeft) return;
|
|
if (!_isOpen) return;
|
|
|
|
_isOpen = false;
|
|
// 关闭插槽
|
|
if (_socketTween.IsActive() && _socketTween.IsPlaying()) _socketTween.Kill(true);
|
|
var sequence = DOTween.Sequence();
|
|
sequence.Join(_socketLeft.DOScale(new Vector3(1, 1, 1), 0.2f).SetEase(Ease.OutSine));
|
|
sequence.Join(_socketRight.DOScale(new Vector3(1, 1, 1), 0.2f).SetEase(Ease.OutSine));
|
|
_socketTween = sequence;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 高亮提示和隐藏
|
|
|
|
public void Highlight(bool enable)
|
|
{
|
|
_wantsHighlight = enable;
|
|
ApplyModuleVisual();
|
|
}
|
|
|
|
public void Alarm(bool enable)
|
|
{
|
|
_wantsAlarm = enable;
|
|
ApplyModuleVisual();
|
|
}
|
|
|
|
private void ApplyModuleVisual()
|
|
{
|
|
if (targetSpriteRenderer == null) return;
|
|
|
|
if (_wantsAlarm && _alarmMaterial != null)
|
|
targetSpriteRenderer.material = _alarmMaterial;
|
|
else if (_wantsHighlight && _highlightMaterial != null)
|
|
targetSpriteRenderer.material = _highlightMaterial;
|
|
else
|
|
targetSpriteRenderer.material = _originalMaterial;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 模块运行状态
|
|
|
|
/// <summary>
|
|
/// 设置模块表现状态。状态只影响独立文字与火花,不影响 Module Pic、插线或切割逻辑。
|
|
/// </summary>
|
|
public bool SetCondition(ModuleCondition newCondition)
|
|
{
|
|
if (conditionVisual == null)
|
|
{
|
|
Debug.LogWarning($"[BodyModule] Module {data.moduleName} has no ModuleConditionVisual configured.", this);
|
|
return false;
|
|
}
|
|
|
|
condition = newCondition;
|
|
conditionVisual.ApplyCondition(newCondition);
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 切割相关
|
|
|
|
public void OnCuttingDown()
|
|
{
|
|
_bodyModuleSystem.RemoveModule(this);
|
|
|
|
DialogController.Instance?.StartDialogNode($"{data.moduleName}CutDown");
|
|
EnumEventSystem.Global.Send(EventEnum.CutEnd);
|
|
Destroy(gameObject);
|
|
}
|
|
|
|
public void SetCutTextVisible(bool visible)
|
|
{
|
|
if (_cutTextRenderer != null)
|
|
{
|
|
_cutTextRenderer.enabled = visible;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按当前 ModuleCondition 同步 CutText 亮灭,与舱外 ModuleText 表现一致。
|
|
/// Normal 常亮 / Damaged 闪烁 / PoweredOff 熄灭。
|
|
/// </summary>
|
|
public void ApplyCutTextFromCondition()
|
|
{
|
|
StopCutTextFlicker(restoreVisible: false);
|
|
|
|
switch (condition)
|
|
{
|
|
case ModuleCondition.Normal:
|
|
SetCutTextVisible(true);
|
|
break;
|
|
case ModuleCondition.Damaged:
|
|
SetCutTextVisible(true);
|
|
StartCutTextFlicker();
|
|
break;
|
|
case ModuleCondition.PoweredOff:
|
|
SetCutTextVisible(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public SpriteRenderer GetCutSpriteRenderer()
|
|
{
|
|
return _cutSpriteRenderer;
|
|
}
|
|
|
|
public SpriteRenderer GetCutTextSpriteRenderer()
|
|
{
|
|
return _cutTextRenderer;
|
|
}
|
|
|
|
public void StartCutTextFlicker()
|
|
{
|
|
if (!isCutTextFlickering)
|
|
{
|
|
isCutTextFlickering = true;
|
|
if (flickerCoroutine != null)
|
|
{
|
|
StopCoroutine(flickerCoroutine);
|
|
}
|
|
|
|
flickerCoroutine = StartCoroutine(CutTextFlickerCoroutine());
|
|
}
|
|
}
|
|
|
|
public void StopCutTextFlicker(bool restoreVisible = true)
|
|
{
|
|
isCutTextFlickering = false;
|
|
if (flickerCoroutine != null)
|
|
{
|
|
StopCoroutine(flickerCoroutine);
|
|
flickerCoroutine = null;
|
|
}
|
|
|
|
if (restoreVisible)
|
|
SetCutTextVisible(true);
|
|
}
|
|
|
|
private IEnumerator CutTextFlickerCoroutine()
|
|
{
|
|
while (isCutTextFlickering)
|
|
{
|
|
// 随机决定是否显示
|
|
bool shouldShow = UnityEngine.Random.value > 0.5f;
|
|
SetCutTextVisible(shouldShow);
|
|
|
|
// 播放相应的音效
|
|
if (shouldShow)
|
|
{
|
|
AudioManager.Instance.PlaySfx("event:/ActionFB/light_flicker_on");
|
|
}
|
|
else
|
|
{
|
|
AudioManager.Instance.PlaySfx("event:/ActionFB/light_flicker_off");
|
|
}
|
|
|
|
// 随机等待时间,模拟接触不良效果
|
|
float waitTime = UnityEngine.Random.Range(cutTextFlickerInterval * 0.5f, cutTextFlickerInterval * 1.5f);
|
|
yield return new WaitForSeconds(waitTime);
|
|
}
|
|
}
|
|
|
|
public void UpdateCutProgress(float progress)
|
|
{
|
|
// 检查是否达到闪烁阈值
|
|
if (!isCutTextFlickering && progress >= cutTextFlickerThreshold)
|
|
{
|
|
StartCutTextFlicker();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
[Serializable]
|
|
public struct BodyModuleData
|
|
{
|
|
public const string PlugInNodeTemplate = "{0}PlugIn";
|
|
|
|
// ID
|
|
public string moduleName;
|
|
|
|
// 只读
|
|
[JsonIgnore] public string PlugInNodeName => string.Format(PlugInNodeTemplate, moduleName);
|
|
}
|
|
|
|
public enum ModuleCondition
|
|
{
|
|
Normal,
|
|
Damaged,
|
|
PoweredOff
|
|
}
|
|
}
|