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

server/2024/10/18 13:30:30/

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

文章目录

  • 前言
  • 一、制作新的场景
    • 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 = ""; //记录最后进入的TransitionPointpublic 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的layerif(!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; } //是否需要卸载掉当前场景的Assetspublic 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,完成。


http://www.ppmy.cn/server/132769.html

相关文章

概率论基本知识

随机变量及其分布 1.定义 随机变量是定义在样本空间上的实值函数&#xff0c;它将样本空间中的每一个样本点映射到一个实数上。通常用大写字母&#xff08;如X、Y&#xff09;表示随机变量&#xff0c;而小写字母&#xff08;如x、y&#xff09;表示随机变量的取值。他有两个…

解锁机器人视觉与人工智能的潜力,从“盲人机器”改造成有视觉能力的机器人(上)

正如人类依赖眼睛和大脑来解读世界&#xff0c;机器人也需要自己的视觉系统来有效运作。没有视觉&#xff0c;机器人就如同蒙上双眼的人类&#xff0c;仅能执行预编程的命令&#xff0c;容易碰撞障碍物&#xff0c;并犯下代价高昂的错误。这正是机器人视觉发挥作用的地方&#…

Steinberg VST Live Pro v2.1.1 演出音频灯光控制软件

现场演出音频视频灯光控制软件 Steinberg VST Live Pro 将让现场表演更轻松。这是一款独特、稳定的软件解决方案&#xff0c;专为想要进行精彩表演的音乐家而设计&#xff0c;无论身在何处都能使用声音、灯光和视频等相关功能。VST Live附带大量虚拟乐器&#xff0c;音乐同步功…

深度学习示例3-卷积神经网络(猫狗大战)_数据增强

一、数据获取 数据获取地址:cats_vs_dogs_small.zip 链接: https://pan.baidu.com/s/1n3pACSk3FWCNKotqWVss6Q 提取码: sij9 数据训练存放目录- cats_vs_dogs_small- test- cat- 1500~2499jpg- dog- 1500~2499jpg- train- cat- 0~999jpg- dog- 0~999jpg- validation- cat- 100…

GR-ConvNet论文 学习笔记

GR-ConvNet 文章目录 GR-ConvNet前言一、引言二、相关研究三、问题阐述四、方法A.推理模块B.控制模块C.模型结构D.训练方法E.损失函数 五、评估A.数据集B.抓取评判标准 六、实验A.设置B.家庭测试物体C.对抗性测试物体D.混合物体 七、结果A.康奈尔数据集B.Jacquard数据集C.抓取新…

苍穹外卖学习笔记(二十五)

文章目录 Spring Task介绍应用场景&#xff1a; cron表达式例如&#xff1a; 入门案例 订单状态定时处理处理超时订单处理一直配送中的订单OrderMapper WebSocket介绍HTTP协议和WebSocket协议对比应用场景&#xff1a;入门案例1. 使用websocket.html作为WebSocket客户端2. 导入…

瘦客户机介绍

瘦客户机&#xff08;Thin Client&#xff09;是一种计算设备&#xff0c;主要用于通过网络连接到远程服务器运行应用程序&#xff0c;而不是在本地进行大量计算和存储。与传统的PC相比&#xff0c;瘦客户机的硬件资源较少&#xff0c;通常依赖服务器进行处理&#xff0c;因此它…

STL-题目解析

下面有关vector和list的区别&#xff0c;描述错误的是( ) A.vector拥有一段连续的内存空间&#xff0c;因此支持随机存取&#xff0c;如果需要高效的随机存取,应该使用vector B.list拥有一段不连续的内存空间&#xff0c;如果需要大量的插入和删除&#xff0c;应该使用list …