M_Studio的教程:【《迷失岛2》游戏框架开发01:实现场景转换|Unity教程】
使用下载好的素材搭建场景H1-H4和H2A、永久的场景Persistent,将场景都拖拽到Hierachy面板,只加载和H1和Persistent场景,将H1作为激活场景,并给每个场景中的场景切换图标添加一个带2DCollider的空物体,只保留Persistent的场景中的相机,其余的场景的相机都删除,将相机的背景颜色设置为黑色,Size设置为5.4,Game面板的展示大小为1920*1080,这样图片就可以刚好完全展示在屏幕中。
实现逻辑:当点击对应的图标的时候,检测到其对应的碰撞体,如果这个碰撞体的游戏对象的标签为指定的类型标签则调用场景转换的方法。由于每个场景中都存在场景转换,所以场景转换的类为单例,便于调用
创建脚本:SingleTon.cs、CursorManager.cs、Teleport.cs、TransitionManager.cs。
// SingleTon.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : Singleton<T> // 类型约束,类T必须继承Singleton
{
// 单例
protected static T instance;
// 设置只读属性
public static T Instance
{
get { return instance; }
}
private void Awake()
{
if (instance == null)
{
instance = (T)this;
}
else
{
Destroy(this);
}
DontDestroyOnLoad(this);
}
// 是否创建实例
private bool IsInitial()
{
return instance != null;
}
// 销毁单例
protected void OnDestroy()
{
if (instance == this)
{
instance = null;
}
}
}
CursorManager.cs脚本编辑:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CursorManager : MonoBehaviour
{
// 将鼠标点击的屏幕坐标转换成世界坐标
private Vector2 mouseWorldPoint => Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0));
// 如果鼠标点击了
private bool isClick;
private Collider2D coll;
// Update is called once per frame
void Update()
{
isClick = ObjectAtMouseClick();
if (isClick && Input.GetMouseButton(0))
{
// 根据游戏对象的不同标签来判断执行什么操作
coll = ObjectAtMouseClick();
ClickAction(coll.gameObject);
}
}
public void ClickAction(GameObject clickObject)
{
switch (coll.gameObject.tag)
{
case "Teleport":
// 获取对象上的Teleport组件,执行场景切换方法
Teleport t = clickObject.GetComponent<Teleport>();
t?.TransitionScene();
break;
default:
break;
}
}
public Collider2D ObjectAtMouseClick()
{
return Physics2D.OverlapPoint(mouseWorldPoint);
}
}
每个场景中的切换图标物体都添加脚本Teleport.cs ,FIle—>Build Setting面板中点击“Add Open Scenes”将所有的场景都添加进去
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SceneName
{
H1,
H2,
H3,
H4,
H2A,
}
public class Teleport : MonoBehaviour
{
// 现在处于的场景
public SceneName from;
// 要切换到的场景
public SceneName to;
public void TransitionScene()
{
// 场景切换
TransitionManager.Instance.TransitionScene(from, to);
}
}
编辑脚本TransitionManager.cs,实现场景切换且未添加淡入淡出,未实现淡入淡出效果的时候的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class TransitionManager : Singleton
{
// 启动协程
public void TransitionScene(SceneName from, SceneName to)
{
// 如果实现切换场景淡入淡出效果则不能进行场景切换
StartCoroutine(SwitchScene(from, to));
}
///
/// 切换场景
///
///
///
///
public IEnumerator SwitchScene(SceneName from, SceneName to)
{
// 屏蔽鼠标操作
anim.gameObject.GetComponent().raycastTarget = true;
// 卸载当前场景
yield return AsyncOperation unloadScene = SceneManager.UnloadSceneAsync(from.ToString());
// 加载要加载的场景
yield return AsyncOperation loadScene = SceneManager.LoadSceneAsync(to.ToString(), LoadSceneMode.Additive);
// 激活场景
Scene newScene = SceneManager.GetSceneByName(to.ToString()); // 通过场景名称来获取场景
SceneManager.SetActiveScene(newScene);
}
}
场景切换是正常的
但是加入淡入淡出相关函数的代码(如下)之后,
public IEnumerator SwitchScene(SceneName from, SceneName to)
{
// 当设置好场景后就加入Fade,1是全黑,0是透明
yield return Fade(1);
// 卸载当前场景
yield return SceneManager.UnloadSceneAsync(from.ToString());
// 加载要加载的场景
yield return SceneManager.LoadSceneAsync(to.ToString(), LoadSceneMode.Additive);
// 激活场景
// Scene newScene = SceneManager.GetSceneByName(to.ToString()); // 通过场景名称来获取场景
Scene newScene = SceneManager.GetSceneByBuildIndex((int)to); // 通过场景在build setting中的序号来获取场景,使用这个也会经常报错
if (newScene == null)
{
SceneManager.SetActiveScene(newScene);
}
// 恢复场景可见
yield return Fade(0);
// 鼠标点击有效
cp.blocksRaycasts = false;
}
///
/// 设置转场时的遮罩颜色渐变
///
///
private IEnumerator Fade(float alpha)
{
// 正在开始淡入特效
isFade = true;
// 屏蔽所有的鼠标点击
cp.blocksRaycasts = true;
// 计算淡入速度, 现在的alpha与目标alpha的差值再除以持续时间得到Fade速度
float speed = Mathf.Abs(cp.alpha - alpha) / fadeDuaration;
while (Mathf.Approximately(cp.alpha, alpha))
{
// 线性差值来实现Fade
cp.alpha = Mathf.Lerp(cp.alpha, alpha, speed * Time.deltaTime);
yield return null;
}
// 淡出后就重新设置isFade
isFade = false;
}
出现报错:ArgumentException: Scene to unload is invalid
具体是这句代码“yield return SceneManager.UnloadSceneAsync(from.ToString());”中的所获取到的Scene无效
查阅网上找到信息:
使用SceneManager.LoadSceneAsync的时候,设置了allowSceneActivation要注意使用它以后的特点:使用它之后
Yield Return
AsyncOperation是永远不会有返回的,意味着程序会卡在这句话上,而且如果有多个AsyncOperation在排队进行,注意在进行中的如果没有结束下一个不会开始,使用UnloadSceneAsync的时候,如果当前只有一个激活的场景,另一个场景因为allowSceneActivation设置为false等还没加载完毕,那UnloadSceneAsync不会正常执行并且会返回空,
此外如果某个AsyncOperation一直进度为0,则它前面一般是有个没有执行完的AsyncOperation,使用要特别注意特点,相关详见Unity
Scripting API搜索allowSceneActivation,LoadSceneAsync和UnloadSceneAsync
仔细看它们的说明和使用示例。 ———————————————— 版权声明:本文为CSDN博主「Peter_Gao_」的原创文章,遵循CC
4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_42672770/article/details/116023292
以为是AsyncOperation没有执行完,所以设置了如下代码检测执行完毕之后再运行
public void TransitionScene(SceneName from, SceneName to)
{
// 如果实现切换场景淡入淡出效果则不能进行场景切换
if (!isFade)
StartCoroutine(SwitchScene2(from, to));
}
///
/// 切换场景
///
///
///
///
public IEnumerator SwitchScene2(SceneName from, SceneName to)
{
yield return Fade(1);
// 卸载当前场景
AsyncOperation unloadScene = SceneManager.UnloadSceneAsync(from.ToString());
while (!unloadScene.isDone)
{
yield return null;
}
// 加载要加载的场景
AsyncOperation loadScene = SceneManager.LoadSceneAsync(to.ToString(), LoadSceneMode.Additive);
loadScene.allowSceneActivation = false;
while (!loadScene.isDone)
{
yield return null;
loadScene.allowSceneActivation = true;
}
// 激活场景
Scene newScene = SceneManager.GetSceneByName(to.ToString()); // 通过场景名称来获取场景
// Scene newScene = SceneManager.GetSceneByBuildIndex((int)to); // 通过场景在build setting中的序号来获取场景
bool isActive = SceneManager.SetActiveScene(newScene);
while (isActive)
{
// 恢复场景可见
yield return Fade(0);
// 鼠标点击有效
cp.blocksRaycasts = false;
}
}
点击切换场景出现报错:
ArgumentException: Scene to unload is invalid
UnityEngine.SceneManagement.SceneManagerAPI.UnloadSceneAsyncByNameOrIndex
(System.String sceneName, System.Int32 sceneBuildIndex, System.Boolean
immediately, UnityEngine.SceneManagement.UnloadSceneOptions options,
System.Boolean& outSuccess) (at <9c2babab467c4d89beebe2f694dc090f>:0)
UnityEngine.SceneManagement.SceneManager.UnloadSceneNameIndexInternal
(System.String sceneName, System.Int32 sceneBuildIndex, System.Boolean
immediately, UnityEngine.SceneManagement.UnloadSceneOptions options,
System.Boolean& outSuccess) (at <9c2babab467c4d89beebe2f694dc090f>:0)
UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync
(System.String sceneName) (at <9c2babab467c4d89beebe2f694dc090f>:0)
TransitionManager+d__5.MoveNext () (at
Assets/Scripts/Transition/TransitionManager.cs:82)
最后找不出原因,只要“yield return Fade(1);” 和“ Scene newScene = SceneManager.GetSceneByBuildIndex((int)to); // 通过场景在build setting中的序号来获取场景,使用这个就会经常报错,视频教程中的另一个建议就是使用StatCrountine去启动Fade,修改之后场景切换正常进行,没有报错但是没有淡入淡出效果
public void TransitionScene(SceneName from, SceneName to)
{
// 进行淡入淡出
StartCoroutine(Fade(1));
// 如果实现切换场景淡入淡出效果则不能进行场景切换
if (!isFade)
StartCoroutine(SwitchScene(from, to));
}
///
/// 切换场景
///
///
///
///
public IEnumerator SwitchScene(SceneName from, SceneName to)
{
// 卸载当前场景
AsyncOperation unloadScene = SceneManager.UnloadSceneAsync(from.ToString());
while (!unloadScene.isDone)
{
yield return null;
}
// 加载要加载的场景
AsyncOperation loadScene = SceneManager.LoadSceneAsync(to.ToString(), LoadSceneMode.Additive);
loadScene.allowSceneActivation = false;
while (!loadScene.isDone)
{
yield return null;
loadScene.allowSceneActivation = true;
}
// 激活场景
Scene newScene = SceneManager.GetSceneByName(to.ToString()); // 通过场景名称来获取场景
bool isActive = SceneManager.SetActiveScene(newScene);
while (isActive)
{
// 恢复场景可见
yield return Fade(0);
// 鼠标点击有效
cp.blocksRaycasts = false;
}
}
最后实在解决不了这个问题,就通过别的方式来实现淡入淡出效果——播放淡入和淡出动画。参考教程:【Unity教程:制作场景切换过度效果】
制作panel的淡入和淡出动画,就是将panel的image组件的color属性alpha改变的两个动画——fadeIn和fadeOut,在animator中创建一个空的动画,然后使Entry指向空动画,空动画设置参数fadeIn为true时播放淡入动画,fadeOut= true时播放淡出动画。动画时长可以设置为0.1s。
重新编辑代码,对于动画的代码设置,直接获取TransitionManager中直接获取panel的状态机,用代码设置播放淡入淡出动画
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;// 可以访问到panel的UI
// TransitionManager.cs
public class TransitionManager : Singleton<TransitionManager>
{
// panel上的淡入淡出的动画状态机,在面板中拖拽添加
public Animator anim;
// 等待时长
public float waitTime = 0.1f;
WaitForSeconds wait;
private void Start()
{
// 较频繁使用的引用类型作
wait = new WaitForSeconds(waitTime);
}
// 启动协程
public void TransitionScene(SceneName from, SceneName to)
{
// 如果实现切换场景淡入淡出效果则不能进行场景切换
StartCoroutine(SwitchScene(from, to));
}
// private IEnumerator Fade()
// {
// anim.SetBool("fadeIn", true);
// anim.SetBool("fadeOut", false);
// anim.gameObject.GetComponent().raycastTarget = true;
// yield return wait;
// }
///
/// 切换场景
///
///
///
///
public IEnumerator SwitchScene(SceneName from, SceneName to)
{
// 播放淡入动画
anim.SetBool("fadeIn", true);
anim.SetBool("fadeOut", false);
// 屏蔽鼠标操作
anim.gameObject.GetComponent<Image>().raycastTarget = true;
// 卸载当前场景
AsyncOperation unloadScene = SceneManager.UnloadSceneAsync(from.ToString());
while (!unloadScene.isDone)
{
yield return wait;
}
// 加载要加载的场景
AsyncOperation loadScene = SceneManager.LoadSceneAsync(to.ToString(), LoadSceneMode.Additive);
loadScene.allowSceneActivation = false;
while (!loadScene.isDone)
{
yield return null;
loadScene.allowSceneActivation = true;
}
// 播放淡出动画
loadScene.completed += OnLoadedScene;
// 激活场景
Scene newScene = SceneManager.GetSceneByName(to.ToString()); // 通过场景名称来获取场景
SceneManager.SetActiveScene(newScene);
}
private void OnLoadedScene(AsyncOperation async)
{
anim.SetBool("fadeOut", true);
anim.SetBool("fadeIn", false);
anim.gameObject.GetComponent<Image>().raycastTarget = false;
}
}
后面在案例中了解到AsyncOperation.completed,感觉可以尝试新的方法。
另外就是在第二集的时候发现一件事情:点击切换场景的按钮的collider没有勾选IsTrigger。关于其他博主对IsTrigger的了解:链接。