提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、制作新的场景
- 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,完成。