地图部分也已经算是可以告一段落了,也需要仔细考虑下接下来该做哪个系统。地图部分可以算是六边形地图的SLG游戏最主要的一部分,所以先做了下练练手。

接下来的工作更多的需要从项目的全局角度来考虑该怎么做。深思熟虑后,觉得现在比较合适的一个入手点是UI部分,利用UI部分可以将整个游戏的流程搭建起来。

先简单整理了一下,最终结果主要有哪些UI:
 


slg游戏的部署架构 slg游戏设计_slg游戏的部署架构


我们可以看到,这个游戏的UI还是很简单的,UI的层次也只有两层。

于是,可以这样规划:

将游戏UI简单分为两类:主界面,界面上的弹窗。

主界面:主界面暂时就是图片中第二层那四个:启动界面、开局初始条件设置界面、国家选择界面、游戏内进行界面。

这几个界面之间是互斥的,即游戏进行时只可能打开其中一个界面。也可以近似的看做游戏的不同状态阶段。

界面上的弹窗:这类界面其实就是点了界面上的按钮后,弹出的一些小窗口,这些窗口可能会覆盖掉整个屏幕(比如科技界面),但实际上并没有作场景切换。

这一类界面之间并不互斥,比如我打开了科技界面后,选择一个科技升级,肯定还是需要一个确认的弹窗。不过,虽然界面之间并不互斥,但同一个界面是不能同时存在两个的。所以我们可以和主界面一样,只实例化一个界面,在不同的场合显示不同的信息来反复使用。

首先,建立一个UI管理类,同时也作为所有UI的根目录:
 

1. public class UIRoot : MonoBehaviour {
2. 
3. }


复制代码

然后再Unity的Hierarchy界面上,右键UI/Canvas新建一个幕布,这时候会自动添加一个Canvas和一个EventSystem,将EventSystem拖到Canvas下作为一个子物体(也可以不拖,这么做纯粹是为了把UI相关的东西都放到一个gameobject下)。而后将Canvas重命名为UIRoot,然后把UIRoot这个脚本挂上去。

如图:
 


slg游戏的部署架构 slg游戏设计_UI_02


然后再右键UIRoot,选择Create Empty,新建一个空物体,命名为Normal UI,作为所有主界面的根目录。

而后将它复制一下,将新的重命名为Keep Above UI,作为所有界面弹窗的根目录。
 


slg游戏的部署架构 slg游戏设计_xml_03


如图所示,只要在这个界面上使得Keep Above UI位于Normal UI的下方,在游戏显示中,就会优先显示Keep Above UI这个物体上的界面。因为在ugui中,下方的物体会被优先显示。

而后我们在UIRoot脚本中,新建两个参数:
 

1. public class UIRoot : MonoBehaviour {
2.         public Transform NormalUI;
3.         public Transform KeepAboveUI;
4. }

复制代码

因为这两个物体主要用于存放窗口,所以存放为transform类型更为方便一些。然后将两个物体拖到上去。
 


slg游戏的部署架构 slg游戏设计_slg游戏的部署架构_04


接下来定义一个UI的基类,用于一些UI的基本操作。
 


1. public abstract class BaseUI : MonoBehaviour
2. {
3.         public void OpenUI()
4.         {
5.                 gameObject.SetActive(true);
6.         }
7. 
8.         public void CloseUI()
9.         {
10.                 gameObject.SetActive(false);
11.         }
12. }


复制代码

BaseUI是一个抽象类,抽象类是无法实例化的,即我们不能把BaseUI挂到一个gameobject上面去,但是一个继承了BaseUI的类,是可以被挂到一个gameobject上去的。

同样,BaseUI虽然不可以被实例化,但我们可以把BaseUI的一个子类,存放到BaseUI类型的变量上去。

而后,定义一个枚举,用于保存UI的类型,比如:启动界面、开局初始条件设置界面、国家选择界面、游戏主界面等。
 


    1. public enum UIType
    2. {
    3. 
    4. }


    复制代码

    好了,开始做第一个UI启动界面,命名为StartUI。

    现在UIType中添加一个对应的枚举,并且新建一个继承自BaseUI的StartUI脚本:
     


      1. public enum UIType
      2. {
      3.         StartUI,
      4. }
      5. public class StartUI : BaseUI {
      6.        
      7. }


      复制代码

      然后在NormalUI这个物体上新建一个UI,叫StartUI,搭一个简单的UI出来,并且把前面的StartUI脚本拖到该UI上:
       


      slg游戏的部署架构 slg游戏设计_UI_05



      slg游戏的部署架构 slg游戏设计_c#_06



      slg游戏的部署架构 slg游戏设计_slg游戏的部署架构_07


      做完后,在将刚刚做好的UI放在Asset/Resources/UIPrefabs目录下(没有这个目录就新建一个),如下图所示:
       


      slg游戏的部署架构 slg游戏设计_UI_08


      同时再新建一个脚本UIConfig,用于存放一些UI的配置:
       

      1. public class UIConfig
      2. {
      3.         public static Dictionary<UIType, string> UIPath = new Dictionary<UIType, string>
      4.         {
      5.                 { UIType.StartUI,"UIPrefabs/StartUI"},
      6.         };
      7. 
      8. }

      复制代码

      在这个脚本中,我暂时只建了一个字典用于记录UI的存放地址。一会用Resources.Load()函数可以读取Resources文件夹里面的内容。

      接下来要做的就是UI切换功能了,做之前需要先封装一下添加子物体的函数:
       


      1. public class UITool{
      2.         public static void AddChild(Transform child, Transform parent)
      3.         {
      4.                 child.SetParent(parent.transform);
      5.                 child.localPosition = Vector3.zero;
      6.                 child.localScale = Vector3.one;
      7.                 child.localEulerAngles = Vector3.zero;
      8.         }
      9. }


      复制代码

      这个函数的功能是将一个子物体放在想要添加的父物体上。

      而后就是UI的切换功能了:
       

      1. public class UIRoot : MonoBehaviour {
      2.         public Transform NormalUI;
      3.         public Transform KeepAboveUI;
      4. 
      5.         private Dictionary<UIType, BaseUI> NormalUIDic = new Dictionary<UIType, BaseUI>();
      6.         private BaseUI CurrentUI;
      7. 
      8. 
      9.         public void OpenNormalUI(UIType uiType)
      10.         {
      11.                 if (CurrentUI != null)
      12.                 {
      13.                         CurrentUI.CloseUI();
      14.                 }
      15.                 if (NormalUIDic.ContainsKey(uiType))
      16.                 {
      17.                         CurrentUI = NormalUIDic[uiType];
      18.                 }
      19.                 else
      20.                 {
      21.                         BaseUI theUI = Instantiate(Resources.Load<BaseUI>(UIConfig.UIPath[uiType])) as BaseUI;
      22.                         UITool.AddChild(theUI.transform, NormalUI);
      23.                         NormalUIDic.Add(uiType, theUI);
      24.                         CurrentUI = theUI;
      25.                 }
      26.                 CurrentUI.OpenUI();
      27.         }
      28. 
      29. }

      复制代码

      我新建了一个字典NormalUIDic用于存放所有已经开启过的UI,同时用CurrentUI用于存放当前正在使用的UI。

      因为只要游戏开启,则必定存在一个normalUI,所以只有OpenNormalUI,每次开启UI之前,都要把上一个UI关闭。而下面则是一个以前是否开启过该UI的函数,如果开启过,则直接从字典中找到那个UI,并开启,如果以前没有开启过,手游账号拍卖平台则加载该UI,并将该UI加入字典。

      接下来就是测试一下该功能是否可用,在UIRoot的Start函数中,加入一行代码:


      1. public class UIRoot : MonoBehaviour {
      2.         ……
      3.         private void Start()
      4.         {
      5.                 OpenNormalUI(UIType.StartUI);
      6.         }
      7. 
      8.         ……
      9.         }
      10. 
      11. }


      复制代码

      点击运行,我们会发现第一个UI已经被加载了。
       


      slg游戏的部署架构 slg游戏设计_xml_09


      加载第一个页面


      接下来我们要制作第二个UI来测试这个页面切换功能。

      我们翻到前面的UI表,可以得知,第二个UI是游戏设置UI,所以我们建第二个UI,名字命名为GameSettingUI,并在左上角加上一个回退按钮,如图所示:
       


      slg游戏的部署架构 slg游戏设计_UI_10


      游戏设置UI


      新建一个GameSettingUI:BaseUI脚本,然后将该UI拖到存放StartUI的预设体的那个目录内:
       

      1. public class GameSettingUI : BaseUI {
      2.        
      3. }


      复制代码


      slg游戏的部署架构 slg游戏设计_c#_11

      接下来要做的就是给两个按钮实现切换功能。但在做这个之前,我们需要在UITool内封装一个新的工具性的函数:
       

      1. public class UITool{
      2.         ……
      3.         public static GameObject FindChildByName(GameObject parent, string childName)
      4.         {
      5.                 if (parent.name == childName)
      6.                 {
      7.                         return parent;
      8.                 }
      9.                 if (parent.transform.childCount < 1)
      10.                 {
      11.                         return null;
      12.                 }
      13.                 GameObject obj = null;
      14.                 for (int i = 0; i < parent.transform.childCount; i++)
      15.                 {
      16.                         GameObject go = parent.transform.GetChild(i).gameObject;
      17.                         obj = FindChildByName(go, childName);
      18.                         if (obj != null)
      19.                         {
      20.                                 break;
      21.                         }
      22.                 }
      23.                 return obj;
      24.         }
      25. 
      26. }


      复制代码

      这个函数的作用是搜索某个物体的子物体,并找到第一个名字为childName的子物体。我们需要用这个函数来找到某个UI下的子按钮。

      之后是将UIRoot这个脚本改成一个单例:
       

      1. public class UIRoot : MonoBehaviour {
      2.         public static UIRoot Instance;
      3. 
      4. ……
      5.         private void Awake()
      6.         {
      7.                 Instance = this;
      8.         }
      9. ……
      10. 
      11. }

      复制代码

      这样,我们就可以通过UIRoot.Instance来操作UI。

      接下来是实现StartUI的按钮:
       


      slg游戏的部署架构 slg游戏设计_xml_12


      先将StartUI预制体上的Button按钮重命名为StartGame。而后在StartUI的脚本上,添加如下函数:
       


      1. using UnityEngine.UI;
      2. public class StartUI : BaseUI {
      3.         private Button btn_Start;
      4.         private void Awake()
      5.         {
      6.                 btn_Start = UITool.FindChildByName(gameObject, "StartGame").GetComponent<Button>();
      7.                 btn_Start.onClick.AddListener(StartGame);
      8.         }
      9. 
      10. 
      11.         private void StartGame()
      12.         {
      13.                 UIRoot.Instance.OpenNormalUI(UIType.GameSettingUI);
      14.         }
      15. }


      复制代码

      其实UGUI本身的编辑器功能可以直接让该按钮能够调用某个脚本的某个函数,但是通过编辑器有时候可能会不小心点错,从而出现bug,尤其是在多人合作的项目中,很容易出现这种问题。所以很多人会习惯用代码来实现这一功能。

      给GameSettingUI也如法炮制:
       


      slg游戏的部署架构 slg游戏设计_slg游戏的部署架构_13


      修改Button名字


      1. using UnityEngine.UI;
      2. 
      3. public class GameSettingUI : BaseUI {
      4.         private Button backToStartUI;
      5.         private void Awake()
      6.         {
      7.                 backToStartUI = UITool.FindChildByName(gameObject, "BackToStartUI").GetComponent<Button>();
      8.                 backToStartUI.onClick.AddListener(StartGame);
      9.         }
      10. 
      11. 
      12.         public void StartGame()
      13.         {
      14.                 UIRoot.Instance.OpenNormalUI(UIType.StartUI);
      15.         }
      16. }


      复制代码


      接下来运行测试一下,就会发现我们可以在两个页面之间切换了。