From 794b1f8d38850371ce4c0dafc6ac2d3792b98399 Mon Sep 17 00:00:00 2001 From: bottlefish <781230111@qq.com> Date: Thu, 12 Feb 2026 22:52:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(sales):=20SliderController=20=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20ParticleSystem=20=E5=AE=9E=E7=8E=B0=E7=AD=89?= =?UTF-8?q?=E7=A6=BB=E5=AD=90=E4=BD=93=E5=BC=A7=E7=BA=BF=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=8B=96=E6=8B=BD=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- Assets/Resources/Materials/PlasmaParticle.mat | 85 ++++ .../Materials/PlasmaParticle.mat.meta | 8 + .../HuoShan/SalesSystem/SliderController.cs | 415 ++++++++++-------- 3 files changed, 327 insertions(+), 181 deletions(-) create mode 100644 Assets/Resources/Materials/PlasmaParticle.mat create mode 100644 Assets/Resources/Materials/PlasmaParticle.mat.meta diff --git a/Assets/Resources/Materials/PlasmaParticle.mat b/Assets/Resources/Materials/PlasmaParticle.mat new file mode 100644 index 000000000..04da1b3af --- /dev/null +++ b/Assets/Resources/Materials/PlasmaParticle.mat @@ -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: [] diff --git a/Assets/Resources/Materials/PlasmaParticle.mat.meta b/Assets/Resources/Materials/PlasmaParticle.mat.meta new file mode 100644 index 000000000..3f422fc8c --- /dev/null +++ b/Assets/Resources/Materials/PlasmaParticle.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3a322f56de44a941b232cf5730be4be +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs index 15e128609..066bf1b47 100644 --- a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs +++ b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs @@ -1,3 +1,4 @@ +using AibisDream; using UnityEngine; using UnityEngine.Events; using DG.Tweening; @@ -7,9 +8,9 @@ namespace AibisDream.MiniGame.HuoShan /// /// 滑片控制器 - 实现滑片沿圆环滑动以及与波形联动 /// 功能: - /// - 滑片拖拽沿外环滑动 + /// - 仅通过Yarn指令控制移动,不接受玩家直接交互 /// - 滑片角度/弧度驱动波形参数 - /// - 等离子体发光段显示 + /// - 等离子体发光段:从起始位置到当前位置显示等离子体进度指示 /// 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 onSliderPositionChanged; - [Tooltip("滑片开始拖拽时触发")] - public UnityEvent onSliderStartDrag; + [Header("调试测试")] + [Tooltip("勾选后,拖拽下方滑块可直接测试滑片与弧线(Play 模式下生效)")] + [SerializeField] private bool useTestSlider; - [Tooltip("滑片释放时触发")] - public UnityEvent 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(); - 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(); + 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 (_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; } - /// - /// 是否正在拖拽 - /// - public bool IsDragging => _isDragging; - /// /// 重置滑片位置 /// @@ -414,7 +468,6 @@ namespace AibisDream.MiniGame.HuoShan _targetAngle = 0f; _currentAngle = 0f; - _isDragging = false; } ///