[Unity Demo]从零开始制作空洞骑士Hollow Knight第十四集:制作新的场景以及制作创建切换管理系统

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

文章目录

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


前言

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

顺便提醒,我的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,完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/896030.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux内核】eBPF基础篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了学习ebpf机制的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于–知乎ebpf专栏文章–进行的&#xff0c;每个知识点的修正和深…

【JavaEE初阶】深入理解TCP协议中的封装分用以及UDP和TCP在网络编程的区别

前言 &#x1f31f;&#x1f31f;本期讲解关于TCP/UDP协议的原理理解~~~ &#x1f308;上期博客在这里&#xff1a;【JavaEE初阶】入门视角-网络原理的基础理论的了解-CSDN博客 &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; …

前端算法:时间复杂度和空间复杂度

一、算法的重要性 1.为什么前端开发需要学习算法&#xff1f; 学习算法可以帮助培养逻辑思维能力&#xff0c;在面对复杂的问题时&#xff0c;能够系统性地分析问题、分解步骤并成功找到的正确的解决方案。 掌握基本的排序、查找算法和时间复杂度分析可以帮助编写更高效的代码…

移动网络知识

一、3G网络 TD-SCDMA&#xff08;时分同步码分多址接入&#xff09;、WCDMA&#xff08;宽带码分多址&#xff09;和CDMA2000三种不同的3G移动通信标准 TD-SCDMA&#xff08;时分同步码分多址接入&#xff09;&#xff1a;中国自主开发的一种3G标准主要用于国内市场&#xff…

零跑“半价平替”杀疯了,没钱别硬上问界理想

文 | AUTO芯球 作者 | 雷慢 你绝对想不到&#xff0c; 现在造车新势力的周销量榜第二名已经是零跑了 来看啊&#xff0c;十月第2周&#xff0c; 零跑周销量8700量&#xff0c;已经超过问界的7100辆&#xff0c; 放以前&#xff0c;问界也是周销量9000台左右的主&#xff0…

RHCE——时间服务器

NTP——网络时间协议&#xff0c;通过udp123端口进行网络时钟同步 chronyd chronyd——一个开源自由的网络时间协议 NTP 的客户端和服务器软件。能让计算机保持系统时钟与时钟服务器&#xff08;NTP&#xff09;同步&#xff0c;从而使计算机保持精确的时间。 Chrony由两个程…

大数据查询引擎之Tez

Apache Tez 是一个用于大数据处理的分布式计算框架&#xff0c;旨在提高 Hadoop 的 MapReduce 计算引擎的效率和性能。它是一个面向 DAG&#xff08;有向无环图&#xff09;任务执行的框架&#xff0c;主要用于大规模数据处理场景中&#xff0c;特别是在 Apache Hadoop 生态系统…

开放式耳机好不好用?盘点开放式蓝牙耳机排行榜前五名

​开放式耳机是好用的&#xff0c;目前非常流行&#xff0c;它们以时尚、美观和舒适著称&#xff0c;迅速赢得了众多用户的喜爱&#xff0c;成为了耳机市场的新宠。与传统的入耳式耳机相比&#xff0c;开放式耳机佩戴更稳固&#xff0c;对耳朵也更为温和。尽管有些人认为它们价…

C++在vscode中的code runner配置/环境配置

C在vscode中快捷运行&#xff08;code runner&#xff09; 一、配置tasks.json 在vscode中创建文件夹或打开文件夹&#xff0c;会发现文件夹下多了一个.vscode文件夹&#xff0c;在该文件夹下创建tasks.json文件&#xff0c;并添加一下内容 {"version": "2.0…

单周期处理器设计思路

目录 单周期处理器设计思路加法器的优化行波进位加法器&#xff08;RCA&#xff09;先行进位加法器&#xff08;CLA&#xff09;两种加法器的对比CLA的再优化可以用加法器实现的其他操作 编写可维护的RTL代码 单周期处理器设计思路 加法器的优化 &#xff08;用综合器综合*/等…

如何修改MAC地址破解网络无线网络限制-担心别人蹭网,路由器设置MAC地址过滤,限定了能访问无线网络的网卡地址-供大家学习参考

路由器都设置了MAC地址过滤&#xff0c;也就是限定了能访问无线网络的网卡的MAC地址。因为无线路由器不一定由自己控制&#xff0c;所以当更换了笔记本或者更换了无线网卡的时候&#xff0c;也许就上不了网了。我们可以修改网卡的MAC地址实现上网。 下载&#xff1a;https://do…

R01 vue+springboot 高考志愿推荐AI问答大数据平台

可以查看本文系统对应的视频讲解&#xff1a; vuespringboot 高考推荐AI问答志愿推荐大数据 R01 带增删改查、大屏、支持爬虫 1 系统背景 近年来&#xff0c;高考作为中国教育体系中最重要的考试之一&#xff0c;承载了无数考生和家庭的梦想。随着信息技术的迅猛发展&#xff…

Linux shell脚本文件通过shc工具加密,生成静态链接可执行文件

要使用 shc 工具对 Linux shell 脚本进行加密并生成静态链接的可执行文件&#xff0c;你可以按照以下步骤操作&#xff1a; 安装 shc 工具&#xff1a; 如果你的系统中还没有安装 shc&#xff0c;可以通过包管理器安装&#xff0c;例如在 Ubuntu 系统中&#xff0c;可以使用以下…

YOLOv11模型改进-模块-引入空间池化模块StripPooling 解决遮挡、小目标

本篇文章将介绍一个新的改进机制——空间池化模块StripPooling&#xff0c;并阐述如何将其应用于YOLOv11中&#xff0c;显著提升模型性能。首先&#xff0c;我们将解析StripPooling的工作原理&#xff0c;SP模块通过条带池化在水平和垂直方向上捕捉长距离依赖关系&#xff0c;增…

如何在线查看近8年的建筑覆盖变化

我们在《谷歌发布建筑数据&#xff0c;高度误差达惊人的1.5米》一文中介绍了谷歌2.5D建筑数据用途、制作方法以及数据下载方式。 现在我们演示下如何在线查看近8年的建筑物覆盖、建筑物质心和建筑物高度的变化。 历史建筑覆盖在线查看 2.5D建筑演变数据集包含2016年至2023年…

碰一碰支付怎么推广的?小白也能看懂的教程来了!

作为支付宝和微信全面力推的一个项目&#xff0c;碰一碰支付的热度可谓是节节攀升&#xff0c;连带着与之相关的话题&#xff0c;如碰一碰支付是什么和碰一碰支付怎么推广的等也因此成为了人们关注的焦点。那么&#xff0c;本期&#xff0c;我们就来详细地解答一下这两个问题。…

2024台州赛CTFwp

备注&#xff1a; 解题过程中&#xff0c;关键步骤不可省略&#xff0c;不可含糊其辞、一笔带过。解题过程中如是自己编写的脚本&#xff0c;不可省略&#xff0c;不可截图&#xff08;代码字体可以调小&#xff1b;而如果代码太长&#xff0c;则贴关键代码函数&#xff09;。…

I.MX6U 字符设备驱动开发指南

目录 一、引言 二、字符设备驱动的基本概念 1.字符设备的定义 2.字符设备驱动的作用 三、I.MX6U 字符设备驱动开发步骤 1.确定设备信息 2.编写设备驱动代码 3.编译和加载驱动 4.测试设备驱动 四、实例分析 1.确定设备信息 2.编写设备驱动代码 3.编译和加载驱动 4.…

iOS 回到主线程刷新UI

在iOS 里面,项目打开就会运行一个主线程,所有的UI都在主线程里进行.其他网络请求或者耗时操作理论上也可以在主线程运行,但是如果太耗时,那么就会影响主线程其他UI.所以需要开字线程来进行耗时操作,子线程进行完耗时操作之后,如果项目需求有需要刷新UI,或者改变UI,一定得回到主…

音频/视频提取器:Python和moviepy实现

在这篇博客中,我们将深入探讨一个使用Python和wxPython构建的音频/视频提取器应用程序。这个应用程序允许用户从视频文件中提取音频,或者从音频文件中截取特定时间段。让我们逐步分析这个程序的功能和实现。 C:\pythoncode\new\MP3towav.py 全部代码 import wx import os imp…