欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

[Unity 演示]从零开始制作《空洞骑士》 第 14 集:制作新场景和创建切换管理系统

最编程 2024-10-18 12:00:33
...

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、制作新的场景
    • 1.重新翻新各种Sprite
    • 2.制作地图前期应该做的事情
    • 3.疯狂的制作地图
  • 二、制作场景切换管理系统
    • 1.制作场景切换点TransitionPoint
    • 2.切换场景时的脚本逻辑处理
  • 总结


前言

hello大家好久没见,之所以隔了这么久才更新并不是因为我又放弃了这个项目,而是接下来要制作的工作太忙碌了,每次我都花了很长的时间解决完一个部分,然后就没力气打开****写文章就直接睡觉去了,现在终于有时间整理下我这半个月都做了什么内容,其实不看我上一篇文章都没发觉我的进程已经快了这么多啊,那么我就顺着上一篇文章开讲现在完成的场景转换系统吧。

顺便提醒,我的Github已经更新了,想要查看最新的内容话请到我的Github主页下载工程吧:

GitHub - ForestDango/Hollow-Knight-Demo: A new Hollow Knight Demo after 2 years!

一、制作新的场景

1.重新翻新各种Sprite

        如果你还记得我之前讲过有关tk2dSpirte的时候,我的很多图都是直接PS扣下来的,因此不仅图像模糊,还可能会漏动画帧的情况,这里我们就对之前讲过的一些要用到的tk2dSpirte重新制作,步骤都是一样的,删除原本tk2dSpirte里面的sprite,把我们新搞到手的sprite拖进去然后commit提交,重新制作动画:

说个搞笑的我之前的waterdrip没扣干净会有马赛克,现在我们终于有了一手新的waterdrip的Sprite:

这个生命水之前也没有扣干净。。而且动画还很鬼畜,这下问题都解决了

然后再重新制作他们的动画就好了 

2.制作地图前期应该做的事情 

接下来就是要制作我们游戏主角的城镇德特茅斯,我们就创建一个新的场景名字就叫Town,添加场景后的第一件事情就是导入到Build Setting中:

然后我们就可以使用tk2dTilemap来制作地图基本的模样了:

 为了效率,我把Tilemap里面的Tile Properties的Size调整成64x64了,这样方便我们更快的画地图:

 3.疯狂的制作地图

有了这个tilemap我们很好的画出了一个地图基本的模样以及地图基本的碰撞框。你已经学会了怎么绘制一张图前期该做的事了,首先创建一个原点位置的游戏对象_Scenery,然后就是疯狂的制作,疯狂的堆叠素材,

        下面试着画出这样的地图吧:

对于城镇,我们少见的可交互对象只有草,还有NPC和几个房屋,但这些我都还没做到,所以我等着后面做UI的时候再着手制作吧。还有别忘了添加Directional Light让场景亮起来。

二、制作场景切换管理系统

1.制作场景切换点TransitionPoint

        我们打算做的事情就是在场景的每个门的位置,不管它是上下左右,是真正的门还是虚空的门,我们都用一个TransitionPoint来管理他们,OK话不多说开始吧,

        首先回到我们之前创建的场景教学关Tutorial_01:中,在出口的转移点,初始的转移点,和隐藏的转移点添加好一个TransitionPoint.cs,而且它们需要亮光来引导玩家往这走,同时它们也能是碰刺复活hazardRespawn的复活点,而且它们还要能阻止敌人往这边走(就是怕敌人掉出地图外了的意思)

这里就以门后的转移点为例,这个转移点就是到Town的,然后它的三个子对象分别实现我上述的三个功能。

 

脚本方面我们创建名字叫TransitionPoint .cs:

using System;
using System.Collections.Generic;
using GlobalEnums;
using UnityEngine;
using UnityEngine.Audio;

public class TransitionPoint : MonoBehaviour
{
    private GameManager gm;
    private PlayerData playerData;
    private bool activated;

    [Header("Door Type Gate Settings")]
    [Space(5f)]
    public bool isADoor; //是否是个门,意思是当你到传送点的时候还需要UI提示后按键输入才能触发转移
    public bool dontWalkOutOfDoor; //不要走出门

    [Header("Gate Entry")]
    [Tooltip("The wait time before entering from this gate (not the target gate).")]
    public float entryDelay; //转移后的延迟
    public bool alwaysEnterRight; //进入这个转移点总是朝右看
    public bool alwaysEnterLeft; //进入这个转移点总是朝左看

    [Header("Force Hard Land (Top Gates Only)")]
    [Space(5f)]
    public bool hardLandOnExit; //强制重着地

    [Header("Destination Scene")]
    [Space(5f)]
    public string targetScene; //目标场景
    public string entryPoint; //进入的点
    public Vector2 entryOffset; //进入时的偏移量

    [SerializeField] private bool alwaysUnloadUnusedAssets;
    public PlayMakerFSM customFadeFSM; //自定义Fade的playmakerFSM

    [Header("Hazard Respawn")]
    [Space(5f)]
    public bool nonHazardGate; //这个门不能用来做HazardRespawn的重生点
    public HazardRespawnMarker respawnMarker;

    [Header("Set Audio Snapshots")]
    [Space(5f)]
    public AudioMixerSnapshot atmosSnapshot;
    public AudioMixerSnapshot enviroSnapshot;
    public AudioMixerSnapshot actorSnapshot;
    public AudioMixerSnapshot musicSnapshot;

    private Color myGreen = new Color(0f, 0.8f, 0f, 0.5f);

    private static List<TransitionPoint> transitionPoints;
    public static string lastEntered = ""; //记录最后进入的TransitionPoint

    public delegate void BeforeTransitionEvent();
    public event BeforeTransitionEvent OnBeforeTransition;

    public static List<TransitionPoint> TransitionPoints
    {
	get
	{
	    return transitionPoints;
	}
    }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void Init()
    {
	    transitionPoints = new List<TransitionPoint>();
    }

    protected void Awake()
    {
	    transitionPoints.Add(this);
    }

    protected void OnDestroy()
    {
	    transitionPoints.Remove(this);
    }

    private void Start()
    {
	    gm = GameManager.instance;
	    playerData = PlayerData.instance;
	    if(!nonHazardGate && respawnMarker == null)
	    {
	        Debug.LogError(string.Concat(new string[]
	        {
		    "Transition Gate ",
		    name,
		    " in ",
		    gm.sceneName,
		    " does not have its respawn marker set in inspector."
	        }));
	    }
    }

    private void OnTriggerEnter2D(Collider2D movingObj)
    {
//判断碰撞对象是否是Player的layer
	if(!isADoor && movingObj .gameObject.layer == 9 && gm.gameState == GameState.PLAYING)
	{
	    if(!string.IsNullOrEmpty(targetScene) && !string.IsNullOrEmpty(entryPoint))
	    {
		if (customFadeFSM)
		{
		    customFadeFSM.SendEvent("FADE");
		}
		if (atmosSnapshot != null)
		{
		    atmosSnapshot.TransitionTo(1.5f);
		}
		if (enviroSnapshot != null)
		{
		    enviroSnapshot.TransitionTo(1.5f);
		}
		if (actorSnapshot != null)
		{
		    actorSnapshot.TransitionTo(1.5f);
		}
		if (musicSnapshot != null)
		{
		    musicSnapshot.TransitionTo(1.5f);
		}
		activated = true;
		lastEntered = gameObject.name;
		if (OnBeforeTransition != null)
		{
		    OnBeforeTransition();
		}
		return;
	    }
	    Debug.LogError(gm.sceneName + " " + name + " no target scene has been set on this gate.");
	}
    }

    private void OnTriggerStay2D(Collider2D movingObj)
    {
	if (!activated)
	{
	    OnTriggerEnter2D(movingObj);
	}
    }

    private void OnDrawGizmos()
    {
	if (transform != null)
	{
	    Vector3 position = transform.position + new Vector3(0f, GetComponent<BoxCollider2D>().bounds.extents.y + 1.5f, 0f);
	    GizmoUtility.DrawText(GUI.skin, targetScene, position, new Color?(myGreen), 10, 0f);
	}
    }

    /// <summary>
    /// 获取当前门的位置,请注意你的TransitionPoint名字一定要有如下字段
    /// </summary>
    /// <returns></returns>
    public GatePosition GetGatePosition()
    {
	string name = base.name;
	if (name.Contains("top"))
	{
	    return GatePosition.top;
	}
	if (name.Contains("right"))
	{
	    return GatePosition.right;
	}
	if (name.Contains("left"))
	{
	    return GatePosition.left;
	}
	if (name.Contains("bot"))
	{
	    return GatePosition.bottom;
	}
	if (name.Contains("door") || isADoor)
	{
	    return GatePosition.door;
	}
	Debug.LogError("Gate name " + name + "does not conform to a valid gate position type. Make sure gate name has the form 'left1'");
	return GatePosition.unknown;
    }

    public void SetTargetSceneName(string newScene)
    {
	targetScene = newScene;
    }

}

然后我们就要给TransitionPoint设置好对应的layer,以及什么能和这个layer发生碰撞检测:

 设置好对应的参数:

回到Town创建中我们也来创建对应的TransitionPoint:

 看到这里,你是否发现有什么不对劲?第一,你就给两个TransitionPoint,我的SceneManager.LoadScene()呢?关有转移点没有转移有嘛用,第二,你的Tutorial_01的转移点在那个门后面,游戏里面都是打了几下门直接就到了Town场景根本就不用走过去,这就引出了我们下面要介绍的切换场景时的脚本逻辑处理

2.切换场景时的脚本逻辑处理

首先我们处理场景转换要用到SceneLoad.cs脚本,我们把切换创建分为六个阶段,分别代表六个事件:FetchComplete -> WillActivate ->  ActivationComplete ->   Complete ->  StartCalled  -> Finish

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneLoad
{
    public bool IsFetchAllowed { get; set; } //是否允许获取下一个场景
    public bool IsActivationAllowed { get; set; } //是否允许激活创建
    public bool IsUnloadAssetsRequired { get; set; } //是否需要卸载掉当前场景的Assets
    public float BeginTime { get; set; } //开启时间
    public bool IsGarbageCollectRequired { get; set; } //是否需要GC垃圾回收

    public delegate void FetchCompleteDelegate(); 
    public event FetchCompleteDelegate FetchComplete;

    public delegate void WillActivateDelegate();
    public event WillActivateDelegate WillActivate;

    public delegate void ActivationCompleteDelegate();
    public event ActivationCompleteDelegate ActivationComplete;

    public delegate void CompleteDelegate();
    public event CompleteDelegate Complete;

    public delegate void StartCalledDelegate();
    public event StartCalledDelegate StartCalled;

    public delegate void FinishDelegate();
    public event FinishDelegate Finish;

    private readonly MonoBehaviour runner;
    private readonly string targetSceneName;
    public const int PhaseCount = 8;
    private readonly PhaseInfo[] phaseInfos;
    public bool IsFinished { get; private set; }

    public SceneLoad(MonoBehaviour runner,string targetSceneName)
    {
	this.runner = runner;
	this.targetSceneName = targetSceneName;
	phaseInfos = new PhaseInfo[PhaseCount];
	for (int i = 0; i < PhaseCount; i++)
	{
	    phaseInfos[i] = new PhaseInfo
	    {
		BeginTime = null
	    };
	}
    }

    public void Begin()
    {
	runner.StartCoroutine(BeginRoutine());
    }

    private IEnumerator BeginRoutine()
    {
	RecordBeginTime(Phases.FetchBlocked);
	while (!IsFetchAllowed)
	{
	    yield return null;
	}
	RecordEndTime(Phases.FetchBlocked);
	RecordBeginTime(Phases.Fetch);
	AsyncOperation loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(targetSceneName, UnityEngine.SceneManagement.LoadSceneMode.Additive);
	loadOperation.allowSceneActivation = true;
	while(loadOperation.progress < 0.9f)
	{
	    yield return null;
	}
	RecordEndTime(Phases.Fetch);
	if (FetchComplete != null)
	{
	    try
	    {
		FetchComplete();
	    }
	    catch (Exception exception)
	    {
		Debug.LogError("Exception in responders to SceneLoad.FetchComplete. Attempting to continue load regardless.");
		Debug.LogException(exception);
	    }
	}
	RecordBeginTime(Phases.ActivationBlocked);
	while (!IsActivationAllowed)
	{
	    yield return null;
	}
	RecordEndTime(Phases.ActivationBlocked);
	RecordBeginTime(Phases.Activation);
	if(WillActivate != null)
	{
	    try
	    {
		WillActivate();
	    }
	    catch (Exception exception2)
	    {
		Debug.LogError("Exception in responders to SceneLoad.WillActivate. Attempting to continue load regardless.");
		Debug.LogException(exception2);
	    }
	}
	loadOperation.allowSceneActivation = true;
	yield return loadOperation;
	RecordEndTime(Phases.Activation);
	if(ActivationComplete != null)
	{
	    try
	    {
		ActivationComplete();
	    }
	    catch (Exception exception3)
	    {
		Debug.LogError("Exception in responders to SceneLoad.ActivationComplete. Attempting to continue load regardless.");
		Debug.LogException(exception3);
	    }
	}
	RecordBeginTime(Phases.UnloadUnusedAssets);
	if (IsUnloadAssetsRequired)
	{
	    AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();
	    yield return asyncOperation;
	}
	RecordEndTime(Phases.UnloadUnusedAssets);
	RecordBeginTime(Phases.GarbageCollect);
	if (IsGarbageCollectRequired)
	{
	    
	}
	RecordEndTime(Phases.GarbageCollect);
	if(Complete != null)
	{
	    try
	    {
		Complete();
	    }
	    catch (Exception exception4)
	    {
		Debug.LogError("Exception in responders to SceneLoad.Complete. Attempting to continue load regardless.");
		Debug.LogException(exception4);
	    }
	}
	RecordBeginTime(Phases.StartCall);
	yield return null;
	RecordEndTime(Phases.StartCall);
	if (StartCalled != null)
	{
	    try
	    {
		StartCalled();
	    }
	    catch (Exception exception5)
	    {
		Debug.LogError("Exception in responders to SceneLoad.StartCalled. Attempting to continue load regardless.");
		Debug.LogException(exception5);
	    }
	}
	IsFinished = true;
	if (Finish != null)
	{
	    try
	    {
		Finish();
		yield break;
	    }
	    catch (Exception exception8)
	    {
		Debug.LogError("Exception in responders to SceneLoad.Finish. Attempting to continue load regardless.");
		Debug.LogException(exception8);
		yield break;
	    }
	}
    }

    /// <summary>
    /// 记录开启转移的时间
    /// </summary>
    /// <param name="phase"></param>
    private void RecordBeginTime(Phases phase)
    {
	phaseInfos[(int)phase].BeginTime = new float?(Time.realtimeSinceStartup);
    }

    /// <summary>
    /// 记录结束转移后的时间
    /// </summary>
    /// <param name="phase"></param>
    private void RecordEndTime(Phases phase)
    {
	phaseInfos[(int)phase].EndTime = new float?(Time.realtimeSinceStartup);
    }

    private class PhaseInfo
    {
	public float? BeginTime;
	public float? EndTime;
    }

    public enum Phases
    {
	FetchBlocked,
	Fetch,
	ActivationBlocked,
	Activation,
	UnloadUnusedAssets,
	GarbageCollect,
	StartCall,
	LoadBoss
    }
}

 在TransitionPoint.cs的Trigger2D函数中,我们来写新的内容:

private void OnTriggerEnter2D(Collider2D movingObj)
    {
	if(!isADoor && movingObj .gameObject.layer == 9 && gm.gameState == GameState.PLAYING)
	{
	    if(!string.IsNullOrEmpty(targetScene) && !string.IsNullOrEmpty(entryPoint))
	    {
		if (customFadeFSM)
		{
		    customFadeFSM.SendEvent("FADE");
		}
		if (atmosSnapshot != null)
		{
		    atmosSnapshot.TransitionTo(1.5f);
		}
		if (enviroSnapshot != null)
		{
		    enviroSnapshot.TransitionTo(1.5f);
		}
		if (actorSnapshot != null)
		{
		    actorSnapshot.TransitionTo(1.5f);
		}
		if (musicSnapshot != null)
		{
		    musicSnapshot.TransitionTo(1.5f);
		}
		activated = true;
		lastEntered = gameObject.name;
		if (OnBeforeTransition != null)
		{
		    OnBeforeTransition();
		}
		gm.BeginSceneTransiton(new GameManager.SceneLoadInfo
		{
		    SceneName = targetScene,
		    EntryGateName = entryPoint,
		    HeroLeaveDirection = new GatePosition?(GetGatePosition()),
		    EntryDelay = entryDelay,
		    WaitForSceneTransitionCameraFade = true,
		    PreventCameraFadeOut = (customFadeFSM != null),
		    Visualization = sceneLoadVisualization,
		    AlwaysUnloadUnusedAssets = alwaysUnloadUnusedAssets,
		    forceWaitFetch = forceWaitFetch
		});
		return;
	    }
	    Debug.LogError(gm.sceneName + " " + name + " no target scene has been set on this gate.");
	}
    }

回到GameManager.cs中,我们创建一个类SceneLoadInfo来对应SceneLoad:

以及还有一些和场景转换相关的变量

public class GameManager : MonoBehaviour
{
...........
  public bool startedOnThisScene = true;
    public float sceneWidth;//场景宽度
    public float sceneHeight;//场景高度
    public tk2dTileMap tilemap{ get; private set; }
    private static readonly string[] SubSceneNameSuffixes = new string[]
	{
	    "_boss_defeated",
	    "_boss",
	    "_preload"
	};

    private SceneLoad sceneLoad;
    public bool RespawningHero { get; set; }
    public bool IsInSceneTransition { get; private set; }
    private bool isLoading;
    private int sceneLoadsWithoutGarbageCollect;
    private SceneLoadVisualizations loadVisualization;
    [Space]
    public string sceneName; //当前场景
    public string nextSceneName; //下一个场景
    public string entryGateName; //进入的门的名字(top,bot,left,right)
    private string targetScene; //目标创建
    private float entryDelay; //进入延迟
    private bool hasFinishedEnteringScene; //是否完成了进入场景的整套行为
    public bool HasFinishedEnteringScene
    {
	get
	{
	    return hasFinishedEnteringScene;
	}
    }

    private bool waitForManualLevelStart;

    public delegate void SceneTransitionBeganDelegate(SceneLoad sceneLoad);
    public static event SceneTransitionBeganDelegate SceneTransitionBegan;

    public delegate void SceneTransitionFinishEvent();
    public event SceneTransitionFinishEvent OnFinishedSceneTransition;

    public delegate void UnloadLevel();
    public event UnloadLevel UnloadingLevel;

    public delegate void EnterSceneEvent();
    public event EnterSceneEvent OnFinishedEnteringScene;
...........
    public class SceneLoadInfo
    {
	public bool IsFirstLevelForPlayer;
	public string SceneName;
	public GatePosition? HeroLeaveDirection;
	public string EntryGateName;
	public float EntryDelay;
	public bool PreventCameraFadeOut;
	public bool WaitForSceneTransitionCameraFade;
	public SceneLoadVisualizations Visualization;
	public bool AlwaysUnloadUnusedAssets;
	public bool forceWaitFetch;

	public virtual void NotifyFetchComplete()
	{
	}

	public virtual bool IsReadyToActivate()
	{
	    return true;
	}

	public virtual void NotifyFinished()
	{
	}
    
     public enum SceneLoadVisualizations
    {
	Default, //默认
	Custom = -1, //自定义
	Dream = 1, //梦境
	Colosseum, //斗兽场
	GrimmDream, //格林梦境
	ContinueFromSave, //从保存的数据中继续
	GodsAndGlory //神居
    }
}

 我们来实现BeginSceneTransition方法:

 public void BeginSceneTransition(SceneLoadInfo info)
    {

	if(info.IsFirstLevelForPlayer)
	{

	}
	Debug.LogFormat("BeginSceneTransiton EntryGateName =" + info.EntryGateName);
	StartCoroutine(BeginSceneTransitionRoutine(info));
    }

    private IEnumerator BeginSceneTransitionRoutine(SceneLoadInfo info)
    {
	if (sceneLoad != null)
	{
	    Debug.LogErrorFormat(this, "Cannot scene transition to {0}, while a scene transition is in progress", new object[]
	    {
		info.SceneName
	    });
	    yield break;
	}
	IsInSceneTransition = true;
	sceneLoad = new SceneLoad(this, info.SceneName);
	isLoading = true;
	loadVisualization = info.Visualization;
	if (hero_ctrl != null)
	{

	    hero_ctrl.proxyFSM.SendEvent("HeroCtrl-LeavingScene");
	    hero_ctrl.SetHeroParent(null);
	}
	if (!info.IsFirstLevelForPlayer)
	{
	    NoLongerFirstGame();
	}
	SaveLevelState();
	SetState(GameState.EXITING_LEVEL);
	entryGateName = info.EntryGateName ?? "";
	targetScene = info.SceneName;
	if (hero_ctrl != null)
	{
	    hero_ctrl.LeaveScene(info.HeroLeaveDirection);
	}
	if (!info.PreventCameraFadeOut)
	{
	    cameraCtrl.FreezeInPlace(true);
	    cameraCtrl.FadeOut(CameraFadeType.LEVEL_TRANSITION);
	}

	startedOnThisScene = false;
	nextSceneName = info.SceneName;
	waitForManualLevelStart = true;
	if (UnloadingLevel != null)
	{
	    UnloadingLevel();
	}
	string lastSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
	sceneLoad.FetchComplete += delegate ()
	{
	    info.NotifyFetchComplete();
	};
	sceneLoad.WillActivate += delegate ()
	{

	    entryDelay = info.EntryDelay;
	};
	sceneLoad.ActivationComplete += delegate ()
	{
	    UnityEngine.SceneManagement.SceneManager.UnloadScene(lastSceneName);
	    RefreshTilemapInfo(info.SceneName);
	    sceneLoad.IsUnloadAssetsRequired = (info.AlwaysUnloadUnusedAssets || IsUnloadAssetsRequired(lastSceneName, info.SceneName));
	    bool flag2 = false;
	    if (!sceneLoad.IsUnloadAssetsRequired)
	    {
		float? beginTime = sceneLoad.BeginTime;
		if (beginTime != null && Time.realtimeSinceStartup - beginTime.Value > 0f && sceneLoadsWithoutGarbageCollect < 0f)
		{
		    flag2 = false;
		}
	    }
	    if (flag2)
	    {
		sceneLoadsWithoutGarbageCollect = 0;
	    }
	    else
	    {
		sceneLoadsWithoutGarbageCollect++;
	    }
	    sceneLoad.IsGarbageCollectRequired = flag2;
	};
	sceneLoad.Complete += delegate ()
	{
	    SetupSceneRefs(false);
	    BeginScene();

	};
	sceneLoad.Finish += delegate ()
	{
	    sceneLoad = null;
	    isLoading = false;
	    waitForManualLevelStart = false;
	    info.NotifyFetchComplete();
	    OnNextLevelReady();
	    IsInSceneTransition = false;
	    if (OnFinishedSceneTransition != null)
	    {
		OnFinishedSceneTransition();
	    }
	};
	if(SceneTransitionBegan != null)
	{
	    try
	    {
		SceneTransitionBegan(sceneLoad);
	    }
	    catch (Exception exception)
	    {
		Debug.LogError("Exception in responders to GameManager.SceneTransitionBegan. Attempting to continue load regardless.");
		Debug.LogException(exception);
	    }
	}
	sceneLoad.IsFetchAllowed = (!info.forceWaitFetch && (info.PreventCameraFadeOut));
	sceneLoad.IsActivationAllowed = false;
	sceneLoad.Begin();
	float cameraFadeTimer = 0.5f;
	for (; ; )
	{
	    bool flag = false;
	    cameraFadeTimer -= Time.unscaledDeltaTime;
	    if (info.WaitForSceneTransitionCameraFade && cameraFadeTimer > 0f)
	    {
		flag = true;
	    }
	    if (!info.IsReadyToActivate())
	    {
		flag = true;
	    }
	    if (!flag)
	    {
		break;
	    }
	    yield return null;
	}
	sceneLoad.IsFetchAllowed = true;
	sceneLoad.IsActivationAllowed = true;
    }

 里面有几个需要实现的方法:首先是HeroController设置角色取消父对象的方法SetHeroParent:

  public void SetHeroParent(Transform newParent)
    {
        transform.parent = newParent;
        if (newParent == null)
        {
        DontDestroyOnLoad(gameObject);
        }
    }

不再是第一次玩游戏

 private void NoLongerFirstGame()
    {
    if (playerData.isFirstGame)
    {
        playerData.isFirstGame = false;
    }
    }

下面这个是这个要等到我们后续做到再实现的暂且TODO:

 public void SaveLevelState()
    {
    //TODO:
    }

回到GlobalEnum创建好数组记录当前游戏状态:

public enum GameState
    {
	INACTIVE, //非活跃
	MAIN_MENU, //主菜单
	LOADING, //加载
	ENTERING_LEVEL, //进入场景
	PLAYING, //游玩
	PAUSED, //暂停
	EXITING_LEVEL, //里面场景
	CUTSCENE, //过场
	PRIMER //先前的
    }

 再回到GameManager.cs中:

public void SetState(GameState newState)
    {
    gameState = newState;
    }

当激活完成后,我们就可以用到我们熟悉的UnityEngine.SceneManagement.SceneManager.UnloadScene(lastSceneName);这个是卸载当前的创建,方法RefreshTilemapInfo(string targetScene)是用来重新获取新场景的tk2dtilemap,如果你看过我们上一期就知道这个tk2dtilemap用来获取每一个场景的宽度和高度:


    /// <summary>
    /// 重新刷新场景的tilemap的信息
    /// </summary>
    /// <param name="targetScene"></param>
    public void RefreshTilemapInfo(string targetScene)
    {
	if (IsNonGameplayScene())
	{
	    return;
	}
	tk2dTileMap tk2dTileMap = null;
	int num = 0;
	while (tk2dTileMap == null && num < UnityEngine.SceneManagement.SceneManager.sceneCount)
	{
	    Scene sceneAt = UnityEngine.SceneManagement.SceneManager.GetSceneAt(num);
	    if (string.IsNullOrEmpty(targetScene) || !(sceneAt.name != targetScene))
	    {
		GameObject[] rootGameObjects = sceneAt.GetRootGameObjects();
		int num2 = 0;
		while (tk2dTileMap == null && num2 < rootGameObjects.Length)
		{
		    tk2dTileMap = GetTileMap(rootGameObjects[num2]);
		    num2++;
		}
	    }
	    num++;
	}
	if (tk2dTileMap == null)
	{
	    Debug.LogErrorFormat("Using fallback 1 to find tilemap. Scene {0} requires manual fixing.", new object[]
	    {
		targetScene
	    });
	    GameObject[] array = GameObject.FindGameObjectsWithTag("TileMap");
	    int num3 = 0;
	    while (tk2dTileMap == null && num3 < array.Length)
	    {
		tk2dTileMap = array[num3].GetComponent<tk2dTileMap>();
		num3++;
	    }
	}
	if (tk2dTileMap == null)
	{
	    Debug.LogErrorFormat("Using fallback 2 to find tilemap. Scene {0} requires manual fixing.", new object[]
	    {
		targetScene
	    });
	    GameObject gameObject = GameObject.Find("TileMap");
	    if (gameObject != null)
	    {
		tk2dTileMap = GetTileMap(gameObject);
	    }
	}
	if (tk2dTileMap == null)
	{
	    Debug.LogErrorFormat("Failed to find tilemap in {0} entirely.", new object[]
	    {
		targetScene
	    });
	    return;
	}
	tilemap = tk2dTileMap;
	sceneWidth = tilemap.width;
	sceneHeight = tilemap.height;
    }

    private static tk2dTileMap GetTileMap(GameObject gameObject)
    {
	if (gameObject.CompareTag("TileMap"))
	{
	    return gameObject.GetComponent<tk2dTileMap>();
	}
	return null;
    }

重新获取场景引用SetupSceneRefs:

 public void SetupSceneRefs(bool refreshTilemapInfo)
    {
	UpdateSceneName();
	if(ui == null)
	{
	    ui = UIManager.instance;
	}
	GameObject gameObject = GameObject.FindGameObjectWithTag("SceneManager");
	if(gameObject != null)
	{
	    sm = gameObject.GetComponent<SceneManager>();
	}
	else
	{
	    Debug.Log("Scene Manager missing from scene " + sceneName);
	}
	if (IsGameplayScene())
	{
	    if (hero_ctrl == null)
	    {
		SetupHeroRefs();
	    }
	    if (refreshTilemapInfo)
	    {
		RefreshTilemapInfo(sceneName);
	    }
	}
    }

 private void SetupHeroRefs()
    {
	hero_ctrl = HeroController.instance;

    }

 还有开启场景后需要做的事情BeginScene:

 public void BeginScene()
    {
	inputHandler.SceneInit();

	if (hero_ctrl)
	{
	    hero_ctrl.SceneInit();
	}
	gameCams.SceneInit();
	if (IsMenuScene())
	{
	    SetState(GameState.MAIN_MENU);
	    UpdateUIStateFromGameState();

	    return;
	}
	if (IsGameplayScene())
	{
	    if ((!Application.isEditor && !Debug.isDebugBuild) || Time.renderedFrameCount > 3)
	    {
		PositionHeroAtSceneEntrance();
	    }
	    if(sm != null)
	    {

		return;
	    }
	}
	else
	{
	    if (IsNonGameplayScene())
	    {
		SetState(GameState.CUTSCENE);
		UpdateUIStateFromGameState();
		return;
	    }
	    Debug.LogError("GM - Scene type is not set to a standard scene type.");
	    UpdateUIStateFromGameState();
	}
    }

我们通过场景名字来判断当前是什么类型的场景:

public bool IsMenuScene()
    {
	    UpdateSceneName();
	    return sceneName == "Menu_Title";
    }

    public bool IsGameplayScene()
    {
	    UpdateSceneName();
	    return !IsNonGameplayScene();
    }

    public bool IsNonGameplayScene()
    {
	    return IsCinematicScene() || sceneName == "Knight Pickup" || sceneName == "Pre_Menu_Intro" || sceneName == "Menu_Title" || sceneName == "End_Credits" || sceneName == "Menu_Credits" || sceneName == "Cutscene_Boss_Door" || sceneName == "PermaDeath_Unlock" || sceneName == "GG_Unlock" || sceneName == "GG_End_Sequence" || sceneName == "End_Game_Completion" || sceneName == "BetaEnd" || sceneName == "PermaDeath" || sceneName == "GG_Entrance_Cutscene" || sceneName == "GG_Boss_Door_Entrance";
    }

    public bool IsCinematicScene()
    {
	    UpdateSceneName();
	    return sceneName == "Intro_Cutscene_Prologue" || sceneName == "Opening_Sequence" || sceneName == "Prologue_Excerpt" || sceneName == "Intro_Cutscene" || sceneName == "Cinematic_Stag_travel" || sceneName == "PermaDeath" || sceneName == "Cinematic_Ending_A" || sceneName == "Cinematic_Ending_B" || sceneName == "Cinematic_Ending_C" || sceneName == "Cinematic_Ending_D" || sceneName == "Cinematic_Ending_E" || sceneName == "Cinematic_MrMushroom" || sceneName == "BetaEnd";
    }

   private void UpdateSceneName()
    {
	    sceneName = GetBaseSceneName(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
    }

 还需要让下一场景的内容做好准备OnNextLevelReady:

public void OnNextLevelReady()
    {
	if (IsGameplayScene())
	{
	    SetState(GameState.ENTERING_LEVEL);
	    playerData.disablePause = false;
	    inputHandler.AllowPause();
	    inputHandler.StartAcceptingInput();
	    Debug.LogFormat("OnNextLevelReady entryGateName =" + entryGateName);
	    EnterHero(true);
	}
    }

角色进入EnterHero():

 public void EnterHero(bool additiveGateSearch = false)
    {
	if (RespawningHero)
	{
	    StartCoroutine(hero_ctrl.Respawn());
	    FinishedEnteringScene();
	    RespawningHero = false;
	    return;
	}
	if (hazardRespawningHero)
	{
	    StartCoroutine(hero_ctrl.HazardRespawn());
	    FinishedEnteringScene();
	    hazardRespawningHero = false;
	    return;
	}
	if (startedOnThisScene)
	{
	    if (IsGameplayScene())
	    {
		FinishedEnteringScene();
		FadeSceneIn();
	    }
	    return;
	}
	SetState(GameState.ENTERING_LEVEL);
	if (string.IsNullOrEmpty(entryGateName))
	{
	    Debug.LogError("No entry gate has been defined in the Game Manager, unable to move hero into position.");
	    FinishedEnteringScene();
	    return;
	}
	if (additiveGateSearch)
	{
	    Debug.Log("Searching for entry gate " + entryGateName + " !in the next scene: " + nextSceneName);
	    foreach (GameObject gameObject in UnityEngine.SceneManagement.SceneManager.GetSceneByName(nextSceneName).GetRootGameObjects() )
	    {
		TransitionPoint component = gameObject.GetComponent<TransitionPoint>();
		if(component != null && component.name == entryGateName)
		{
		    Debug.Log("SUCCESS - Found as root object");
		    StartCoroutine(hero_ctrl.EnterScene(component, entryDelay));
		    return;
		}
		if(gameObject.name == "_Transition Gates")
		{
		    TransitionPoint[] componentsInChildren = gameObject.GetComponentsInChildren<TransitionPoint>();
		    for (int i = 0; i < componentsInChildren.Length; i++)
		    {
			if(componentsInChildren[i].name == entryGateName)
			{
			    Debug.Log("SUCCESS - Found in _Transition Gates folder");
			    StartCoroutine(hero_ctrl.EnterScene(componentsInChildren[i], entryDelay));
			    return;
			}
		    }
		}
		TransitionPoint[] componentsInChildren2 = gameObject.GetComponentsInChildren<TransitionPoint>();
		for (int j = 0; j < componentsInChildren2.Length; j++)
		{
		    if (componentsInChildren2[j].name == entryGateName)
		    {
			Debug.Log("SUCCESS - Found in _Transition Gates folder");
			StartCoroutine(hero_ctrl.EnterScene(componentsInChildren2[j], entryDelay));
			return;
		    }
		}
	    }
	    Debug.LogError("Searching in next scene for TransitionGate failed.");
	    return;
	}
	GameObject gameObject2 = GameObject.Find(entryGateName);
	if(gameObject2 != null)
	{
	    TransitionPoint component2 = gameObject2.GetComponent<TransitionPoint>();
	    StartCoroutine(hero_ctrl.EnterScene(component2, entryDelay));
	    return;
	}
	Debug.LogError(string.Concat(new string[]
	{
	    "No entry point found with the name \"",
	    entryGateName,
	    "\" in this scene (",
	    sceneName,
	    "). Unable to move hero into position, trying alternative gates..."
	}));
	TransitionPoint[] array = FindObjectsOfType<TransitionPoint>();
	if(array != null)
	{
	    StartCoroutine(hero_ctrl.EnterScene(array[0], entryDelay));
	    return;
	}
	Debug.LogError("Could not find any gates in this scene. Trying last ditch spawn...");
	hero_ctrl.transform.SetPosition2D(tilemap.width / 2f, tilemap.height / 2f);
    }

 public void FinishedEnteringScene()
    {
	SetState(GameState.PLAYING);
	entryDelay = 0f;
	hasFinishedEnteringScene = true;
	if (OnFinishedSceneTransition != null)
	{
	    OnFinishedSceneTransition();
	}
    }
 public void FadeSceneIn()
    {
	cameraCtrl.FadeSceneIn();
    }

 回到HeroController.cs中,首先我们要制作玩家离开创建时的行为:

 public void LeaveScene(GatePosition? gate = null)
    {
        isHeroInPosition = false;
        IgnoreInputWithoutReset();
        ResetHardLandingTimer();
        SetState(ActorStates.no_input);
        SetDamageMode(DamageMode.NO_DAMAGE);
	transitionState = HeroTransitionState.EXITING_SCENE;
        CancelFallEffects();
        tilemapTestActive = false;
        SetHeroParent(null);
        StopTilemapTest();
        if(gate != null)
	{
	    switch (gate.Value)
	    {
		case GatePosition.top:
                    transition_vel = new Vector2(0f, MIN_JUMP_SPEED);
                    cState.onGround = false;
		    break;
		case GatePosition.right:
                    transition_vel = new Vector2(RUN_SPEED, 0f);
                    break;
		case GatePosition.left:
                    transition_vel = new Vector2(-RUN_SPEED, 0f);
                    break;
		case GatePosition.bottom:
                    transition_vel = Vector2.zero;
                    cState.onGround = false;
		    break;
	    }
	}
        cState.transitioning = true;
    }

然后我们来制作一个协程处理进入场景:

     private bool stopWalkingOut;
    public float TIME_TO_ENTER_SCENE_BOT;
    public float TIME_TO_ENTER_SCENE_HOR;
    public float SPEED_TO_ENTER_SCENE_HOR;
    public float SPEED_TO_ENTER_SCENE_UP;
    public float SPEED_TO_ENTER_SCENE_DOWN; 

public IEnumerator EnterScene(TransitionPoint enterGate, float delayBeforeEnter)
    {
        IgnoreInputWithoutReset();
        ResetMotion();
        airDashed = false;

        ResetHardLandingTimer();

        AffectedByGravity(false);
        sceneEntryGate = enterGate;
        SetState(ActorStates.no_input);
        transitionState = HeroTransitionState.WAITING_TO_ENTER_LEVEL;

	if (!cState.transitioning)
	{
            cState.transitioning = true;
	}
        gatePosition = enterGate.GetGatePosition();
        if (gatePosition == GatePosition.top)
        {
            cState.onGround = false;
            enteringVertically = true;

            renderer.enabled = false;
            float x2 = enterGate.transform.position.x + enterGate.entryOffset.x;
            float y2 = enterGate.transform.position.y + enterGate.entryOffset.y;
            transform.SetPosition2D(x2, y2);
            if (heroInPosition != null)
            {
                heroInPosition(false);
            }
            yield return new WaitForSeconds(0.165f);
            if (!enterGate.customFade)
            {
                gm.FadeSceneIn();
            }
            if (delayBeforeEnter > 0f)
            {
                yield return new WaitForSeconds(delayBeforeEnter);
            }
            if (enterGate.entryDelay > 0f)
            {
                yield return new WaitForSeconds(enterGate.entryDelay);
            }
            yield return new WaitForSeconds(0.4f);
            renderer.enabled = true;
            rb2d.velocity = new Vector2(0f, SPEED_TO_ENTER_SCENE_DOWN);
            transitionState = HeroTransitionState.ENTERING_SCENE;
            transitionState = HeroTransitionState.DROPPING_DOWN;
            AffectedByGravity(true);
            if (enterGate.hardLandOnExit)
            {
                cState.willHardLand = true;
            }
            yield return new WaitForSeconds(0.33f);
            transitionState = HeroTransitionState.ENTERING_SCENE;
            if (transitionState != HeroTransitionState.WAITING_TO_TRANSITION)
            {
                FinishedEnteringScene(true, false);
            }
        }
        else if (gatePosition == GatePosition.bottom)
        {
            cState.onGround = false;
            enteringVertically = true;

            if (enterGate.alwaysEnterRight)
            {
                FaceRight();
            }
            if (enterGate.alwaysEnterLeft)
            {
                FaceLeft();
            }
            float x = enterGate.transform.position.x + enterGate.entryOffset.x;
            float y = enterGate.transform.position.y + enterGate.entryOffset.y + 3f;
            transform.SetPosition2D(x, y);
            if (heroInPosition != null)
            {
                heroInPosition(false);
            }
            yield return new WaitForSeconds(0.165f);
            if (delayBeforeEnter > 0f)
            {
                yield return new WaitForSeconds(delayBeforeEnter);
            }
            if (enterGate.entryDelay > 0f)
            {
                yield return new WaitForSeconds(enterGate.entryDelay);
            }
            yield return new WaitForSeconds(0.4f);
            if (!enterGate.customFade)
            {
                gm.FadeSceneIn();
            }
            if (cState.facingRight)
            {
                transition_vel = new Vector2(SPEED_TO_ENTER_SCENE_HOR, SPEED_TO_ENTER_SCENE_UP);
            }
            else
            {
                transition_vel = new Vector2(-SPEED_TO_ENTER_SCENE_HOR, SPEED_TO_ENTER_SCENE_UP);
            }
            transitionState = HeroTransitionState.ENTERING_SCENE;
            transform.SetPosition2D(x, y);
            yield return new WaitForSeconds(TIME_TO_ENTER_SCENE_BOT);
            transition_vel = new Vector2(rb2d.velocity.x, 0f);
            AffectedByGravity(true);
            transitionState = HeroTransitionState.DROPPING_DOWN;
        }
        else if (gatePosition == GatePosition.left)
	{
            cState.onGround = true;
            enteringVertically = false;
            SetState(ActorStates.no_input);
            float num = enterGate.transform.position.x + enterGate.entryOffset.x;
            float y3 = FindGroundPointY(num + 2f, enterGate.transform.position.y, false);
            transform.SetPosition2D(num, y3);
            if(heroInPosition != null)
	    {
                heroInPosition(true);
	    }
            FaceRight();
            yield return new WaitForSeconds(0.165f);
            if (!enterGate.customFade)
            {
                gm.FadeSceneIn();
            }
            if (delayBeforeEnter > 0f)
            {
                yield return new WaitForSeconds(delayBeforeEnter);
            }
            if (enterGate.entryDelay > 0f)
            {
                yield return new WaitForSeconds(enterGate.entryDelay);
            }
            yield return new WaitForSeconds(0.4f);
            transition_vel = new Vector2(RUN_SPEED, 0f);
            transitionState = HeroTransitionState.ENTERING_SCENE;
            yield return new WaitForSeconds(0.33f);
            FinishedEnteringScene(true, true);
        }
        else if(gatePosition == GatePosition.right)
	{
            cState.onGround = true;
            enteringVertically = false;
            SetState(ActorStates.no_input);
            float num2 = enterGate.transform.position.x + enterGate.entryOffset.x;
            float y4 = FindGroundPointY(num2, enterGate.transform.position.y, false);
            transform.SetPosition2D(num2, y4);
            if(heroInPosition != null)
	    {
                heroInPosition(true);
	    }
            FaceLeft();
            yield return new WaitForSeconds(0.165f);
            if (!enterGate.customFade)
            {
                gm.FadeSceneIn();
            }
            if (delayBeforeEnter > 0f)
            {
                yield return new WaitForSeconds(delayBeforeEnter);
            }
            if (enterGate.entryDelay > 0f)
            {
                yield return new WaitForSeconds(enterGate.entryDelay);
            }
            yield return new WaitForSeconds(0.4f);
            transition_vel = new Vector2(-RUN_SPEED, 0f);
            transitionState = HeroTransitionState.ENTERING_SCENE;
            yield return new WaitForSeconds(0.33f);
            FinishedEnteringScene(true, true);
        }
        else if(gatePosition == GatePosition.door)
	{
	    if (enterGate.alwaysEnterRight)
	    {
                FaceRight();
	    }
            if (enterGate.alwaysEnterLeft)
            {
                FaceLeft();
            }
            cState.onGround = true;
            enteringVertically = false;
            SetState(ActorStates.no_input);
            SetState(ActorStates.idle);
            animCtrl.PlayClip("Idle");
            transform.SetPosition2D(FindGroundPoint(enterGate.transform.position, false));
            if(heroInPosition != null)
	    {
                heroInPosition(false);
	    }
            yield return new WaitForEndOfFrame();
            if (delayBeforeEnter > 0f)
            {
                yield return new WaitForSeconds(delayBeforeEnter);
            }
            if (enterGate.entryDelay > 0f)
            {
                yield return new WaitForSeconds(enterGate.entryDelay);
            }
            yield return new WaitForSeconds(0.4f);
            if (!enterGate.customFade)
            {

            }
            float realTimeSinceStartup = Time.realtimeSinceStartup;
	    if (enterGate.dontWalkOutOfDoor)
	    {
                yield return new WaitForSeconds(0.33f);
	    }
	    else
	    {
                float clipDuration = animCtrl.GetClipDuration("Exit Door To Idle");
                animCtrl.PlayClip("Exit Door To Idle");
                if(clipDuration > 0f)
		{
                    yield return new WaitForSeconds(clipDuration);
		}
		else
		{
                    yield return new WaitForSeconds(0.33f);
                }
	    }
            FinishedEnteringScene(true, false);
        }
    }

来到GlobalEnum添加好角色转移的状态:

 public enum HeroTransitionState
    {
	WAITING_TO_TRANSITION,
	EXITING_SCENE,
	WAITING_TO_ENTER_LEVEL,
	ENTERING_SCENE,
	DROPPING_DOWN
    }

 找到落地点:

 public Vector3 FindGroundPoint(Vector2 startPoint,bool useExtended = false)
    {
        float num = FIND_GROUND_POINT_DISTANCE;
	if (useExtended)
	{
            num = FIND_GROUND_POINT_DISTANCE_EXT;
        }
        RaycastHit2D raycastHit2D = Physics2D.Raycast(startPoint, Vector2.down, num, LayerMask.GetMask("Terrain"));
        if(raycastHit2D.collider == null)
	{
            Debug.LogErrorFormat("FindGroundPoint: Could not find ground point below {0}, check reference position is not too high (more than {1} tiles).", new object[]
            {
                startPoint.ToString(),
                num
            });
        }
        return new Vector3(raycastHit2D.point.x, raycastHit2D.point.y + col2d.bounds.extents.y - col2d.offset.y + 0.01f, transform.position.z);
    }

    private float FindGroundPointY(float x, float y, bool useExtended = false)
    {
        float num = FIND_GROUND_POINT_DISTANCE;
        if (useExtended)
        {
            num = FIND_GROUND_POINT_DISTANCE_EXT;
        }
        RaycastHit2D raycastHit2D = Physics2D.Raycast(new Vector2(x, y), Vector2.down, num, LayerMask.GetMask("Terrain"));
        if (raycastHit2D.collider == null)
        {
            Debug.LogErrorFormat("FindGroundPoint: Could not find ground point below ({0},{1}), check reference position is not too high (more than {2} tiles).", new object[]
            {
                x,
                y,
                num
            });
        }
        return raycastHit2D.point.y + col2d.bounds.extents.y - col2d.offset.y + 0.01f;
    }

 还有我么上期降到的:

 private void FinishedEnteringScene(bool setHazardMarker = true, bool preventRunBob = false)
    {
        if(isEnteringFirstLevel)
	{
            isEnteringFirstLevel = false;
	}
        else
	{
            playerData.disablePause = false;
        }
        cState.transitioning = false;
        transitionState = HeroTransitionState.WAITING_TO_TRANSITION;
        stopWalkingOut = false;
	SetStartingMotionState(preventRunBob);
        AffectedByGravity(true);
	if (setHazardMarker)
	{
            if (sceneEntryGate == null)
            {
                playerData.SetHazardRespawn(transform.position, cState.facingRight);
            }
            else if (!sceneEntryGate.nonHazardGate)
            {
                playerData.SetHazardRespawn(sceneEntryGate.respawnMarker);
            }
        }
        SetDamageMode(DamageMode.FULL_DAMAGE);
	if (enterWithoutInput)
	{
            enterWithoutInput = false;
	}
	else
	{
            AcceptInput();
	}
        gm.FinishedEnteringScene();
        positionHistory[0] = transform.position;
        positionHistory[1] = transform.position;
        tilemapTestActive = true;
    }

至此我们制作了完整的切换创建的脚本逻辑处理,但还有一个问题没解决,那就是上面将的如何打完门直接场景转换呢?

当然使用到我们的playmaker了!

         

如果是激活状态,我们就直接销毁它,但这个要到我们后面做到可持续化数据才能用到 

检查攻击者的类型,也就是骨钉攻击 

 

 

 

 为玩家创建新的方法EnterWithoutInput():

 public void EnterWithoutInput(bool flag)
    {
        enterWithoutInput = flag;
    }

然后就是自定义playmakerFSM:我们还是用到了GameManager的BeginSceneTransition()方法

using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory("Game Manager")]
    [Tooltip("Perform a generic scene transition.")]
    public class BeginSceneTransition : FsmStateAction
    {
	public FsmString sceneName;
	public FsmString entryGateName;
	public FsmFloat entryDelay;
	[ObjectType(typeof(GameManager.SceneLoadVisualizations))]
	public FsmEnum visualization;

	public bool preventCameraFadeOut;

	public override void Reset()
	{
	    sceneName = "";
	    entryGateName = "left1";
	    entryDelay = 0f;
	    visualization = new FsmEnum
	    {
		Value = GameManager.SceneLoadVisualizations.Default
	    };
	    preventCameraFadeOut = false;
	}

	public override void OnEnter()
	{
	    GameManager unsafeInstance = GameManager.instance;
	    if (unsafeInstance == null)
	    {
		LogError("Cannot BeginSceneTransition() before the game manager is loaded.");
	    }
	    else
	    {
		unsafeInstance.BeginSceneTransition(new GameManager.SceneLoadInfo
		{
		    SceneName = sceneName.Value,
		    EntryGateName = entryGateName.Value,
		    EntryDelay = entryDelay.Value,
		    Visualization = (GameManager.SceneLoadVisualizations)visualization.Value,
		    PreventCameraFadeOut = true,
		    WaitForSceneTransitionCameraFade = !preventCameraFadeOut,
		    AlwaysUnloadUnusedAssets = false
		});
	    }
	    Finish();
	}


    }

}


总结

最后我们来看看效果吧

上面的UI是我后面做的先别管:

 

然后黑屏

 到达Town:

OK能移动还没有Error,完成。