Unity+FGUI列表制作滚筒抽奖动画

news/2025/11/14 12:06:45/文章来源:https://www.cnblogs.com/CloverJoyi/p/19221532

Unity+FGUI列表制作滚筒抽奖动画


概述

本文将深入分析一个基于Unity和FairyGUI实现的滚筒抽奖效果脚本,该脚本可适用于游戏中的词条洗炼、抽奖等系统,通过精美的视觉效果和流畅的动画体验,为玩家提供类似抽奖机的抽奖体验。

列表滚动缩放方法参考了FGUI-Unity官方案例“LoopList”

在FGUI中

首先我使用FGUI制作一个简易列表加一个按钮,如下即可

image-20251113175255706

注意要禁用自动调整项目大小和惯性

image-20251113175046367

image-20251113175102076

导出到Unity,然后开始写代码

在Unity中

一、实现列表动态缩放

想要实现滚筒效果,需要将位于列表中间的项放大,距离中心越远的项越小,营造一种透视效果,这部分在官方案例中已有实现,我们直接看代码

  1. 首先我们在Strart中进行初始化,进行必要的设置和数据获取:
GComponent _mainView;
GList _list;
void Start()
{//将帧率设置为60帧Application.targetFrameRate = 60;//获取UI组件_mainView = this.GetComponent<UIPanel>().ui;//获取列表,并将列表设置为虚拟循环列表_list = _mainView.GetChild("list").asList;_list.SetVirtualAndLoop();//为列表设置回调函数,初始化列表_list.itemRenderer = RenderListItem;_list.numItems = 5;//添加滚动事件触发函数_list.scrollPane.onScroll.Add(DoSpecialEffect);//初始化进行一次缩放DoSpecialEffect();
}
  1. 核心,实现动态缩放

思路为:计算列表各个子项距离列表中心点的距离,距离越远越小

//实现动态缩放
void DoSpecialEffect()
{// 计算列表视图的中心Y坐标float midY = _list.scrollPane.posY + _list.viewHeight / 2;int childCount = _list.numChildren;for (int i = 0; i < childCount; i++){GObject obj = _list.GetChildAt(i);if (obj != null){// 计算子项中心到列表中心的垂直距离float dist = Mathf.Abs(midY - obj.y - obj.height / 2);// 距离范围:使用一个合适的范围来计算缩放效果// 当距离大于等于1.5个子项高度时,缩放到最小值float maxDistance = obj.height * 1.5f;float clampedDistance = Mathf.Min(dist, maxDistance);// 距离中心越远,缩放比例越小//可以通过调节0.25f这个系数的大小调节缩放float scale = 1.0f - (clampedDistance / maxDistance) * 0.25f;//应用缩放obj.SetScale(scale, scale);}}
}
  1. 至此,核心逻辑已完成,你可以使用RenderListItem方法对列表内容进行初始化。参考:列表 - 编辑器教程 | FairyGUI
    将这个脚本挂载在UIPanel上,载入在FGUI中制作的列表,运行。应该可以看到效果了。

image-20251113194344435

现在你可以拖动列表,可以发现位于中间的子项永远是最大的。

如果你想完成一个类似于拨轮的控件,那么到这里就基本完成了

[!TIP]

如果你想让中间的子项能够自动吸附到列表中间,可以在FGUI中打开“滚动位置自动贴近元件”

二、实现列表自动滚动

与我们显示中的抽奖转盘不同,游戏中的抽奖逻辑,一般是点击抽奖后立刻进行计算,得出计算结果,然后播放抽奖动画,最后控制奖池停止在已经计算完成的抽检结果上。那么这一板块,我们实现点击按钮使列表快速滚动,速度逐渐变慢停止,最后位于中间的子项恰好是我们指定的项。

首先我们先实现根据索引计算目标项位置

  1. 我想要支持在索引超出列表大小时也可以滚动到正确的位置,所以先对传入的索引进行计算,得出真实索引的位置

  2. 利用真实索引计算目标项在转一圈的情况下的绝对位置

  3. 计算居中偏移,使最后目标项能够停在视口中间

    要令视口中心=目标词条中心:

    \[\]

    \[\]

    \[\]

    \[\]

  4. 计算滚动距离

  5. 因为只能向前滚动,所以我们要分两种情况计算(目标在当前位置之前/目标在当前位置之后)

/// <summary>
/// 计算虚拟循环列表的目标位置(支持多圈滚动,始终向前)
/// </summary>
/// <param name="targetIndex">目标索引(可超过 numItems)</param>
private (float targetPos, int actualIndex) CalculateCyclicTargetPosition(int targetIndex)
{int numItems = _list.numItems;float itemHeight = GetItemHeight();float viewHeight = _list.viewHeight;float contentHeight = numItems * itemHeight;// 获取当前滚动位置(标准化后的位置,范围在0到contentHeight之间)float currentPos = _list.scrollPane.posY;// 直接使用 targetIndex 计算绝对位置// 例如:targetIndex=20时,计算的是第20项的位置,而不是第0项float targetItemPosition = targetIndex * itemHeight;// 计算居中偏移:让目标项显示在列表视图的中心位置float centerOffset = (viewHeight - itemHeight) / 2f;float targetPositionAbsolute = targetItemPosition - centerOffset;// 计算实际显示的项索引(仅用于返回值)// 例如:targetIndex=20, numItems=5 → actualItemIndex=0(显示Item 0)// targetIndex=21, numItems=5 → actualItemIndex=1(显示Item 1)int actualItemIndex = targetIndex % numItems;if (actualItemIndex < 0) actualItemIndex += numItems;// 确保至少向前滚动(如果计算出的目标位置在当前位置之前,则加上额外圈数)float finalTargetPos = targetPositionAbsolute;if (finalTargetPos <= currentPos){// 如果目标位置在当前位置之前,加上足够的圈数,确保向前滚动int additionalLoops = (int)Mathf.Ceil((currentPos - finalTargetPos) / contentHeight) + 1;finalTargetPos += additionalLoops * contentHeight;}return (finalTargetPos, actualItemIndex);
}

实现列表滚动

这里使用的方案是设置滚动时间固定,滚动速度自适应

  1. 使用刚刚实现的CalculateCyclicTargetPosition函数计算目标位置和实际索引
  2. 设置和记录必要参数
  3. 使用异步每帧循环执行滚动逻辑(使用OutQuart缓动函数)
  4. 待滚动结束后,将目标项标准化到视口中心,保证最终位置精确
  5. 最后执行一次缩放
/// <summary>
/// 抽奖式滚动到目标位置
/// </summary>
/// <param name="targetIndex">目标词条索引(可超过列表项数量,会自动计算循环圈数)		</param>
private async UniTask LotteryScrollToTargetAsync(int targetIndex)
{(float targetPos, int actualIndex) = CalculateCyclicTargetPosition(targetIndex);// 记录起始位置float startPos = m_list_entry.scrollPane.posY;float totalDistance = targetPos - startPos;//这里设置滚动事件固定为10秒const float fixedDuration = 10f;//记录开始时间float startTime = Time.time;//开始执行滚动while (Time.time - startTime < fixedDuration){//计算已经过去的时间float elapsedTime = Time.time - startTime;//时间归一化float normalizedTime = Mathf.Clamp01(elapsedTime / fixedDuration);//使用缓动函数计算速度float easedProgress = CalculateEaseOutQuart(normalizedTime);//计算当前滚动位置float newPos = startPos + totalDistance * easedProgress;//应用位置,应用缩放m_list_entry.scrollPane.SetPosY(newPos, true);DoScrollScaleEffect();//这里是引入了UniTask,异步每帧执行await UniTask.Yield(PlayerLoopTiming.Update);}// 确保最终位置精确m_list_entry.scrollPane.SetPosY(targetPos, true);DoScrollScaleEffect();// 等待稳定await UniTask.Delay(100);// 标准化位置float contentHeight = m_list_entry.numItems * GetItemHeight();float normalizedPos = targetPos % contentHeight;if (normalizedPos < 0){normalizedPos += contentHeight;}// 标准化时使用 false 立即跳转m_list_entry.scrollPane.SetPosY(normalizedPos, false);//等待一帧,确保虚拟列表重新渲染子项后再执行缩放效果await UniTask.Yield(PlayerLoopTiming.Update);DoScrollScaleEffect();
}/// <summary>
/// OutQuart 缓动函数(更平滑的减速)
/// </summary>
/// <param name="t">归一化时间 [0, 1]</param>
/// <returns>缓动后的进度值 [0, 1]</returns>
private float CalculateEaseOutQuart(float t)
{if (t >= 1f) return 1f;// OutQuart缓动公式:1 - (1 - t)^4float p = 1f - t;return 1f - p * p * p * p;
}

[!CAUTION]

实现列表自动滚动要取消注册滚动事件,将_list.scrollPane.onScroll.Add(DoSpecialEffect);在Start中注释掉,我们将实现滚动动态缩放改为在每帧滚动结束后手动触发。

如果依赖滚动事件在高频更新下会可能造成事件队列延迟与帧同步冲突,结果就是缩放计算会滞后,导致视觉上不连贯或是其他问题

完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FairyGUI;
using System.Threading.Tasks;
using System.Linq;public class Test : MonoBehaviour
{GComponent _mainView;GList _list;GButton _btn;private bool _isLotteryScrolling = false;void Start(){Application.targetFrameRate = 60;_mainView = this.GetComponent<UIPanel>().ui;_list = _mainView.GetChild("list").asList;_btn = _mainView.GetChild("btn").asButton;_btn.onClick.Set(OnBtn);_list.SetVirtualAndLoop();_list.itemRenderer = RenderListItem;_list.numItems = 5;_list.touchable = false;DoSpecialEffect();}void DoSpecialEffect(){// 计算列表视图的中心Y坐标float midY = _list.scrollPane.posY + _list.viewHeight / 2;int childCount = _list.numChildren;for (int i = 0; i < childCount; i++){GObject obj = _list.GetChildAt(i);if (obj != null){// 计算子项中心到列表中心的垂直距离float dist = Mathf.Abs(midY - obj.y - obj.height / 2);// 距离范围:使用一个合适的范围来计算缩放效果// 当距离大于等于1.5个子项高度时,缩放到最小值float maxDistance = obj.height * 1.5f;float clampedDistance = Mathf.Min(dist, maxDistance);// 距离中心越远,缩放比例越小float scale = 1.0f - (clampedDistance / maxDistance) * 0.25f;obj.SetScale(scale, scale);}}}void RenderListItem(int index, GObject obj){GLabel item = (GLabel)obj;item.SetPivot(0.5f, 0.5f);item.title = "Item " + index;}/// <summary>/// 抽奖式滚动到目标位置/// </summary>/// <param name="targetIndex">目标词条索引(可超过列表项数量,会自动计算循环圈数)</param>private async Task LotteryScrollToTargetAsync(int targetIndex){(float targetPos, int actualIndex) = CalculateCyclicTargetPosition(targetIndex);// 记录起始位置float startPos = _list.scrollPane.posY;float totalDistance = targetPos - startPos;const float fixedDuration = 10f;float startTime = Time.time;while (Time.time - startTime < fixedDuration){float elapsedTime = Time.time - startTime;float normalizedTime = Mathf.Clamp01(elapsedTime / fixedDuration);float easedProgress = CalculateEaseOutQuart(normalizedTime);float newPos = startPos + totalDistance * easedProgress;_list.scrollPane.SetPosY(newPos, true);DoSpecialEffect();await Task.Yield();}// 确保最终位置精确_list.scrollPane.SetPosY(targetPos, true);DoSpecialEffect();// 等待稳定await Task.Delay(100);// 标准化位置float contentHeight = _list.numItems * GetItemHeight();float normalizedPos = targetPos % contentHeight;if (normalizedPos < 0){normalizedPos += contentHeight;}// 标准化时使用 false 立即跳转_list.scrollPane.SetPosY(normalizedPos, false);//等待一帧,确保虚拟列表重新渲染子项后再执行缩放效果await Task.Yield();DoSpecialEffect();}/// <summary>/// OutQuart 缓动函数(更平滑的减速)/// </summary>/// <param name="t">归一化时间 [0, 1]</param>/// <returns>缓动后的进度值 [0, 1]</returns>private float CalculateEaseOutQuart(float t){if (t >= 1f) return 1f;// OutQuart缓动公式:1 - (1 - t)^4float p = 1f - t;return 1f - p * p * p * p;}/// <summary>/// 计算虚拟循环列表的目标位置(支持多圈滚动,始终向前)/// </summary>/// <param name="targetIndex">目标索引(可超过 numItems)</param>private (float targetPos, int actualIndex) CalculateCyclicTargetPosition(int targetIndex){int numItems = _list.numItems;float itemHeight = GetItemHeight();float viewHeight = _list.viewHeight;float contentHeight = numItems * itemHeight;// 获取当前滚动位置(标准化后的位置,范围在0到contentHeight之间)float currentPos = _list.scrollPane.posY;// 直接使用 targetIndex 计算绝对位置(不取模)// 例如:targetIndex=20时,计算的是第20项的位置,而不是第0项float targetItemPosition = targetIndex * itemHeight;// 计算居中偏移:让目标项显示在列表视图的中心位置float centerOffset = (viewHeight - itemHeight) / 2f;float targetPositionAbsolute = targetItemPosition - centerOffset;// 计算实际显示的项索引(仅用于返回值)// 例如:targetIndex=20, numItems=5 → actualItemIndex=0(显示Item 0)// targetIndex=21, numItems=5 → actualItemIndex=1(显示Item 1)int actualItemIndex = targetIndex % numItems;if (actualItemIndex < 0) actualItemIndex += numItems;// 确保至少向前滚动(如果计算出的目标位置在当前位置之前,则加上额外圈数)float finalTargetPos = targetPositionAbsolute;if (finalTargetPos <= currentPos){// 如果目标位置在当前位置之前,加上足够的圈数,确保向前滚动int additionalLoops = (int)Mathf.Ceil((currentPos - finalTargetPos) / contentHeight) + 1;finalTargetPos += additionalLoops * contentHeight;}return (finalTargetPos, actualItemIndex);}/// <summary>/// 获取列表项高度/// </summary>/// <returns>列表项高度(像素)</returns>private float GetItemHeight(){if (_list.numChildren > 0){GObject firstItem = _list.GetChildAt(0);return firstItem.height;}// 如果没有子项,使用估算值return 100f; // 默认高度}async void OnBtn(){// 如果正在滚动中,不重复触发if (_isLotteryScrolling)return;// 开始洗炼流程await RefineProcessAsync();}private async Task RefineProcessAsync(){_isLotteryScrolling = true;// 修改:随机最终停止的项int finalTargetItem = Random.Range(0, _list.numItems);// 再加上固定的圈数,确保滚动效果int minLoops = 4;int resultId = finalTargetItem + (minLoops * _list.numItems);Debug.Log($"Refine Result ID: {resultId}, Final Item: {finalTargetItem}");// 开始抽奖式滚动await LotteryScrollToTargetAsync(resultId);_isLotteryScrolling = false;}
}

运行后点击按钮列表开始滚动,控制台输出了目标子项索引,最终列表停止,目标子项位于列表视口中央

结尾

最终我们完整实现了一个基于 Unity + FairyGUI 的滚筒抽奖动画效果。从 FGUI 列表的基础配置,到动态缩放的核心算法,再到支持多圈滚动的抽奖逻辑,每一步都进行了详细分析与代码实现。你可以在此基础上进一步扩展,如加入音效、粒子特效、多列表联动等,打造更丰富的抽奖体验。希望本文能为你开发类似系统提供参考,如有疑问或建议,欢迎交流讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/965240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年靠谱的金属网厂家最新TOP实力排行

2025年靠谱的金属网厂家最新TOP实力排行行业背景与市场趋势金属网行业作为建筑装饰和工业应用的重要配套产业,近年来呈现出稳健增长态势。根据中国金属制品行业协会最新发布的《2024-2025年中国金属网市场分析报告》显…

AWS iOS SDK 开发指南:构建云端移动应用的完整解决方案

AWS iOS SDK 为开发者提供了完整的云端服务集成方案,支持身份认证、数据存储、推送通知等功能,帮助快速构建功能丰富的移动应用程序。AWS iOS SDK for iOS 开发指南 项目概述 AWS iOS SDK 是一个功能完整的移动开发框…

2025年口碑好的螺旋防腐钢管厂家推荐及选择参考

2025年口碑好的螺旋防腐钢管厂家推荐及选择参考行业背景与市场趋势螺旋防腐钢管作为现代工业管道系统的重要组成部分,近年来随着我国基础设施建设的持续投入和能源行业的快速发展,市场需求呈现稳定增长态势。据中国钢…

2025年靠谱的管道厂家最新实力排行

2025年靠谱的管道厂家最新实力排行行业背景与市场趋势随着中国基础设施建设的持续投入和城市化进程的加速推进,管道行业迎来了新一轮发展机遇。根据中国管道行业协会最新数据,2024年我国管道市场规模已突破4500亿元,…

完整教程:基于单片机的多模式自动洗衣机设计与实现

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年口碑好的工业型无线测力称重变送器厂家选购指南与推荐

2025年口碑好的工业型无线测力称重变送器厂家选购指南与推荐行业背景与市场趋势随着工业4.0和智能制造技术的快速发展,工业型无线测力称重变送器作为自动化生产线的核心组件,市场需求呈现持续增长态势。根据Marketsa…

物理模型的图像去雾算法MATLAB实现

物理模型的图像去雾算法MATLAB实现 结合了大气散射模型、暗通道先验和优化算法一、算法原理与流程 1. 大气散射物理模型 \(I(x)=J(x)t(x)+A(1−t(x))\)\(I(x)\):有雾图像 \(J(x)\):无雾图像(目标) \(t(x)\):透…

DDR4仿真之仿真环境搭建(二)

1.添加空白仿真文件,选择SystemVerilog类型(必须是sv) 2.根据ip设置的参考时钟频率,创建仿真时钟;设置时钟尺度timescale为 1ps/1ps,这样更方便使用整数产生时钟(我的参考时钟是100M)3.打开IP example,在工程…

2025年评价高的悍高同款衣帽间收纳精品推荐榜

2025年评价高的悍高同款衣帽间收纳精品推荐榜行业背景与市场趋势随着中国家居消费升级趋势持续深化,衣帽间收纳系统作为提升居住品质的重要环节,正迎来快速增长期。据中国家具协会最新数据显示,2024年中国定制衣柜及…

2025年评价高的发热管缩管机行业内知名厂家排行榜

2025年评价高的发热管缩管机行业内知名厂家排行榜行业背景与市场趋势发热管缩管机作为电热设备制造领域的关键设备,近年来随着全球电热元件需求的持续增长而迎来快速发展。根据中国电器工业协会电热元件分会最新发布的…

2025年质量好的减速机配件厂家最新推荐权威榜

2025年质量好的减速机配件厂家最新推荐权威榜行业背景与市场趋势减速机作为工业传动系统的核心部件,广泛应用于冶金、矿山、起重、运输、化工等多个领域。据中国机械工业联合会最新统计数据显示,2024年中国减速机市场…

2025年知名的螺旋压榨机厂家最新TOP实力排行

2025年知名的螺旋压榨机厂家最新TOP实力排行行业背景与市场趋势螺旋压榨机作为固液分离领域的关键设备,近年来随着环保政策的日益严格和资源回收需求的增长,市场规模持续扩大。据中国环保机械行业协会最新数据显示,…

2025年生态花岗石定做厂家权威推荐榜单:生态地铺石/石英砖/陶瓷PC砖源头厂家精选

面对市场品牌繁杂、产品质量参差不齐的现状,权威榜单为您的采购决策提供专业参考。 随着城市化进程的加快和建筑装饰行业的发展,生态花岗石作为一种环保、耐用的建材,在市政工程、园林景观和商业地产等领域的应用愈…

2025年淬火油冷却塔订制厂家权威推荐榜单:PAG冷却塔/无锡冷却塔/封闭式凉水塔源头厂家精选

在制造业转型升级的背景下,淬火油冷却系统作为热处理生产线的核心环节,其性能直接影响产品质量与能耗水平。 淬火油冷却塔作为工业热处理领域的关键设备,其换热效率与稳定性对产品质量具有决定性影响。根据2025年工…

PVE中,在CPU为非HOST模式下,SR-IOV直通显卡代码43问题的解决方法

前因:因为发现Windows系统在CPU为HOST模式下,AIDA64测试内存性能非常的差,在经过各种资料查找原因之后,确定问题在QEMU启动时给虚拟机传递的CPU参数,详细情况可以参考这篇文章: https://blog.bairuo.net/2025/03…

2025年比较好的成都中空板厂家最新推荐权威榜

2025年比较好的成都中空板厂家最新推荐权威榜行业背景与市场趋势中空板作为一种轻质、高强度、环保的新型包装材料,近年来在光伏、新能源、电子包装等领域得到广泛应用。根据中国包装联合会最新数据显示,2024年中国中…

2025年比较好的无尘车间净化铝材厂家推荐及采购参考

2025年比较好的无尘车间净化铝材厂家推荐及采购参考行业背景与市场趋势随着半导体、生物医药、精密电子等高端制造业的快速发展,无尘车间净化铝材市场需求持续增长。据中国建筑材料联合会铝型材分会统计,2024年我国净…

2025年靠谱的高速提升机TOP品牌厂家排行榜

2025年靠谱的高速提升机TOP品牌厂家排行榜行业背景与市场趋势随着工业4.0和智能制造的快速发展,高速提升机作为现代物流自动化系统中的核心设备,市场需求持续增长。据《2024-2025年中国物流自动化设备行业分析报告》…

2025年比较好的三菱印刷机胶辊厂家推荐及选择指南

2025年比较好的三菱印刷机胶辊厂家推荐及选择指南行业背景与市场趋势印刷机械胶辊作为印刷设备的核心部件之一,其性能直接影响印刷质量和生产效率。根据中国印刷及设备器材工业协会最新发布的《2024-2025年中国印刷器…

2025人工智能教育最新top5推荐:深度解析产业适配与教学实力

在人工智能技术飞速发展的今天,如何从众多AI教育机构中选出真正具备实力且与产业需求匹配的伙伴,成为企业与个人面临的关键课题。随着人工智能技术持续革新,AI教育市场呈现多元化竞争态势。据第三方数据显示,2025年…