Unity 离线建造系统
很多游戏,特别是养成类手游,都会有自己独特的建造系统,一个建造装置的状态循环或者说生命周期一般是这样的:
1.准备建造,设置各项资源的投入等
2.等待一段倒计时,正在建造中
3.建造结束,选择是否收取资源
大体上,可以将建造盒子分为以下三种状态,每一个状态的逻辑和显示的页面不同:
1 public enum BuildBoxState
2 {
3 Start,
4 Doing,
5 Complete
6 }
1 private void ShiftState(BuildBoxState state)
2 {
3 switch (state)
4 {
5 case BuildBoxState.Start:
6 Start.SetActive(true);
7 Doing.SetActive(false);
8 Complete.SetActive(false);
9
10 ResetResCount();
11 break;
12 case BuildBoxState.Doing:
13 Start.SetActive(false);
14 Doing.SetActive(true);
15 Complete.SetActive(false);
16
17 StartCoroutine(ShowBuildTime());
18 break;
19 case BuildBoxState.Complete:
20 Start.SetActive(false);
21 Doing.SetActive(false);
22 Complete.SetActive(true);
23
24 break;
25 }
26 CurState = state;
27 }
这里值得思考的并非是状态的切换或者基础的按钮侦听,视图资源更新等。
如何在离线一段时间后重新获取目前对应建造盒子所处的状态才是重点;并且如果处于建造中状态的话,还应该能正确的显示剩余时间的倒计时。
一个非常常见的想法是,在建造开始时记录一份开始建造的时间数据给服务器或存在本地离线数据中,当下一次再登录时读取当前系统的时间,并通过总共需要的建造时长来计算剩余时间。
但假如总共需要的建造时长与当时投入的资源类型和量都有关系,这时就需要至少额外记载一类数据来进行计算。那么,有没有方法仅通过一个数据得到剩余时长呢?
答案是,不记录开始建造的时刻,改为记录拟定建造完成的时刻。
如此一来,每次离线登录后,只需要干两件事既可以判断出所有状态视图:
1.是否存在该建造盒子ID对应的拟定建造完成时刻的数据,如果不存在,一定是处于准备状态,即Start状态。
2.如果存在,对比当前系统时刻与拟定建造完成时刻的数据大小,大于等于则处于完成状态,小于则依然在建造中,并按秒显示差值更新。
记录的时刻如下:
1 public string BuildCompleteTime
2 {
3 get
4 {
5 if (PlayerPrefs.HasKey(ID.ToString()))
6 return PlayerPrefs.GetString(ID.ToString());
7 return S_Null;
8 }
9 set
10 {
11 PlayerPrefs.SetString(ID.ToString(), value);
12 PlayerPrefs.Save();
13 }
14 }
每次开始时,只需要判断这个数据是否存在:
1 protected override void InitState()
2 {
3 View = HudView as BuildBoxView;
4 if (BuildCompleteTime == S_Null)
5 {
6 ShiftState(BuildBoxState.Start);
7 }
8 else
9 {
10 ShiftState(BuildBoxState.Doing);
11 }
12 }
通过建造中的时刻关系自动判断是否完成:
1 IEnumerator ShowBuildTime()
2 {
3 var ct = GetCompleteTime();
4 if (CheckBuildCompleted(ct))
5 {
6 ShiftState(BuildBoxState.Complete);
7 yield break;
8 }
9 else
10 {
11 for (; ; )
12 {
13 View.SetTime(CalNeedTime(ct));
14 yield return new WaitForSeconds(1);
15 }
16 }
17 }
当建造完成点击收取资源时,切换为准备状态的同时,自动清空拟定建造完成时刻的数据记录:
1 private void OnClickGet()
2 {
3 Canvas.SendEvent(new GetItemEvent());
4 ClearCompleteTime();
5 ShiftState(BuildBoxState.Start);
6 }
这里有一个问题是,为什么不在建造完成时就清理数据呢,因为有一种情况是,建造完成后,玩家还没来得及点击收取,就直接进入了离线状态,如果此时再次登录时数据已经清空,那他将做了一场无用功。
说不定直接垃圾游戏毁我青春败我前程了,为了避免这种情况发生,我们只有确保玩家真正收取到资源的那一刻才能清理数据。
到此,整个建造的基础逻辑已经梳理完毕。如果要实现快速建造的话,也只不过是将拟定的完成时间直接设置为此刻即可。如果之前记录的是开始建造的时刻,此时又会进行更多额外计算。
接下来,关于时间的坑这里也略提一二吧,一开始我以为记录时刻只需要记录时分秒即可,因为最多的建造时长也不超过10小时一般,游戏要保证玩家每天登陆,不可能动用海量的时间去建造一个资源。
如若如此,策划很可能会马上被抓出来祭天,并被玩家评论区冰冷的口水淹没。
但后来写着写着就发现了一个问题,那就是好多天没登录的玩家怎么办,只记录时分秒根本没办法判断时间的早晚,后来想一会还是把日期也记录下来吧。
1 public struct TimeData
2 {
3 public int DayOfYear;
4 public int Hour;
5 public int Minute;
6 public int Second;
7 }
要是你问,那一年以上没登录怎么办,那只能说,你建造的资源已经被时光的齿轮碾碎了(允悲...)。后来突然想起来如果是某一年的最后一天呢...emm,还是老实写全吧:
1 public struct TimeData
2 {
3 public int Year;
4 public int DayOfYear;
5 public int Hour;
6 public int Minute;
7 public int Second;
8
9 public TimeData(int year,int dayOfYear,int hour,int minute,int second)
10 {
11 Year = year;
12 DayOfYear = dayOfYear;
13 Hour = hour;
14 Minute = minute;
15 Second = second;
16 }
17 }
完整时间数据管理脚本:
View Code
完整建造脚本:
View Code
View Code
这里用到的UI基础类可详见之前写过的随笔:
https://www.cnblogs.com/koshio0219/p/12808063.html
单例模式:
https://www.cnblogs.com/koshio0219/p/11203631.html
补充:
通用确认弹窗:
View Code
效果: