Unity 带阻尼感的转盘
通过上下滑动,实现带自动回正带阻尼感的转盘,上图所示的正方向在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 detection
SlidingAreaDetection();
}
// Add detection: Print logs when the turntable turns over a new segment center
private 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