使用动画实现简单的场景转换的思路是怎样的?

使用动画实现简单的场景转换的思路是怎样的?

文章目录

前言

大家好,我是FEZ98。

由于今天在一个小项目中需要实现场景转换效果,所以使用Animator简单的实现了这个需求。 同时也想和大家分享一下今天的收获。 如有错误,希望朋友指正。

使用动画实现简单场景转换的思路是在场景中添加一个处理转换效果的Image(UGUI),同时为它添加一个Animator,在Aniamtor中添加两个State,一个设置为设置为Layer Defalut State,即默认状态是播放进入场景后需要的效果,另一个设置为关闭场景需要的效果,添加转场,并设置相应的参数,这样当我们加载下一个场景,我们只需要在脚本中设置相应的参数即可调用关闭场景所需的效果。

1.制作LevelLoader

首先我们需要制作一个GameObject来存放场景转场效果需要的对象和脚本,同时我们可以添加一个Camera来单独显示转场效果。 如下所示:

二、制作相应的场景转场效果 1、淡入淡出效果

在LevelLoader中添加一个Canvas,命名为Crossfade,添加一个Image,命名为Mask_Img,并将这个Image设置为全黑,如下:

此时,我们可以通过Animator直接控制Image's Color的Alpha来调整图片的透明度,达到逐渐透明的效果,从而达到淡出淡出的效果。

但是,如果直接通过控制Alpha来调整透明度,如果后面我们需要在场景转场效果中添加一些文字或者图片,那么我们就需要分别设置这些新添加的组件的透明度,会很麻烦。 因此,我们可以添加一个像Mask_Img这样的Canvas Group组件,然后将需要添加的组件作为Mask_Img的子节点,这样就可以避免做一些多余的工作。

在Mask_Img中添加一个Text子组件,然后直接控制Canvas Group就可以看到效果了。 如下所示:

延期:

画布组提供对整个 UI 元素组的某些方面的集中控制,而无需单独处理每个元素。 画布组的属性会影响它所在的​​游戏对象以及所有子对象。

回到正文,添加Mask_Img后,我们可以通过Animator控制它的Canvas Group来制作动画,在Hierarchy中选中Crossfade对象,按Ctrl+6打开Animation。

然后命名为Crossfade_End,即创建一个Animator Clip,Unity会自动为我们创建一个名为Crossfade的Animator Controller,并将其添加到Crossfade自动添加的Animator组件中。

点击Animation Record按钮进入动画录制模式,当然你也可以点击Preview按钮进入预览模式。 然后在时间轴上选择1:00的位置,这串数字的第一个代表秒,第二个代表帧,1:00就是1秒60帧。

然后将Mask_Img的Canvas Group中的Alpha值由1改为0,这样就完成了淡入场景过渡的动画效果。

之后,我们可以先点击选中0:00处的关键帧,按住Shift键,点击1:00处的关键帧,按Ctrl+C进行复制。 然后创建一个新的动画剪辑并将其命名为 Crossfade_Start。

然后将两个关键帧的位置反转,这样我们就完成了Crossfade_Start的制作。

打开Crossfade Animator Controller进入Animator界面,这里我们设置动画的过渡状态,添加一个'Trigger'参数,命名为Start,控制Animator在切换场景时播放Crossfade_Start效果。

然后右击Make Transition from Crossfade_End to Crossfade_Start,Transition设置如下

然后我们取消勾选Crossfade_End和Crossfade_Start的Loop Time,因为我们只需要他们播放一次。

好了,这样我们就完成了淡入淡出的动画过渡效果。 当然目前还不能真正应用到场景中,因为还缺少控制脚本。 我们会在第三部分讲到同步和异步加载场景的控制脚本。

2. 循环擦除效果

首先,我们使用Photoshop制作一张png图片如下。

之后unity加载场景进度条,按照上述步骤创建一个名为CircleWipe的Canvas,然后在其中添加一个椭圆形的图片,使其能够完全覆盖CircleWipe。

之后将它拖到右边,这样它就不会出现在 CircleWipe 中。

我们创建一个Aniamtor Override Controller,它的作用是扩展Animator Controller,这样我们就可以用新的动画替换原来Animator Controller中使用的动画游戏素材,但保留其原有的结构、参数和逻辑。

之后将其添加到 CircleWipe 的 Animator

下面我们制作一个名为 CircleWipe_End 的动画,它将一个椭圆从 Canvas 的中间向左移动。

然后我们复制CircleWipe_End的关键帧unity加载场景进度条,同时反转。 请注意,此处 0:00 关键帧的位置是相反的,因此它从右侧移动到中心。

之后,我们设置CircleWipeAnimator Override Controller游戏素材,使用CrossfadeAnimator Controller 作为原来的控制器,然后用我们的两个新动画来替换它。

这样,我们就完成了圆形擦除场景转场效果的制作。

3. Logo旋转效果

Logo旋转场景转换效果步骤与循环擦除大致相同。 下面是LogoRotate_End和LogoRotate_Start的Animation Clips。

三、编写控制场景转换效果的脚本 1、同步加载场景 (1)在LevelLoader中添加控制脚本。

public class LoadLevelsManager : MonoBehaviour
{
    Animator animator;
    private void Awake()
    {
        animator = GameObject.Find("Crossfade").GetComponent<Animator>();
    }
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            LoadNextLevel();
        }
    }
    public void LoadNextLevel()
    {
        StartCoroutine(LoadLvel(SceneManager.GetActiveScene().buildIndex + 1));
    }
    IEnumerator LoadLvel(int levelBuildIndex)
    {
        animator.SetTrigger("Start");   //播放淡出特效
        yield return new WaitForSeconds(1);   //等待1秒,因为单个场景转换特效的时长是1秒
        SceneManager.LoadScene(levelBuildIndex);   //加载Scenes In Build 中下一场景
    }
}

(2)新建一个场景,添加LevelLoader。

(3) 将场景添加到Build Settings。

(4) 最终淡入淡出效果。

(5) 最后的圆形橡皮擦效果。

(6) 最后的Logo旋转效果。

2. 异步加载场景 (1) 分析需求。

在这一部分,我们将在场景的异步加载过程中添加场景过渡效果。

首先我们设置Crossfade的Animator如下

其中 Crossfade_Idle 是一个什么都不做的动画。

因为每次新建一个场景,都需要在场景中添加LevelLoader prefab,会很麻烦,所以我考虑使用单例模式加上DontDestroyOnLoad方法,这样LevelLoader不会在第一次加载后被销毁。 自然不需要在新添加的场景中添加LevelLoader。 之后我们也希望通过一个LevelLoader来使用不同的场景转场动画,方便我们后续添加和使用新的场景转场动画。 所以我们需要添加一个脚本来控制不同场景的转场动画。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum Effect  //三种场景转换特效
{
    Crossfade = 0,  //淡入淡出
    CircleWipe = 1, //圆形擦除
    LogoRotate = 2, //Logo旋转
}
public class TransitionEffect : MonoBehaviour
{
    private static TransitionEffect _instance;
    public static TransitionEffect Instance { get { return _instance; } }
    Animator m_CrossfadeAnim;   //淡入淡出
    Animator m_CircleWipeAnim;  //圆形擦除
    Animator m_LogoRotateAnim;  //Logo旋转
    private void Awake()
    {
        if (_instance == null)
        {
            DontDestroyOnLoad(this);
            _instance = this;
        }
        else
        {
            Destroy(this);
        }
        m_CrossfadeAnim = GameObject.Find("Crossfade").GetComponent<Animator>();
        m_CircleWipeAnim = GameObject.Find("CircleWipe").GetComponent<Animator>();
        m_LogoRotateAnim = GameObject.Find("LogoRotate").GetComponent<Animator>();
    }
    /// 
    /// 播放指定类型场景转换结束动画
    /// 
    /// 场景转换特效类型
    public void HandleAllAnimationEnd(Effect effect)
    {
        switch (effect)
        {
            case Effect.Crossfade:
                SetTransitionEffectEnd(m_CrossfadeAnim);
                break;
            case Effect.CircleWipe:
                SetTransitionEffectEnd(m_CircleWipeAnim);
                break;
            case Effect.LogoRotate:
                SetTransitionEffectEnd(m_LogoRotateAnim);
                break;
            default:
                break;
        }
    }
    private void SetTransitionEffectEnd(Animator animator)
    {
        animator.SetTrigger("End");
        Debug.Log("AnimationClip_End is Start!");
    }
    /// 
    /// 播放指定类型场景转换开始动画
    /// 
    /// 场景转换特效类型
    public void HandleAllAnimationStart(Effect effect)
    {
        switch (effect)
        {
            case Effect.Crossfade:
                SetTransitionEffectStart(m_CrossfadeAnim);
                break;
            case Effect.CircleWipe:
                SetTransitionEffectStart(m_CircleWipeAnim);
                break;
            case Effect.LogoRotate:
                SetTransitionEffectStart(m_LogoRotateAnim);
                break;
            default:
                break;
        }
    }
    private void SetTransitionEffectStart(Animator animator)
    {
        animator.SetTrigger("Start");
        Debug.Log("AnimationClip_Start is Start!");
    }
    /// 
    /// 判断当前动画是否完成播放
    /// 
    /// 场景转换特效类型
    /// 
    public bool HandleAllAnimationDone(Effect effect)
    {
        switch (effect)
        {
            case Effect.Crossfade:
                return IsAnimationClipDone(m_CrossfadeAnim);
            case Effect.CircleWipe:
                return IsAnimationClipDone(m_CircleWipeAnim);
            case Effect.LogoRotate:
                return IsAnimationClipDone(m_LogoRotateAnim);
            default:
                break;
        }
        return false;
    }
    private bool IsAnimationClipDone(Animator animator)
    {
        //normalizedTime:整数部分为状态已循环的次数。小数部分为当前循环的进度百分比 (0-1)
        if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1)
        {
            return false;
        }
        Debug.Log("AnimationClip is Over!");
        return true;
    }
}

代码比较简单,已经注释了,就不详细解释了。 TransitionEffect主要使用单例模式。 使用 DontDestroyOnLoad,它可以在运行时始终存在。 我们不需要像同步加载的场景一样不断的给新创建的场景添加LoadLevelManager。

接下来就是控制场景的异步加载和选择使用的场景转场效果。

LoadNextLevel(string levelName, Effect effect)方法有两个参数,一个是要加载的下一个场景的名称,另一个用于控制场景过渡效果的类型(淡入淡出、圆圈擦除、Logo旋转等)。 ).

LoadLevel(string levelName, Effect effect) 是一个实现异步加载的协程。 我们需要在启动异步加载后将 allowSceneActivation 设置为 false,这样 asyncOperation.progre 就会在 0.9 处停止,而 asyncOperation.isDone 将始终为 false。 将 LoadAble 设置为 false,以便在当前协程完成执行之前无法调用加载另一个场景的方法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class AsyncLoadLevelManager : MonoBehaviour
{
    private static AsyncLoadLevelManager _instance;
    public static AsyncLoadLevelManager Instance { get { return _instance; } }
    public bool LoadAble { get; private set; }  //当前是否可以场景转换
    private void Awake()
    {
        if (_instance == null)
        {
            DontDestroyOnLoad(this);
            _instance = this;
        }
        else
        {
            Destroy(this);
        }
    }
    private void Start()
    {
        LoadAble = true;    //初始允许场景转换
    }
    /// 
    /// 使用指定场景转换特效异步加载下一场景
    /// 
    /// 下一场景名称
    /// 场景转换特效类型
    public void LoadNextLevel(string levelName, Effect effect)
    {
        if (!LoadAble)
        {
            return;
        }
        StartCoroutine(LoadLevel(levelName, effect));
    }
    IEnumerator LoadLevel(string levelName, Effect effect)
    {
        AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(levelName); //异步加载下一场景
        asyncOperation.allowSceneActivation = false;
        //当allowSceneActivation为false时,asyncOperation.progre会停在0.9并且asyncOperation.isDone也会一直为false
        LoadAble = false;   //当前不允许转换场景
        TransitionEffect.Instance.HandleAllAnimationStart(effect);  //播放场景转换开始动画
        yield return null;
        while (asyncOperation.progress < 0.9f)  //检查是否已完成异步加载
        {
            yield return null;
        }
        while (!TransitionEffect.Instance.HandleAllAnimationDone(effect))   //检查当前动画是否完成播放
        {
            yield return null;
        }
        asyncOperation.allowSceneActivation = true; //允许场景准备就绪后立即激活场景
        while (!asyncOperation.isDone)  //检查是否已经激活场景
        {
            yield return null;
            TransitionEffect.Instance.HandleAllAnimationEnd(effect);    //播放场景转换结束动画
        }
        while (!TransitionEffect.Instance.HandleAllAnimationDone(effect))   //检查当前动画是否完成播放
        {
            yield return null;
        }
        LoadAble = true;    //可以继续转换场景
    }
}

之后,我们使用测试脚本来调用该方法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))    //点击鼠标左键
        {
            AsyncLoadLevelManager.Instance.LoadNextLevel("SceneB", Effect.Crossfade);
        }
        else if (Input.GetMouseButtonDown(1))   //点击鼠标右键
        {
            AsyncLoadLevelManager.Instance.LoadNextLevel("SceneC", Effect.CircleWipe);
        }
        else if (Input.GetMouseButtonDown(2))   //点击鼠标中键
        {
            AsyncLoadLevelManager.Instance.LoadNextLevel("SceneA", Effect.LogoRotate);
        }
    }
}

最终效果如下:

扩张

背景图像移动

using UnityEngine;
using UnityEngine.UI;
public class BackgroundMove : MonoBehaviour
{
   public float speed = 0; //移动速度
   float pos = 0;  //当前位置
   private RawImage image;
   void Start()
   {
       image = GetComponent<RawImage>();
   }
   void Update()
   {
       pos += speed;
       if (pos > 1.0F)
           pos -= 1.0F;
       image.uvRect = new Rect(pos, 0, 1, 1);
   }
}

四、完成