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

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

文章目录

  • 前言
  • 一、制作UI系统的主菜单界面
    • 1.选择存档界面制作 
    • 2.代码的逻辑处理
  • 二、制作UI系统的选择存档界面
    • 1.选择存档界面制作
    • 2.代码的逻辑处理
  • 总结


前言

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

        废话少说,上两期我们已经制作了遗忘十字路的两个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创建好数组:

  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系统:

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播放声音:

脚本内容也很简单:

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没有就不用管了。

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脚本的按钮:

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选择导航功能的脚本:

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声音播放

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可激活即可

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当中,我们接下来就要做选择存档界面了。

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

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
上一篇:详解Rust标准库:BTreeMap


下一篇:flutter 语法糖库 flutter_magic 发布 1.0.1