feat(sales): SliderController 改用 ParticleSystem 实现等离子体弧线,移除拖拽交互

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-12 22:52:21 +08:00
co-authored by Cursor
parent fb0f4658b8
commit 794b1f8d38
3 changed files with 327 additions and 181 deletions
@@ -0,0 +1,85 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: PlasmaParticle
m_Shader: {fileID: 200, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 2800000, guid: 72872287c0cfd8a468e047b93b3d8413, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _InvFade: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _TintColor: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c3a322f56de44a941b232cf5730be4be
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -1,3 +1,4 @@
using AibisDream;
using UnityEngine;
using UnityEngine.Events;
using DG.Tweening;
@@ -7,9 +8,9 @@ namespace AibisDream.MiniGame.HuoShan
/// <summary>
/// 滑片控制器 - 实现滑片沿圆环滑动以及与波形联动
/// 功能:
/// - 滑片拖拽沿外环滑动
/// - 仅通过Yarn指令控制移动,不接受玩家直接交互
/// - 滑片角度/弧度驱动波形参数
/// - 等离子体发光段
/// - 等离子体发光段:从起始位置到当前位置显示等离子体进度指
/// </summary>
public class SliderController : MonoBehaviour
{
@@ -36,28 +37,45 @@ namespace AibisDream.MiniGame.HuoShan
[Tooltip("滑片电夹Transform")]
[SerializeField] private Transform clampTransform;
[Tooltip("滑片拖拽阻力")]
[SerializeField] private float dragResistance = 0.05f;
[Tooltip("是否自动吸附到离散位置")]
[SerializeField] private bool snapToSegments = true;
[Tooltip("离散段数量")]
[Tooltip("离散段数量(用于slide_to_segment等Yarn命令)")]
[SerializeField] private int segmentCount = 12;
[Header("等离子体发光")]
[Tooltip("等离子体发光对象(可多个)")]
[SerializeField] private GameObject[] plasmaObjects;
[Header("等离子体弧线(Particle System")]
[Tooltip("用于等离子体效果的粒子系统,不赋值则自动创建")]
[SerializeField] private ParticleSystem plasmaParticleSystem;
[Tooltip("等离子体发光材质属性名")]
[SerializeField] private string glowProperty = "_GlowIntensity";
[Tooltip("粒子数量下限(短弧时)")]
[SerializeField] [Min(2)] private int minParticleCount = 20;
[Tooltip("发光强度")]
[SerializeField] private float glowIntensity = 1f;
[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;
@@ -69,53 +87,42 @@ namespace AibisDream.MiniGame.HuoShan
[Tooltip("滑片位置改变时触发(参数:标准化位置0-1)")]
public UnityEvent<float> onSliderPositionChanged;
[Tooltip("滑片开始拖拽时触发")]
public UnityEvent onSliderStartDrag;
[Header("调试测试")]
[Tooltip("勾选后,拖拽下方滑块可直接测试滑片与弧线(Play 模式下生效)")]
[SerializeField] private bool useTestSlider;
[Tooltip("滑片释放时触发")]
public UnityEvent<float> onSliderReleased;
[Tooltip("测试用滑块:0=起始位置,1=终点位置")]
[SerializeField] [Range(0f, 1f)] private float testSliderPosition;
private bool _isDragging = false;
private float _currentAngle = 0f;
private float _targetAngle = 0f;
private float _startAngle = 0f;
private Camera _mainCamera;
private Vector3 _dragStartPosition;
private Material[] _plasmaMaterials;
private int _glowPropertyID;
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()
{
_mainCamera = Camera.main;
_glowPropertyID = Shader.PropertyToID(glowProperty);
if (sliderTransform == null)
{
sliderTransform = transform;
}
// 缓存等离子体材质
if (plasmaObjects != null && plasmaObjects.Length > 0)
// 禁用旧版 LineRenderer(改用 ParticleSystem
if (plasmaLineRenderer != null)
{
_plasmaMaterials = new Material[plasmaObjects.Length];
for (int i = 0; i < plasmaObjects.Length; i++)
{
if (plasmaObjects[i] != null)
{
Renderer renderer = plasmaObjects[i].GetComponent<Renderer>();
if (renderer != null)
{
_plasmaMaterials[i] = renderer.material;
}
}
}
plasmaLineRenderer.enabled = false;
}
// 初始化等离子体粒子系统
EnsurePlasmaParticleSystem();
// 获取波形渲染器
if (waveformDetector != null && waveformDetector.IsShown)
{
@@ -125,105 +132,16 @@ namespace AibisDream.MiniGame.HuoShan
private void Update()
{
HandleMouseInput();
UpdateSliderPosition();
UpdatePlasmaGlow();
}
#endregion
#region
private void HandleMouseInput()
private void OnDestroy()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);
if (hit.collider != null && hit.collider.gameObject == gameObject)
{
StartDrag();
}
}
if (_isDragging)
{
if (Input.GetMouseButton(0))
{
ContinueDrag();
}
else if (Input.GetMouseButtonUp(0))
{
EndDrag();
}
}
}
#endregion
#region
private void StartDrag()
{
_isDragging = true;
_dragStartPosition = Input.mousePosition;
_startAngle = GetMouseAngle();
if (_moveTween != null)
{
_moveTween.Kill();
_moveTween = null;
}
onSliderStartDrag?.Invoke();
}
private void ContinueDrag()
{
// 计算鼠标相对于中心的角度
float mouseAngle = GetMouseAngle();
float deltaAngle = mouseAngle - _startAngle;
// 限制角度范围
deltaAngle = Mathf.Clamp(deltaAngle, 0f, slideAngleRange);
// 应用阻力
_targetAngle = deltaAngle * (1f - dragResistance);
// 更新起始角度用于下次计算
_startAngle = mouseAngle;
}
private void EndDrag()
{
_isDragging = false;
// 如果启用了离散位置,吸附到最近的段
if (snapToSegments)
{
SnapToNearestSegment();
}
// 触发释放事件
float normalizedPosition = _currentAngle / slideAngleRange;
onSliderReleased?.Invoke(normalizedPosition);
}
private float GetMouseAngle()
{
Vector3 mouseWorldPos = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
Vector3 direction = mouseWorldPos - ringCenter.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
if (angle < 0) angle += 360f;
return angle;
}
private void SnapToNearestSegment()
{
float segmentAngle = slideAngleRange / segmentCount;
int targetSegment = Mathf.RoundToInt(_currentAngle / segmentAngle);
_targetAngle = targetSegment * segmentAngle;
if (_plasmaMaterialInstance != null)
Destroy(_plasmaMaterialInstance);
if (_generatedSoftCircleTexture != null)
Destroy(_generatedSoftCircleTexture);
}
#endregion
@@ -232,6 +150,14 @@ namespace AibisDream.MiniGame.HuoShan
private void UpdateSliderPosition()
{
#if UNITY_EDITOR
// 调试模式:Inspector 滑块直接驱动滑片
if (useTestSlider && Application.isPlaying)
{
_targetAngle = testSliderPosition * slideAngleRange;
}
#endif
// 平滑移动到目标角度
_currentAngle = Mathf.Lerp(_currentAngle, _targetAngle, Time.deltaTime * 10f);
@@ -245,21 +171,13 @@ namespace AibisDream.MiniGame.HuoShan
sliderTransform.position = position;
// 滑片朝向中心
Vector3 direction = ringCenter.position - position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
sliderTransform.rotation = Quaternion.Euler(0f, 0f, angle);
// 滑片沿轨道顺时针方向(切线方向,与逆时针相反)
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);
// 更新电夹位置(保持在圆环上)
if (clampTransform != null)
{
Vector3 clampPosition = ringCenter.position + new Vector3(
Mathf.Cos(radian) * (outerRadius - 0.2f),
Mathf.Sin(radian) * (outerRadius - 0.2f),
clampTransform.position.z
);
clampTransform.position = clampPosition;
}
// 夹子不跟随滑片移动,保持原位
// 触发位置改变事件
float normalizedPosition = _currentAngle / slideAngleRange;
@@ -273,37 +191,178 @@ namespace AibisDream.MiniGame.HuoShan
#endregion
#region
#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 (_plasmaMaterials == null || _plasmaMaterials.Length == 0) return;
if (plasmaParticleSystem == null || ringCenter == null || _particles == null) return;
// 计算滑片当前位置占整个圆环的比例
float progress = _currentAngle / slideAngleRange;
float glowRange = progress * glowIntensity;
float progress = Mathf.Clamp01(_currentAngle / slideAngleRange);
// 更新每个等离子体对象的发光强度
for (int i = 0; i < _plasmaMaterials.Length; i++)
// 进度为 0 时隐藏粒子
if (progress <= 0.001f)
{
if (_plasmaMaterials[i] != null)
{
// 根据等离子体对象的索引计算其位置
float plasmaProgress = (float)i / _plasmaMaterials.Length;
// 只有在滑片经过的位置才发光
if (plasmaProgress <= progress)
{
float localGlow = Mathf.Lerp(0f, glowIntensity, 1f - (progress - plasmaProgress));
_plasmaMaterials[i].SetFloat(_glowPropertyID, localGlow);
_plasmaMaterials[i].SetColor("_Color", glowColor);
}
else
{
_plasmaMaterials[i].SetFloat(_glowPropertyID, 0f);
}
}
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
@@ -396,11 +455,6 @@ namespace AibisDream.MiniGame.HuoShan
return _currentAngle / slideAngleRange;
}
/// <summary>
/// 是否正在拖拽
/// </summary>
public bool IsDragging => _isDragging;
/// <summary>
/// 重置滑片位置
/// </summary>
@@ -414,7 +468,6 @@ namespace AibisDream.MiniGame.HuoShan
_targetAngle = 0f;
_currentAngle = 0f;
_isDragging = false;
}
/// <summary>