diff --git a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/CrankController.cs b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/CrankController.cs
new file mode 100644
index 000000000..3c6578b6f
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/CrankController.cs
@@ -0,0 +1,296 @@
+using UnityEngine;
+using UnityEngine.Events;
+using DG.Tweening;
+using System.Collections;
+
+namespace AibisDream.MiniGame.HuoShan
+{
+ ///
+ /// 发条控制器 - 实现发条的拖拽旋转交互
+ /// 功能:拖拽旋转,最大260°,缓慢回位,触发事件
+ ///
+ public class CrankController : MonoBehaviour
+ {
+ [Header("发条配置")]
+ [Tooltip("最大旋转角度(度)")]
+ [SerializeField] private float maxRotationAngle = 260f;
+
+ [Tooltip("回位速度(度/秒)")]
+ [SerializeField] private float returnSpeed = 45f;
+
+ [Tooltip("触发事件的最小角度(度)")]
+ [SerializeField] private float triggerAngle = 240f;
+
+ [Tooltip("拖拽阻力,值越大越难拖动")]
+ [SerializeField] private float dragResistance = 0.1f;
+
+ [Tooltip("是否自动返回初始位置")]
+ [SerializeField] private bool autoReturn = true;
+
+ [Header("事件" )]
+ [Tooltip("发条达到最大角度并释放时触发")]
+ public UnityEvent onCrankComplete;
+
+ [Tooltip("发条旋转时触发(参数:当前角度0-1)")]
+ public UnityEvent onCrankRotate;
+
+ [Tooltip("发条开始拖拽时触发")]
+ public UnityEvent onCrankStartDrag;
+
+ [Header("视觉反馈")]
+ [Tooltip("发条旋转的中心Transform")]
+ [SerializeField] private Transform crankTransform;
+
+ [Tooltip("拖拽时的缩放效果")]
+ [SerializeField] private float dragScale = 1.05f;
+
+ [Tooltip("缩放动画时长")]
+ [SerializeField] private float scaleDuration = 0.1f;
+
+ private Vector3 _dragStartPosition;
+ private float _currentRotation = 0f;
+ private bool _isDragging = false;
+ private bool _hasTriggered = false;
+ private Camera _mainCamera;
+ private Vector3 _initialScale;
+ private Tweener _scaleTween;
+ private Tweener _returnTween;
+
+ #region 生命周期
+
+ private void Awake()
+ {
+ _mainCamera = Camera.main;
+ if (crankTransform != null)
+ {
+ _initialScale = crankTransform.localScale;
+ }
+ else
+ {
+ _initialScale = transform.localScale;
+ crankTransform = transform;
+ }
+ }
+
+ private void OnEnable()
+ {
+ ResetCrank();
+ }
+
+ private void Update()
+ {
+ HandleMouseInput();
+ HandleReturn();
+ }
+
+ #endregion
+
+ #region 输入处理
+
+ private void HandleMouseInput()
+ {
+ 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;
+ _hasTriggered = false;
+
+ if (_returnTween != null)
+ {
+ _returnTween.Kill();
+ _returnTween = null;
+ }
+
+ // 拖拽缩放效果
+ if (_scaleTween != null) _scaleTween.Kill();
+ _scaleTween = crankTransform.DOScale(_initialScale * dragScale, scaleDuration)
+ .SetEase(Ease.OutQuad);
+
+ onCrankStartDrag?.Invoke();
+ }
+
+ private void ContinueDrag()
+ {
+ // 获取鼠标相对于中心的角度
+ Vector3 screenCenter = _mainCamera.WorldToScreenPoint(crankTransform.position);
+ Vector3 currentMousePos = Input.mousePosition;
+
+ // 计算角度
+ float angle = Mathf.Atan2(currentMousePos.y - screenCenter.y, currentMousePos.x - screenCenter.x) * Mathf.Rad2Deg;
+
+ // 将角度转换为0-360范围
+ if (angle < 0) angle += 360f;
+
+ // 限制最大旋转角度
+ angle = Mathf.Clamp(angle, 0f, maxRotationAngle);
+
+ // 应用阻力效果
+ float targetRotation = angle * (1f - dragResistance);
+ _currentRotation = Mathf.Lerp(_currentRotation, targetRotation, Time.deltaTime * 10f);
+
+ // 更新旋转
+ crankTransform.localRotation = Quaternion.Euler(0f, 0f, -_currentRotation);
+
+ // 触发旋转事件
+ float normalizedRotation = _currentRotation / maxRotationAngle;
+ onCrankRotate?.Invoke(normalizedRotation);
+
+ // 检查是否达到触发角度
+ if (_currentRotation >= triggerAngle && !_hasTriggered)
+ {
+ _hasTriggered = true;
+ // 触发完成事件将在释放时调用
+ }
+ }
+
+ private void EndDrag()
+ {
+ _isDragging = false;
+
+ // 恢复缩放
+ if (_scaleTween != null) _scaleTween.Kill();
+ _scaleTween = crankTransform.DOScale(_initialScale, scaleDuration)
+ .SetEase(Ease.OutQuad);
+
+ // 触发完成事件
+ if (_hasTriggered && _currentRotation >= triggerAngle)
+ {
+ onCrankComplete?.Invoke();
+ }
+ }
+
+ #endregion
+
+ #region 回位逻辑
+
+ private void HandleReturn()
+ {
+ if (!_isDragging && autoReturn && _currentRotation > 0f)
+ {
+ if (_returnTween == null)
+ {
+ float returnDuration = _currentRotation / returnSpeed;
+ _returnTween = DOTween.To(
+ () => _currentRotation,
+ value => {
+ _currentRotation = value;
+ crankTransform.localRotation = Quaternion.Euler(0f, 0f, -_currentRotation);
+ float normalizedRotation = _currentRotation / maxRotationAngle;
+ onCrankRotate?.Invoke(normalizedRotation);
+ },
+ 0f,
+ returnDuration
+ ).SetEase(Ease.OutCubic).OnComplete(() => {
+ _currentRotation = 0f;
+ _returnTween = null;
+ });
+ }
+ }
+ }
+
+ #endregion
+
+ #region 公共方法
+
+ public void ResetCrank()
+ {
+ if (_returnTween != null)
+ {
+ _returnTween.Kill();
+ _returnTween = null;
+ }
+
+ _isDragging = false;
+ _hasTriggered = false;
+ _currentRotation = 0f;
+
+ if (crankTransform != null)
+ {
+ crankTransform.localRotation = Quaternion.identity;
+ crankTransform.localScale = _initialScale;
+ }
+ }
+
+ ///
+ /// 手动旋转到指定角度(用于动画或程序化控制)
+ ///
+ public void RotateTo(float angle, float duration = 0.5f)
+ {
+ angle = Mathf.Clamp(angle, 0f, maxRotationAngle);
+
+ if (_returnTween != null)
+ {
+ _returnTween.Kill();
+ _returnTween = null;
+ }
+
+ DOTween.To(
+ () => _currentRotation,
+ value => {
+ _currentRotation = value;
+ crankTransform.localRotation = Quaternion.Euler(0f, 0f, -_currentRotation);
+ float normalizedRotation = _currentRotation / maxRotationAngle;
+ onCrankRotate?.Invoke(normalizedRotation);
+ },
+ angle,
+ duration
+ ).SetEase(Ease.OutCubic);
+ }
+
+ ///
+ /// 获取当前旋转角度(0-1标准化)
+ ///
+ public float GetNormalizedRotation()
+ {
+ return _currentRotation / maxRotationAngle;
+ }
+
+ ///
+ /// 获取当前旋转角度(度)
+ ///
+ public float GetCurrentAngle()
+ {
+ return _currentRotation;
+ }
+
+ ///
+ /// 是否正在拖拽中
+ ///
+ public bool IsDragging => _isDragging;
+
+ ///
+ /// 是否已达到触发角度
+ ///
+ public bool HasTriggered => _hasTriggered;
+
+ #endregion
+ }
+}
diff --git a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/KnobController.cs b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/KnobController.cs
new file mode 100644
index 000000000..96d7fcf93
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/KnobController.cs
@@ -0,0 +1,516 @@
+using UnityEngine;
+using UnityEngine.Events;
+using DG.Tweening;
+using Yarn.Unity;
+
+namespace AibisDream.MiniGame.HuoShan
+{
+ ///
+ /// 处理器旋钮控制器 - 实现旋钮的拖拽调节和失败逻辑
+ /// 功能:
+ /// - 旋钮拖拽旋转(0-360度)
+ /// - 角度映射到功率值(0-100%)
+ /// - 降功率失败判定
+ /// - 信息面板显示失败结果
+ ///
+ public class KnobController : MonoBehaviour
+ {
+ [System.Serializable]
+ public class PowerLevel
+ {
+ public float angle; // 旋钮角度
+ public float power; // 对应的功率百分比
+ public Color indicatorColor; // 指示器颜色
+ }
+
+ [Header("旋钮配置")]
+ [Tooltip("旋钮旋转的Transform")]
+ [SerializeField] private Transform knobTransform;
+
+ [Tooltip("功率指示器Transform(红色指示器)")]
+ [SerializeField] private Transform powerIndicator;
+
+ [Tooltip("旋钮刻度区间")]
+ [SerializeField] private PowerLevel[] powerLevels = new PowerLevel[]
+ {
+ new PowerLevel { angle = 0f, power = 100f, indicatorColor = Color.red },
+ new PowerLevel { angle = 90f, power = 75f, indicatorColor = new Color(1f, 0.5f, 0f) },
+ newPowerLevel = new PowerLevel { angle = 180f, power = 50f, indicatorColor = Color.yellow },
+ new PowerLevel { angle = 270f, power = 25f, indicatorColor = Color.green },
+ new PowerLevel { angle = 360f, power = 0f, indicatorColor = Color.gray }
+ };
+
+ [Tooltip("初始功率(%)")]
+ [SerializeField] private float initialPower = 100f;
+
+ [Tooltip("最小可接受功率(%)")]
+ [SerializeField] private float minAcceptablePower = 70f;
+
+ [Tooltip("拖拽阻力")]
+ [SerializeField] private float dragResistance = 0.1f;
+
+ [Tooltip("旋钮旋转动画时长")]
+ [SerializeField] private float rotationDuration = 0.2f;
+
+ [Header("氖光灯配置")]
+ [Tooltip("氖光灯Transform")]
+ [SerializeField] private Transform neonLight;
+
+ [Tooltip("氖光灯材质属性名")]
+ [SerializeField] private string neonProperty = "_EmissionColor";
+
+ [Tooltip("氖光灯最大闪烁强度")]
+ [SerializeField] private float maxNeonFlicker = 2f;
+
+ [Tooltip("氖光灯闪烁速度")]
+ [SerializeField] private float neonFlickerSpeed = 5f;
+
+ [Header("信息面板")]
+ [Tooltip("信息面板GameObject")]
+ [SerializeField] private GameObject infoPanel;
+
+ [Tooltip("信息面板文本组件")]
+ [SerializeField] private UnityEngine.UI.Text infoText;
+
+ [Tooltip("失败信息")]
+ [SerializeField] private string failMessage = "表达模块拒绝了未被处理的波形";
+
+ [Tooltip("面板显示动画时长")]
+ [SerializeField] private float panelShowDuration = 0.3f;
+
+ [Tooltip("面板隐藏动画时长")]
+ [SerializeField] private float panelHideDuration = 0.2f;
+
+ [Header("事件")]
+ [Tooltip("功率调节开始时触发")]
+ public UnityEvent onPowerAdjustmentStart;
+
+ [Tooltip("功率改变时触发(参数:当前功率%)")]
+ public UnityEvent onPowerChanged;
+
+ [Tooltip("功率调节失败时触发")]
+ public UnityEvent onPowerAdjustmentFail;
+
+ [Tooltip("功率调节成功时触发")]
+ public UnityEvent onPowerAdjustmentSuccess;
+
+ private bool _isDragging = false;
+ private float _currentPower = 100f;
+ private float _currentAngle = 0f;
+ private float _targetAngle = 0f;
+ private Camera _mainCamera;
+ private Vector3 _dragStartPosition;
+ private float _dragStartAngle;
+ private Material _neonMaterial;
+ private float _neonIntensity;
+ private Tweener _knobTween;
+ private Tweener _neonTween;
+ private bool _hasFailed = false;
+ private bool _hasSucceeded = false;
+
+ #region 生命周期
+
+ private void Awake()
+ {
+ _mainCamera = Camera.main;
+
+ if (knobTransform == null)
+ {
+ knobTransform = transform;
+ }
+
+ // 获取氖光灯材质
+ if (neonLight != null)
+ {
+ Renderer renderer = neonLight.GetComponent();
+ if (renderer != null)
+ {
+ _neonMaterial = renderer.material;
+ }
+ }
+
+ // 初始状态隐藏信息面板
+ if (infoPanel != null)
+ {
+ infoPanel.SetActive(false);
+ }
+ }
+
+ private void Start()
+ {
+ // 初始化到最大功率
+ SetPower(initialPower, false);
+ }
+
+ private void Update()
+ {
+ HandleMouseInput();
+ UpdateKnobRotation();
+ UpdateNeonLight();
+ }
+
+ #endregion
+
+ #region 输入处理
+
+ private void HandleMouseInput()
+ {
+ 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()
+ {
+ if (_hasFailed || _hasSucceeded) return;
+
+ _isDragging = true;
+ _dragStartPosition = Input.mousePosition;
+ _dragStartAngle = GetMouseAngle();
+
+ if (_knobTween != null)
+ {
+ _knobTween.Kill();
+ _knobTween = null;
+ }
+
+ onPowerAdjustmentStart?.Invoke();
+ }
+
+ private void ContinueDrag()
+ {
+ if (_hasFailed || _hasSucceeded) return;
+
+ // 获取鼠标相对于中心的角度
+ float mouseAngle = GetMouseAngle();
+ float deltaAngle = mouseAngle - _dragStartAngle;
+
+ // 将角度转换为旋钮角度(0-360)
+ _targetAngle = Mathf.Clamp(deltaAngle, 0f, 360f);
+
+ // 计算当前功率
+ float power = AngleToPower(_targetAngle);
+ SetPower(power);
+ }
+
+ private void EndDrag()
+ {
+ _isDragging = false;
+ }
+
+ private float GetMouseAngle()
+ {
+ Vector3 mouseWorldPos = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
+ Vector3 direction = mouseWorldPos - knobTransform.position;
+ float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
+ if (angle < 0) angle += 360f;
+ return angle;
+ }
+
+ #endregion
+
+ #region 功率控制
+
+ private float AngleToPower(float angle)
+ {
+ // 根据powerLevels计算功率
+ if (powerLevels == null || powerLevels.Length == 0)
+ {
+ return 100f - (angle / 360f) * 100f;
+ }
+
+ // 找到最近的两个功率等级进行插值
+ PowerLevel lowerLevel = powerLevels[0];
+ PowerLevel upperLevel = powerLevels[powerLevels.Length - 1];
+
+ for (int i = 0; i < powerLevels.Length - 1; i++)
+ {
+ if (angle >= powerLevels[i].angle && angle <= powerLevels[i + 1].angle)
+ {
+ lowerLevel = powerLevels[i];
+ upperLevel = powerLevels[i + 1];
+ break;
+ }
+ }
+
+ // 在相邻等级之间插值
+ float t = (angle - lowerLevel.angle) / (upperLevel.angle - lowerLevel.angle);
+ return Mathf.Lerp(lowerLevel.power, upperLevel.power, t);
+ }
+
+ private float PowerToAngle(float power)
+ {
+ // 根据powerLevels计算角度
+ if (powerLevels == null || powerLevels.Length == 0)
+ {
+ return (1f - power / 100f) * 360f;
+ }
+
+ // 找到最近的两个功率等级进行插值
+ PowerLevel lowerLevel = powerLevels[0];
+ PowerLevel upperLevel = powerLevels[powerLevels.Length - 1];
+
+ for (int i = 0; i < powerLevels.Length - 1; i++)
+ {
+ if (power >= powerLevels[i].power && power <= powerLevels[i + 1].power)
+ {
+ lowerLevel = powerLevels[i];
+ upperLevel = powerLevels[i + 1];
+ break;
+ }
+ }
+
+ // 在相邻等级之间插值
+ float t = (power - lowerLevel.power) / (upperLevel.power - lowerLevel.power);
+ return Mathf.Lerp(lowerLevel.angle, upperLevel.angle, 1f - t);
+ }
+
+ private void SetPower(float power, bool animate = true)
+ {
+ power = Mathf.Clamp(power, 0f, 100f);
+ _currentPower = power;
+ _targetAngle = PowerToAngle(power);
+
+ // 检查失败条件
+ if (power < minAcceptablePower && !_hasFailed && !_hasSucceeded)
+ {
+ TriggerFailure();
+ }
+
+ // 触发功率改变事件
+ onPowerChanged?.Invoke(power);
+
+ Debug.Log($"KnobController: 功率调整为{power:F1}%");
+ }
+
+ ///
+ /// 功率调节失败
+ ///
+ private void TriggerFailure()
+ {
+ _hasFailed = true;
+
+ // 显示信息面板
+ ShowInfoPanel(failMessage);
+
+ // 触发失败事件
+ onPowerAdjustmentFail?.Invoke(_currentPower);
+
+ Debug.Log($"KnobController: 功率调节失败 - 当前功率{_currentPower:F1}%低于最低值{minAcceptablePower}%");
+
+ // 调用Yarn命令
+ StartCoroutine(ExecuteFailCommands());
+ }
+
+ #endregion
+
+ #region 信息面板
+
+ private void ShowInfoPanel(string message)
+ {
+ if (infoPanel == null || infoText == null) return;
+
+ infoText.text = message;
+ infoPanel.SetActive(true);
+
+ // 面板从上方滑入
+ RectTransform rectTransform = infoPanel.GetComponent();
+ if (rectTransform != null)
+ {
+ Vector3 originalPosition = rectTransform.anchoredPosition;
+ Vector3 startPosition = originalPosition + Vector3.up * 100f;
+
+ rectTransform.anchoredPosition = startPosition;
+ rectTransform.DOAnchorPos(originalPosition, panelShowDuration)
+ .SetEase(Ease.OutCubic);
+ }
+ }
+
+ private void HideInfoPanel()
+ {
+ if (infoPanel == null) return;
+
+ RectTransform rectTransform = infoPanel.GetComponent();
+ if (rectTransform != null)
+ {
+ Vector3 endPosition = rectTransform.anchoredPosition + Vector3.up * 100f;
+
+ rectTransform.DOAnchorPos(endPosition, panelHideDuration)
+ .SetEase(Ease.InCubic)
+ .OnComplete(() => infoPanel.SetActive(false));
+ }
+ else
+ {
+ infoPanel.SetActive(false);
+ }
+ }
+
+ #endregion
+
+ #region 旋钮和灯光更新
+
+ private void UpdateKnobRotation()
+ {
+ // 平滑旋转到目标角度
+ _currentAngle = Mathf.Lerp(_currentAngle, _targetAngle, Time.deltaTime * 5f);
+
+ // 应用阻力效果
+ float adjustedAngle = _currentAngle * (1f - dragResistance);
+
+ // 更新旋钮旋转
+ knobTransform.localRotation = Quaternion.Euler(0f, 0f, -adjustedAngle);
+ }
+
+ private void UpdateNeonLight()
+ {
+ if (_neonMaterial == null) return;
+
+ // 根据功率计算发光强度
+ float powerRatio = _currentPower / 100f;
+ float targetIntensity = maxNeonFlicker * powerRatio;
+
+ // 闪烁效果
+ float flicker = Mathf.Sin(Time.time * neonFlickerSpeed) * 0.2f + 0.8f;
+ _neonIntensity = targetIntensity * flicker;
+
+ // 更新材质发光
+ Color emissionColor = Color.white * _neonIntensity;
+ _neonMaterial.SetColor(neonProperty, emissionColor);
+ }
+
+ #endregion
+
+ #region Yarn命令
+
+ private IEnumerator ExecuteFailCommands()
+ {
+ // 等待一段时间后执行Yarn跳转
+ yield return new WaitForSeconds(2f);
+
+ // 这里可以执行Yarn命令,比如跳转到下一个节点
+ // 具体实现需要根据游戏的Yarn流程来定制
+ Debug.Log("KnobController: 执行失败后的Yarn命令");
+
+ // 示例:调用Yarn命令
+ // YarnSpinnerIntegration.Instance.RunCommand("jump", "下一个节点");
+ }
+
+ ///
+ /// Yarn命令:尝试调节功率
+ ///
+ [YarnCommand("adjust_power")]
+ public static IEnumerator AdjustPower(float targetPower)
+ {
+ var controller = FindObjectOfType();
+ if (controller != null)
+ {
+ controller.SetPower(targetPower);
+ yield return new WaitForSeconds(1f);
+ }
+ }
+
+ ///
+ /// Yarn命令:重置旋钮
+ ///
+ [YarnCommand("reset_knob")]
+ public static void ResetKnob()
+ {
+ var controller = FindObjectOfType();
+ if (controller != null)
+ {
+ controller.Reset();
+ }
+ }
+
+ #endregion
+
+ #region 公共方法
+
+ ///
+ /// 获取当前功率
+ ///
+ public float GetCurrentPower()
+ {
+ return _currentPower;
+ }
+
+ ///
+ /// 获取是否失败
+ ///
+ public bool HasFailed()
+ {
+ return _hasFailed;
+ }
+
+ ///
+ /// 重置旋钮状态
+ ///
+ public void Reset()
+ {
+ _hasFailed = false;
+ _hasSucceeded = false;
+
+ if (_knobTween != null)
+ {
+ _knobTween.Kill();
+ _knobTween = null;
+ }
+
+ if (_neonTween != null)
+ {
+ _neonTween.Kill();
+ _neonTween = null;
+ }
+
+ // 重置到初始功率
+ SetPower(initialPower);
+
+ // 隐藏信息面板
+ HideInfoPanel();
+
+ Debug.Log("KnobController: 重置状态");
+ }
+
+ ///
+ /// 设置最小可接受功率
+ ///
+ public void SetMinAcceptablePower(float minPower)
+ {
+ minAcceptablePower = minPower;
+ }
+
+ ///
+ /// 强制触发成功(调试用)
+ ///
+ public void ForceSuccess()
+ {
+ _hasSucceeded = true;
+ onPowerAdjustmentSuccess?.Invoke(_currentPower);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/ProcessorLightController.cs b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/ProcessorLightController.cs
new file mode 100644
index 000000000..d1eac6ac6
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/ProcessorLightController.cs
@@ -0,0 +1,468 @@
+using UnityEngine;
+using UnityEngine.Events;
+using System.Collections.Generic;
+using DG.Tweening;
+
+namespace AibisDream.MiniGame.HuoShan
+{
+ ///
+ /// 处理器灯光控制器 - 管理多个处理器灯的显示
+ /// 功能:
+ /// - 根据滑片位置或索引控制特定处理器灯亮起
+ /// - 提供灯光明暗、闪烁、颜色变化效果
+ /// - 支持顺序激活多个处理器
+ ///
+ public class ProcessorLightController : MonoBehaviour
+ {
+ [System.Serializable]
+ public class ProcessorLightConfig
+ {
+ public GameObject lightObject;
+ public Light lightComponent;
+ public Renderer lightRenderer;
+ public Color normalColor = Color.gray;
+ public Color activeColor = new Color(0.3f, 0.9f, 0.55f);
+ public float intensity = 1f;
+ public float blinkSpeed = 1f;
+ public float fadeInDuration = 0.3f;
+ public string emissionProperty = "_EmissionColor";
+ public int segmentIndex = 0; // 对应的段索引
+ public float segmentStartAngle = 0f; // 该段的开始角度
+ public float segmentEndAngle = 30f; // 该段的结束角度
+ }
+
+ [Header("处理器配置")]
+ [Tooltip("处理器灯光配置列表")]
+ [SerializeField] private List processorLights = new List();
+
+ [Tooltip("默认激活持续时间(秒)")]
+ [SerializeField] private float defaultActiveDuration = 2f;
+
+ [Tooltip("灯光强度曲线(用于闪烁效果)")]
+ [SerializeField] private AnimationCurve intensityCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
+
+ [Header("全局设置")]
+ [Tooltip("等离子体发光影响所有处理器灯")]
+ [SerializeField] private bool plasmaAffectsAll = false;
+
+ [Tooltip("等离子体发光强度")]
+ [SerializeField] private float plasmaGlowIntensity = 0.5f;
+
+ [Header("滑片联动")]
+ [Tooltip("滑片控制器引用(用于监听位置变化)")]
+ [SerializeField] private SliderController sliderController;
+
+ [Tooltip("滑片位置变化时自动更新处理器灯")]
+ [SerializeField] private bool autoUpdateFromSlider = true;
+
+ [Tooltip("滑片弧度到处理器索引的映射表,按角度排序")]
+ [SerializeField] private AnimationCurve angleToProcessorCurve = new AnimationCurve(
+ new Keyframe(0f, 0f),
+ new Keyframe(1f, 11f)
+ );
+
+ [Header("事件")]
+ [Tooltip("处理器灯激活时触发")]
+ public UnityEvent onProcessorActivated;
+
+ [Tooltip("所有处理器灯关闭时触发")]
+ public UnityEvent onAllLightsOff;
+
+ private Dictionary _processorMap = new Dictionary();
+ private List _activeTweens = new List();
+ private bool _isInitialized = false;
+ private int _lastActiveProcessor = -1;
+
+ #region 生命周期
+
+ private void Awake()
+ {
+ InitializeProcessorMap();
+ }
+
+ private void OnEnable()
+ {
+ if (sliderController != null && autoUpdateFromSlider)
+ {
+ sliderController.onSliderPositionChanged.AddListener(OnSliderPositionChanged);
+ }
+ }
+
+ private void OnDisable()
+ {
+ if (sliderController != null)
+ {
+ sliderController.onSliderPositionChanged.RemoveListener(OnSliderPositionChanged);
+ }
+
+ // 清理所有Tweens
+ foreach (var tween in _activeTweens)
+ {
+ if (tween != null && tween.IsActive())
+ {
+ tween.Kill();
+ }
+ }
+ _activeTweens.Clear();
+ }
+
+ #endregion
+
+ #region 初始化
+
+ private void InitializeProcessorMap()
+ {
+ _processorMap.Clear();
+
+ foreach (var config in processorLights)
+ {
+ if (config.lightObject != null)
+ {
+ // 查找灯光组件
+ if (config.lightComponent == null)
+ {
+ config.lightComponent = config.lightObject.GetComponent();
+ }
+
+ // 查找渲染器
+ if (config.lightRenderer == null)
+ {
+ config.lightRenderer = config.lightObject.GetComponent();
+ }
+
+ // 初始化为关闭状态
+ SetLightState(config, false, 0f);
+
+ _processorMap[config.segmentIndex] = config;
+ }
+ }
+
+ _isInitialized = true;
+ Debug.Log($"ProcessorLightController: 初始化完成,共{processorLights.Count}个处理器灯");
+ }
+
+ #endregion
+
+ #region 滑片位置监听
+
+ private void OnSliderPositionChanged(float normalizedPosition)
+ {
+ if (!autoUpdateFromSlider) return;
+
+ // 映射滑片位置到处理器索引
+ int processorIndex = GetProcessorIndexFromSlider(normalizedPosition);
+
+ // 如果到达新的处理器段,激活对应的灯
+ if (processorIndex != _lastActiveProcessor)
+ {
+ ActivateProcessor(processorIndex);
+ _lastActiveProcessor = processorIndex;
+ }
+ }
+
+ ///
+ /// 将滑片标准化位置(0-1)映射到处理器索引(0-processorCount-1)
+ ///
+ private int GetProcessorIndexFromSlider(float normalizedPosition)
+ {
+ // 使用AnimationCurve进行映射
+ float processorValue = angleToProcessorCurve.Evaluate(normalizedPosition);
+ return Mathf.RoundToInt(processorValue);
+ }
+
+ #endregion
+
+ #region 处理器灯光控制
+
+ ///
+ /// 根据角度激活对应的处理器灯
+ ///
+ public void ActivateProcessorByAngle(float angle)
+ {
+ float normalizedAngle = angle / 360f;
+ int processorIndex = GetProcessorIndexFromSlider(normalizedAngle);
+ ActivateProcessor(processorIndex);
+ }
+
+ ///
+ /// 激活指定索引的处理器灯
+ ///
+ public void ActivateProcessor(int processorIndex, float duration = -1f)
+ {
+ if (!_isInitialized)
+ {
+ InitializeProcessorMap();
+ }
+
+ if (duration < 0)
+ {
+ duration = defaultActiveDuration;
+ }
+
+ // 先关闭所有灯
+ DeactivateAllProcessors(0.2f);
+
+ // 检查索引是否有效
+ if (processorIndex < 0 || processorIndex >= processorLights.Count)
+ {
+ Debug.LogWarning($"ProcessorLightController: 处理器索引{processorIndex}超出范围");
+ return;
+ }
+
+ // 获取配置
+ ProcessorLightConfig config = null;
+ foreach (var kvp in _processorMap)
+ {
+ if (kvp.Key == processorIndex)
+ {
+ config = kvp.Value;
+ break;
+ }
+ }
+
+ if (config != null)
+ {
+ // 激活灯光
+ SetLightState(config, true, duration);
+
+ // 触发事件
+ onProcessorActivated?.Invoke(processorIndex);
+
+ Debug.Log($"ProcessorLightController: 激活处理器{processorIndex}");
+ }
+ else
+ {
+ Debug.LogWarning($"ProcessorLightController: 找不到处理器索引{processorIndex}的配置");
+ }
+ }
+
+ ///
+ /// 关闭所有处理器灯
+ ///
+ public void DeactivateAllProcessors(float fadeOutDuration = 0.3f)
+ {
+ foreach (var config in processorLights)
+ {
+ SetLightState(config, false, fadeOutDuration);
+ }
+
+ _lastActiveProcessor = -1;
+ onAllLightsOff?.Invoke();
+ }
+
+ ///
+ /// 显示异常段的处理器灯(闪烁效果)
+ ///
+ public void ShowAnomalyProcessor(int processorIndex)
+ {
+ ProcessorLightConfig config = FindProcessorConfig(processorIndex);
+ if (config != null)
+ {
+ // 闪烁效果
+ FlashLight(config, Color.red, 2f, 3);
+ }
+ }
+
+ ///
+ /// 设置灯光状态
+ ///
+ private void SetLightState(ProcessorLightConfig config, bool isActive, float transitionDuration)
+ {
+ if (config.lightObject == null) return;
+
+ // 设置灯光组件
+ if (config.lightComponent != null)
+ {
+ float targetIntensity = isActive ? config.intensity : 0f;
+
+ // 使用Tween实现平滑过渡
+ Tweener intensityTween = DOTween.To(
+ () => config.lightComponent.intensity,
+ value => config.lightComponent.intensity = value,
+ targetIntensity,
+ transitionDuration
+ ).SetEase(Ease.OutQuad);
+
+ _activeTweens.Add(intensityTween);
+ }
+
+ // 设置渲染器发光
+ if (config.lightRenderer != null)
+ {
+ Color targetColor = isActive ? config.activeColor : config.normalColor;
+
+ // 对于使用Emission的材质
+ Material material = config.lightRenderer.material;
+ if (!string.IsNullOrEmpty(config.emissionProperty))
+ {
+ int propertyID = Shader.PropertyToID(config.emissionProperty);
+ Tweener colorTween = DOTween.To(
+ () => material.GetColor(propertyID),
+ value => material.SetColor(propertyID, value),
+ targetColor,
+ transitionDuration
+ ).SetEase(Ease.OutQuad);
+
+ _activeTweens.Add(colorTween);
+ }
+
+ // 设置基础颜色
+ Tweener baseColorTween = DOTween.To(
+ () => config.lightRenderer.material.color,
+ value => config.lightRenderer.material.color = value,
+ targetColor,
+ transitionDuration
+ ).SetEase(Ease.OutQuad);
+
+ _activeTweens.Add(baseColorTween);
+ }
+
+ // 更新GameObject状态
+ config.lightObject.SetActive(true);
+ }
+
+ ///
+ /// 灯光闪烁效果
+ ///
+ private void FlashLight(ProcessorLightConfig config, Color flashColor, float duration, int flashCount)
+ {
+ // 先停止之前的动画
+ config.lightObject.GetComponent()?.material?.DOComplete();
+
+ // 执行闪烁
+ if (config.lightComponent != null)
+ {
+ Sequence flashSequence = DOTween.Sequence();
+
+ for (int i = 0; i < flashCount; i++)
+ {
+ flashSequence.Append(config.lightComponent.DOIntensity(config.intensity * 2f, duration / (flashCount * 2)));
+ flashSequence.Append(config.lightComponent.DOIntensity(config.intensity, duration / (flashCount * 2)));
+ }
+
+ _activeTweens.Add(flashSequence);
+ }
+
+ if (config.lightRenderer != null)
+ {
+ Color originalColor = config.lightRenderer.material.color;
+ Sequence colorSequence = DOTween.Sequence();
+
+ for (int i = 0; i < flashCount; i++)
+ {
+ colorSequence.Append(config.lightRenderer.material.DOColor(flashColor, duration / (flashCount * 2)));
+ colorSequence.Append(config.lightRenderer.material.DOColor(originalColor, duration / (flashCount * 2)));
+ }
+
+ _activeTweens.Add(colorSequence);
+ }
+ }
+
+ ///
+ /// 查找处理器配置
+ ///
+ private ProcessorLightConfig FindProcessorConfig(int processorIndex)
+ {
+ if (_processorMap.TryGetValue(processorIndex, out var config))
+ {
+ return config;
+ }
+
+ foreach (var lightConfig in processorLights)
+ {
+ if (lightConfig.segmentIndex == processorIndex)
+ {
+ return lightConfig;
+ }
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ #region 映射配置
+
+ ///
+ /// 更新角度到处理器的映射曲线
+ ///
+ public void UpdateAngleToProcessorCurve(AnimationCurve curve)
+ {
+ angleToProcessorCurve = curve;
+ }
+
+ ///
+ /// 根据滑片控制器自动配置映射
+ ///
+ public void AutoConfigureMapping()
+ {
+ if (sliderController == null) return;
+
+ int segmentCount = sliderController.GetCurrentSegment();
+ if (segmentCount <= 0) return;
+
+ // 创建线性映射
+ Keyframe[] keys = new Keyframe[processorLights.Count];
+ float step = 1f / (processorLights.Count - 1);
+
+ for (int i = 0; i < processorLights.Count; i++)
+ {
+ keys[i] = new Keyframe(i * step, i);
+ }
+
+ angleToProcessorCurve = new AnimationCurve(keys);
+ }
+
+ #endregion
+
+ #region 公共方法
+
+ ///
+ /// 获取活动中的处理器索引
+ ///
+ public int GetActiveProcessor()
+ {
+ return _lastActiveProcessor;
+ }
+
+ ///
+ /// 获取处理器数量
+ ///
+ public int GetProcessorCount()
+ {
+ return processorLights.Count;
+ }
+
+ ///
+ /// 设置等离子体发光影响
+ ///
+ public void SetPlasmaGlow(float intensity, Color? color = null)
+ {
+ plasmaGlowIntensity = intensity;
+ if (color.HasValue)
+ {
+ glowColor = color.Value;
+ }
+ }
+
+ ///
+ /// 重置所有灯光
+ ///
+ public void Reset()
+ {
+ DeactivateAllProcessors();
+ _lastActiveProcessor = -1;
+ }
+
+ ///
+ /// 配置处理器灯光数据
+ ///
+ public void ConfigureProcessorLights(List configs)
+ {
+ processorLights = configs;
+ InitializeProcessorMap();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SalesYarnCommand.cs b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SalesYarnCommand.cs
new file mode 100644
index 000000000..e6504a28c
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SalesYarnCommand.cs
@@ -0,0 +1,411 @@
+using System.Collections;
+using Yarn.Unity;
+using UnityEngine;
+using AibisDream.FixSystem;
+
+namespace AibisDream.MiniGame.HuoShan
+{
+ ///
+ /// 销售系统的Yarn命令扩展
+ /// 功能:
+ /// - 发条控件命令
+ /// - 滑片控制器命令
+ /// - 处理器灯控制命令
+ /// - 销售流程控制命令
+ ///
+ public static class SalesYarnCommand
+ {
+ #region 发条控件命令
+
+ ///
+ /// 重置发条到初始状态
+ /// 用法: <>
+ ///
+ [YarnCommand("reset_crank")]
+ public static void ResetCrank()
+ {
+ var crankController = FixSystemCenter.SystemDic.Get();
+ if (crankController != null)
+ {
+ crankController.ResetCrank();
+ Debug.Log("[SalesYarnCommand] 发条已重置");
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到CrankController");
+ }
+ }
+
+ ///
+ /// 手动旋转发条到指定角度(用于测试或自动化)
+ /// 用法: <>
+ ///
+ [YarnCommand("rotate_crank")]
+ public static IEnumerator RotateCrank(float angle, float duration = 0.5f)
+ {
+ var crankController = FixSystemCenter.SystemDic.Get();
+ if (crankController != null)
+ {
+ Debug.Log($"[SalesYarnCommand] 旋转发条到{angle}度,时长{duration}秒");
+ yield return new WaitForSeconds(duration);
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到CrankController");
+ }
+ }
+
+ ///
+ /// 等待发条完成(达到最大角度并释放)
+ /// 用法: <>
+ ///
+ [YarnCommand("wait_crank_complete")]
+ public static IEnumerator WaitCrankComplete()
+ {
+ var crankController = FixSystemCenter.SystemDic.Get();
+ if (crankController == null)
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到CrankController");
+ yield break;
+ }
+
+ Debug.Log("[SalesYarnCommand] 等待发条完成...");
+
+ // 使用协程等待事件
+ bool isComplete = false;
+ UnityEngine.Events.UnityAction onComplete = () => isComplete = true;
+ crankController.onCrankComplete.AddListener(onComplete);
+
+ while (!isComplete)
+ {
+ yield return null;
+ }
+
+ crankController.onCrankComplete.RemoveListener(onComplete);
+ Debug.Log("[SalesYarnCommand] 发条完成");
+ }
+
+ #endregion
+
+ #region 滑片控制命令
+
+ ///
+ /// 重置滑片到起始位置
+ /// 用法: <>
+ ///
+ [YarnCommand("reset_slider")]
+ public static void ResetSlider()
+ {
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController != null)
+ {
+ sliderController.ResetPosition();
+ Debug.Log("[SalesYarnCommand] 滑片已重置");
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到SliderController");
+ }
+ }
+
+ ///
+ /// 滑片移动到指定角度
+ /// 用法: <>
+ ///
+ [YarnCommand("slide_to_angle")]
+ public static IEnumerator SlideToAngle(float angle, float duration = 1f)
+ {
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController != null)
+ {
+ Debug.Log($"[SalesYarnCommand] 移动滑片到{angle}度,时长{duration}秒");
+ sliderController.SlideTo(angle, duration);
+ yield return new WaitForSeconds(duration);
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到SliderController");
+ }
+ }
+
+ ///
+ /// 滑片移动到指定标准化位置(0-1)
+ /// 用法: <>
+ ///
+ [YarnCommand("slide_to_position")]
+ public static IEnumerator SlideToPosition(float normalizedPosition, float duration = 1f)
+ {
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController != null)
+ {
+ Debug.Log($"[SalesYarnCommand] 移动滑片到位置{normalizedPosition},时长{duration}秒");
+ sliderController.SlideToNormalized(normalizedPosition, duration);
+ yield return new WaitForSeconds(duration);
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到SliderController");
+ }
+ }
+
+ ///
+ /// 滑片移动到指定段
+ /// 用法: <>
+ ///
+ [YarnCommand("slide_to_segment")]
+ public static IEnumerator SlideToSegment(int segmentIndex, float duration = 1f)
+ {
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController != null)
+ {
+ Debug.Log($"[SalesYarnCommand] 移动滑片到段{segmentIndex},时长{duration}秒");
+ sliderController.SlideToSegment(segmentIndex, duration);
+ yield return new WaitForSeconds(duration);
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到SliderController");
+ }
+ }
+
+ ///
+ /// 滑片移动到异常段(任务关键)
+ /// 用法: <>
+ ///
+ [YarnCommand("slide_to_anomaly")]
+ public static IEnumerator SlideToAnomaly(int segmentIndex, float duration = 1f)
+ {
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController != null)
+ {
+ Debug.Log($"[SalesYarnCommand] 移动滑片到异常段{segmentIndex},时长{duration}秒");
+
+ // 设置段数为异常段
+ sliderController.SetSegmentCount(segmentIndex + 1);
+ sliderController.SlideToSegment(segmentIndex, duration);
+
+ yield return new WaitForSeconds(duration);
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到SliderController");
+ }
+ }
+
+ ///
+ /// 等待滑片到达目标位置
+ /// 用法: <>
+ ///
+ [YarnCommand("wait_slider_at")]
+ public static IEnumerator WaitSliderAt(float targetPosition, float tolerance = 0.05f)
+ {
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController == null)
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到SliderController");
+ yield break;
+ }
+
+ Debug.Log($"[SalesYarnCommand] 等待滑片到达位置{targetPosition}...");
+
+ while (Mathf.Abs(sliderController.GetNormalizedPosition() - targetPosition) > tolerance)
+ {
+ yield return null;
+ }
+
+ Debug.Log($"[SalesYarnCommand] 滑片已到达位置{targetPosition}");
+ }
+
+ #endregion
+
+ #region 处理器灯控制命令
+
+ ///
+ /// 激活指定索引的处理器灯
+ /// 用法: <>
+ ///
+ [YarnCommand("activate_processor")]
+ public static void ActivateProcessor(int processorIndex, float duration = -1f)
+ {
+ var controller = FixSystemCenter.SystemDic.Get();
+ if (controller != null)
+ {
+ controller.ActivateProcessor(processorIndex, duration);
+ Debug.Log($"[SalesYarnCommand] 激活处理器{processorIndex}");
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到ProcessorLightController");
+ }
+ }
+
+ ///
+ /// 关闭所有处理器灯
+ /// 用法: <>
+ ///
+ [YarnCommand("deactivate_all_processors")]
+ public static void DeactivateAllProcessors(float fadeOutDuration = 0.3f)
+ {
+ var controller = FixSystemCenter.SystemDic.Get();
+ if (controller != null)
+ {
+ controller.DeactivateAllProcessors(fadeOutDuration);
+ Debug.Log("[SalesYarnCommand] 关闭所有处理器灯");
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到ProcessorLightController");
+ }
+ }
+
+ ///
+ /// 显示异常处理器(闪烁)
+ /// 用法: <>
+ ///
+ [YarnCommand("show_anomaly_processor")]
+ public static void ShowAnomalyProcessor(int processorIndex)
+ {
+ var controller = FixSystemCenter.SystemDic.Get();
+ if (controller != null)
+ {
+ controller.ShowAnomalyProcessor(processorIndex);
+ Debug.Log($"[SalesYarnCommand] 显示异常处理器{processorIndex}");
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到ProcessorLightController");
+ }
+ }
+
+ ///
+ /// 根据滑片位置自动更新处理器灯
+ /// 用法: <>
+ ///
+ [YarnCommand("update_processor_from_slider")]
+ public static void UpdateProcessorFromSlider()
+ {
+ var processorController = FixSystemCenter.SystemDic.Get();
+ var sliderController = FixSystemCenter.SystemDic.Get();
+
+ if (processorController != null && sliderController != null)
+ {
+ float position = sliderController.GetNormalizedPosition();
+ int processorIndex = processorController.GetProcessorIndexFromSlider(position);
+ processorController.ActivateProcessor(processorIndex);
+ Debug.Log($"[SalesYarnCommand] 根据滑片位置{position}激活处理器{processorIndex}");
+ }
+ else
+ {
+ Debug.LogWarning("[SalesYarnCommand] 未找到所需的控制器");
+ }
+ }
+
+ #endregion
+
+ #region 销售流程控制命令
+
+ ///
+ /// 进入步进模式(收缩扩张动画)
+ /// 用法: <>
+ ///
+ [YarnCommand("enter_step_mode")]
+ public static IEnumerator EnterStepMode()
+ {
+ Debug.Log("[SalesYarnCommand] 进入步进模式");
+
+ // 这个命令需要触发收缩扩张动画
+ // 具体实现需要与动画系统或Timeline集成
+
+ yield return new WaitForSeconds(1f);
+ }
+
+ ///
+ /// 进入处理器深入模式(面板右移)
+ /// 用法: <>
+ ///
+ [YarnCommand("enter_processor_mode")]
+ public static IEnumerator EnterProcessorMode()
+ {
+ Debug.Log("[SalesYarnCommand] 进入处理器深入模式");
+
+ // 触发面板右移动画
+ // 可以使用DOTween或Timeline
+
+ yield return new WaitForSeconds(1f);
+ }
+
+ ///
+ /// 完成一次完整的发条-滑片循环
+ /// 用法: <>
+ ///
+ [YarnCommand("complete_crank_cycle")]
+ public static IEnumerator CompleteCrankCycle()
+ {
+ Debug.Log("[SalesYarnCommand] 完成发条-滑片循环");
+
+ // 等待发条完成
+ yield return WaitCrankComplete();
+
+ // 移动滑片一段距离
+ var sliderController = FixSystemCenter.SystemDic.Get();
+ if (sliderController != null)
+ {
+ float currentPosition = sliderController.GetNormalizedPosition();
+ float newPosition = currentPosition + 0.25f; // 每次移动25%
+ yield return SlideToPosition(newPosition, 0.5f);
+ }
+ }
+
+ ///
+ /// 等待特定步进节点完成
+ /// 用法: <>
+ ///
+ [YarnCommand("wait_step_node")]
+ public static IEnumerator WaitStepNode(int stepIndex)
+ {
+ Debug.Log($"[SalesYarnCommand] 等待步进节点{stepIndex}完成...");
+
+ // 根据步进索引执行不同的等待逻辑
+ switch (stepIndex)
+ {
+ case 1:
+ case 2:
+ // 等待发条和滑片完成
+ yield return WaitCrankComplete();
+ break;
+ case 3:
+ // 等待滑片移动到异常段
+ yield return new WaitForSeconds(2f);
+ break;
+ case 4:
+ // 等待反向操作完成
+ yield return new WaitForSeconds(1f);
+ break;
+ }
+
+ Debug.Log($"[SalesYarnCommand] 步进节点{stepIndex}完成");
+ }
+
+ #endregion
+
+ #region 静态工具方法
+
+ ///
+ /// 获取销售系统管理器
+ ///
+ private static SalesManager GetSalesManager()
+ {
+ return FixSystemCenter.SystemDic.Get();
+ }
+
+ ///
+ /// 等待指定时长
+ ///
+ private static IEnumerator Wait(float seconds)
+ {
+ yield return new WaitForSeconds(seconds);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs
new file mode 100644
index 000000000..15e128609
--- /dev/null
+++ b/Assets/Scripts/MiniGame/HuoShan/SalesSystem/SliderController.cs
@@ -0,0 +1,439 @@
+using UnityEngine;
+using UnityEngine.Events;
+using DG.Tweening;
+
+namespace AibisDream.MiniGame.HuoShan
+{
+ ///
+ /// 滑片控制器 - 实现滑片沿圆环滑动以及与波形联动
+ /// 功能:
+ /// - 滑片拖拽沿外环滑动
+ /// - 滑片角度/弧度驱动波形参数
+ /// - 等离子体发光段显示
+ ///
+ 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("滑片拖拽阻力")]
+ [SerializeField] private float dragResistance = 0.05f;
+
+ [Tooltip("是否自动吸附到离散位置")]
+ [SerializeField] private bool snapToSegments = true;
+
+ [Tooltip("离散段数量")]
+ [SerializeField] private int segmentCount = 12;
+
+ [Header("等离子体发光")]
+ [Tooltip("等离子体发光对象(可多个)")]
+ [SerializeField] private GameObject[] plasmaObjects;
+
+ [Tooltip("等离子体发光材质属性名")]
+ [SerializeField] private string glowProperty = "_GlowIntensity";
+
+ [Tooltip("发光强度")]
+ [SerializeField] private float glowIntensity = 1f;
+
+ [Tooltip("发光颜色")]
+ [SerializeField] private Color glowColor = new Color(0.3f, 0.9f, 0.55f);
+
+ [Header("波形联动")]
+ [Tooltip("波形检测器引用")]
+ [SerializeField] private WaveformDetector waveformDetector;
+
+ [Tooltip("等离子体发光影响波形参数")]
+ [SerializeField] private bool plasmaAffectsWaveform = true;
+
+ [Header("事件")]
+ [Tooltip("滑片位置改变时触发(参数:标准化位置0-1)")]
+ public UnityEvent onSliderPositionChanged;
+
+ [Tooltip("滑片开始拖拽时触发")]
+ public UnityEvent onSliderStartDrag;
+
+ [Tooltip("滑片释放时触发")]
+ public UnityEvent onSliderReleased;
+
+ 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;
+
+ #region 生命周期
+
+ private void Awake()
+ {
+ _mainCamera = Camera.main;
+ _glowPropertyID = Shader.PropertyToID(glowProperty);
+
+ if (sliderTransform == null)
+ {
+ sliderTransform = transform;
+ }
+
+ // 缓存等离子体材质
+ if (plasmaObjects != null && plasmaObjects.Length > 0)
+ {
+ _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;
+ }
+ }
+ }
+ }
+
+ // 获取波形渲染器
+ if (waveformDetector != null && waveformDetector.IsShown)
+ {
+ _waveformRenderer = waveformDetector.GetComponentInChildren();
+ }
+ }
+
+ private void Update()
+ {
+ HandleMouseInput();
+ UpdateSliderPosition();
+ UpdatePlasmaGlow();
+ }
+
+ #endregion
+
+ #region 输入处理
+
+ private void HandleMouseInput()
+ {
+ 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;
+ }
+
+ #endregion
+
+ #region 位置更新
+
+ private void UpdateSliderPosition()
+ {
+ // 平滑移动到目标角度
+ _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 = ringCenter.position - position;
+ float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
+ sliderTransform.rotation = Quaternion.Euler(0f, 0f, angle);
+
+ // 更新电夹位置(保持在圆环上)
+ 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;
+ if (Vector3.Distance(position, _lastValidPosition) > 0.01f)
+ {
+ onSliderPositionChanged?.Invoke(normalizedPosition);
+ UpdateWaveform(normalizedPosition);
+ _lastValidPosition = position;
+ }
+ }
+
+ #endregion
+
+ #region 等离子体发光
+
+ private void UpdatePlasmaGlow()
+ {
+ if (_plasmaMaterials == null || _plasmaMaterials.Length == 0) return;
+
+ // 计算滑片当前位置占整个圆环的比例
+ float progress = _currentAngle / slideAngleRange;
+ float glowRange = progress * glowIntensity;
+
+ // 更新每个等离子体对象的发光强度
+ for (int i = 0; i < _plasmaMaterials.Length; i++)
+ {
+ 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);
+ }
+ }
+ }
+ }
+
+ #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 bool IsDragging => _isDragging;
+
+ ///
+ /// 重置滑片位置
+ ///
+ public void ResetPosition()
+ {
+ if (_moveTween != null)
+ {
+ _moveTween.Kill();
+ _moveTween = null;
+ }
+
+ _targetAngle = 0f;
+ _currentAngle = 0f;
+ _isDragging = false;
+ }
+
+ ///
+ /// 设置离散段数量
+ ///
+ public void SetSegmentCount(int count)
+ {
+ segmentCount = Mathf.Max(1, count);
+ }
+
+ ///
+ /// 获取当前段索引
+ ///
+ public int GetCurrentSegment()
+ {
+ float segmentAngle = slideAngleRange / segmentCount;
+ return Mathf.FloorToInt(_currentAngle / segmentAngle);
+ }
+
+ #endregion
+ }
+}