493 lines
17 KiB
C#
493 lines
17 KiB
C#
using AibisDream;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using DG.Tweening;
|
||
|
||
namespace AibisDream.MiniGame.HuoShan
|
||
{
|
||
/// <summary>
|
||
/// 滑片控制器 - 实现滑片沿圆环滑动以及与波形联动
|
||
/// 功能:
|
||
/// - 仅通过Yarn指令控制移动,不接受玩家直接交互
|
||
/// - 滑片角度/弧度驱动波形参数
|
||
/// - 等离子体发光段:从起始位置到当前位置显示等离子体进度指示
|
||
/// </summary>
|
||
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<float> 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<WaveformRenderer>();
|
||
}
|
||
}
|
||
|
||
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<ParticleSystem>();
|
||
ConfigureParticleSystem(plasmaParticleSystem);
|
||
|
||
var renderer = go.GetComponent<ParticleSystemRenderer>();
|
||
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<ParticleSystemRenderer>();
|
||
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 公共方法
|
||
|
||
/// <summary>
|
||
/// 移动到指定角度
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移动到指定位置(0-1标准化)
|
||
/// </summary>
|
||
public void SlideToNormalized(float normalizedPosition, float duration = 0.5f)
|
||
{
|
||
float angle = normalizedPosition * slideAngleRange;
|
||
SlideTo(angle, duration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移动到指定段
|
||
/// </summary>
|
||
public void SlideToSegment(int segmentIndex, float duration = 0.5f)
|
||
{
|
||
if (segmentIndex < 0 || segmentIndex > segmentCount) return;
|
||
|
||
float segmentAngle = slideAngleRange / segmentCount * segmentIndex;
|
||
SlideTo(segmentAngle, duration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前角度
|
||
/// </summary>
|
||
public float GetCurrentAngle()
|
||
{
|
||
return _currentAngle;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取标准化位置(0-1)
|
||
/// </summary>
|
||
public float GetNormalizedPosition()
|
||
{
|
||
return _currentAngle / slideAngleRange;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置滑片位置
|
||
/// </summary>
|
||
public void ResetPosition()
|
||
{
|
||
if (_moveTween != null)
|
||
{
|
||
_moveTween.Kill();
|
||
_moveTween = null;
|
||
}
|
||
|
||
_targetAngle = 0f;
|
||
_currentAngle = 0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置离散段数量
|
||
/// </summary>
|
||
public void SetSegmentCount(int count)
|
||
{
|
||
segmentCount = Mathf.Max(1, count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前段索引
|
||
/// </summary>
|
||
public int GetCurrentSegment()
|
||
{
|
||
float segmentAngle = slideAngleRange / segmentCount;
|
||
return Mathf.FloorToInt(_currentAngle / segmentAngle);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|