通过上下滑动,实现带自动回正带阻尼感的转盘,上图所示的正方向在9点钟方向,即正西方向
实现过程,找到一个1:1的方形图片,制作一个圆环,我这里是将圆环分成了14份,代码里可以调整,添加代码,手动调整动态参数即可。
直接上代码:
using UnityEngine;
using UnityEngine.EventSystems;public class LotteryWheel : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{[Header("转盘")]public RectTransform wheel;[Header("旋转参数")][Tooltip("手势滑动灵敏度,值越大旋转越明显")]public float swipeSensitivity = 0.5f;[Tooltip("惯性减速速率(角度/秒²)")]public float deceleration = 500f;[Tooltip("回弹对齐的旋转速度(角度/秒)")]public float snapSpeed = 300f;[Header("分段设置")]public int segmentCount = 14; // 分成14份[Header("回正偏移设置")][Tooltip("回正时的目标偏移角度,此处为-90°表示最终使分段中心与-90°对齐")]public float snapOffsetAngle = -90f;public AudioClip audioClip;// 内部状态private float currentVelocity = 0f; // 当前旋转速度(角度/秒)private bool isDragging = false; // 是否处于拖拽手势中private bool isSnapping = false; // 是否正在自动回弹对齐private Vector2 lastPointerPosition; // 记录上一次手指位置// 用于记录上一次经过的分段中心,防止重复打印日志private int lastLoggedSegment = -1;void Start(){// 根据转盘初始角度计算当前分段中心索引float currentAngle = wheel.eulerAngles.z;float segmentAngle = 360f / segmentCount;float relativeAngle = (currentAngle - snapOffsetAngle + 360f) % 360f;lastLoggedSegment = Mathf.RoundToInt(relativeAngle / segmentAngle) % segmentCount;}void Update(){// 非拖拽且未处于回弹对齐状态时,利用惯性旋转并逐渐减速if (!isDragging && !isSnapping){if (Mathf.Abs(currentVelocity) > 0.01f){float deltaAngle = currentVelocity * Time.deltaTime;wheel.Rotate(0, 0, deltaAngle);currentVelocity = Mathf.MoveTowards(currentVelocity, 0, deceleration * Time.deltaTime);// 当速度足够小时,进入回弹对齐状态if (Mathf.Abs(currentVelocity) < 0.1f){currentVelocity = 0;isSnapping = true;}}}// 回弹对齐阶段:计算目标角度(基于偏移)if (isSnapping){float currentAngle = wheel.eulerAngles.z;float segmentAngle = 360f / segmentCount;// 以 snapOffsetAngle 为参考,计算最近的分段中心角度float targetAngle = Mathf.Round((currentAngle - snapOffsetAngle) / segmentAngle) * segmentAngle + snapOffsetAngle;float newAngle = Mathf.MoveTowardsAngle(currentAngle, targetAngle, snapSpeed * Time.deltaTime);wheel.eulerAngles = new Vector3(0, 0, newAngle);if (Mathf.Abs(Mathf.DeltaAngle(newAngle, targetAngle)) < 0.1f){isSnapping = false;// 计算当前分段编号,归一化取模,确保编号在0~segmentCount-1内int segmentIndex = Mathf.RoundToInt((targetAngle - snapOffsetAngle) / segmentAngle);segmentIndex = (segmentIndex % segmentCount + segmentCount) % segmentCount;int number = segmentIndex + 1;Debug.Log("当前编号: " + number);}}// Add detectionSlidingAreaDetection();}// Add detection: Print logs when the turntable turns over a new segment centerprivate void SlidingAreaDetection(){// 获取当前转盘角度(0~360°),计算相对于 snapOffsetAngle 的归一化角度float currentAngle = wheel.eulerAngles.z;float segmentAngle = 360f / segmentCount;float relativeAngle = (currentAngle - snapOffsetAngle + 360f) % 360f;// 通过四舍五入得到当前经过的分段中心索引int currentCenterIndex = Mathf.RoundToInt(relativeAngle / segmentAngle) % segmentCount;if (currentCenterIndex != lastLoggedSegment){// 此处打印日志,后续可在此处接入音效播放//Debug.Log("经过分段中心, 当前分段: " + (currentCenterIndex + 1));if (audioClip != null && audioClip.length > 0){GetComponent<AudioSource>().PlayOneShot(audioClip);}lastLoggedSegment = currentCenterIndex;}}public void OnPointerDown(PointerEventData eventData){isDragging = true;isSnapping = false; // 中断回弹对齐lastPointerPosition = eventData.position;currentVelocity = 0;}public void OnDrag(PointerEventData eventData){Vector2 pointerDelta = eventData.position - lastPointerPosition;// 根据手指垂直位移决定旋转方向:上滑(delta.y正)顺时针旋转,反之逆时针旋转float rotationDelta = -pointerDelta.y * swipeSensitivity;wheel.Rotate(0, 0, rotationDelta);currentVelocity = -pointerDelta.y * swipeSensitivity / Time.deltaTime;lastPointerPosition = eventData.position;}public void OnPointerUp(PointerEventData eventData){isDragging = false;if (Mathf.Abs(currentVelocity) < 10f){currentVelocity = 0;isSnapping = true;}}
}
有点像手机闹钟,设置时间时的丝滑感觉。
下面的代码是将所划分的区域段数实时的展示在Scene界面,可用可不用,需要自取
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ShowRotaryWheel : MonoBehaviour
{public int sectorCount = 14; // 区域块数量#if UNITY_EDITOR// 在类末尾添加以下方法:private void OnDrawGizmos(){if (sectorCount < 1) return;RectTransform rt = GetComponent<RectTransform>();if (!rt) return;float radius = rt.rect.width / 2 * rt.lossyScale.x;Vector3 center = rt.position;float sectionAngle = 360f / sectorCount;Gizmos.color = Color.yellow;for (int i = 0; i < sectorCount; i++){float angle = i * sectionAngle;Vector3 direction = Quaternion.Euler(0, 0, angle) * Vector3.up;Gizmos.DrawLine(center, center + direction * radius);}}
#endif
}
如果有帮到你还望给个三连吧,感谢您的支持。
@Liam