UI界面搭建

进入GameStart场景,在Hierarchy窗口确认GameStart这个Prefab已经放好。

在GameStart——UIRoot——WndRoot下新建子GameObject,命名为HotFixPanel,并设为stretch窗口

HotFixPanel下新建Image命名为BG,Color设为黑色,改为Stretch模式。

在BG下新建Image命名为ProgressBar,改为Filled——Horizontal类型。

下载时进度条走一次、解压时进度条走一次、验证时再走一次(可选)

在BG下新建SpeedText,放在BG靠近右下角ProgressBar上方的位置,在其中输入“3m/s”。

在BG下新建ProcessText,放在SpeedText左侧,在其中输入“下载中……”

HotFixPanel下新建Image命名为Info

在Info下新建Title(Text),在其中输入“热更内容”,调整位置到Info中间上部

在Info下新建Scroll View,在它的Content子节点下新建Text,在其中输入“1.热更修复了XXX”

想要让Content自适应,在Text上添加ContentSizeFitter组件,并且选择Vertical Fit为“Preferred Size”。然后要记住将Text组件的RectTransform的Pivot设为X:0,Y:1

HotFixPanel

在Scripts——UGUI——Panel文件夹下新建HotFixPanel文件

1
2
3
4
5
6
7
8
9
10
11
12
using UnityEngine;
using UnityEngine.UI;

public class HotFixPanel : MonoBehaviour
{
public Image m_Image;
public Text m_Text;
public Text m_ProcessText;
[Header("热更信息界面")]
public GameObject m_InfoPanel;
public Text m_HotContentText;
}

把它挂载到HotFixPanel上,并将指定的对象拖拽进来。关闭Info对象

将HotFixPanel拖拽到Resources文件夹内。然后点击选中这个Prefab——右键——离线数据——生成UI离线数据。(不用热更的Prefab其实不需要离线数据,这里只为了演示流程)

HotFixWnd

在Scripts——UGUI——Window文件夹下新建HotFixWnd文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using System.Collections;
using UnityEngine;

public class HotFixWnd : WindowBase
{
private HotFixPanel m_Panel;
private float m_SumTime;//累计下载时长,用来计算速度
public override void OnAwake(params object[] args)//当我们打开HotFixWnd窗口时,就直接开始验证服务器了
{
m_SumTime = 0;
m_Panel = GameObject.GetComponent<HotFixPanel>();
m_Panel.m_Image.fillAmount = 0;
m_Panel.m_Text.text = string.Format("{0:F}M/S", 0);
HotPatchManager.Instance.ServerInfoError += ServerInfoError;
HotPatchManager.Instance.ItemDownloadError += ItemError;
HotFix();
}
public override void OnClose()
{
HotPatchManager.Instance.ServerInfoError -= ServerInfoError;
HotPatchManager.Instance.ItemDownloadError -= ItemError;

//GameSceneManager.Instance.LoadScene(ConstantStr.MENUSCENE);
}
void HotFix()
{
if(Application.internetReachability == NetworkReachability.NotReachable)
{
//提示网络错误,检测网络链接是否正常
GameStart.Instance.OpenCommonWnd("网络链接失败", "网络链接失败,请检查网络链接是否正常?", () =>
{
Application.Quit();
}, () => { Application.Quit(); });
}
else
{
CheckVersion();
}
}
void CheckVersion()
{
HotPatchManager.Instance.CheckVersion(hot =>
{
if (hot)
{
//提示玩家是否确定热更下载
GameStart.Instance.OpenCommonWnd("热更确定",
string.Format("当前版本为{0},有{1:F}M大小热更包,是否确定下载?",
HotPatchManager.Instance.CurVersion,
HotPatchManager.Instance.LoadSumSize/1024.0f),
OnClickConfirmDownload,
OnClickCancelDownload);
}
else
{
StartOnFinish();
}
});
}
void OnClickConfirmDownload()
{
if(Application.platform == RuntimePlatform.Android ||
Application.platform == RuntimePlatform.IPhonePlayer)
{
if(Application.internetReachability == NetworkReachability.ReachableViaCarrierDataNetwork)
{
GameStart.Instance.OpenCommonWnd("下载确认", "是否使用流量下载?", StartDownload, OnClickCancelDownload);
}
}
else
{
StartDownload();
}
}
void OnClickCancelDownload()
{
Application.Quit();
}
/// <summary>
/// 正式开始下载
/// </summary>
void StartDownload()
{
m_Panel.m_ProcessText.text = "下载中......";
m_Panel.m_InfoPanel.SetActive(true);
m_Panel.m_HotContentText.text = HotPatchManager.Instance.CurrentPatches.Description;
GameStart.Instance.StartCoroutine(HotPatchManager.Instance.StartDownloadAB(StartOnFinish));
}
/// <summary>
/// 下载完成回调,或者没有下载的东西直接进入游戏
/// </summary>
void StartOnFinish()
{
GameStart.Instance.StartCoroutine(OnFinish());
}
IEnumerator OnFinish()
{
yield return GameStart.Instance.StartCoroutine(GameStart.Instance.StartGame(m_Panel.m_Image, m_Panel.m_ProcessText));
UIManager.Instance.CloseWnd(this);
}
public override void OnUpdate()
{
if (HotPatchManager.Instance.IsStartDownload)
{
m_SumTime += Time.deltaTime;
m_Panel.m_Image.fillAmount = HotPatchManager.Instance.GetProgress();
float speed = (HotPatchManager.Instance.GetLoadSize() / 1024.0f) / m_SumTime;
m_Panel.m_Text.text = string.Format("{0:F}m/s", speed);
}
}
void ServerInfoError()
{
GameStart.Instance.OpenCommonWnd("服务器列表获取失败",
"服务器列表获取失败,请检查网络链接是否正常?尝试重新下载?",
CheckVersion,
Application.Quit);
}

void ItemError(string error)
{
GameStart.Instance.OpenCommonWnd("资源下载失败",
string.Format("{0}等资源下载失败,请重新尝试下载!",error),
ANewDownload,
Application.Quit);
}
/// <summary>
/// 用在下载文件出错时的回调,不再进行下载确认的弹窗了
/// </summary>
void ANewDownload()
{
HotPatchManager.Instance.CheckVersion(hot =>
{
if (hot)
{
StartDownload();
}
else
{
StartOnFinish();
}
});
}

}

GameStart中注册HotFixWnd

1
2
3
4
5
6
void RegisterUI()
{
UIManager.Instance.Register<MenuWnd>(ConstantStr.MENUPANEL);
UIManager.Instance.Register<LoadingWnd>(ConstantStr.LOADINGPANEL);
UIManager.Instance.Register<HotFixWnd>(ConstantStr.HOTFIXPANEL);
}

ConstantStr中添加

1
public const string HOTFIXPANEL = "HotFixPanel.prefab";

共有界面搭建

在Hierarchy——GameStart——UIRoot——WndRoot中新建GameObject,命名为CommonConfirm。

在CommonConfirm下新建BG(Image),在BG下新建Title(Text),Content(Text),ConfirmButton(Button)、CancelButton(Button)

将此CommonConfirm拖拽到Resources文件夹下。

在Scripts——UGUI——Item文件夹下新建CommonConfirm脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using UnityEngine.UI;

public class CommonConfirm : ItemBase
{
public Text m_Title;
public Text m_Content;
public Button m_ConfirmBtn;
public Button m_CancelBtn;
public void Show(string title,string content,UnityEngine.Events.UnityAction confirmAction,UnityEngine.Events.UnityAction cancelAction)
{
m_Title.text = title;
m_Content.text = content;
AddButtonClickListener(m_ConfirmBtn, () =>
{
confirmAction();
Destroy(gameObject);
});
AddButtonClickListener(m_CancelBtn, () =>
{
cancelAction();
Destroy(gameObject);
});
}
}

共有界面属于UI里面的Item组件,在上述代码可以看到这种组件不管按什么键都是直接销毁

将此脚本挂载在CommonConfirm下,拖拽好对应的组件后apply一下Prefab。

注意事项

我们的HotFixPanel和共有界面的prefab,都是放在Resources文件夹下的,这意味着除非大版本更新,这两个UI窗口不参与热更。

这是因为当玩家打开客户端时,一定要显示这两个UI,如果热更它们,客户端并不能第一时间应用这两个UI的更新,只能热更结束之后再重新加载,这是很矛盾的。因为这有可能导致我们需要加载两次assetbundle配置,老的配置需要加载完再丢弃。

加入热更之后,我们软件的启动顺序是:

检查服务器是否有热更,有热更先下载热更——加载assetbundle配置,如果热更文件有新的assetbundle配置就使用热更文件的assetbundle配置。

我们已经修改了AssetBundleManagerLoadAssetBundle方法,让它优先加载热更包,我们也需要修改LoadAssetBundleConfig方法,优先加载热更包的assetbundle配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    /// <summary>
/// 加载AB配置表
/// </summary>
public bool LoadAssetBundleConfig()
{
#if UNITY_EDITOR
if(!ResourceManager.Instance.m_LoadFromAssetBundle)
return false;
#endif
m_ResourceItemDic.Clear();
string configPath = ABLoadPath + m_ABConfigABName;
string hotABPath = HotPatchManager.Instance.ComputeABPath(m_ABConfigABName);//+++
configPath = string.IsNullOrEmpty(hotABPath) ? configPath : hotABPath;//+++
AssetBundle configAB = AssetBundle.LoadFromFile(configPath);
//...
}

我们之前打热更包时,assetbundleconfig并没有出现在热更包里,这是因为我们并没有在Unity编辑器内增删需要热更的文件,也没有修改需要热更文件的名字,我们的资源管理框架只通过名字来确定资源(CRC码是通过资源名生成的)。

共有界面启用方法

共有界面在游戏各个模块都有可能用到,我们在程序入口,也就是GameStart脚本内添加启用它的方法:

1
2
3
4
5
6
7
8
public void OpenCommonWnd(string title,string content,UnityEngine.Events.UnityAction confirmAction, UnityEngine.Events.UnityAction cancelAction)
{
GameObject wndObj = Instantiate(Resources.Load<GameObject>("CommonConfirm"));

wndObj.transform.SetParent(windowRoot,false);
CommonConfirm commonConfirm = wndObj.GetComponent<CommonConfirm>();
commonConfirm.Show(title, content, confirmAction, cancelAction);
}

修改GameStart

HotFixPanel搭建好之后,进入游戏并初始化时我们可以给Panel提供初始化的信息。这需要我们修改GameStart部分的逻辑

首先将GameStartStart方法内部的UIManager.Instance.Init(uiRoot, windowRoot, uiCamera, eventSystem);移动到Awake方法体中

然后将GameStartStart方法内部的//GameSceneManager.Instance.LoadScene(ConstantStr.MENUSCENE);移动到HotFixWnd——OnClose方法体中

最后在GameStart中新建public IEnumerator StartGame(Image image,Text text)方法,将GameStartStart方法内部的代码全都转移到这里来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);

ResourceManager.Instance.Init(this);//初始化异步加载
ObjectPoolManager.Instance.Init(recyclePoolTrans,sceneTrans);
HotPatchManager.Instance.Init(this);
UIManager.Instance.Init(uiRoot, windowRoot, uiCamera, eventSystem);
RegisterUI();
}
private void Start()
{
UIManager.Instance.CreateWindow(ConstantStr.HOTFIXPANEL,isFromResFolder:true);
}
public IEnumerator StartGame(Image image,Text text)
{
image.fillAmount = 0f;
yield return null;
text.text = "加载本地数据... ...";
AssetBundleManager.Instance.LoadAssetBundleConfig();

image.fillAmount = 0.2f;
yield return null;
text.text = "加载配置数据... ...";
LoadConfig();

image.fillAmount = 0.7f;
yield return null;
text.text = "注册组件... ...";

image.fillAmount = 0.9f;
yield return null;
text.text = "初始化入口... ...";
GameSceneManager.Instance.Init(this);

image.fillAmount = 1f;
yield return null;


}

最后,在HotFixWndStartOnFinish方法中开启这个协程

1
2
3
4
5
6
7
8
9
10
11
12
13
/// <summary>
/// 下载完成回调,或者没有下载的东西直接进入游戏
/// </summary>
void StartOnFinish()
{
GameStart.Instance.StartCoroutine(OnFinish());
}
IEnumerator OnFinish()
{
yield return GameStart.Instance.StartCoroutine(GameStart.Instance.StartGame(m_Panel.m_Image, m_Panel.m_ProcessText));
UIManager.Instance.CloseWnd(this);
}