using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.VFX; namespace AibisDream.FixSystem { /// /// 模块运行状态的纯表现层:Module Pic 始终显示,文字负责常亮/闪烁/熄灭, /// 火花依据 Module Pic 的世界包围盒收束在底边,并渲染在本体下层。 /// public class ModuleConditionVisual : MonoBehaviour { private static readonly Vector2[] SparkDirections = { new(-0.5f, -0.86f), new(0f, -0.86f), new(0.5f, -0.86f) }; [Header("模块引用")] [SerializeField] private SpriteRenderer modulePicRenderer; [SerializeField] private SpriteRenderer moduleTextRenderer; [Header("损坏文字闪烁")] [SerializeField, Min(0.01f)] private float flickerMinInterval = 0.06f; [SerializeField, Min(0.01f)] private float flickerMaxInterval = 0.18f; [Header("损坏火花")] [SerializeField] private VisualEffectAsset sparkAsset; [SerializeField, Min(0.01f)] private float sparkMinInterval = 0.3f; [SerializeField, Min(0.01f)] private float sparkMaxInterval = 1f; [SerializeField, Min(0f)] private float sparkBoundsPadding = 0.01f; [SerializeField] private Vector2 sparkBoundsScale = new(0.9f, 0.92f); [SerializeField] private Vector2 sparkCenterOffset = Vector2.zero; [SerializeField] private Vector2 sparkBurstCount = new(3f, 6f); [SerializeField] private Vector2 sparkLifetime = new(0.1f, 0.24f); [SerializeField] private Vector2 sparkSizeFactor = new(0.032f, 0.065f); [SerializeField, Min(0.01f)] private float sparkWidth = 0.45f; [SerializeField, Min(0f)] private float sparkVelocityFactor = 0.9f; private readonly List _sparkEmitters = new(); private Coroutine _textFlickerCoroutine; private Coroutine _sparkCoroutine; private Color _originalTextColor = Color.white; private ModuleCondition _condition = ModuleCondition.Normal; private bool _initialized; private bool _hasAppliedCondition; public bool IsConfigured => modulePicRenderer != null && moduleTextRenderer != null; /// /// 文字当前的亮/灭状态(含 Damaged 闪烁中的瞬时相位),供切割视图入场时对齐。 /// public bool IsTextCurrentlyVisible => moduleTextRenderer != null && moduleTextRenderer.enabled; private void Awake() { Initialize(); } private void OnEnable() { if (_initialized && _hasAppliedCondition) ApplyConditionInternal(_condition, true); } private void OnDisable() { StopRunningEffects(); } public void ApplyCondition(ModuleCondition condition) { Initialize(); ApplyConditionInternal(condition, false); } private void Initialize() { if (_initialized) return; _initialized = true; if (moduleTextRenderer != null) _originalTextColor = moduleTextRenderer.color; CreateSparkEmitters(); } private void ApplyConditionInternal(ModuleCondition condition, bool force) { if (!force && _hasAppliedCondition && _condition == condition) return; _condition = condition; _hasAppliedCondition = true; StopRunningEffects(); if (!IsConfigured) return; switch (condition) { case ModuleCondition.Normal: SetTextVisible(true); break; case ModuleCondition.Damaged: SetTextVisible(true); if (isActiveAndEnabled) { _textFlickerCoroutine = StartCoroutine(FlickerText()); if (_sparkEmitters.Count > 0) _sparkCoroutine = StartCoroutine(PlayIntermittentSparks()); } break; case ModuleCondition.PoweredOff: SetTextVisible(false); break; } } private void StopRunningEffects() { if (_textFlickerCoroutine != null) { StopCoroutine(_textFlickerCoroutine); _textFlickerCoroutine = null; } if (_sparkCoroutine != null) { StopCoroutine(_sparkCoroutine); _sparkCoroutine = null; } foreach (var effect in _sparkEmitters) { if (effect == null) continue; effect.Stop(); effect.gameObject.SetActive(false); } } private IEnumerator FlickerText() { while (_condition == ModuleCondition.Damaged) { SetTextVisible(Random.value > 0.4f); yield return new WaitForSeconds(Random.Range( Mathf.Min(flickerMinInterval, flickerMaxInterval), Mathf.Max(flickerMinInterval, flickerMaxInterval))); } } private IEnumerator PlayIntermittentSparks() { yield return new WaitForSeconds(Random.Range(0.05f, 0.25f)); while (_condition == ModuleCondition.Damaged) { PlaySparkBurst(); yield return new WaitForSeconds(Random.Range( Mathf.Min(sparkMinInterval, sparkMaxInterval), Mathf.Max(sparkMinInterval, sparkMaxInterval))); } } private void PlaySparkBurst() { if (_sparkEmitters.Count == 0) return; var effect = _sparkEmitters[Random.Range(0, _sparkEmitters.Count)]; if (effect == null) return; effect.gameObject.SetActive(true); effect.Reinit(); effect.Play(); } private void SetTextVisible(bool visible) { if (moduleTextRenderer == null) return; moduleTextRenderer.color = _originalTextColor; moduleTextRenderer.enabled = visible; } private void CreateSparkEmitters() { if (sparkAsset == null || modulePicRenderer == null) return; var bounds = modulePicRenderer.bounds; var scaledExtents = new Vector2( bounds.extents.x * Mathf.Max(0.01f, sparkBoundsScale.x), bounds.extents.y * Mathf.Max(0.01f, sparkBoundsScale.y)); var sizeBase = Mathf.Max(0.01f, Mathf.Min(bounds.size.x, bounds.size.y)); foreach (var rawDirection in SparkDirections) { var direction = rawDirection.normalized; var worldPosition = (Vector2)bounds.center + sparkCenterOffset + new Vector2( rawDirection.x * scaledExtents.x, rawDirection.y * scaledExtents.y) + direction * sparkBoundsPadding; var emitterObject = new GameObject($"Damage Spark {rawDirection.x:0},{rawDirection.y:0}"); emitterObject.transform.SetParent(transform, false); emitterObject.transform.localPosition = transform.InverseTransformPoint(worldPosition); var effect = emitterObject.AddComponent(); effect.visualEffectAsset = sparkAsset; ConfigureSparkEffect(effect, direction, sizeBase); var renderer = emitterObject.GetComponent(); if (renderer != null) { renderer.sortingLayerID = modulePicRenderer.sortingLayerID; renderer.sortingOrder = modulePicRenderer.sortingOrder - 1; } effect.Stop(); emitterObject.SetActive(false); _sparkEmitters.Add(effect); } } private void ConfigureSparkEffect(VisualEffect effect, Vector2 direction, float sizeBase) { if (effect.HasFloat("Spawn Rate")) effect.SetFloat("Spawn Rate", 0f); if (effect.HasVector2("Burst Count Min/Max")) effect.SetVector2("Burst Count Min/Max", sparkBurstCount); if (effect.HasVector2("Lifetime Min/Max")) effect.SetVector2("Lifetime Min/Max", sparkLifetime); if (effect.HasVector2("Size Min/Max")) effect.SetVector2("Size Min/Max", sparkSizeFactor * sizeBase); if (effect.HasFloat("Sparkle Width")) effect.SetFloat("Sparkle Width", sparkWidth); if (effect.HasVector3("Initial Velocity_vector")) { var speed = sizeBase * sparkVelocityFactor; effect.SetVector3("Initial Velocity_vector", new Vector3(direction.x, direction.y, 0f) * speed); } } } }