当前位置: 首页 > article >正文

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


http://www.kler.cn/a/581560.html

相关文章:

  • Helm 安装zookeeper集群
  • Linux网络编程——UDP网络通信的简单实现
  • 【洛谷P1080国王游戏】2025-3-7
  • 【leetcode hot 100 25】K个一组翻转链表
  • 每天五分钟深度学习框架PyTorch:ResNet算法模型完成CAFIR十分类
  • 小红书代运营公司-品融电商:助力品牌在小红书平台实现全域增长
  • Stable Diffusion游戏底模推荐
  • 基于ThinkPHP6用户登录逻辑,结合FastAPI框架实现用户登录系统的全流程解析
  • 碰一碰发视频系统---原生态网页端技术开发逻辑
  • 理解字符流和字节流,节点流和处理流、缓冲流、InputStreamReader、BufferInputStream、BufferReader...
  • Java直通车系列28【Spring Boot】(数据访问Spring Data JPA)
  • Qt5.10版本以下 qml ui语言动态切换
  • ​【C++设计模式】第十九篇:状态模式(State)
  • 备赛蓝桥杯之第XX届职业院校组省赛第七题:Github 明星项目统计
  • 买瓜 第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 A 组
  • 常用的接口重试方案!
  • JAVASE(五)
  • 【BUG】类文件具有错误的版本 61.0, 应为 52.0,请删除该文件或确保该文件位于正确的类路径子目录中。
  • Datawhale AI + 办公 笔记2
  • Spring Boot 调用DeepSeek API的详细教程