☰
Unity锻造结算UI动效:从进度条到粒子光效的完整实现
2026/10/17 10:51:59 网站建设 项目流程

在动作、MMORPG、放置类游戏里,锻造、合成、抽卡这一类“等待过程 + 结果结算”的玩法,几乎是最考验 UI 手感的地方。很多项目逻辑早就写完了,可是玩家点下“锻造”按钮后,进度条干巴巴地走完,结算面板“啪”地出现,数字直接显示最终值,光效也只是一个简单闪烁——功能没有缺失,但就是少了“获得宝物”的仪式感。

这篇文章就围绕 Unity 中锻造结算界面的 UI 动效实现,把从触发锻造、进度条播放、结算面板弹入,到数字滚动、品质粒子、关闭回收这一整条链路完整拆解出来。适合刚入手 Unity UI 动效的开发者、在做卡牌/MMO/放置类项目界面表现的同学,以及准备把通用结算流程整理成可复用模块的团队。

读完后你可以掌握:

  • 锻造结算 UI 的通用节点结构与状态设计
  • 使用 DOTween 编排面板入场、数字滚动、关闭动画的完整写法
  • 如何把 UGUI 与 Particle System 结合做品质光效
  • 常见性能与层级问题的排查思路

1. 锻造结算动效的背景与设计思路

1.1 一个结算界面需要哪些动效节点

先说结论:锻造结算这类界面,通常不是“弹窗 + 关闭按钮”这么简单,它至少包含三个层面的表现:

  • 锻造过程层:进度条、扫描光带、锻造图标、背景框。
  • 结果表现层:结算面板、品质边框、数字文字、粒子特效。
  • 交互处理层:全屏遮罩、关闭按钮、背景模糊或变暗。

把它们拆开看,是因为这三部分的生命周期不同。锻造过程层在“玩家点击锻造”到“结果回调”期间运行;结果表现层在拿到服务器或本地模拟数据后才启动;交互处理层则要防止玩家在动画播放期间反复点击或穿透点击。

举个例子,一个完整的锻造流程可以这样描述:

  1. 玩家点击“锻造”按钮。
  2. 界面切换为锻造状态,进度条从 0 走到 1,扫描光带扫过。
  3. 进度条满后,客户端向服务器请求结果,或者直接使用本地模拟结果。
  4. 拿到“品质 + 数量”后,先播放一个屏幕微闪或抖动效果。
  5. 结算面板以缩放 + 淡入的方式弹入。
  6. 品质粒子播放,数字从 0 滚动到最终值。
  7. 玩家点击“领取”或关闭按钮,面板缩小淡出。
  8. 界面回到待机状态。

每一步之间都有明显的“节点切换”。如果全部写在一个 Update 里,代码会迅速变得无法维护;正确做法是先设计好状态与节点,再用 Tween 或 Timeline 把表现串起来。

1.2 为什么要用节点结构而不是一个整图

很多初学者做 UI 动效时,喜欢把“整个结算图”做成一张大图,然后只做一个整体的缩放淡入。这样做的问题很明显:

  • 进度条和结果面板无法独立控制。
  • 不同品质的光效、边框颜色难以替换。
  • 关闭动画只能整体缩小,无法单独处理数字、粒子、按钮。
  • 想加“数字滚动”时,不得不写额外逻辑去控制文字节点。

所以更合理的做法是把每个表现拆成独立节点,例如:

ForgePanel (CanvasGroup) ├── Blocker (全屏点击遮罩) ├── ProgressArea │ ├── ProgressBg │ ├── ProgressFill (Image, Filled) │ └── ScanLight (Image, 扫描光带) └── ResultRoot (初始隐藏) ├── PanelWin ├── Frame ├── Flash_Screen ├── Particle_Common ├── Particle_Rare ├── Particle_Epic ├── CountText └── CloseButton

这样设计后,锻造过程中可以隐藏 ResultRoot,结果出现时再激活;粒子按品质单独控制;数字文字独立更新;关闭动画也可以把“面板缩小”和“粒子停止”安排在不同时间点。

1.3 动效节奏的三个阶段

锻造结算的动效节奏,从手感角度可以分成三个阶段:

  • 蓄力期:进度条快速或缓慢填满,配合扫描光带,制造“马上要出结果”的期待感。
  • 爆发期:屏幕闪光、面板弹入、粒子播放,给玩家明确的“出货”反馈。
  • 落定期:数字滚动结束、品质框变色、按钮可用,玩家可以点击关闭,完成闭环。

这三个阶段不能重叠:如果面板还没弹入就开始播放数字滚动,玩家会看不清结果;如果数字滚动还没结束就允许关闭,又容易导致 Tween 被中途打断,出现残影。所以后面代码里我会用一个简单的状态枚举来控制流程,避免动画互相打架。

2. 环境准备与项目结构

2.1 本文使用的开发环境

下面的示例以 Unity 2021.3 LTS 为例,整体思路在 Unity 2020、2022、2023 系列中同样适用。你只需要保证项目里集成了 UGUI、TextMeshPro 和一个 Tween 插件即可。

  • Unity:2021.3 LTS
  • UI 方案:UGUI + TextMeshPro
  • Tween 插件:DOTween(DG.Tweening)
  • 粒子方案:Particle System
  • 版本要求:未强制指定 DOTween 版本,项目已有即可

如果项目还没有 DOTween,可以从 Asset Store 导入,或者使用项目已有的其他 Tween 插件。本文代码中的DOTween.Sequence()、DOFade()、DOScale()、DOFillAmount()都是 DOTween 最常用的扩展 API,只要版本不太旧,写法基本一致。

2.2 场景与节点层级

为了方便演示,我们在场景中只创建一个 Canvas,名为ForgeCanvas。它的子节点结构如下:

ForgeCanvas (Screen Space - Overlay) ├── ForgePanel │ ├── Blocker │ ├── ProgressArea │ │ ├── ProgressBg │ │ ├── ProgressFill │ │ └── ScanLight │ └── ResultRoot │ ├── PanelWin │ ├── Flash_Screen │ ├── Particle_Common │ ├── Particle_Rare │ ├── Particle_Epic │ ├── CountText │ └── CloseButton

这里有几个细节需要提前设置好:

  • ProgressFill的 Image Type 要改为 Filled,FillMethod 选 Horizontal。
  • Flash_Screen是一张全屏白色 Image,初始 alpha 为 0,用来做屏幕闪光。
  • ScanLight是一个窄条白色图片,初始放在进度条最左侧。
  • ResultRoot在场景中保持未激活状态,由代码在需要时激活。
  • 所有纯表现节点,例如ScanLight、Flash_Screen、粒子节点,不要勾选 Raycast Target,否则会挡住点击事件。

2.3 关于 Canvas 与粒子层级

UI 上的粒子特效经常出现两种问题:要么粒子被 UI 挡住,要么粒子挡住按钮点击。为了减少这类问题,建议把粒子节点放到结果面板内部,或者单独设置一个“特效 Canvas”。

在本文示例中,粒子直接挂在ResultRoot下,作为结算面板的一部分。这样粒子会跟随面板一起缩放和淡出,表现更整体。如果项目里粒子需要覆盖在多个 UI 之上,可以创建一个 Sorting Order 更高的EffectCanvas,并把粒子放进去。

3. 核心实现:锻造过程与结算面板

3.1 用状态机管理流程

在写动效代码之前,先定义好 UI 状态。因为锻造结算的步骤很多,状态管理是必须的,否则会出现“玩家连续点击锻造,进度条和结算面板重叠播放”的竞态问题。

public enum ForgeUIState { Idle, Forging, ShowingResult, Closing }
  • Idle:初始状态,可以开始锻造。
  • Forging:进度条播放中,禁止再次触发。
  • ShowingResult:结算面板已打开,等待玩家关闭。
  • Closing:正在播放关闭动画,点击按钮不再响应。

后面所有方法的第一步,都应该判断当前状态是否允许执行对应操作。

3.2 进度条与扫描光带

锻造过程中的核心表现有两个:进度条填充和扫描光带移动。

进度条使用Image.fillAmount播放,Tween 的时长对应“锻造时间”。扫描光带本质上是一张窄条图片,从左端位移到右端,用同样的时长同步移动。

先看核心代码:

// 文件路径:Assets/Scripts/UI/ForgeProgressPlayer.cs using System; using DG.Tweening; using UnityEngine; using UnityEngine.UI; public class ForgeProgressPlayer : MonoBehaviour { [Header("进度条")] public Image progressFill; public float fillDuration = 1.8f; [Header("扫描光带")] public RectTransform scanLight; public float scanStartX = -300f; public float scanEndX = 300f; private Sequence forgeSequence; public void Play(Action onCompleted) { KillCurrent(); progressFill.fillAmount = 0f; scanLight.localPosition = new Vector3(scanStartX, scanLight.localPosition.y, 0f); forgeSequence = DOTween.Sequence(); forgeSequence.Append(progressFill.DOFillAmount(1f, fillDuration).SetEase(Ease.Linear)); forgeSequence.Join(scanLight.DOLocalMoveX(scanEndX, fillDuration).SetEase(Ease.Linear)); forgeSequence.OnComplete(() => { onCompleted?.Invoke(); }); } public void KillCurrent() { if (forgeSequence != null) { forgeSequence.Kill(); forgeSequence = null; } } }

这里有一个关键点:DOLocalMoveX修改的是localPosition.x,所以扫描光带的初始本地坐标最好和scanStartX一致,否则动画开始时会从当前位置跳过去。更稳妥的做法是在 Inspector 中直接摆好初始位置,然后把该位置赋值给scanStartX。

Join的作用是让扫描光带与进度条同步播放,而不是等进度条结束后再移动。在整个锻造过程中,扫描光带始终处于进度区域内部,位置从左侧移动到右侧,给玩家一种“能量正在充满”的感觉。

3.3 结算面板弹入动画

进度条走完之后,不能直接把结算面板显示出来就完事。合理的表现是先来一个极短的全屏闪光,然后面板以“放大 + 淡入”的方式弹出。

使用 DOTween Sequence 编排这三个动作:

private void PlayOpenAnimation(Action onFinished) { resultRoot.SetActive(true); panelCanvasGroup.alpha = 0f; panelRoot.localScale = new Vector3(0.85f, 0.85f, 1f); blocker.blocksRaycasts = true; Sequence openSeq = DOTween.Sequence(); openSeq.Append(panelCanvasGroup.DOFade(1f, 0.12f).SetEase(Ease.OutQuad)); openSeq.Join(panelRoot.DOScale(1.05f, 0.22f).SetEase(Ease.OutBack)); openSeq.Append(panelRoot.DOScale(1f, 0.1f).SetEase(Ease.InOutQuad)); openSeq.AppendCallback(() => PlayFlash()); openSeq.AppendCallback(() => onFinished?.Invoke()); }

参数说明:

  • 0.85f是面板缩放起点,保留 15% 的缩小空间,让弹入时有“蹦出来”的感觉。
  • Ease.OutBack自带回弹效果,适合弹窗入场。
  • 最后再补一个DOScale(1f),是因为OutBack在接近结束时会稍微越界,通常需要再收尾一次。

如果你希望面板入场时同时有轻微抖动,可以追加一行:

openSeq.Join(panelRoot.DOShakePosition(0.25f, new Vector3(6f, 6f, 0f), 20, 90f, false, false));

不过要注意,DOShakePosition会影响面板的本地坐标,如果后续还有子物体动画依赖面板坐标,建议只在小范围内使用,或者在晃动结束后把位置归零。

4. 数字滚动与品质粒子

4.1 数字滚动

锻造结果里的数量、金币、材料数,最好不要直接设置最终值,而是做一个从 0 滚到目标值的动画。这个效果在抽卡、开箱、结算界面里非常常见,实现也不复杂。

单独抽一个UINumberRoller组件,方便复用:

// 文件路径:Assets/Scripts/UI/UINumberRoller.cs using System; using DG.Tweening; using TMPro; using UnityEngine; public class UINumberRoller : MonoBehaviour { public TextMeshProUGUI numberText; public float rollDuration = 0.6f; public Ease rollEase = Ease.OutQuad; public void RollTo(int target, Action onFinished = null) { DOTween.Kill(this); int current = 0; DOTween.To( () => current, value => { current = Mathf.RoundToInt(value); numberText.text = current.ToString(); }, target, rollDuration ) .SetEase(rollEase) .SetId(this) .OnComplete(() => { numberText.text = target.ToString(); onFinished?.Invoke(); }); } public void ResetText() { DOTween.Kill(this); numberText.text = "0"; } }

DOTween.To的第一个参数是获取当前值的委托,第二个参数是每帧更新的委托,第三个参数是目标值,第四个参数是时长。这里用局部变量current保存中间值,每一帧更新到TextMeshProUGUI.text上。

有几个细节值得注意:

  • 每次滚动前最好DOTween.Kill(this),防止上一次滚动没结束又被触发。
  • SetId(this)是为了让后续可以精确定位并杀死这个 Tween。
  • 结束时手动把 text 设为目标值,避免浮点精度问题导致显示9999而不是10000。

如果需要“整数满位后加号”的显示,比如+50,可以在RollTo里加一个前缀参数,本文为了保持代码简洁就不再扩展。

4.2 粒子光效与品质配置

锻造结果往往有品质区分:普通、稀有、史诗。不同品质的光效完全不同,如果写在同一个方法里,方法会越来越臃肿。工程化做法是用一个配置类,把每种品质对应的颜色、粒子、屏幕闪光行为统一管理起来。

先定义品质枚举和配置类:

public enum ForgeQuality { Common, Rare, Epic } [System.Serializable] public class QualityConfig { public ForgeQuality quality; public Color frameColor = Color.white; public ParticleSystem particle; public bool useScreenFlash = true; }

然后在主控制器里维护一个QualityConfig数组,供 Inspector 拖拽赋值。

播放粒子时,先清理所有粒子,再播放对应品质的粒子:

private void PlayQualityParticle(ForgeQuality quality) { foreach (var config in qualityConfigs) { if (config.particle == null) { continue; } config.particle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } var targetConfig = Array.Find(qualityConfigs, config => config.quality == quality); if (targetConfig != null && targetConfig.particle != null) { targetConfig.particle.Play(); } frameImage.color = targetConfig != null ? targetConfig.frameColor : Color.white; }

Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear)的作用是立即停止所有粒子并把已在场景中的粒子清空。这样每次播放前,画面不会残留上一次的粒子。

再说一下屏幕闪光。闪光可以放在粒子播放前,也可以放在粒子播放后,具体看表现需求。通常“闪烁一帧”后紧接着粒子爆开,视觉效果更有冲击力:

private void PlayFlash() { if (flashImage == null) { return; } flashImage.color = new Color(1f, 1f, 1f, 0f); flashImage.DOKill(); flashImage.DOFade(0.8f, 0.06f).SetEase(Ease.OutQuad).OnComplete(() => { flashImage.DOFade(0f, 0.25f); }); }

4.3 点击关闭与动画清理

结算动画播放完毕后,玩家点击“关闭”按钮。此时要做的事情包括:停止粒子、播放收尾动画、清理 Tween、释放点击锁。

关闭动画不能太慢,一般 0.1 到 0.2 秒即可:

public void Close() { if (state != ForgeUIState.ShowingResult) { return; } state = ForgeUIState.Closing; blocker.blocksRaycasts = false; Sequence closeSeq = DOTween.Sequence(); closeSeq.Append(panelCanvasGroup.DOFade(0f, 0.12f).SetEase(Ease.InQuad)); closeSeq.Join(panelRoot.DOScale(0.92f, 0.12f).SetEase(Ease.InQuad)); closeSeq.AppendCallback(() => { StopAllParticles(); numberRoller.ResetText(); resultRoot.SetActive(false); state = ForgeUIState.Idle; }); }

关闭动画开始后,点击按钮应不再响应。这个由状态ForgeUIState.Closing负责保护。同时blocker.blocksRaycasts要在动画开始前设为 false,否则关闭动画期间点击其他按钮仍然会被拦截。

5. 完整代码串联与运行演示

5.1 ForgeResultFxController 完整代码

把上面的模块整合到同一个控制器里。这个控制器挂在ForgePanel根节点上,负责调用进度条组件、数字滚动组件、粒子播放和状态切换。

// 文件路径:Assets/Scripts/UI/ForgeResultFxController.cs using System; using DG.Tweening; using UnityEngine; using UnityEngine.UI; public enum ForgeUIState { Idle, Forging, ShowingResult, Closing } public enum ForgeQuality { Common, Rare, Epic } [System.Serializable] public class QualityConfig { public ForgeQuality quality; public Color frameColor = Color.white; public ParticleSystem particle; public bool useScreenFlash = true; } public class ForgeResultFxController : MonoBehaviour { [Header("状态")] public ForgeUIState state = ForgeUIState.Idle; [Header("锻造过程")] public ForgeProgressPlayer progressPlayer; [Header("结算面板")] public GameObject resultRoot; public RectTransform panelRoot; public CanvasGroup panelCanvasGroup; public Image blocker; public Image frameImage; [Header("闪屏")] public Image flashImage; [Header("数字滚动")] public UINumberRoller numberRoller; [Header("品质配置")] public QualityConfig[] qualityConfigs; private void Start() { blocker.blocksRaycasts = false; resultRoot.SetActive(false); // 演示:进入场景后自动开始一次锻造 StartForge(1.8f); } public void StartForge(float duration) { if (state != ForgeUIState.Idle && state != ForgeUIState.Closing) { return; } state = ForgeUIState.Forging; StopAllParticles(); numberRoller.ResetText(); // 模拟服务器结果:实际项目中,这里应该等待服务器返回后再调用 ShowResult progressPlayer.Play(() => { int count = UnityEngine.Random.Range(1, 20); ForgeQuality quality = (ForgeQuality)UnityEngine.Random.Range(0, 3); ShowResult(quality, count); }); } public void ShowResult(ForgeQuality quality, int count) { if (state != ForgeUIState.Forging) { return; } state = ForgeUIState.ShowingResult; PlayOpenAnimation(() => { PlayQualityParticle(quality); numberRoller.RollTo(count); }); } private void PlayOpenAnimation(Action onFinished) { resultRoot.SetActive(true); panelCanvasGroup.alpha = 0f; panelRoot.localScale = new Vector3(0.85f, 0.85f, 1f); blocker.blocksRaycasts = true; Sequence openSeq = DOTween.Sequence(); openSeq.Append(panelCanvasGroup.DOFade(1f, 0.12f).SetEase(Ease.OutQuad)); openSeq.Join(panelRoot.DOScale(1.05f, 0.22f).SetEase(Ease.OutBack)); openSeq.Append(panelRoot.DOScale(1f, 0.1f).SetEase(Ease.InOutQuad)); openSeq.AppendCallback(() => PlayFlash()); openSeq.AppendCallback(() => onFinished?.Invoke()); } private void PlayQualityParticle(ForgeQuality quality) { foreach (var config in qualityConfigs) { if (config.particle == null) { continue; } config.particle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } var targetConfig = Array.Find(qualityConfigs, config => config.quality == quality); if (targetConfig != null && targetConfig.particle != null) { targetConfig.particle.Play(); } if (frameImage != null) { frameImage.color = targetConfig != null ? targetConfig.frameColor : Color.white; } } private void PlayFlash() { if (flashImage == null) { return; } flashImage.color = new Color(1f, 1f, 1f, 0f); flashImage.DOKill(); flashImage.DOFade(0.8f, 0.06f).SetEase(Ease.OutQuad).OnComplete(() => { flashImage.DOFade(0f, 0.25f); }); } public void Close() { if (state != ForgeUIState.ShowingResult) { return; } state = ForgeUIState.Closing; blocker.blocksRaycasts = false; Sequence closeSeq = DOTween.Sequence(); closeSeq.Append(panelCanvasGroup.DOFade(0f, 0.12f).SetEase(Ease.InQuad)); closeSeq.Join(panelRoot.DOScale(0.92f, 0.12f).SetEase(Ease.InQuad)); closeSeq.AppendCallback(() => { StopAllParticles(); numberRoller.ResetText(); resultRoot.SetActive(false); state = ForgeUIState.Idle; }); } private void StopAllParticles() { foreach (var config in qualityConfigs) { if (config.particle != null) { config.particle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } } } }

这段代码把“锻造中”和“结算展示”完全分开了:StartForge只负责播放进度条,结果由回调触发ShowResult;ShowResult只负责结算面板的弹入动画、粒子播放和数字滚动;Close负责回收。这样即使后续要接入服务器异步数据,也只需要把回调里的 content 替换成真正的服务器返回内容。

5.2 如何触发一次锻造

在场景里模拟玩家点击时,只需要调用:

forgeController.StartForge(1.8f);

如果点击的是“锻造”按钮,把它绑定到按钮事件即可。实际项目中,服务器返回结果前可能有一段网络延迟,那时就是“Forging”状态持续等待,不需要额外改面板逻辑。

5.3 预期运行效果

正确运行后,你会看到这样的表现:

  1. 进度条在 1.8 秒内从空到满,扫描光带同步从左到右移动。
  2. 进度条满后,全屏闪光出现一次。
  3. 结算面板从 0.85 倍大小弹入,带一点回弹效果。
  4. 品质粒子播放,边框颜色变为对应品质颜色。
  5. 数量文字从 0 滚动到随机目标值。
  6. 点击关闭按钮,面板缩小淡出,粒子清空,界面回到待机状态。

如果某个环节没有效果,先检查节点引用是否拖拽正确,再检查对应 Image 或 ParticleSystem 的初始状态。

6. 常见问题与排查

问题现象常见原因解决思路
粒子不显示或显示在 UI 下层粒子节点位于 Canvas 排序较低层单独设置特效 Canvas 并把 Sorting Order 调高
数字只显示 0 或直接跳到最终值数字滚动 Tween 被重复 Kill确保每次滚动前使用独立的 SetId
点击按钮时面板不关闭状态不是 ShowingResult检查 Close 方法的状态判断
面板多次点击会连续触发锻造状态机没有限制 ForgingStartForge 开头检查是否处于 Idle 状态
粒子残留,关闭后仍有光点关闭时未清理粒子在关闭回调里调用 StopAllParticles
扫描光带移动时跳位置scanStartX 与初始 localPosition 不一致Inspector 中摆好初始位置后读取对应 X 值
弹窗挡住下层按钮点击弹窗以外的区域没有遮罩添加全屏 Blocker 并保持 RaycastTarget 为 true
使用 TextMeshPro 后数字滚动卡顿每帧修改 text 产生 GC优先使用 SetText 或预分配 String 缓存

这里补充一个常见误区:很多人会把blocker.blocksRaycasts = false写在动画开始时,但关闭动画还没播完,玩家又点击了其他 UI,可能触发新的锻造流程。比较稳妥的方式是关闭动画开始时将blocksRaycasts设为 false,并在动画结束回调里才把状态切回Idle,给动画留出完整的播放时间。

7. 工程最佳实践与扩展方向

7.1 工程建议

  • 数据与表现分离。服务器返回的数据只包含品质、数量、物品 ID 等业务信息,界面表现由控制器根据这些数据驱动。不要为了动画方便,把业务数据和 UI 动态逻辑混在同一个类里。
  • Tween 统一管理。每次创建 Sequence 时记录引用,在OnDestroy或场景切换时及时 Kill,防止 Tween 泄漏。DOTween 的全局设置里也可以开启DOTween.KillAll()兜底。
  • 粒子生命周期要回收。生产环境尽量使用对象池管理粒子,避免频繁创建销毁。本文示例中直接使用场景里的ParticleSystem播发,逻辑更清晰,但粒子数量多时还需要进一步优化。
  • 性能预算要控制。UI 粒子不要叠加太多,尤其是移动端。普通品质可以只播放一个 10 到 20 个粒子的简单爆点,史诗品质再考虑加拖尾、屏幕闪光和镜头抖动。
  • 按钮点击锁要严谨。除了状态机,还可以在StartForge里增加一个“是否正在锻造”的布尔判断,双保险避免多点触控导致动画错乱。
  • 配置驱动表现。品质颜色、音效、粒子、边框样式建议全部做成配置,不要写死在 if-else 里,否则后续加新品质要改很多代码。

7.2 后续扩展方向

这套锻造结算逻辑并不局限于“锻造”,还可以直接复用到合成、开箱、抽卡、完成任务结算等场景。常见的扩展方向有:

  • 装备图标从面板底部飞入。在ShowResult回调里追加一个图标的DOScale+DOMove动画。
  • 不同品质增加不同音效。把音效 Clip 加入QualityConfig,在播放粒子时同时播放。
  • 使用 Timeline 编排复杂演出。如果锻造过程涉及 3D 场景镜头、多个阶段特效,可以考虑用 Timeline 而不是代码串联,方便策划和美术调整。
  • 加入“再来一次”按钮。在关闭动画结束后直接再次调用StartForge,形成循环体验。
  • 结果展示后自动关闭。比如普通品质显示 2 秒后自动关闭,史诗品质则等待玩家手动点击,让稀有结果停留时间更长。

8. 总结

锻造结算 UI 动效看似只是“弹窗 + 粒子”,实际做好的关键在于节奏控制和状态隔离。本文从节点结构、状态机、进度条、面板弹入、数字滚动、品质粒子和关闭回收几个方面,完整演示了一套可复用的 Unity UI 动效实现方案。

建议你先照着节点结构搭一个 Demo 场景,把核心代码跑通,再根据自己的项目风格调整动画曲线、粒子颜色和音效。动效的最终目标是让玩家在“等待”和“收获”两个节点获得清晰反馈,特效数量和华丽程度反而不是第一优先级。把这套基础模块沉淀下来,以后再做合成、开箱、抽卡结算界面,就是复制粘贴加微调的事了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询