518 lines
15 KiB
C#
518 lines
15 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using DG.Tweening;
|
||
using Yarn.Unity;
|
||
|
||
namespace AibisDream.MiniGame.HuoShan
|
||
{
|
||
/// <summary>
|
||
/// 处理器旋钮控制器 - 实现旋钮的拖拽调节和失败逻辑
|
||
/// 功能:
|
||
/// - 旋钮拖拽旋转(0-360度)
|
||
/// - 角度映射到功率值(0-100%)
|
||
/// - 降功率失败判定
|
||
/// - 信息面板显示失败结果
|
||
/// </summary>
|
||
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) },
|
||
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<float> onPowerChanged;
|
||
|
||
[Tooltip("功率调节失败时触发")]
|
||
public UnityEvent<float> onPowerAdjustmentFail;
|
||
|
||
[Tooltip("功率调节成功时触发")]
|
||
public UnityEvent<float> 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<Renderer>();
|
||
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}%");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 功率调节失败
|
||
/// </summary>
|
||
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<RectTransform>();
|
||
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<RectTransform>();
|
||
if (rectTransform != null)
|
||
{
|
||
Vector2 endPosition = rectTransform.anchoredPosition + Vector2.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", "下一个节点");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Yarn命令:尝试调节功率
|
||
/// </summary>
|
||
[YarnCommand("adjust_power")]
|
||
public static IEnumerator AdjustPower(float targetPower)
|
||
{
|
||
var controller = FindObjectOfType<KnobController>();
|
||
if (controller != null)
|
||
{
|
||
controller.SetPower(targetPower);
|
||
yield return new WaitForSeconds(1f);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Yarn命令:重置旋钮
|
||
/// </summary>
|
||
[YarnCommand("reset_knob")]
|
||
public static void ResetKnob()
|
||
{
|
||
var controller = FindObjectOfType<KnobController>();
|
||
if (controller != null)
|
||
{
|
||
controller.Reset();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 公共方法
|
||
|
||
/// <summary>
|
||
/// 获取当前功率
|
||
/// </summary>
|
||
public float GetCurrentPower()
|
||
{
|
||
return _currentPower;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取是否失败
|
||
/// </summary>
|
||
public bool HasFailed()
|
||
{
|
||
return _hasFailed;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置旋钮状态
|
||
/// </summary>
|
||
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: 重置状态");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置最小可接受功率
|
||
/// </summary>
|
||
public void SetMinAcceptablePower(float minPower)
|
||
{
|
||
minAcceptablePower = minPower;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 强制触发成功(调试用)
|
||
/// </summary>
|
||
public void ForceSuccess()
|
||
{
|
||
_hasSucceeded = true;
|
||
onPowerAdjustmentSuccess?.Invoke(_currentPower);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|