using AibisDream; using UnityEngine; using UnityEngine.Events; using DG.Tweening; namespace AibisDream.MiniGame.HuoShan { /// /// 滑片控制器 - 实现滑片沿圆环滑动以及与波形联动 /// 功能: /// - 仅通过Yarn指令控制移动,不接受玩家直接交互 /// - 滑片角度/弧度驱动波形参数 /// - 等离子体发光段:从起始位置到当前位置显示等离子体进度指示 /// public class SliderController : MonoBehaviour { [Header("圆环配置")] [Tooltip("圆环中心Transform")] [SerializeField] private Transform ringCenter; [Tooltip("外环半径")] [SerializeField] private float outerRadius = 2f; [Tooltip("内环半径(等离子体发光段)")] [SerializeField] private float innerRadius = 1.5f; [Tooltip("滑片滑动弧度范围(度)")] [SerializeField] private float slideAngleRange = 360f; [Tooltip("滑片起始角度偏移(度)")] [SerializeField] private float startAngleOffset = 0f; [Header("滑片配置")] [Tooltip("滑片Transform")] [SerializeField] private Transform sliderTransform; [Tooltip("滑片电夹Transform")] [SerializeField] private Transform clampTransform; [Tooltip("离散段数量(用于slide_to_segment等Yarn命令)")] [SerializeField] private int segmentCount = 12; [Header("等离子体弧线(Particle System)")] [Tooltip("用于等离子体效果的粒子系统,不赋值则自动创建")] [SerializeField] private ParticleSystem plasmaParticleSystem; [Tooltip("粒子数量下限(短弧时)")] [SerializeField] [Min(2)] private int minParticleCount = 20; [Tooltip("粒子数量上限(满弧时)")] [SerializeField] [Min(2)] private int maxParticleCount = 48; [Tooltip("粒子大小")] [SerializeField] private float particleSize = 0.08f; [Tooltip("粒子材质(Additive 发光,需手动引用)")] [SerializeField] private Material plasmaParticleMaterial; [Tooltip("粒子纹理(圆形中心亮边沿渐隐,不引用则自动生成软圆)")] [SerializeField] private Texture2D plasmaParticleTexture; [Tooltip("流动速度(小球沿弧线移动速度,1=每秒跑完一整圈)")] [SerializeField] private float flowSpeed = 3f; [Tooltip("发光颜色")] [SerializeField] private Color glowColor = new Color(0.3f, 0.9f, 0.55f); [CustomSortingLayer] [Tooltip("粒子渲染 Sorting Layer")] [SerializeField] private int plasmaSortingLayerId; [Tooltip("粒子渲染 Sorting Order")] [SerializeField] private int plasmaSortingOrder = 51; [Header("兼容旧版 LineRenderer(可留空)")] [Tooltip("若存在则禁用,改用 ParticleSystem")] [SerializeField] private LineRenderer plasmaLineRenderer; [Header("波形联动")] [Tooltip("波形检测器引用")] [SerializeField] private WaveformDetector waveformDetector; [Tooltip("等离子体发光影响波形参数")] [SerializeField] private bool plasmaAffectsWaveform = true; [Header("事件")] [Tooltip("滑片位置改变时触发(参数:标准化位置0-1)")] public UnityEvent onSliderPositionChanged; [Header("调试测试")] [Tooltip("勾选后,拖拽下方滑块可直接测试滑片与弧线(Play 模式下生效)")] [SerializeField] private bool useTestSlider; [Tooltip("测试用滑块:0=起始位置,1=终点位置")] [SerializeField] [Range(0f, 1f)] private float testSliderPosition; private float _currentAngle = 0f; private float _targetAngle = 0f; private WaveformRenderer _waveformRenderer; private Tweener _moveTween; private Vector3 _lastValidPosition; private ParticleSystem.Particle[] _particles; private float[] _particlePhases; private Material _plasmaMaterialInstance; private Texture2D _generatedSoftCircleTexture; #region 生命周期 private void Awake() { if (sliderTransform == null) { sliderTransform = transform; } // 禁用旧版 LineRenderer(改用 ParticleSystem) if (plasmaLineRenderer != null) { plasmaLineRenderer.enabled = false; } // 初始化等离子体粒子系统 EnsurePlasmaParticleSystem(); // 获取波形渲染器 if (waveformDetector != null && waveformDetector.IsShown) { _waveformRenderer = waveformDetector.GetComponentInChildren(); } } private void Update() { UpdateSliderPosition(); UpdatePlasmaGlow(); } private void OnDestroy() { if (_plasmaMaterialInstance != null) Destroy(_plasmaMaterialInstance); if (_generatedSoftCircleTexture != null) Destroy(_generatedSoftCircleTexture); } #endregion #region 位置更新 private void UpdateSliderPosition() { #if UNITY_EDITOR // 调试模式:Inspector 滑块直接驱动滑片 if (useTestSlider && Application.isPlaying) { _targetAngle = testSliderPosition * slideAngleRange; } #endif // 平滑移动到目标角度 _currentAngle = Mathf.Lerp(_currentAngle, _targetAngle, Time.deltaTime * 10f); // 计算滑片在世界空间的位置 float radian = (_currentAngle + startAngleOffset) * Mathf.Deg2Rad; Vector3 position = ringCenter.position + new Vector3( Mathf.Cos(radian) * outerRadius, Mathf.Sin(radian) * outerRadius, sliderTransform.position.z ); sliderTransform.position = position; // 滑片沿轨道顺时针方向(切线方向,与逆时针相反) Vector3 direction = position - ringCenter.position; float radialAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; float tangentAngle = radialAngle - 90f; // 顺时针切线 sliderTransform.rotation = Quaternion.Euler(0f, 0f, tangentAngle); // 夹子不跟随滑片移动,保持原位 // 触发位置改变事件 float normalizedPosition = _currentAngle / slideAngleRange; if (Vector3.Distance(position, _lastValidPosition) > 0.01f) { onSliderPositionChanged?.Invoke(normalizedPosition); UpdateWaveform(normalizedPosition); _lastValidPosition = position; } } #endregion #region 等离子体弧线 private void EnsurePlasmaParticleSystem() { if (plasmaParticleSystem != null) { ConfigureParticleSystem(plasmaParticleSystem); return; } // 自动创建粒子系统 var go = new GameObject("PlasmaParticles"); go.transform.SetParent(transform); go.transform.localPosition = Vector3.zero; go.transform.localScale = Vector3.one; plasmaParticleSystem = go.AddComponent(); ConfigureParticleSystem(plasmaParticleSystem); var renderer = go.GetComponent(); if (renderer != null) renderer.renderMode = ParticleSystemRenderMode.Billboard; } private void ConfigureParticleSystem(ParticleSystem ps) { var main = ps.main; main.simulationSpace = ParticleSystemSimulationSpace.World; main.loop = false; main.startLifetime = 999f; main.startSize = particleSize; main.maxParticles = maxParticleCount; main.playOnAwake = false; main.startSpeed = 0f; main.startColor = glowColor; var emission = ps.emission; emission.enabled = false; var velocity = ps.velocityOverLifetime; velocity.enabled = false; var colorOverLifetime = ps.colorOverLifetime; colorOverLifetime.enabled = false; var sizeOverLifetime = ps.sizeOverLifetime; sizeOverLifetime.enabled = false; var renderer = ps.GetComponent(); if (renderer != null) { ApplyPlasmaMaterial(renderer); int layerId = SortingLayer.IsValid(plasmaSortingLayerId) ? plasmaSortingLayerId : SortingLayer.layers[0].id; renderer.sortingLayerID = layerId; renderer.sortingOrder = plasmaSortingOrder; } // 初始化粒子数组 _particles = new ParticleSystem.Particle[maxParticleCount]; _particlePhases = new float[maxParticleCount]; for (int i = 0; i < maxParticleCount; i++) { _particlePhases[i] = (float)i / maxParticleCount; } ps.Play(); ps.Emit(maxParticleCount); } private void ApplyPlasmaMaterial(ParticleSystemRenderer renderer) { var tex = plasmaParticleTexture != null ? plasmaParticleTexture : GetOrCreateSoftCircleTexture(); if (tex == null) return; Material mat = plasmaParticleMaterial != null ? plasmaParticleMaterial : null; if (mat == null) { var shader = Shader.Find("Legacy Shaders/Particles/Additive") ?? Shader.Find("Particles/Additive"); if (shader == null) return; mat = new Material(shader); mat.SetColor("_TintColor", Color.white); } else { mat = new Material(plasmaParticleMaterial); } mat.mainTexture = tex; _plasmaMaterialInstance = mat; renderer.material = mat; } private Texture2D GetOrCreateSoftCircleTexture() { if (_generatedSoftCircleTexture != null) return _generatedSoftCircleTexture; const int size = 64; _generatedSoftCircleTexture = new Texture2D(size, size); _generatedSoftCircleTexture.wrapMode = TextureWrapMode.Clamp; _generatedSoftCircleTexture.filterMode = FilterMode.Bilinear; var pixels = new Color[size * size]; float center = (size - 1) * 0.5f; float radius = center; for (int y = 0; y < size; y++) for (int x = 0; x < size; x++) { float dx = x - center; float dy = y - center; float dist = Mathf.Sqrt(dx * dx + dy * dy); float t = 1f - Mathf.Clamp01(dist / radius); float alpha = t * t; pixels[y * size + x] = new Color(1f, 1f, 1f, alpha); } _generatedSoftCircleTexture.SetPixels(pixels); _generatedSoftCircleTexture.Apply(); return _generatedSoftCircleTexture; } private void UpdatePlasmaGlow() { if (plasmaParticleSystem == null || ringCenter == null || _particles == null) return; float progress = Mathf.Clamp01(_currentAngle / slideAngleRange); // 进度为 0 时隐藏粒子 if (progress <= 0.001f) { plasmaParticleSystem.SetParticles(_particles, 0); return; } float startRad = startAngleOffset * Mathf.Deg2Rad; float endRad = (startAngleOffset + _currentAngle) * Mathf.Deg2Rad; float arcSpan = endRad - startRad; float z = sliderTransform != null ? sliderTransform.position.z : ringCenter.position.z; Vector3 center = ringCenter.position; int aliveCount = plasmaParticleSystem.GetParticles(_particles); if (aliveCount == 0) { plasmaParticleSystem.Emit(maxParticleCount); return; } // 随进度在 min~max 间平滑过渡粒子数量 float countF = Mathf.Lerp(minParticleCount, maxParticleCount, progress); int actualCount = Mathf.Clamp(Mathf.RoundToInt(countF), minParticleCount, maxParticleCount); for (int i = 0; i < maxParticleCount; i++) { // 相位沿弧线流动 _particlePhases[i] += flowSpeed * Time.deltaTime; if (_particlePhases[i] >= 1f) _particlePhases[i] -= 1f; if (_particlePhases[i] < 0f) _particlePhases[i] += 1f; float t = _particlePhases[i]; float angle = startRad + arcSpan * t; Vector3 pos = center + new Vector3(Mathf.Cos(angle) * innerRadius, Mathf.Sin(angle) * innerRadius, z); _particles[i].position = pos; _particles[i].remainingLifetime = 999f; _particles[i].startLifetime = 999f; _particles[i].startSize = particleSize; _particles[i].startColor = Color.Lerp( new Color(glowColor.r, glowColor.g, glowColor.b, 0.5f), glowColor, t ); } plasmaParticleSystem.SetParticles(_particles, actualCount); } #endregion #region 波形联动 private void UpdateWaveform(float normalizedPosition) { if (!plasmaAffectsWaveform || waveformDetector == null) return; // 将滑片位置映射到波形参数 float waveformPosition = normalizedPosition; // 更新波形检测器的扫描位置 if (waveformDetector.IsScanMode) { // 如果处于扫描模式,更新扫描滑块 // 这会是集成的一部分,具体实现需要根据游戏流程调整 } // 更新波形渲染器 if (_waveformRenderer != null) { // 根据滑片位置调整波形清晰度 float clarity = Mathf.Lerp(0f, 1f, normalizedPosition); _waveformRenderer.SetClarity(clarity); // 根据滑片位置调整波形位置 _waveformRenderer.SetClarityPosition(normalizedPosition); } } #endregion #region 公共方法 /// /// 移动到指定角度 /// public void SlideTo(float angle, float duration = 0.5f) { angle = Mathf.Clamp(angle, 0f, slideAngleRange); if (_moveTween != null) { _moveTween.Kill(); } _moveTween = DOTween.To( () => _targetAngle, value => _targetAngle = value, angle, duration ).SetEase(Ease.OutCubic); } /// /// 移动到指定位置(0-1标准化) /// public void SlideToNormalized(float normalizedPosition, float duration = 0.5f) { float angle = normalizedPosition * slideAngleRange; SlideTo(angle, duration); } /// /// 移动到指定段 /// public void SlideToSegment(int segmentIndex, float duration = 0.5f) { if (segmentIndex < 0 || segmentIndex > segmentCount) return; float segmentAngle = slideAngleRange / segmentCount * segmentIndex; SlideTo(segmentAngle, duration); } /// /// 获取当前角度 /// public float GetCurrentAngle() { return _currentAngle; } /// /// 获取标准化位置(0-1) /// public float GetNormalizedPosition() { return _currentAngle / slideAngleRange; } /// /// 重置滑片位置 /// public void ResetPosition() { if (_moveTween != null) { _moveTween.Kill(); _moveTween = null; } _targetAngle = 0f; _currentAngle = 0f; } /// /// 设置离散段数量 /// public void SetSegmentCount(int count) { segmentCount = Mathf.Max(1, count); } /// /// 获取当前段索引 /// public int GetCurrentSegment() { float segmentAngle = slideAngleRange / segmentCount; return Mathf.FloorToInt(_currentAngle / segmentAngle); } #endregion } }