[Unity Demo]从零开始制作空洞骑士Hollow Knight第十八集:制作UI系统的主菜单界面和选择存档界面

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

文章目录


前言

hello大家好久没见,之所以隔了这么久才更新并不是因为我又放弃了这个项目,而是接下来要制作的工作太忙碌了,每次我都花了很长的时间解决完一个部分,然后就没力气打开CSDN写文章就直接睡觉去了,现在终于有时间整理下我这半个月都做了什么内容。

废话少说,上两期我们已经制作了遗忘十字路的两个BOSS,那为什么我不做假骑士呢,其实我是做好了的,但还是有一些很奇怪的bug亟需修理,等我搞的差不多再发出来吧,我们先来制作初始阶段的UI系统吧,这期就先将两个部分主菜单界面选择存档界面

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

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


一、制作UI系统的主菜单界面

1.选择存档界面制作

导入完素材后我们创建一个新的创建就叫Menu_Title,这个就是我们的主菜单界面的场景了。

做好后是这个样子的,首先这里有一个title标题的logo,还有一个副标题,但我觉得好像没用到的时候就先隐藏了。

然后主菜单有不同的样式Menu_Styles,这里我先只制作最初始的样式,虚空之心样式

第一部分是粒子系统,黑雾浓烟

还有一些其他没那么重要的小粒子系统我就直接贴出来了:

几束光:

还有一个类似于场景边界的黑幕:

然后开始创建一个_UIManager,创建一个用于初始化公共变量UIManager的playmakerFSM:

我先来说个大概的框架吧,首先这个UIManager当然是不随场景销毁的游戏对象,也就是单例模式,然后它的孩子有EventSystem,有总画布UICanvas,还有控制ui语音播放的UIAudioPlayer。这三个最重要的当然是UICanvas,它的孩子有主菜单界面MainMenuScreen,选择存档界面SaveProfileScreen等等。我们这一节先来讲主菜单界面MainMenuScreen。

2.代码的逻辑处理

开始之前先到GlobalEnums创建好数组:

cs 复制代码
  public enum MainMenuState
    {
	LOGO, //logo界面
	MAIN_MENU, //主菜单界面
	OPTIONS_MENU, //选项界面
	GAMEPAD_MENU, //手柄界面
	KEYBOARD_MENU, //键盘界面
	SAVE_PROFILES, //保存确认界面
	AUDIO_MENU, //声音设置界面
	VIDEO_MENU, //视频设置界面
	EXIT_PROMPT, //退出游戏确认界面
	OVERSCAN_MENU, //分辨率界面
	GAME_OPTIONS_MENU, //游戏选项界面
	ACHIEVEMENTS_MENU, //成就界面
	QUIT_GAME_PROMPT, //退出游戏确认界面
	RESOLUTION_PROMPT, //分辨率界面
	BRIGHTNESS_MENU, //亮度界面
	PAUSE_MENU, //暂停菜单界面
	PLAY_MODE_MENU, //游戏模式界面(普通,钢魂,寻神者)
	EXTRAS_MENU, //额外内容界面
	REMAP_GAMEPAD_MENU, //重新绑定手柄按键界面
	EXTRAS_CONTENT_MENU, //额外内容界面
	ENGAGE_MENU, //确认界面
	NO_SAVE_MENU //不保存界面
    }

 public enum UIState
    {
	INACTIVE,
	MAIN_MENU_HOME,
	LOADING,
	CUTSCENE,
	PLAYING,
	PAUSED,
	OPTIONS
    }

创建一个同名脚本UIManager.cs ,这将管理我们这个UI系统:

cs 复制代码
using System;
using System.Collections;
using GlobalEnums;
using InControl;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    [Header("State")]
    [Space(6f)]
    public UIState uiState; //UI状态
    public MainMenuState menuState; //主菜单的界面

    [Header("Event System")]
    [Space(6f)]
    public EventSystem eventSystem;

    [Header("Main Elements")]
    [Space(6f)]
    public Canvas UICanvas;

    [Header("Main Menu")]
    [Space(6f)]
    public CanvasGroup mainMenuScreen; //主菜单界面的Cg
    public MainMenuOptions mainMenuButtons; //主菜单button选项
    public SpriteRenderer gameTitle; //游戏标题
    public PlayMakerFSM subTitleFSM; //游戏副标题FSM

    [Header("Save Profile Menu")]



    private float startMenuTime;
    private bool isFadingMenu;
    public bool IsFadingMenu
    {
	get
	{
	    return isFadingMenu || Time.time < startMenuTime;
	}
    }

    private int menuAnimationCounter;
    public bool IsAnimatingMenu
    {
	get
	{
	    return menuAnimationCounter > 0;
	}
    }

    private GameManager gm;
    private HeroController hero_ctrl;
    private PlayerData playerData;
    private InputHandler ih;
    private GraphicRaycaster graphicRaycaster;
    public MenuAudioController uiAudioPlayer;

    [Space]
    public float MENU_FADE_SPEED = 3.2f;

    private static UIManager _instance; //单例模式
    public static UIManager instance
    {
	get
	{
	    if (_instance == null)
	    {
		_instance = FindObjectOfType<UIManager>();
		if (_instance == null)
		{
		    Debug.LogError("Couldn't find a UIManager, make sure one exists in the scene.");
		}
		if (Application.isPlaying)
		{
		    DontDestroyOnLoad(_instance.gameObject);
		}
	    }
	    return _instance;
	}
    }

    private void Awake()
    {
	if(_instance == null)
	{
	    _instance = this;
	    DontDestroyOnLoad(this);
	}
	else if(this != _instance)
	{
	    Destroy(gameObject);
	    return;
	}
	graphicRaycaster = GetComponentInChildren<GraphicRaycaster>();
    }

    public void SceneInit()
    {
	if (this == UIManager._instance)
	{
	    SetupRefs();
	}
    }

    private void Start()
    {
	if(this == _instance)
	{
	    SetupRefs();
	    if (gm.IsMenuScene()) //判断当前场景是否是菜单场景
	    {
		startMenuTime = Time.time + 0.5f;
		GameCameras.instance.cameraController.FadeSceneIn();
		ConfigureMenu();
	    }
	    if(graphicRaycaster && InputHandler.Instance)
	    {
		InputHandler.Instance.OnCursorVisibilityChange += delegate (bool isVisible)
		{
		    graphicRaycaster.enabled = isVisible;
		};
	    }
	}
    }

    private void SetupRefs()
    {
	gm = GameManager.instance;

	playerData = PlayerData.instance;
	ih = gm.inputHandler;
	if (gm.IsGameplayScene())
	{
	    hero_ctrl = HeroController.instance;
	}
	if(gm.IsMenuScene() && gameTitle == null)
	{
	    gameTitle = GameObject.Find("LogoTitle").GetComponent<SpriteRenderer>();
	}
	if(UICanvas.worldCamera == null)
	{
	    UICanvas.worldCamera = GameCameras.instance.mainCamera;
	}
    }
    public void ConfigureMenu()
    {
	if(mainMenuButtons != null)
	{
	    mainMenuButtons.ConfigureNavigation();
	}
	if(uiState == UIState.MAIN_MENU_HOME)
	{
	    //TODO:
	}
    }

    /// <summary>
    /// 设置UI状态
    /// </summary>
    /// <param name="newState"></param>
    public void SetState(UIState newState)
    {
	if (gm == null) 
	{
	    gm = GameManager.instance;
	}
	if(newState != uiState)
	{
	    if (uiState == UIState.PAUSED && newState == UIState.PLAYING)
	    {

	    }
	    else if (uiState == UIState.PLAYING && newState == UIState.PAUSED)
	    {
	    }
	    else if (newState == UIState.INACTIVE)
	    {
		DisableScreens();
	    }
	    else if (newState == UIState.MAIN_MENU_HOME)
	    {
		//TODO:
		UIGoToMainMenu();
	    }
	    else if (newState == UIState.LOADING)
	    {
		DisableScreens();
	    }
	    else if (newState == UIState.PLAYING)
	    {
		DisableScreens();
	    }
	    else if (newState == UIState.CUTSCENE)
	    {
		DisableScreens();
	    }
	    uiState = newState;
	    return;
	}
	if (newState == UIState.MAIN_MENU_HOME)
	{
	    UIGoToMainMenu();
	}
    }

    /// <summary>
    /// 关闭某些特定的屏幕
    /// </summary>
    private void DisableScreens()
    {
	for (int i = 0; i < UICanvas.transform.childCount; i++)
	{
	    if (!(UICanvas.transform.GetChild(i).name == "PauseMenuScreen"))
	    {
		UICanvas.transform.GetChild(i).gameObject.SetActive(false);
	    }
	}
    }

    /// <summary>
    /// 设置UI初始状态
    /// </summary>
    /// <param name="gameState"></param>
    public void SetUIStartState(GameState gameState)
    {
	if (gameState == GameState.MAIN_MENU)
	{
	    SetState(UIState.MAIN_MENU_HOME);
	    return;
	}
	if (gameState == GameState.LOADING)
	{
	    SetState(UIState.LOADING);
	    return;
	}
	if (gameState == GameState.ENTERING_LEVEL)
	{
	    SetState(UIState.PLAYING);
	    return;
	}
	if (gameState == GameState.PLAYING)
	{
	    SetState(UIState.PLAYING);
	    return;
	}
	if (gameState == GameState.CUTSCENE)
	{
	    SetState(UIState.CUTSCENE);
	}
    }

    /// <summary>
    /// 设置新的主菜单界面
    /// </summary>
    /// <param name="newState"></param>
    private void SetMenuState(MainMenuState newState)

    {
	menuState = newState;
    }
    public void UIGoToMainMenu()
    {
	StartMenuAnimationCoroutine(GoToMainMenu());
    }

    /// <summary>
    /// 前往主菜单界面
    /// </summary>
    /// <returns></returns>
    private IEnumerator GoToMainMenu()
    {
	Debug.LogFormat("Go To Main Menu");
	if(ih == null)
	{
	    ih = InputHandler.Instance;
	}
	ih.StopUIInput();
	if (menuState == MainMenuState.OPTIONS_MENU || menuState == MainMenuState.ACHIEVEMENTS_MENU || menuState == MainMenuState.QUIT_GAME_PROMPT || menuState == MainMenuState.EXTRAS_MENU || menuState == MainMenuState.ENGAGE_MENU || menuState == MainMenuState.NO_SAVE_MENU || menuState == MainMenuState.PLAY_MODE_MENU)
	{
	    yield return StartCoroutine(HideCurrentMenu());
	}
	else if(menuState == MainMenuState.SAVE_PROFILES)
	{
	    yield return StartCoroutine(HideSaveProfileMenu());
	}
	ih.StopUIInput();
	gameTitle.gameObject.SetActive(true);
	mainMenuScreen.gameObject.SetActive(true);

	StartCoroutine(FadeInSprite(gameTitle));
	subTitleFSM.SendEvent("FADE IN");
	yield return StartCoroutine(FadeInCanvasGroup(mainMenuScreen));
	mainMenuScreen.interactable = true;
	ih.StartUIInput();
	yield return null;
	mainMenuButtons.HighlightDefault(false);
	SetMenuState(MainMenuState.MAIN_MENU);
    }


    private Coroutine StartMenuAnimationCoroutine(IEnumerator routine)
    {
	return StartCoroutine(StartMenuAnimationCoroutineWorker(routine));
    }
    private IEnumerator StartMenuAnimationCoroutineWorker(IEnumerator routine)
    {
	menuAnimationCounter++;
	yield return StartCoroutine(routine);
	menuAnimationCounter--;
    }
    /// <summary>
    /// 线性插值淡入CanvasGroup
    /// </summary>
    /// <param name="cg"></param>
    /// <returns></returns>
    public IEnumerator FadeInCanvasGroup(CanvasGroup cg)
    {
	float loopFailsafe = 0f;
	cg.alpha = 0f;
	cg.gameObject.SetActive(true);
	while (cg.alpha < 1f)
	{
	    cg.alpha += Time.unscaledDeltaTime * MENU_FADE_SPEED;
	    loopFailsafe += Time.unscaledDeltaTime;
	    if (cg.alpha >= 0.95f)
	    {
		cg.alpha = 1f;
		break;
	    }
	    if (loopFailsafe >= 2f)
	    {
		break;
	    }
	    yield return null;
	}
	cg.alpha = 1f;
	cg.interactable = true;
	cg.gameObject.SetActive(true);
	yield return null;
	yield break;
    }
    /// <summary>
    /// 线性插值淡出CanvasGroup
    /// </summary>
    /// <param name="cg"></param>
    /// <returns></returns>
    public IEnumerator FadeOutCanvasGroup(CanvasGroup cg)
    {
	float loopFailsafe = 0f;
	cg.interactable = false;
	while(cg.alpha > 0.05f)
	{
	    cg.alpha -= Time.unscaledDeltaTime * MENU_FADE_SPEED;
	    loopFailsafe += Time.unscaledDeltaTime;
	    if(cg.alpha <= 0.05f || loopFailsafe >= 2f)
	    {
		break;
	    }
	    yield return null;
	}
	cg.alpha = 0f;
	cg.gameObject.SetActive(false);
	yield return null;
    }
    /// <summary>
    /// 线性插值淡入SpriteRenderer
    /// </summary>
    /// <param name="sprite"></param>
    /// <returns></returns>
    private IEnumerator FadeInSprite(SpriteRenderer sprite)
    {
	while (sprite.color.a < 1f)
	{
	    sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, sprite.color.a + Time.unscaledDeltaTime * MENU_FADE_SPEED);
	    yield return null;
	}
	sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, 1f);
	yield return null;
    }
    /// <summary>
    /// 线性插值淡出SpriteRenderer
    /// </summary>
    /// <param name="sprite"></param>
    /// <returns></returns>
    private IEnumerator FadeOutSprite(SpriteRenderer sprite)
    {
	while(sprite.color.a > 0f)
	{
	    sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, sprite.color.a - Time.unscaledDeltaTime * MENU_FADE_SPEED);
	    yield return null;
	}
	sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, 0f);
	yield return null;
    }
}

我们先来制作最简单的UI播放声音:

脚本内容也很简单:

cs 复制代码
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MenuAudioController : MonoBehaviour
{
    private AudioSource audioSource;

    [Header("Sound Effects")]
    public AudioClip select;
    public AudioClip submit;
    public AudioClip cancel;
    public AudioClip slider;
    public AudioClip startGame;

    private void Awake()
    {
	audioSource = GetComponent<AudioSource>();
    }

    public void PlaySelect()
    {
	if (select)
	{
	    audioSource.PlayOneShot(select);
	}
    }
    public void PlaySubmit()
    {
	if (submit)
	{
	    audioSource.PlayOneShot(submit);
	}
    }

    public void PlayCancel()
    {
	if (cancel)
	{
	    audioSource.PlayOneShot(cancel);
	}
    }

    public void PlaySlider()
    {
	if (slider)
	{
	    audioSource.PlayOneShot(slider);
	}
    }

    public void PlayStartGame()
    {
	if (startGame)
	{
	    audioSource.PlayOneShot(startGame);
	}
    }
}

回到Unity编辑中,我们来制作主菜单的界面布置,其实我不太会玩UI,所以什么布局之类的都很烂,没事就先做个大概的以后再来完善:

我使用Vertical Layout Group来确保位置间隔相当:

每一个Button都配置一个Text:

然后Text的子对象有一对Fleur,表明玩家正在选择这一个按钮,它也是有动画的:

Animator连线如下:

最后是按钮点击后会产生动画闪烁的Flash Effect

其它两个按钮的原理也如上所示。

然后就是通过代码的逻辑处理来管理繁杂的UI系统,是真的繁杂反正我写的时候真的红温了,首先MainMenuScreen中创建为每一个界面menuscreen准备的脚本MenuScreen.cs:

核心就是获得每一个界面第一个选择的可交互控件。我们的主菜单界面肯定是Start Game的Button啊。剩下的什么fleur没有就不用管了。

cs 复制代码
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class MenuScreen : MonoBehaviour
{
    public CanvasGroup title;
    public Animator topFleur;
    public Animator bottomFleur;
    public CanvasGroup content;
    public CanvasGroup controls;
    public Selectable defaultHighlight;

    public CanvasGroup screenCanvasGroup
    {
	get
	{
	    return GetComponent<CanvasGroup>();
	}
    }

    public void HighlightDefault()
    {
	EventSystem current = EventSystem.current;
	if (defaultHighlight != null && current.currentSelectedGameObject == null)
	{
	    Selectable firstInteractable = defaultHighlight.GetFirstInteractable();
	    if (firstInteractable)
	    {
		firstInteractable.Select();
		foreach (object obj in defaultHighlight.transform)
		{
		    Animator component = ((Transform)obj).GetComponent<Animator>();
		    if (component != null)
		    {
			component.ResetTrigger("hide");
			component.SetTrigger("show");
			break;
		    }
		}
	    }
	}
    }

}

然后就是MenuButtonList .cs,通过列表存储每一个界面有几个Menu Button脚本的按钮:

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using GlobalEnums;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;

public class MenuButtonList : MonoBehaviour
{
    private MenuSelectable lastSelected;
    private List<Selectable> activeSelectables;
    private static List<MenuButtonList> menuButtonLists = new List<MenuButtonList>();
    private bool started;

    private void Awake()
    {
	MenuScreen component = GetComponent<MenuScreen>();
	if(component != null)
	{
	    component.defaultHighlight = null;
	}
    }


    protected void Start()
    {
	menuButtonLists.Add(this);
	activeSelectables = new List<Selectable>();

    }

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

来到MainMenuScreen的子对象MainMenuButtons

然后就是管理Navigation也就是UI选择导航功能的脚本:

cs 复制代码
using System;
using UnityEngine.UI;

public class MainMenuOptions : PreselectOption
{
    public MenuButton startButton;
    public MenuButton optionsButton;

    public MenuButton quitButton;

    public void ConfigureNavigation()
    {
	Navigation navigation = optionsButton.navigation;
	Navigation navigation2 = quitButton.navigation;
	navigation.selectOnDown = quitButton;
	navigation2.selectOnUp = optionsButton;
    }
}

然后三个按钮我们这期先制作一个StartGameButton,就是进入选择存档界面的按钮,

这里我们需要重写几个方法,我们都知道这些Button都继承了Selectable这个Unity自带的基类,以及一些接口比如ISelectHandler, IEventSystemHandler, IDeselectHandler, ICancelHandler, IPointerExitHandler来实现选择,取消选择,取消,点击离开之类的功能,我们先写一个MenuSelectable的类,相当于Selectable的扩展版,那为什么要扩展呢?当然是因为让我们制作的动画以及效果能够作为固定变量出现在每一个需要Selectable的UI控件上了:就比如我们上面创建的leftCursor和rightCursor,以及控制UI声音播放

cs 复制代码
using System;
using System.Collections;
using GlobalEnums;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;

namespace UnityEngine.UI
{
    public class MenuSelectable : Selectable, ISelectHandler, IEventSystemHandler, IDeselectHandler, ICancelHandler, IPointerExitHandler
    {
	[Header("On Cancel")]
	public CancelAction cancelAction;

	[Header("Fleurs")]
	public Animator leftCursor;
	public Animator rightCursor;

	[Header("Highlight")]
	public Animator selectHighlight;
	public bool playSubmitSound = true;

	protected MenuAudioController uiAudioPlayer;
	protected GameObject prevSelectedObject; //先前选定的selectable对象
	protected bool deselectWasForced; //强制执行取消

	protected bool dontPlaySelectSound;
	public bool DontPlaySelectSound
	{
	    get
	    {
		return dontPlaySelectSound;
	    }
	    set
	    {
		dontPlaySelectSound = value;
	    }
	}

	private MenuButtonList parentList;

	public delegate void OnSelectedEvent(MenuSelectable self);
	public event OnSelectedEvent OnSelected;

	private new void Awake()
	{
	    transition = Transition.None;
	    if(navigation.mode != Navigation.Mode.Explicit)
	    {
		navigation = new Navigation
		{
		    mode = Navigation.Mode.Explicit
		};
	    }
	}

	private new void Start()
	{
	    HookUpAudioPlayer();
	}

	protected void HookUpAudioPlayer()
	{
	    uiAudioPlayer = UIManager.instance.uiAudioPlayer;
	}

	public new void OnSelect(BaseEventData eventData)
	{
	    if (!interactable)
	    {
		return;
	    }
	    if(OnSelected != null)
	    {
		OnSelected(this);
	    }
	    if (leftCursor != null)
	    {
		leftCursor.ResetTrigger("hide");
		leftCursor.SetTrigger("show");
	    }
	    if (rightCursor != null)
	    {
		rightCursor.ResetTrigger("hide");
		rightCursor.SetTrigger("show");
	    }
	    if (selectHighlight != null)
	    {
		selectHighlight.ResetTrigger("hide");
		selectHighlight.SetTrigger("show");
	    }
	    if (!DontPlaySelectSound)
	    {
		try
		{
		    uiAudioPlayer.PlaySelect();
		    return;
		}
		catch (Exception ex)
		{
		    string name = base.name;
		    string str = " doesn't have a select sound specified. ";
		    Exception ex2 = ex;
		    Debug.LogError(name + str + ((ex2 != null) ? ex2.ToString() : null));
		    return;
		}
	    }
	    dontPlaySelectSound = false;
	}

	public new void OnDeselect(BaseEventData eventData)
	{
	    StartCoroutine(ValidateDeselect());
	}

	private IEnumerator ValidateDeselect()
	{
	    prevSelectedObject = EventSystem.current.currentSelectedGameObject;
	    yield return new WaitForEndOfFrame();
	    if (EventSystem.current.currentSelectedGameObject != null)
	    {
		if (leftCursor != null)
		{
		    leftCursor.ResetTrigger("show");
		    leftCursor.SetTrigger("hide");
		}
		if (rightCursor != null)
		{
		    rightCursor.ResetTrigger("show");
		    rightCursor.SetTrigger("hide");
		}
		if (selectHighlight != null)
		{
		    selectHighlight.ResetTrigger("show");
		    selectHighlight.SetTrigger("hide");
		}
		deselectWasForced = false;
	    }
	    else if (deselectWasForced)
	    {
		if (leftCursor != null)
		{
		    leftCursor.ResetTrigger("show");
		    leftCursor.SetTrigger("hide");
		}
		if (rightCursor != null)
		{
		    rightCursor.ResetTrigger("show");
		    rightCursor.SetTrigger("hide");
		}
		if (selectHighlight != null)
		{
		    selectHighlight.ResetTrigger("show");
		    selectHighlight.SetTrigger("hide");
		}
		deselectWasForced = false;
	    }
	    else
	    {
		deselectWasForced = false;
		dontPlaySelectSound = true;
		EventSystem.current.SetSelectedGameObject(prevSelectedObject);
	    }
	}

	public void OnCancel(BaseEventData eventData)
	{
	    if(cancelAction != CancelAction.DoNothing)
	    {
		ForceDeselect();
	    }
	    if (!parentList)
	    {
		parentList = GetComponentInParent<MenuButtonList>();
	    }
	    if (parentList)
	    {
		
	    }
	    if(cancelAction != CancelAction.DoNothing)
	    {
		if(cancelAction == CancelAction.GoToMainMenu)
		{
		    UIManager.instance.UIGoToMainMenu();
		}
	    }
	    if (cancelAction != CancelAction.DoNothing)
	    {
		PlayCancelSound();
	    }
	}

	protected void ForceDeselect()
	{
	    if (EventSystem.current.currentSelectedGameObject != null)
	    {
		deselectWasForced = true;
		EventSystem.current.SetSelectedGameObject(null);
	    }
	}

	protected void PlaySubmitSound()
	{
	    if (playSubmitSound)
	    {
		uiAudioPlayer.PlaySubmit();
	    }
	}

	protected void PlayCancelSound()
	{
	    uiAudioPlayer.PlayCancel();
	}

	protected void PlaySelectSound()
	{
	    uiAudioPlayer.PlaySelect();
	}
    }
}

创建好后我们就可以用菜单按钮MenuButton继承这个类了:由于上面的功能已经很完善了,我们只需要播放flashEffect动画和判断按钮MenuButtonType可激活即可

cs 复制代码
using System;
using UnityEngine.EventSystems;

namespace UnityEngine.UI
{
    public class MenuButton : MenuSelectable,ISubmitHandler,IEventSystemHandler,IPointerClickHandler
    {
	public MenuButtonType buttonType;
	public Animator flashEffect;
	private new void Start()
	{
	    HookUpAudioPlayer();
	}

	public void OnPointerClick(PointerEventData eventData)
	{
	    OnSubmit(eventData);
	}

	public void OnSubmit(BaseEventData eventData)
	{
	    if(buttonType == MenuButtonType.Proceed)
	    {
		try
		{
		    flashEffect.ResetTrigger("Flash");
		    flashEffect.SetTrigger("Flash");
		}
		catch
		{

		}
		ForceDeselect();
	    }
	    else if(buttonType == MenuButtonType.Activate)
	    {
		try
		{
		    flashEffect.ResetTrigger("Flash");
		    flashEffect.SetTrigger("Flash");
		}
		catch
		{

		}
		PlaySubmitSound();
	    }
	}

        public enum MenuButtonType
	{
	    Proceed,
	    Activate
	}
    }
}

设置好这三个按钮:

制作好主菜单界面后,接下来就到了切换界面的时候了,这里我暂时没想到好方法,就用Unity自带的EventRegister用就好了:

二、制作UI系统的选择存档界面

1.选择存档界面制作

选择存档界面并没有那么好做,因为UI控件太多了,我们都知道这个界面有四个存档SaveSlot可供选择,

首先是制作界面的标题:

头顶的动画:

控制Control,里面有一个返回按钮 ,当然是返回到主菜单界面了,里面的Text和上面讲到的同理:

最后一个是界面里的内容Content,它有四个子对象,分别代表SaveSlots就是四个存档,ClearSaveButtons清除存档的四个按钮,ClearSavePrompts确认清除存档的四个确认栏。ClearSaveBlockers防止玩家点到其它的存档清除了。

在SaveSlots中我们先来制作第一个SlotOne,其它三个就是Slot Number的数量不同而已。

首先是当前所处区域的背景:

然后是第几个slot:

然后如果是空的存档的话就显示的新游戏的Text:

选择的slot左右指针Cursor,上面讲过了:

这个是钢魂模式小骑士死亡以后的背景:

选择后的高光:Selector

钢魂模式死亡后的文字提示:你已经寄了:

每一个slot的头顶框:他也是有四个动画,原理和我上面讲的一样:

下面这个看图,又是一个悲伤的故事:

接下来的是当这个存档不是新游戏而是已经激活的状态下ActiveSaveSlot1:

布局:

下面的我就不一一讲了,玩过游戏的懂的都懂:

然后是第二个子对象ClearSaveButtons,暂时还没能实现该功能,所以它就只是个空对象,但我们也得让它显示出来装一下:

第三个子对象ClearSavePrompts也是同理,属于不能用的

这个第四个子对象ClearSaveBlockers的格挡也很简单实现,你只需要在清除存档的确认阶段,用两个大一点的UI对象屏蔽掉其它可交互对象的射线检测,就不会点到其它的存档上了:

OK我们终于完成了初始阶段的选择存档界面的制作,接下来更让人头疼的代码逻辑处理了

2.代码的逻辑处理

来到UIManager当中,我们接下来就要做选择存档界面了。

cs 复制代码
using System;
using System.Collections;
using GlobalEnums;
using InControl;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    [Header("State")]
    [Space(6f)]
    public UIState uiState; //UI状态
    public MainMenuState menuState; //主菜单的界面

    [Header("Event System")]
    [Space(6f)]
    public EventSystem eventSystem;

    [Header("Main Elements")]
    [Space(6f)]
    public Canvas UICanvas;

    [Header("Main Menu")]
    [Space(6f)]
    public CanvasGroup mainMenuScreen; //主菜单界面的Cg
    public MainMenuOptions mainMenuButtons; //主菜单button选项
    public SpriteRenderer gameTitle; //游戏标题
    public PlayMakerFSM subTitleFSM; //游戏副标题FSM

    [Header("Save Profile Menu")]
    [Space(6f)]
    public CanvasGroup saveProfileScreen;
    public CanvasGroup saveProfileTitle;
    public CanvasGroup saveProfileControls;
    public Animator saveProfileTopFleur;
    public PreselectOption saveSlots;
    public SaveSlotButton slotOne;
    public SaveSlotButton slotTwo;
    public SaveSlotButton slotThree;
    public SaveSlotButton slotFour;

    [Header("Cinematics")]
    [SerializeField] private CinematicSkipPopup cinematicSkipPopup;

    public MenuScreen playModeMenuScreen;



    private float startMenuTime;
    private bool isFadingMenu;
    public bool IsFadingMenu
    {
	get
	{
	    return isFadingMenu || Time.time < startMenuTime;
	}
    }

    private int menuAnimationCounter;
    public bool IsAnimatingMenu
    {
	get
	{
	    return menuAnimationCounter > 0;
	}
    }

    private GameManager gm;
    private HeroController hero_ctrl;
    private PlayerData playerData;
    private InputHandler ih;
    private GraphicRaycaster graphicRaycaster;
    public MenuAudioController uiAudioPlayer;
    public HollowKnightInputModule inputModule;

    [Space]
    public float MENU_FADE_SPEED = 3.2f;

    private static UIManager _instance; //单例模式
    public static UIManager instance
    {
	get
	{
	    if (_instance == null)
	    {
		_instance = FindObjectOfType<UIManager>();
		if (_instance == null)
		{
		    Debug.LogError("Couldn't find a UIManager, make sure one exists in the scene.");
		}
		if (Application.isPlaying)
		{
		    DontDestroyOnLoad(_instance.gameObject);
		}
	    }
	    return _instance;
	}
    }

    private void Awake()
    {
	if(_instance == null)
	{
	    _instance = this;
	    DontDestroyOnLoad(this);
	}
	else if(this != _instance)
	{
	    Destroy(gameObject);
	    return;
	}
	graphicRaycaster = GetComponentInChildren<GraphicRaycaster>();
    }

    public void SceneInit()
    {
	if (this == UIManager._instance)
	{
	    SetupRefs();
	}
    }

    private void Start()
    {
	if(this == _instance)
	{
	    SetupRefs();
	    if (gm.IsMenuScene()) //判断当前场景是否是菜单场景
	    {
		startMenuTime = Time.time + 0.5f;
		GameCameras.instance.cameraController.FadeSceneIn();
		ConfigureMenu();
	    }
	    if(graphicRaycaster && InputHandler.Instance)
	    {
		InputHandler.Instance.OnCursorVisibilityChange += delegate (bool isVisible)
		{
		    graphicRaycaster.enabled = isVisible;
		};
	    }
	}
    }

    private void SetupRefs()
    {
	gm = GameManager.instance;

	playerData = PlayerData.instance;
	ih = gm.inputHandler;
	if (gm.IsGameplayScene())
	{
	    hero_ctrl = HeroController.instance;
	}
	if(gm.IsMenuScene() && gameTitle == null)
	{
	    gameTitle = GameObject.Find("LogoTitle").GetComponent<SpriteRenderer>();
	}
	if(UICanvas.worldCamera == null)
	{
	    UICanvas.worldCamera = GameCameras.instance.mainCamera;
	}
    }
    public void ConfigureMenu()
    {
	if(mainMenuButtons != null)
	{
	    mainMenuButtons.ConfigureNavigation();
	}
	if(uiState == UIState.MAIN_MENU_HOME)
	{
	    //TODO:
	}
    }

    /// <summary>
    /// 设置UI状态
    /// </summary>
    /// <param name="newState"></param>
    public void SetState(UIState newState)
    {
	if (gm == null) 
	{
	    gm = GameManager.instance;
	}
	if(newState != uiState)
	{
	    if (uiState == UIState.PAUSED && newState == UIState.PLAYING)
	    {

	    }
	    else if (uiState == UIState.PLAYING && newState == UIState.PAUSED)
	    {
	    }
	    else if (newState == UIState.INACTIVE)
	    {
		DisableScreens();
	    }
	    else if (newState == UIState.MAIN_MENU_HOME)
	    {
		//TODO:
		UIGoToMainMenu();
	    }
	    else if (newState == UIState.LOADING)
	    {
		DisableScreens();
	    }
	    else if (newState == UIState.PLAYING)
	    {
		DisableScreens();
	    }
	    else if (newState == UIState.CUTSCENE)
	    {
		DisableScreens();
	    }
	    uiState = newState;
	    return;
	}
	if (newState == UIState.MAIN_MENU_HOME)
	{
	    UIGoToMainMenu();
	}
    }

    /// <summary>
    /// 关闭某些特定的屏幕
    /// </summary>
    private void DisableScreens()
    {
	for (int i = 0; i < UICanvas.transform.childCount; i++)
	{
	    if (!(UICanvas.transform.GetChild(i).name == "PauseMenuScreen"))
	    {
		UICanvas.transform.GetChild(i).gameObject.SetActive(false);
	    }
	}
    }

    /// <summary>
    /// 设置UI初始状态
    /// </summary>
    /// <param name="gameState"></param>
    public void SetUIStartState(GameState gameState)
    {
	if (gameState == GameState.MAIN_MENU)
	{
	    SetState(UIState.MAIN_MENU_HOME);
	    return;
	}
	if (gameState == GameState.LOADING)
	{
	    SetState(UIState.LOADING);
	    return;
	}
	if (gameState == GameState.ENTERING_LEVEL)
	{
	    SetState(UIState.PLAYING);
	    return;
	}
	if (gameState == GameState.PLAYING)
	{
	    SetState(UIState.PLAYING);
	    return;
	}
	if (gameState == GameState.CUTSCENE)
	{
	    SetState(UIState.CUTSCENE);
	}
    }

    /// <summary>
    /// 设置新的主菜单界面
    /// </summary>
    /// <param name="newState"></param>
    private void SetMenuState(MainMenuState newState)

    {
	menuState = newState;
    }
    public void UIGoToMainMenu()
    {
	StartMenuAnimationCoroutine(GoToMainMenu());
    }

    /// <summary>
    /// 前往主菜单界面
    /// </summary>
    /// <returns></returns>
    private IEnumerator GoToMainMenu()
    {
	Debug.LogFormat("Go To Main Menu");
	if(ih == null)
	{
	    ih = InputHandler.Instance;
	}
	ih.StopUIInput();
	if (menuState == MainMenuState.OPTIONS_MENU || menuState == MainMenuState.ACHIEVEMENTS_MENU || menuState == MainMenuState.QUIT_GAME_PROMPT || menuState == MainMenuState.EXTRAS_MENU || menuState == MainMenuState.ENGAGE_MENU || menuState == MainMenuState.NO_SAVE_MENU || menuState == MainMenuState.PLAY_MODE_MENU)
	{
	    yield return StartCoroutine(HideCurrentMenu());
	}
	else if(menuState == MainMenuState.SAVE_PROFILES)
	{
	    yield return StartCoroutine(HideSaveProfileMenu());
	}
	ih.StopUIInput();
	gameTitle.gameObject.SetActive(true);
	mainMenuScreen.gameObject.SetActive(true);

	StartCoroutine(FadeInSprite(gameTitle));
	subTitleFSM.SendEvent("FADE IN");
	yield return StartCoroutine(FadeInCanvasGroup(mainMenuScreen));
	mainMenuScreen.interactable = true;
	ih.StartUIInput();
	yield return null;
	mainMenuButtons.HighlightDefault(false);
	SetMenuState(MainMenuState.MAIN_MENU);
    }

    public void UIGoToProfileMenu()
    {
	StartMenuAnimationCoroutine(GoToProfileMenu());
    }

    /// <summary>
    /// 前往存档选择界面
    /// </summary>
    /// <returns></returns>
    private IEnumerator GoToProfileMenu()
    {
	ih.StopUIInput();
	if(menuState == MainMenuState.MAIN_MENU)
	{
	    StartCoroutine(FadeOutSprite(gameTitle));
	    subTitleFSM.SendEvent("FADE OUT");
	    yield return StartCoroutine(FadeOutCanvasGroup(mainMenuScreen));
	}
	else if(menuState == MainMenuState.PLAY_MODE_MENU)
	{
	    yield return StartCoroutine(HideCurrentMenu());
	    ih.StopUIInput();
	}
	StartCoroutine(FadeInCanvasGroup(saveProfileScreen));
	saveProfileTopFleur.ResetTrigger("hide");
	saveProfileTopFleur.SetTrigger("show");
	StartCoroutine(FadeInCanvasGroup(saveProfileTitle));
	StartCoroutine(FadeInCanvasGroup(saveProfileScreen));
	StartCoroutine(PrepareSaveFilesInOrder());
	yield return new WaitForSeconds(0.165f);
	SaveSlotButton[] slotButtons = new SaveSlotButton[]
	{
	    slotOne,
	    slotTwo,
	    slotThree,
	    slotFour
	};
	int num;
	for (int i = 0; i < slotButtons.Length; i++)
	{
	    slotButtons[i].ShowRelevantModeForSaveFileState();
	    yield return new WaitForSeconds(0.165f);
	    num = i + 1;
	}
	yield return StartCoroutine(gm.timeTool.TimeScaleIndependentWaitForSeconds(0.695f));
	StartCoroutine(FadeInCanvasGroup(saveProfileControls));
	ih.StartUIInput();
	yield return null;
	saveSlots.HighlightDefault(false);
	SetMenuState(MainMenuState.SAVE_PROFILES);

    }

    public void UIStartNewGame()
    {
	StartNewGame(false, false);
    }

    /// <summary>
    /// 开启新存档游戏
    /// </summary>
    /// <param name="permaDeath"></param>
    /// <param name="bossRush"></param>
    private void StartNewGame(bool permaDeath = false, bool bossRush = false)
    {
	uiAudioPlayer.PlayStartGame();
	gm.EnsureSaveSlotSpace(delegate (bool hasSpace)
	{
	    if (hasSpace)
	    {
		if (menuState == MainMenuState.SAVE_PROFILES)
		{
		    StartCoroutine(HideSaveProfileMenu());
		}
		else
		{
		    StartCoroutine(HideCurrentMenu());
		}
		uiAudioPlayer.PlayStartGame();
		gm.StartNewGame(permaDeath, bossRush);
		return;
	    }
	    ih.StartUIInput();
	    SaveSlotButton saveSlotButton;
	    switch (gm.profileID)
	    {
		default:
		    saveSlotButton = slotOne;
		    break;
		case 2:
		    saveSlotButton = slotTwo;
		    break;
		case 3:
		    saveSlotButton = slotThree;
		    break;
		case 4:
		    saveSlotButton = slotFour;
		    break;
	    }
	    saveSlotButton.Select();
	});
    }

    /// <summary>
    /// 预备保存存档顺序队列
    /// </summary>
    /// <returns></returns>
    private IEnumerator PrepareSaveFilesInOrder()
    {
	SaveSlotButton[] slotButtons = new SaveSlotButton[]
	{
	    slotOne,
	    slotTwo,
	    slotThree,
	    slotFour
	};
	int num;
	for (int i = 0; i < slotButtons.Length; i++)
	{
	    SaveSlotButton slotButton = slotButtons[i];
	    if (slotButton.saveFileState == SaveSlotButton.SaveFileStates.NotStarted)
	    {
		slotButton.Prepare(gm, false);
		while (slotButton.saveFileState == SaveSlotButton.SaveFileStates.OperationInProgress)
		{
		    yield return null;
		}
	    }
	    slotButton = null;
	    num = i + 1;
	}
	yield return null;
    }

    /// <summary>
    /// 隐藏选择存档界面
    /// </summary>
    /// <returns></returns>
    public IEnumerator HideSaveProfileMenu()
    {
	StartCoroutine(FadeOutCanvasGroup(saveProfileTitle));
	saveProfileTopFleur.ResetTrigger("show");
	saveProfileTopFleur.SetTrigger("hide");
	yield return StartCoroutine(gm.timeTool.TimeScaleIndependentWaitForSeconds(0.165f));
	slotOne.HideSaveSlot();
	yield return StartCoroutine(gm.timeTool.TimeScaleIndependentWaitForSeconds(0.165f));
	slotTwo.HideSaveSlot();
	yield return StartCoroutine(gm.timeTool.TimeScaleIndependentWaitForSeconds(0.165f));
	slotThree.HideSaveSlot();
	yield return StartCoroutine(gm.timeTool.TimeScaleIndependentWaitForSeconds(0.165f));
	slotFour.HideSaveSlot();
	yield return StartCoroutine(gm.timeTool.TimeScaleIndependentWaitForSeconds(0.33f));
	yield return StartCoroutine(FadeOutCanvasGroup(saveProfileControls));
	yield return StartCoroutine(FadeOutCanvasGroup(saveProfileScreen));
    }

    /// <summary>
    /// 隐藏当前界面
    /// </summary>
    /// <returns></returns>
    public IEnumerator HideCurrentMenu()
    {
	isFadingMenu = true;
	MenuScreen menu;
	switch (menuState)
	{
	    case MainMenuState.OPTIONS_MENU:
		break;
	    case MainMenuState.GAMEPAD_MENU:
		break;
	    case MainMenuState.KEYBOARD_MENU:
		break;
	    case MainMenuState.SAVE_PROFILES:
		break;
	    case MainMenuState.AUDIO_MENU:
		break;
	    case MainMenuState.VIDEO_MENU:
		break;
	    case MainMenuState.EXIT_PROMPT:
		break;
	    case MainMenuState.OVERSCAN_MENU:
		break;
	    case MainMenuState.GAME_OPTIONS_MENU:
		break;
	    case MainMenuState.ACHIEVEMENTS_MENU:
		break;
	    case MainMenuState.QUIT_GAME_PROMPT:
		break;
	    case MainMenuState.RESOLUTION_PROMPT:
		break;
	    case MainMenuState.BRIGHTNESS_MENU:
		break;
	    case MainMenuState.PAUSE_MENU:
		break;
	    case MainMenuState.PLAY_MODE_MENU:
		menu = playModeMenuScreen;
		break;
	    case MainMenuState.EXTRAS_MENU:
		break;
	    case MainMenuState.REMAP_GAMEPAD_MENU:
		break;
	    case MainMenuState.ENGAGE_MENU:
		break;
	    case MainMenuState.NO_SAVE_MENU:
		break;
	    default:
		yield break;
	}
	ih.StopUIInput();
	//TODO:
	yield return null;
	ih.StartUIInput();
	isFadingMenu = false;
    }

    public void ShowCutscenePrompt(CinematicSkipPopup.Texts text)
    {
	cinematicSkipPopup.gameObject.SetActive(true);
	cinematicSkipPopup.Show(text);
    }

    public void HideCutscenePrompt()
    {
	cinematicSkipPopup.Hide();
    }


    public void MakeMenuLean()
    {
	Debug.Log("Making UI menu lean.");
	if (saveProfileScreen)
	{
	    Destroy(saveProfileScreen.gameObject);
	    saveProfileScreen = null;
	}
	//TODO:
    }

    private Coroutine StartMenuAnimationCoroutine(IEnumerator routine)
    {
	return StartCoroutine(StartMenuAnimationCoroutineWorker(routine));
    }
    private IEnumerator StartMenuAnimationCoroutineWorker(IEnumerator routine)
    {
	menuAnimationCounter++;
	yield return StartCoroutine(routine);
	menuAnimationCounter--;
    }
    /// <summary>
    /// 线性插值淡入CanvasGroup
    /// </summary>
    /// <param name="cg"></param>
    /// <returns></returns>
    public IEnumerator FadeInCanvasGroup(CanvasGroup cg)
    {
	float loopFailsafe = 0f;
	cg.alpha = 0f;
	cg.gameObject.SetActive(true);
	while (cg.alpha < 1f)
	{
	    cg.alpha += Time.unscaledDeltaTime * MENU_FADE_SPEED;
	    loopFailsafe += Time.unscaledDeltaTime;
	    if (cg.alpha >= 0.95f)
	    {
		cg.alpha = 1f;
		break;
	    }
	    if (loopFailsafe >= 2f)
	    {
		break;
	    }
	    yield return null;
	}
	cg.alpha = 1f;
	cg.interactable = true;
	cg.gameObject.SetActive(true);
	yield return null;
	yield break;
    }
    /// <summary>
    /// 线性插值淡出CanvasGroup
    /// </summary>
    /// <param name="cg"></param>
    /// <returns></returns>
    public IEnumerator FadeOutCanvasGroup(CanvasGroup cg)
    {
	float loopFailsafe = 0f;
	cg.interactable = false;
	while(cg.alpha > 0.05f)
	{
	    cg.alpha -= Time.unscaledDeltaTime * MENU_FADE_SPEED;
	    loopFailsafe += Time.unscaledDeltaTime;
	    if(cg.alpha <= 0.05f || loopFailsafe >= 2f)
	    {
		break;
	    }
	    yield return null;
	}
	cg.alpha = 0f;
	cg.gameObject.SetActive(false);
	yield return null;
    }
    /// <summary>
    /// 线性插值淡入SpriteRenderer
    /// </summary>
    /// <param name="sprite"></param>
    /// <returns></returns>
    private IEnumerator FadeInSprite(SpriteRenderer sprite)
    {
	while (sprite.color.a < 1f)
	{
	    sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, sprite.color.a + Time.unscaledDeltaTime * MENU_FADE_SPEED);
	    yield return null;
	}
	sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, 1f);
	yield return null;
    }
    /// <summary>
    /// 线性插值淡出SpriteRenderer
    /// </summary>
    /// <param name="sprite"></param>
    /// <returns></returns>
    private IEnumerator FadeOutSprite(SpriteRenderer sprite)
    {
	while(sprite.color.a > 0f)
	{
	    sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, sprite.color.a - Time.unscaledDeltaTime * MENU_FADE_SPEED);
	    yield return null;
	}
	sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, 0f);
	yield return null;
    }
}

由于两个界面的Button需要实现的功能和动画效果的数量差异明显,我们需要一个有别于MainMenuButton的脚本的另一个脚本:SaveSlotButton.cs

cs 复制代码
using System;
using System.Collections;
using GlobalEnums;
using UnityEngine.EventSystems;

namespace UnityEngine.UI
{
    public class SaveSlotButton : MenuButton,ISelectHandler,IEventSystemHandler,IDeselectHandler,ISubmitHandler,IPointerClickHandler
    {
	private bool verboseMode = true;
	[Header("Slot Number")]
	public SaveSlot saveSlot;

	[Header("Animation")]
	public Animator topFleur;
	public Animator highlight;

	[Header("Canvas Group")]
	public CanvasGroup newGameText;
	public CanvasGroup saveCorruptedText;
	public CanvasGroup loadingText;
	public CanvasGroup activeSaveSlot;
	public CanvasGroup clearSaveButton;
	public CanvasGroup clearSavePrompt;
	public CanvasGroup backgroundCg;
	public CanvasGroup slotNumberText;
	public CanvasGroup myCanvasGroup;
	public CanvasGroup defeatedText;
	public CanvasGroup defeatedBackground;
	public CanvasGroup brokenSteelOrb;

	[Header("Text Elements")]
	public Text geoText;
	public Text locationText;
	public Text playTimeText;
	public Text completionText;

	[Header("Soul Orbs")]
	public CanvasGroup normalSoulOrbCg;
	public CanvasGroup hardcoreSoulOrbCg;
	public CanvasGroup ggSoulOrbCg;

	[Header("Visual Elements")]
	public Image background;
	public Image soulOrbIcon;

	public Image geoIcon;

	[Header("Raycast Blocker")]
	public GameObject clearSaveBlocker;

	private GameManager gm;
	private UIManager ui;
	private InputHandler ih;
	private CoroutineQueue coroutineQueue;
	private PreselectOption clearSavePromptHighlight;

	private Navigation noNav;
	private Navigation fullSlotNav;
	private Navigation emptySlotNav;

	private IEnumerator currentLoadingTextFadeIn;
	private bool didLoadSaveStats;

	[SerializeField] public SlotState state { get; private set; }
	public SaveFileStates saveFileState;
	[SerializeField] private SaveStats saveStats;

	private int SaveSlotIndex
	{
	    get
	    {
		switch (saveSlot)
		{
		    case SaveSlot.SLOT_1:
			return 1;
		    case SaveSlot.SLOT_2:
			return 2;
		    case SaveSlot.SLOT_3:
			return 3;
		    case SaveSlot.SLOT_4:
			return 4;
		    default:
			return 0;
		}
	    }
	} //获取当前的SaveSlot的值

	private new void Awake()
	{
	    gm = GameManager.instance;
	    clearSavePromptHighlight = clearSavePrompt.GetComponent<PreselectOption>();
	    coroutineQueue = new CoroutineQueue(gm);
	    SetupNavs();
	}

	private new void OnEnable()
	{
	    if(saveStats != null && saveFileState == SaveFileStates.LoadedStats)
	    {
		PresentSaveSlot(saveStats);
	    }
	}

	private new void Start()
	{
	    if (!Application.isPlaying)
	    {
		return;
	    }
	    ui = UIManager.instance;
	    ih = gm.inputHandler;
	    HookUpAudioPlayer();
	}

	/// <summary>
	/// 设置好每一个不同状态下的导航系统
	/// </summary>
	private void SetupNavs()
	{
	    noNav = new Navigation
	    {
		mode = Navigation.Mode.Explicit,
		selectOnLeft = null,
		selectOnRight = null,
		selectOnUp = navigation.selectOnUp,
		selectOnDown = navigation.selectOnDown
	    };
	    emptySlotNav = new Navigation
	    {
		mode = Navigation.Mode.Explicit,
		selectOnRight = null,
		selectOnUp = navigation.selectOnUp,
		selectOnDown = navigation.selectOnDown
	    };
	    fullSlotNav = new Navigation
	    {
		mode = Navigation.Mode.Explicit,
		selectOnRight = clearSaveButton.GetComponent<ClearSaveButton>(),
		selectOnUp = navigation.selectOnUp,
		selectOnDown = navigation.selectOnDown
	    };
	}

	/// <summary>
	/// 准备阶段
	/// </summary>
	/// <param name="gameManager"></param>
	/// <param name="isReload"></param>
	public void Prepare(GameManager gameManager,bool isReload = false) 
	{
	    if(saveFileState == SaveFileStates.NotStarted || (isReload && saveFileState == SaveFileStates.Corrupted))
	    {
		//TODO:先将SaveFileState更改成空闲的状态,等以后做了可持续化数据系统再来完善这段。
		ChangeSaveFileState(SaveFileStates.Empty);
	    }
	}

	private void ChangeSaveFileState(SaveFileStates nextSaveFileState)
	{
	    saveFileState = nextSaveFileState;
	    if (isActiveAndEnabled)
	    {
		ShowRelevantModeForSaveFileState();
	    }
	}

	private void PresentSaveSlot(SaveStats saveStats)
	{
	    geoIcon.enabled = true;
	    geoText.enabled = true;
	    completionText.enabled = true;
	    if (saveStats.bossRushMode)
	    {

	    }
	    else if (saveStats.permadeathMode == 0)
	    {
		normalSoulOrbCg.alpha = 1f;
		hardcoreSoulOrbCg.alpha = 0f;
		ggSoulOrbCg.alpha = 0f;

		geoText.text = saveStats.geo.ToString();
		if (saveStats.unlockedCompletionRate)
		{
		    completionText.text = saveStats.completionPercentage.ToString() + "%";
		}
		else
		{
		    completionText.text = "";
		}
		playTimeText.text = saveStats.GetPlaytimeHHMM();

	    }
	    else if (saveStats.permadeathMode == 1)
	    {

	    }
	    else if(saveStats.permadeathMode == 2)
	    {
		normalSoulOrbCg.alpha = 0f;
		hardcoreSoulOrbCg.alpha = 0f;
		ggSoulOrbCg.alpha = 0f;
	    }
	    locationText.text = "KING'S PASS";
	}

	/// <summary>
	/// 动画化的切换saveslot状态
	/// </summary>
	/// <param name="nextState"></param>
	/// <returns></returns>
	private IEnumerator AnimateToSlotState(SlotState nextState)
	{
	    SlotState state = this.state;
	    if(state == nextState)
	    {
		yield break;
	    }
	    if(currentLoadingTextFadeIn != null)
	    {
		StartCoroutine(currentLoadingTextFadeIn);
		currentLoadingTextFadeIn = null;
	    }
	    if (verboseMode)
	    {
		Debug.LogFormat("{0} SetState: {1} -> {2}", new object[]
		{
		    name,
		    this.state,
		    nextState
		});
	    }
	    this.state = nextState;
	    switch (nextState)
	    {
		case SlotState.HIDDEN:
		case SlotState.OPERATION_IN_PROGRESS:
		    navigation = noNav;
		    break;
		case SlotState.EMPTY_SLOT:
		    navigation = emptySlotNav;
		    break;
		case SlotState.SAVE_PRESENT:
		case SlotState.CORRUPTED:
		case SlotState.CLEAR_PROMPT:
		    navigation = fullSlotNav;
		    break;
	    }
	    //如果当前状态是隐藏
	    if(state == SlotState.HIDDEN)
	    {
		if(nextState == SlotState.OPERATION_IN_PROGRESS)
		{
		    topFleur.ResetTrigger("hide");
		    topFleur.SetTrigger("show");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(currentLoadingTextFadeIn = FadeInCanvasGroupAfterDelay(5f, loadingText));
		}
		else if(nextState == SlotState.EMPTY_SLOT)
		{
		    topFleur.ResetTrigger("hide");
		    topFleur.SetTrigger("show");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText)); //最后的alpha为1f
		    StartCoroutine(ui.FadeInCanvasGroup(newGameText));
		}
		else if(nextState == SlotState.SAVE_PRESENT)
		{
		    topFleur.ResetTrigger("hide");
		    topFleur.SetTrigger("show");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(backgroundCg));
		    StartCoroutine(ui.FadeInCanvasGroup(activeSaveSlot));
		    StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		}
		else if(nextState == SlotState.DEFEATED)
		{
		    topFleur.ResetTrigger("hide");
		    topFleur.SetTrigger("show");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeInCanvasGroup(defeatedBackground));
		    StartCoroutine(ui.FadeInCanvasGroup(defeatedText));
		    StartCoroutine(ui.FadeInCanvasGroup(brokenSteelOrb));
		    StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    myCanvasGroup.blocksRaycasts = true;
		}
		else if(nextState == SlotState.CORRUPTED)
		{
		    topFleur.ResetTrigger("hide");
		    topFleur.SetTrigger("show");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(saveCorruptedText));
		    StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    myCanvasGroup.blocksRaycasts = true;
		}
	    }
	    //如果当前状态是如果正在执行操作
	    else if(state == SlotState.OPERATION_IN_PROGRESS)
	    {
		if(nextState == SlotState.EMPTY_SLOT)
		{
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(loadingText));
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(newGameText));
		}
		else if(nextState == SlotState.SAVE_PRESENT)
		{
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(loadingText));
		    //TODO:
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(backgroundCg)); 
		    StartCoroutine(ui.FadeInCanvasGroup(activeSaveSlot));
		    StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		}
		else if(nextState == SlotState.DEFEATED)
		{
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(loadingText));
		    StartCoroutine(ui.FadeInCanvasGroup(defeatedBackground));
		    StartCoroutine(ui.FadeInCanvasGroup(defeatedText));
		    StartCoroutine(ui.FadeInCanvasGroup(brokenSteelOrb));
		    StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    myCanvasGroup.blocksRaycasts = true;
		}
		else if(nextState == SlotState.CORRUPTED)
		{
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(loadingText));
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(saveCorruptedText));
		    StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    myCanvasGroup.blocksRaycasts = true;
		}
	    }
	    //如果当前状态是已经保存了的slot
	    else if(state == SlotState.SAVE_PRESENT)
	    {
		if (nextState == SlotState.CLEAR_PROMPT)
		{
		    ih.StopUIInput();
		    interactable = false;
		    myCanvasGroup.blocksRaycasts = true;
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText)); //从1到0
		    StartCoroutine(ui.FadeOutCanvasGroup(activeSaveSlot));
		    StartCoroutine(ui.FadeOutCanvasGroup(backgroundCg));
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = false;
		    clearSaveBlocker.SetActive(true);
		    yield return StartCoroutine(ui.FadeInCanvasGroup(clearSavePrompt)); //从0到1
		    clearSavePrompt.interactable = true;
		    clearSavePrompt.blocksRaycasts = true;
		    clearSavePromptHighlight.HighlightDefault(false);
		    ih.StartUIInput();
		}
		else if(nextState == SlotState.HIDDEN)
		{
		    topFleur.ResetTrigger("show");
		    topFleur.SetTrigger("hide");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText)); //从1到0
		    StartCoroutine(ui.FadeOutCanvasGroup(backgroundCg));
		    StartCoroutine(ui.FadeOutCanvasGroup(activeSaveSlot));
		    StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = false;
		}
	    }
	    //如果当前状态是 清除存档确认
	    else if (state == SlotState.CLEAR_PROMPT)
	    {
		if(nextState == SlotState.SAVE_PRESENT) //相当于反悔了回到SAVE_PRESENT状态
		{
		    ih.StopUIInput();
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSavePrompt));
		    clearSaveBlocker.SetActive(false);
		    clearSavePrompt.interactable = false;
		    clearSavePrompt.blocksRaycasts = false;
		    //TODO:
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(activeSaveSlot));
		    StartCoroutine(ui.FadeInCanvasGroup(backgroundCg));
		    yield return StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    interactable = true;
		    myCanvasGroup.blocksRaycasts = true;
		    Select();
		    ih.StartUIInput();
		}
		else if(nextState == SlotState.EMPTY_SLOT) //清除存档了就到EMPTY_SLOT状态了
		{
		    ih.StopUIInput();
		    StartCoroutine(ui.FadeOutCanvasGroup(backgroundCg)); //1->0
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSavePrompt));
		    clearSavePrompt.interactable = false;
		    clearSavePrompt.blocksRaycasts = false;
		    clearSaveBlocker.SetActive(false);
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    yield return StartCoroutine(ui.FadeInCanvasGroup(newGameText));
		    myCanvasGroup.blocksRaycasts = true;
		    Select();
		    ih.StartUIInput();
		}
		else if(nextState == SlotState.DEFEATED)
		{
		    ih.StopUIInput();
		    StartCoroutine(ui.FadeOutCanvasGroup(backgroundCg)); //1 -> 0
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSavePrompt));
		    clearSavePrompt.interactable = false;
		    clearSavePrompt.blocksRaycasts = false;
		    clearSaveBlocker.SetActive(false);
		    StartCoroutine(ui.FadeInCanvasGroup(defeatedBackground)); //0 -> 1
		    StartCoroutine(ui.FadeInCanvasGroup(defeatedText));
		    StartCoroutine(ui.FadeInCanvasGroup(brokenSteelOrb));
		    yield return StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    myCanvasGroup.blocksRaycasts = true;
		    Select();
		    ih.StartUIInput();
		}
		else if(nextState == SlotState.HIDDEN)
		{
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSavePrompt));
		}
		else if(nextState == SlotState.CORRUPTED)
		{
		    ih.StopUIInput();
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSavePrompt));
		    clearSavePrompt.interactable = false;
		    clearSavePrompt.blocksRaycasts = false;
		    clearSaveBlocker.SetActive(false);
		    StartCoroutine(ui.FadeInCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeInCanvasGroup(saveCorruptedText));
		    yield return StartCoroutine(ui.FadeInCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = true;
		    myCanvasGroup.blocksRaycasts = true;
		    Select();
		    ih.StartUIInput();
		}
	    }
	    //如果当前状态是空的
	    else if(state == SlotState.EMPTY_SLOT)
	    {
		if(nextState == SlotState.HIDDEN)
		{
		    topFleur.ResetTrigger("show");
		    topFleur.SetTrigger("hide");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeOutCanvasGroup(backgroundCg));
		    StartCoroutine(ui.FadeOutCanvasGroup(newGameText));
		}
	    }
	    //如果当前状态是钢魂档破碎
	    else if (state == SlotState.DEFEATED)
	    {
		if(nextState == SlotState.CLEAR_PROMPT) //进入清除确认状态
		{
		    ih.StopUIInput();
		    interactable = false;
		    myCanvasGroup.blocksRaycasts = false;
		    StartCoroutine(ui.FadeOutCanvasGroup(defeatedBackground));
		    StartCoroutine(ui.FadeOutCanvasGroup(defeatedText));
		    StartCoroutine(ui.FadeOutCanvasGroup(brokenSteelOrb));
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = false;
		    clearSaveBlocker.SetActive(true);
		    yield return StartCoroutine(ui.FadeInCanvasGroup(clearSavePrompt));
		    clearSavePrompt.interactable = true;
		    clearSavePrompt.blocksRaycasts = true;
		    clearSavePromptHighlight.HighlightDefault(false);
		    interactable = false;
		    myCanvasGroup.blocksRaycasts = false;
		    ih.StartUIInput();
		}
		else if(nextState == SlotState.HIDDEN)
		{
		    topFleur.ResetTrigger("show");
		    topFleur.SetTrigger("hide");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText)); // 1-> 0
		    StartCoroutine(ui.FadeOutCanvasGroup(backgroundCg));
		    StartCoroutine(ui.FadeOutCanvasGroup(activeSaveSlot));
		    StartCoroutine(ui.FadeOutCanvasGroup(defeatedBackground));
		    StartCoroutine(ui.FadeOutCanvasGroup(defeatedText));
		    StartCoroutine(ui.FadeOutCanvasGroup(brokenSteelOrb));
		    StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = false;
		}
	    }
	    else if(state == SlotState.CORRUPTED)
	    {
		if(nextState == SlotState.CLEAR_PROMPT)
		{
		    ih.StopUIInput();
		    interactable = false;
		    myCanvasGroup.blocksRaycasts = false;
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeOutCanvasGroup(saveCorruptedText));
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = false;
		    clearSaveBlocker.SetActive(true);
		    yield return StartCoroutine(ui.FadeInCanvasGroup(clearSavePrompt));
		    clearSavePrompt.interactable = true;
		    clearSavePrompt.blocksRaycasts = true;
		    clearSavePromptHighlight.HighlightDefault(false);
		    interactable = false;
		    myCanvasGroup.blocksRaycasts = false;
		    ih.StartUIInput();
		}
		else if(nextState == SlotState.HIDDEN)
		{
		    topFleur.ResetTrigger("show");
		    topFleur.SetTrigger("hide");
		    yield return new WaitForSeconds(0.2f);
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeOutCanvasGroup(saveCorruptedText));
		    StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    clearSaveButton.blocksRaycasts = false;
		}
		else if(nextState == SlotState.OPERATION_IN_PROGRESS)
		{
		    StartCoroutine(ui.FadeOutCanvasGroup(slotNumberText));
		    StartCoroutine(ui.FadeOutCanvasGroup(saveCorruptedText));
		    yield return StartCoroutine(ui.FadeOutCanvasGroup(clearSaveButton));
		    StartCoroutine(currentLoadingTextFadeIn = FadeInCanvasGroupAfterDelay(5f, loadingText));
		}
	    }
	    else if(state == SlotState.OPERATION_IN_PROGRESS && nextState == SlotState.HIDDEN)
	    {
		topFleur.ResetTrigger("show");
		topFleur.SetTrigger("hide");
		yield return new WaitForSeconds(0.2f);
		StartCoroutine(ui.FadeOutCanvasGroup(loadingText));
	    }
	}

	public void ShowRelevantModeForSaveFileState()
	{
	    switch (saveFileState)
	    {
		case SaveFileStates.Empty:
		    coroutineQueue.Enqueue(AnimateToSlotState(SlotState.EMPTY_SLOT));
		    return;
		case SaveFileStates.LoadedStats:
		    if(saveStats.permadeathMode == 2)
		    {
			coroutineQueue.Enqueue(AnimateToSlotState(SlotState.DEFEATED));
			return;
		    }
		    coroutineQueue.Enqueue(AnimateToSlotState(SlotState.SAVE_PRESENT));
		    break;
		case SaveFileStates.Corrupted:
		    coroutineQueue.Enqueue(AnimateToSlotState(SlotState.CORRUPTED));
		    break;
		default:
		    break;
	    }
	}

	/// <summary>
	/// 进入清除确认状态CLEAR_PROMPT
	/// </summary>
	public void ClearSavePrompt()
	{
	    coroutineQueue.Enqueue(AnimateToSlotState(SlotState.CLEAR_PROMPT));
	}

	/// <summary>
	/// 进入隐藏状态HIDDEN
	/// </summary>
	public void HideSaveSlot()
	{
	    coroutineQueue.Enqueue(AnimateToSlotState(SlotState.HIDDEN));
	}

	private IEnumerator FadeInCanvasGroupAfterDelay(float delay, CanvasGroup cg)
	{
	    for (float timer = 0f; timer < delay; timer += Time.unscaledDeltaTime)
	    {
		yield return null;
	    }
	    yield return ui.FadeInCanvasGroup(cg);
	}

	public enum SaveSlot
	{
	    SLOT_1,
	    SLOT_2,
	    SLOT_3,
	    SLOT_4
	}

	public enum SaveFileStates
	{
	    NotStarted, //还未开始
	    OperationInProgress, //进展阶段
	    Empty, //空
	    LoadedStats, //上传后阶段
	    Corrupted //被破坏阶段
	}

	public enum SlotState //SaveSlot的状态
	{
	    HIDDEN, //隐藏
	    OPERATION_IN_PROGRESS, //进展
	    EMPTY_SLOT, //空的slot
	    SAVE_PRESENT, //保存当前的
	    CORRUPTED, //被破坏
	    CLEAR_PROMPT, //清除确认
	    DEFEATED //钢魂死亡
	}
    }
}

回到编辑器中为每一个saveslot都添加好状态:

来到gamemanager.cs当中,我们还有一个没写完的函数叫gm.StartNewGame(permaDeath, bossRush);第一个参数是是否永久死亡也就是钢魂,第二个是寻神者模式,肯定没这么快所以都得是零蛋

cs 复制代码
using System;
using GlobalEnums;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public int profileID; //这个用来记录这是第几个saveslot的,但需要注意的是,第2,3,4个slot对应的是相同的数字,而第一个slot是除了2,3,4数字以外的数字。
    public GameState gameState;
    public bool isPaused;

    public TimeScaleIndependentUpdate timeTool;
    private int timeSlowedCount;
    public bool TimeSlowed
    {
	get
	{
	    return timeSlowedCount > 0;
	}
    }

    [SerializeField] public PlayerData playerData;
    public InputHandler inputHandler{ get; private set; }
    public SceneManager sm { get; private set; }
    public HeroController hero_ctrl { get; private set; }
    public CameraController cameraCtrl { get; private set; }
    public UIManager ui { get; private set; }

    public PlayMakerFSM soulOrb_fsm { get; private set; }
    public PlayMakerFSM soulVessel_fsm { get; private set; }

    [SerializeField] private AudioManager audioManager;
    public AudioManager AudioManager
    {
	get
	{
	    return audioManager;
	}
    }
    private GameCameras gameCams;

    private bool hazardRespawningHero;

    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;
    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 static GameManager _instance;
    public static GameManager instance
    {
	get
	{
	    if (_instance == null)
	    {
		_instance = FindObjectOfType<GameManager>();
		if (_instance == null)
		{
		    Debug.LogError("Couldn't find a Game Manager, make sure one exists in the scene.");
		}
		else if (Application.isPlaying)
		{
		    DontDestroyOnLoad(_instance.gameObject);
		}
	    }
	    return _instance;
	}
    }
    public static GameManager UnsafeInstance
    {
	get
	{
	    return _instance;
	}
    }

    private void Awake()
    {
	if(_instance == null)
	{
	    _instance = this;
	    DontDestroyOnLoad(this);
	    SetupGameRefs();
	    return;
	}
	if(this != _instance)
	{
	    Destroy(gameObject);
	    return;
	}
	SetupGameRefs();
    }

    private void SetupGameRefs()
    {
	playerData = PlayerData.instance;
	gameCams = GameCameras.instance;
	cameraCtrl = gameCams.cameraController;
	inputHandler = GetComponent<InputHandler>();
	if (inputHandler == null)
	{
	    Debug.LogError("Couldn't find InputHandler component.");
	}
	UnityEngine.SceneManagement.SceneManager.activeSceneChanged += LevelActivated;

    }

    private void OnDisable()
    {
	UnityEngine.SceneManagement.SceneManager.activeSceneChanged -= LevelActivated;
    }

    private void LevelActivated(Scene sceneFrom, Scene sceneTo)
    {
	if(this == _instance)
	{
	    if (!waitForManualLevelStart)
	    {
		Debug.LogFormat(this, "Performing automatic level start.", Array.Empty<object>());
		if (startedOnThisScene && IsGameplayScene())
		{

		}
		SetupSceneRefs(true);
		BeginScene();
		OnNextLevelReady();
		return;
	    }
	}
    }

    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);
	    }
	    soulOrb_fsm = gameCams.soulOrbFSM;
	    soulVessel_fsm = gameCams.soulVesselFSM;
	}
    }

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

    }

    public void StartNewGame(bool permadeathMode = false, bool bossRushMode = false)
    {
	if (permadeathMode)
	{

	}
	else
	{

	}
	if (bossRushMode)
	{

	}
	StartCoroutine(RunStartNewGame());
    }

    private IEnumerator RunStartNewGame()
    {
	cameraCtrl.FadeOut(CameraFadeType.START_FADE);
	//TODO:AudioSnap

	yield return new WaitForSeconds(2.6f);
	ui.MakeMenuLean();
	BeginSceneTransition(new SceneLoadInfo
	{
	    AlwaysUnloadUnusedAssets = true,
	    IsFirstLevelForPlayer = true,
	    PreventCameraFadeOut = true,
	    WaitForSceneTransitionCameraFade = false,
	    SceneName = "Opening_Sequence",
	    Visualization = SceneLoadVisualizations.Custom
	});
    }

    public void OnWillActivateFirstLevel()
    {
	HeroController.instance.isEnteringFirstLevel = true;
	entryGateName = "top1";
	SetState(GameState.PLAYING);
	ui.ConfigureMenu();
    }

    public IEnumerator LoadFirstScene()
    {
	yield return new WaitForEndOfFrame();
	OnWillActivateFirstLevel();
	LoadScene("Tutorial_01");
    }

    public void LoadOpeningCinematic()
    {
	SetState(GameState.CUTSCENE);
	LoadScene("Intro_Cutscene");
    }

    public void LoadScene(string destName)
    {

	startedOnThisScene = false;
	nextSceneName = destName;
	if (UnloadingLevel != null)
	{
	    UnloadingLevel();
	}
	UnityEngine.SceneManagement.SceneManager.UnloadScene(destName);
    }

    public IEnumerator LoadSceneAdditive(string destScene)
    {

	startedOnThisScene = false;
	nextSceneName = destScene;
	if (UnloadingLevel != null)
	{
	    UnloadingLevel();
	}
	string exitingScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
	AsyncOperation asyncOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(destScene, LoadSceneMode.Additive);
	asyncOperation.allowSceneActivation = true;
	yield return asyncOperation;
	UnityEngine.SceneManagement.SceneManager.UnloadScene(exitingScene);
	RefreshTilemapInfo(destScene);
	if (IsUnloadAssetsRequired(exitingScene, destScene))
	{
	    Debug.LogFormat(this, "Unloading assets due to zone transition", Array.Empty<object>());
	    yield return Resources.UnloadUnusedAssets();
	}
	SetupSceneRefs(true);
	BeginScene();
	OnNextLevelReady();
	waitForManualLevelStart = false;
    }

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

    public string GetSceneNameString()
    {
	UpdateSceneName();
	return sceneName;
    }

    /// <summary>
    /// 获取场景的基础名字
    /// </summary>
    /// <param name="fullSceneName"></param>
    /// <returns></returns>
    public static string GetBaseSceneName(string fullSceneName)
    {
	for (int i = 0; i < SubSceneNameSuffixes.Length; i++)
	{
	    string text = SubSceneNameSuffixes[i];
	    if (fullSceneName.EndsWith(text, StringComparison.InvariantCultureIgnoreCase))
	    {
		return fullSceneName.Substring(0, fullSceneName.Length - text.Length);
	    }
	}
	return fullSceneName;
    }

    /// <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;
    }

    public IEnumerator PlayerDeadFromHazard(float waitTime)
    {
	cameraCtrl.FreezeInPlace(true);
	NoLongerFirstGame();
	SaveLevelState();
	yield return new WaitForSeconds(waitTime);
	cameraCtrl.FadeOut(CameraFadeType.HERO_HAZARD_DEATH);
	yield return new WaitForSeconds(0.8f);
	PlayMakerFSM.BroadcastEvent("HAZARD RELOAD");
	HazardRespawn();
    }

    public void HazardRespawn()
    {
	hazardRespawningHero = true;
	entryGateName = "";
	cameraCtrl.ResetStartTimer();
	cameraCtrl.camTarget.mode = CameraTarget.TargetMode.FOLLOW_HERO;
	EnterHero(false);
    }

    public void ReadyForRespawn(bool isFirstLevelForPlayer)
    {
	RespawningHero = true;
	BeginSceneTransition(new SceneLoadInfo
	{
	    PreventCameraFadeOut = true,
	    WaitForSceneTransitionCameraFade = false,
	    EntryGateName = "",
	    SceneName = playerData.respawnScene,
	    Visualization = (isFirstLevelForPlayer ? SceneLoadVisualizations.ContinueFromSave : SceneLoadVisualizations.Default),
	    AlwaysUnloadUnusedAssets = true,
	    IsFirstLevelForPlayer = isFirstLevelForPlayer
	});
    }

    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 EnterWithoutInput()
    {

    }

    public void SetCurrentMapZoneAsRespawn()
    {
	playerData.mapZone = sm.mapZone;
    }

    public void FadeSceneIn()
    {
	cameraCtrl.FadeSceneIn();
    }
    public IEnumerator FadeSceneInWithDelay(float delay)
    {
	if (delay >= 0f)
	{
	    yield return new WaitForSeconds(delay);
	}
	else
	{
	    yield return null;
	}
	FadeSceneIn();
	yield break;
    }

    public void SaveLevelState()
    {
	//TODO:
    }

    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;
    }

    public void LeftScene(bool doAdditiveLoad = false)
    {
	UnityEngine.SceneManagement.SceneManager.GetSceneByName(targetScene);	
	if (doAdditiveLoad)
	{
	    StartCoroutine(LoadSceneAdditive(targetScene));
	    return;
	};
	LoadScene(targetScene);
    }

    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();
	}
    }

    private void UpdateUIStateFromGameState()
    {
	if(ui != null)
	{
	    ui.SetUIStartState(gameState);
	    return;
	}
	ui = FindObjectOfType<UIManager>();
	if (ui != null)
	{
	    ui.SetUIStartState(gameState);
	    return;
	}
	Debug.LogError("GM: Could not find the UI manager in this scene.");
    }

    private void PositionHeroAtSceneEntrance()
    {
	Vector2 position = FindEntryPoint(entryGateName, default(Scene)) ?? new Vector2(-2000f, 2000f);
	if(hero_ctrl != null)
	{
	    hero_ctrl.transform.SetPosition2D(position);
	}
    }

    private Vector2? FindEntryPoint(string entryPointName, Scene filterScene)
    {
	if (RespawningHero)
	{
	    Transform transform = hero_ctrl.LocateSpawnPoint();
	    if(transform != null)
	    {
		return new Vector2?(transform.transform.position);
	    }
	    return null;
	}
	else
	{
	    if (hazardRespawningHero)
	    {
		return new Vector2?(playerData.hazardRespawnLocation);
	    }
	    TransitionPoint transitionPoint = FindTransitionPoint(entryPointName, filterScene, true);
	    if(transitionPoint != null)
	    {
		return new Vector2?((Vector2)transitionPoint.transform.position + transitionPoint.entryOffset);
	    }
	    return null;
	}
    }

    private TransitionPoint FindTransitionPoint(string entryPointName, Scene filterScene, bool fallbackToAnyAvailable)
    {
	List<TransitionPoint> transitionPoints = TransitionPoint.TransitionPoints;
	for (int i = 0; i < transitionPoints.Count; i++)
	{
	    TransitionPoint transitionPoint = transitionPoints[i];
	    if(transitionPoint.name == entryPointName && (!filterScene.IsValid() || transitionPoint.gameObject.scene == filterScene))
	    {
		return transitionPoint;
	    }
	}
	if(fallbackToAnyAvailable && transitionPoints.Count > 0)
	{
	    return transitionPoints[0];
	}
	return null;
    }

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

    public bool IsUnloadAssetsRequired(string sourceSceneName, string destinationSceneName)
    {
	return false;
    }
    public string GetCurrentMapZone()
    {
	return sm.mapZone.ToString();
    }

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

    //TODO:
    public void SaveGame()
    {
	Debug.LogFormat("TODO:Save Game");
    }

    //TODO:
    public void TimePasses()
    {
	Debug.LogFormat("TODO:Time Passes");
    }

    public void CheckCharmAchievements()
    {
	Debug.LogFormat("TODO:Check Charm Achievements");
    }

    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";
    }

    public bool ShouldKeepHUDCameraActive()
    {
	UpdateSceneName();
	return sceneName == "GG_Entrance_Cutscene" || sceneName == "GG_Boss_Door_Entrance" || sceneName == "GG_End_Sequence" || sceneName == "Cinematic_Ending_D";
    }

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

    public int GetPlayerDataInt(string intName)
    {
	return playerData.GetInt(intName);
    }

    public bool GetPlayerDataBool(string boolName)
    {
	return playerData.GetBool(boolName);
    }

    public void IncrementPlayerDataInt(string intName)
    {
	playerData.IncrementInt(intName);
    }

    public void SetPlayerDataInt(string intName, int value)
    {
	playerData.SetInt(intName, value);
    }

    public void SetPlayerDataBool(string boolName,bool value)
    {
	playerData.SetBool(boolName, value); 
    }

    public void SetPlayerDataString(string stringName, string value)
    {
	playerData.SetString(stringName, value);
    }

    private IEnumerator SetTimeScale(float newTimeScale,float duration)
    {
	float lastTimeScale = TimeController.GenericTimeScale;
	for (float timer = 0f; timer < duration; timer += Time.unscaledDeltaTime)
	{
	    float t = Mathf.Clamp01(timer / duration);
	    SetTimeScale(Mathf.Lerp(lastTimeScale, newTimeScale, t));
	    yield return null;
	}
	SetTimeScale(newTimeScale);
    }

    private void SetTimeScale(float newTimeScale)
    {
	if(timeSlowedCount > 1)
	{
	    newTimeScale = Mathf.Min(newTimeScale, TimeController.GenericTimeScale);
	}
	TimeController.GenericTimeScale = ((newTimeScale > 0.01f) ? newTimeScale : 0f);
    }

    public void FreezeMoment(int type)
    {
	if (type == 0)
	{
	    StartCoroutine(FreezeMoment(0.01f, 0.35f, 0.1f, 0f));
	}
	else if (type == 1)
	{
	    StartCoroutine(FreezeMoment(0.04f, 0.03f, 0.04f, 0f));
	}
	else if (type == 2)
	{
	    StartCoroutine(FreezeMoment(0.25f, 2f, 0.25f, 0.15f));
	}
	else if (type == 3)
	{
	    StartCoroutine(FreezeMoment(0.01f, 0.25f, 0.1f, 0f));
	}
	if (type == 4)
	{
	    StartCoroutine(FreezeMoment(0.01f, 0.25f, 0.1f, 0f));
	}
	if (type == 5)
	{
	    StartCoroutine(FreezeMoment(0.01f, 0.25f, 0.1f, 0f));
	}
    }

    public IEnumerator FreezeMoment(float rampDownTime,float waitTime,float rampUpTime,float targetSpeed)
    {
	timeSlowedCount++;
	yield return StartCoroutine(SetTimeScale(targetSpeed, rampDownTime));
	for (float timer = 0f; timer < waitTime; timer += Time.unscaledDeltaTime)
	{
	    yield return null;
	}
	yield return StartCoroutine(SetTimeScale(1f, rampUpTime));
	timeSlowedCount--;
    }
    public void EnsureSaveSlotSpace(Action<bool> callback)
    {
	Platform.Current.EnsureSaveSlotSpace(profileID, callback);
    }

    public void AddToBenchList()
    {
	if (!playerData.scenesEncounteredBench.Contains(GetSceneNameString()))
	{
	    playerData.scenesEncounteredBench.Add(GetSceneNameString());
	}
    }

    public void ResetSemiPersistentItems()
    {
	Debug.LogFormat("TODO:ResetSemiPersistentItems");
    }

    public bool IsGamePaused()
    {
	return gameState == GameState.PAUSED;
    }

    public void StoryRecord_acquired(string item)
    {
	Debug.LogFormat("StoryRecord_acquired" + item);
    }

    public void StoryRecord_rest(string item)
    {
	Debug.LogFormat("StoryRecord_rest" + item);
    }


    public void AwardAchievement(string key)
    {
	//TODO:
    }

    public void CheckAllAchievements()
    {
	//TODO:
    }

    public void CountJournalEntries()
    {
	playerData.CountJournalEntries();
    }

    public void SkipCutscene()
    {
	StartCoroutine(SkipCutsceneNoMash());
    }

    private IEnumerator SkipCutsceneNoMash()
    {
	if(gameState == GameState.CUTSCENE)
	{
	    ui.HideCutscenePrompt();
	    //TODO:
	    OpeningSequence openingSequence = FindObjectOfType<OpeningSequence>();
	    if(openingSequence != null)
	    {
		yield return StartCoroutine(openingSequence.Skip());
		inputHandler.skippingCutscene = false;
		yield break;
	    }
	    CinematicPlayer cinematicPlayer = FindObjectOfType<CinematicPlayer>();
	    if (cinematicPlayer != null)
	    {
		yield return StartCoroutine(cinematicPlayer.SkipVideo());
		inputHandler.skippingCutscene = false;
		yield break;
	    }
	    Debug.LogError("Unable to skip, please ensure there is a CinematicPlayer or CutsceneHelper in this scene.");
	}
	yield return null;
    }

    public float GetImplicitCinematicVolume()
    {
	//TODO:
	return Mathf.Clamp01(10f / 10f) * Mathf.Clamp01(10f / 10f);
    }

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

    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 ReturnToMainMenuSaveModes
    {
	SaveAndCancelOnFail,
	SaveAndContinueOnFail,
	DontSave
    }

    public enum ControllerConnectionStates
    {
	DetachedDevice,
	DummyDevice,
	NullDevice,
	PossiblyConnected,
	ConnectedAndReady
    }

}

\这里我们要从主菜单场景切换到一个新的场景叫Opening_Sequence,这个就是我们在新开一个存档就会有两段过场动画,我们还需要再UIManager中销毁掉saveprofilescreen这个界面,这个新场景我会在下一期详细写出来,因为这期字超过7w了所以卡的不行图片也发不出来。,

cs 复制代码
 public void MakeMenuLean()
    {
	Debug.Log("Making UI menu lean.");
	if (saveProfileScreen)
	{
	    Destroy(saveProfileScreen.gameObject);
	    saveProfileScreen = null;
	}
	//TODO:
    }

最后我好像漏讲了一个PreselectOption.cs,就是简单 的高亮哪一个按钮button

cs 复制代码
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class PreselectOption : MonoBehaviour
{
    public Selectable itemToHighlight;

    public void HighlightDefault(bool deselect = false)
    {
	if(EventSystem.current.currentSelectedGameObject == null || deselect)
	{
	    if (itemToHighlight is MenuSelectable)
	    {
		((MenuSelectable)itemToHighlight).DontPlaySelectSound = true;
	    }
	    itemToHighlight.Select();
	    if (itemToHighlight is MenuSelectable)
	    {
		((MenuSelectable)itemToHighlight).DontPlaySelectSound = false;
	    }
	    foreach (object obj in itemToHighlight.transform)
	    {
		Animator component = ((Transform)obj).GetComponent<Animator>();
		if (component != null)
		{
		    component.ResetTrigger("hide");
		    component.SetTrigger("show");
		}
	    }
	}
    }

    public void SetDefaultHighlight(Button button)
    {
	itemToHighlight = button;
    }

    public void DeselectAll()
    {
	StartCoroutine(ForceDeselect());
    }

    private IEnumerator ForceDeselect()
    {
	yield return new WaitForSeconds(0.165f);
	UIManager.instance.eventSystem.SetSelectedGameObject(null);
    }
}

总结

然后来看看今天做的UI效果吧。可惜我做UI的经验不太够,所以还是挺流脓:

这里我感觉动画播放太慢了,得调整一下:

切换到主菜单界面:

Selector高光:

回到主菜单界面:

点击新游戏,界面逐渐淡出,进入新场景Opening_Sequence,不过这都是下一期的故事了,那就等我睡醒我们下一期再见把。

相关推荐
UXbot1 天前
如何选择适合公司项目的UI设计工具?企业选型指南
前端·低代码·ui·团队开发·原型模式·设计规范·web app
UXbot1 天前
原型设计工具如何帮助新人快速进入产品行业?
前端·低代码·ui·交互·团队开发·原型模式·web app
烈焰晴天1 天前
Codex 桌面端如何链接Figma MCP 服务器拿到 Figma设计稿精准尺寸等结构化数据 来精准还原UI
服务器·ui·figma
狼哥16861 天前
防沉迷控制实战新特性接入
ui·华为·harmonyos
狼哥16862 天前
学习卡片案例新特性接入
ui·华为·harmonyos
zdr尽职尽责2 天前
Unity录像功能
学习·ui·unity·游戏引擎
山东布谷网络科技2 天前
海外直播语聊APP功能与UI升级的关键关注点
开发语言·ui·app store·谷歌上架·海外直播app开发·海外语聊平台搭建·多语言直播平台定制
鹤卿1232 天前
(OC)UI学习——网易云仿写
ui·ios·objective-c
一个被程序员耽误的厨师2 天前
04-实践篇-让AI生成可视化页面-ai-json-ui的落地实践
人工智能·ui·json
秋雨梧桐叶落莳2 天前
iOS——QQ音乐仿写项目总结
学习·macos·ui·ios·mvc·objective-c·xcode