Unity游戏开发文档(3.1.1):弹窗效果

前言

   该文档为《Unity游戏开发文档(3):Dancing Line》的附属文档,亦可看作是单独的技术总结文档。



综述

无论是在游戏中还是在其他应用程序中,我们都经常使用到 “点击按钮 — 弹出对话框” 这一功能。这个功能最简单的形式就是点击某个按钮,然后对话框直接出现在用户的屏幕上,点击关闭按钮,对话框又直接从屏幕上消失。

在游戏中我们会希望往这一个交互动作中再加入一些特效,例如让对话框从屏幕的边缘滑动到指定的位置,同时辅佐以一些互动音效。此外还会在对话框的滑动上做一些手脚,例如让对话框做一个非匀速运动,刚出现时速度是比较快的,但随着对话框离指定位置越来越近,滑动的速度也随之降低。从而使这一互动显得更加生动有趣。


对话框的非匀速滑动

我们可以使用Untiy中的 动画曲线(Animation Curve) 组件,来实现上述的功能。

在这里插入图片描述

动画曲线是一条可由用户自由编辑形状的的曲线,动画曲线的定于域与值域均为 [ 0 , 1 ] [0,1] [0,1]。用户可以通过在曲线上右键 “add key” 来添加控制曲线形状的键值点。一般情况下,我们会把动画曲线当作是一个连续的 字典(Dictionary) 来使用(标准库提供的字典是离散的),即通过 X X X 值来获取我们所希望得到的 Y Y Y 值。

那么我们如何使用动画曲线来为UI实现非匀速的滑动效果呢?在这里我们可以借助Unity 线性插值函数(Lerp Function) 的力量来实现这一个功能。线性插值函数的数学含义是通过用户提供的起始数值 a a a 和末端数值 b b b 以及插值范围 t t t,计算并返回一个大于 a a a 小于 b b b 且与 t t t 呈对应比例的数值,其数学表达式如下:

L e r p ( a , b , t ) = a + ( b − a ) ∗ t t ∈ [ 0 , 1 ] Lerp(a, b, t) = a + (b-a)*t \qquad t\in[0,1] Lerp(a,b,t)=a+(ba)tt[0,1]

如果我们把UI的初始位置设为 a a a,UI的目标位置设为 b b b,那么通过令 t t t 0 0 0 递增至 1 1 1 便可实现UI从其初始位置滑动到某个特定位置的的效果。如果我们进一步调整 t t t 的递增速度,便可实现UI的非匀速滑动效果。例如令 t t t 以指数形式递增,UI便会以初始慢、结尾快的速度滑动;令 t t t 以对数形式递增,UI则会以初始快、结尾慢的速度滑动。再结合我们刚刚介绍过的动画曲线。如果我们令 t t t 值的变化,与动画曲线 Y Y Y 值的变化一致,便可自由地调整UI滑动的速度了。

以下是具体的实现代码:

private float anime_time = 0.5f;
public AnimationCurve anime_curve;

void PopOutAnime(GameObject panel) {
        float timer = 0.0f;
        Vector3 origin_pos = panel.transform.position;
        Vector3 target_pos = panel_pos - new Vector3(0.0f, panel.transform.position.y, 0.0f);

        while (timer < anime_time) {
            timer += Time.deltaTime;

            if (timer > anime_time)
                timer = anime_time;

            float lerp_ration = timer / anime_time;

            panel.transform.position = Vector3.Lerp(origin_pos, target_pos, anime_curve.Evaluate(lerp_ration));
            
        }
}

在上述代码中,我们手动设定了面板滑出动画的时间,然后令动画进行时间与动画曲线的 X X X 值呈映射关系,并获取曲线上相对应的 Y Y Y 值来算出面板位置的插值,从而得到了非匀速的滑动效果。

面板消失的实现逻辑与面板弹出是一致的,相当于面板弹出的逆过程。


对话框动画的异步运行

接下来让我们理一理弹窗特效在游戏中的运行逻辑。一般来说我们希望呈现给用户的的效果是:

  • 用户点击按钮或其他交互组件。
  • 交互组件接收到交互指令,或是被交互指令触发。
  • 交互组件唤醒弹窗,并命令弹窗播放特效。
  • 用户看到弹窗出现。

顺着上述的思路,并代入到Unity的框架下构思代码实现逻辑,我们首先能想到的是玩家点击按钮后程序立即调用上文中的 PopOutAnime() 函数来播放弹窗特效。但这样存在一个问题是,PopOutAnime() 被调用后会在一帧内执行完毕。也就是说在玩家点击按钮后的下一帧里,对话框会在一帧的时间内走完整个滑动流程。玩家所能看到的是自己点击按钮后,对话框立马凭空出现在屏幕上。这明显是不对的。

我们希望的是玩家点击按钮后,PopOutAnime() 能够“缓慢地”执行,这样玩家才能完整地看到整个弹窗特效。在这种情况下,异步(Asynchronous) 运行的强大之处就得以体现了。 在Unity中,我们可以通过创建 协程(Coroutines) 来异步运行任务。

从定义上来讲,Unity的协程是一个能够暂停执行,在暂停结束后又能立即恢复执行的函数。当我们开启了一个协程后,主函数会开始执行协程中的内容,直到协程被暂停。协程被暂停后会立即返回到主函数,并继续执行主函数的剩余部分,直到暂停状态结束,协程恢复执行。通过协程的这种特性,我们便可以实现 PopOutAnime() 的“缓慢执行”。先上代码:

private float anime_time = 0.5f;
private Button help_button_;
private GameObject help_panel_;
public AnimationCurve anime_curve;

void Start() {
	help_button_.onClick.AddListener(() => {StartCoroutine(PopOutAnime(help_panel_));});
}

IEnumerator PopOutAnime(GameObject panel) {
    float timer = 0.0f;
    Vector3 panel_pos = panel.transform.position;
    Vector3 target_pos = panel_pos - new Vector3(0.0f, panel_distance_, 0.0f);

    while (timer < anime_time) {
        timer += Time.deltaTime;

        if (timer > anime_time)
            timer = anime_time;

        float lerp_ration = timer / anime_time;

        panel.transform.position = Vector3.Lerp(panel_pos, target_pos, anime_curve.Evaluate(lerp_ration));
        
        yield return null;
    }
    yield break;
}

在上述代码中,我们为按钮添加了监听器。当按钮监听到了玩家的点击事件,会就为 PopOutAnime() 开启一个协程。在协程内部,当 while() 循环每执行完一次,协程就会通过 yield return null 指令被暂停,直到当前帧结束,下一帧开始后,协程才会继续执行 while() 循环的剩余部分。当循环结束后,即对话框已经到达了指定位置,协程通过 yield break 结束掉自己,以释放系统资源。

通过这样 PopOutAnime() 函数在每一帧只会令对话框移动一小段距离。最终呈现在玩家面前的就是正常的滑出效果了。


最终效果

在这里插入图片描述


参考资料

Unity中协程(IEnumerator)的使用方法介绍: https://blog.csdn.net/beihuanlihe130/article/details/76098844?spm=1001.2014.3001.5502
Unity UGUI 按钮绑定事件的 4 种方式:https://www.cnblogs.com/isayes/p/6370168.html
关于unity中AddListener的传参问题的研究: https://blog.csdn.net/qq_42097011/article/details/103712333?spm=1001.2014.3001.5501

原创博客,不得转载、抄袭

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值