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 + } +}