IGuideBehaviour

在Scripts文件夹中新建Guide——Frame文件夹,新建IGuideBehavior文件

OnEnter方法初始化引导行为,OnExit方法退出行为。

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
using System;

/// <summary>
/// 引导行为接口
/// </summary>
public interface IGuideBehaviour : IGuide
{

}

public abstract class GuideBehaviourBase : IGuideBehaviour
{
private Action _callBack;
public virtual void OnEnter(Action callBack)
{
_callBack = callBack;
OnEnterLogic();
}
public virtual void Update()
{

}
protected abstract void OnEnterLogic();

protected virtual void OnExit()
{
OnExitLogic();
_callBack?.Invoke();
}
protected abstract void OnExitLogic();
}

IGuideGroup

需要配置每个Group内部的IGuideBehaviour

Group也可以嵌套Group,因为有时会有多个Group组合的情况,我们使用GuideGroupGroupBase作为这种情况的基类。

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
using System.Collections.Generic;
using System;

public interface IGuideGroup : IGuide
{
}

public abstract class GuideGroupBase<T> : IGuideGroup where T : IGuide
{
private Queue<T> _guideItems;
private Action _complete;
/// <summary>
/// 满足触发的条件
/// </summary>
protected abstract bool IsTrigger { get; }
/// <summary>
/// 此引导组的ID
/// </summary>
protected abstract int GroupID { get; }
/// <summary>
/// 经过和parentId位运算合并后的id,也是数据持久化的键值
/// </summary>
protected int _dataId;
/// <summary>
/// 是否是第一次执行引导逻辑
/// </summary>
private bool _firstExecute;

public GuideGroupBase(int parentId)
{
if (parentId < 0)//表示不参与存档
{
_dataId = parentId;
return;
}
_dataId = GetDataId(parentId);
}
private int GetDataId(int parentId)
{
return parentId << 8 + GroupID;
}
public void OnEnter(Action action)
{
if (GuideDataMgr.Instance.GetData(_dataId))
{
return;
}
SaveData();
_firstExecute = true;
_complete = action;
_guideItems = GetGuideItems();
}
private void SaveData()
{
if (_dataId < 0) { return; }

GuideDataMgr.Instance.SaveData(_dataId);
}
public virtual void Update()
{

if (IsTrigger && _firstExecute)
{
_firstExecute = false;
ExecuteGuideItem();
}

}
protected abstract Queue<T> GetGuideItems();

private bool ExecuteGuideItem()
{
if (_guideItems.Count > 0)
{
T item = _guideItems.Dequeue();
item.OnEnter(QueueEnd);
return true;
}
return false;
}

private void QueueEnd()
{
if (!ExecuteGuideItem())
{
_complete?.Invoke();
}
}
}

public abstract class GuideBehaviourGroupBase : GuideGroupBase<IGuideBehaviour>
{
public GuideBehaviourGroupBase(int parentId) : base(parentId)
{

}
}

public abstract class GuideGroupGroupBase : GuideGroupBase<IGuideGroup>
{
public GuideGroupGroupBase(int parentId) : base(parentId)
{

}
}

IGuide和IGuideRoot

在Guide——Frame文件夹,新建IGuide文件,配置内部的IGuideGroup

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
using System;
using System.Collections.Generic;

public interface IGuide
{
void OnEnter(Action callback = null);
void Update();//新手引导需要监听鼠标输入事件,所以提供Update接口
}
public interface IGuideRoot : IGuide
{
string GetViewName();
}

public abstract class GuideBase : IGuideRoot
{
private Queue<IGuideGroup> _groups;
/// <summary>
/// 此UI View的ID,也就是整个引导的ID
/// </summary>
protected abstract int GuideID { get; }
public virtual void OnEnter(Action callback = null)
{
_groups = GetGroups();
ExecuteChildEnter();
}
/// <summary>
/// 此Guide对应的View UI名称
/// </summary>
/// <returns>View UI名称</returns>
public abstract string GetViewName();
public void Update()
{
if (_groups == null) return;

foreach (var group in _groups)
{
group.Update();
}
}
protected abstract Queue<IGuideGroup> GetGroups();

private void ExecuteChildEnter()
{
foreach (IGuideGroup group in _groups)
{
group.OnEnter();//这里不需要给Group传Action,它会在GuideGroupGroupBase传入
}
}
}

GuideMgrBase和GuideMgr

Frame文件夹,新建GuideMgrBase文件

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
using System.Collections.Generic;
using UnityEngine;

public abstract class GuideMgrBase<T> where T : new()
{
protected static T m_Instance;
public static T Instance => m_Instance ??= new T();
private Dictionary<string, IGuideRoot> _viewGuidesDic;
private IGuideRoot _currentGuide;//当前正在执行的引导,防止同时开启多个引导
public virtual void InitGuide()
{
_viewGuidesDic = GetViewGuidesDic();
}
protected abstract Dictionary<string, IGuideRoot> GetViewGuidesDic();
protected void ShowUI(string uiPath)
{
if (_viewGuidesDic.ContainsKey(uiPath))
{
_currentGuide = _viewGuidesDic[uiPath];
_currentGuide.OnEnter();
}
}
protected void HideUI(string uiPath)
{
if (_currentGuide.GetViewName() == uiPath)
{
_currentGuide = null;
}
}
public void Update() => _currentGuide?.Update();
}

在Guide文件夹中新建Demo文件夹,在其中新建GuideMgr

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
using System.Collections.Generic;

public class GuideMgr : GuideMgrBase<GuideMgr>, IUpdate
{
public int Times { get; set; }
public int UpdateTimes { get; }

public override void InitGuide()
{
base.InitGuide();
UIMgr.Instance.AddHideListener(HideUI);//这里属于业务逻辑部分,所以需要在这里和UIMgr交互
UIMgr.Instance.AddShowListener(ShowUI);
LifeCycleMgr.Instance.Add(LifeName.UPDATE, this);
}

public void UpdateFun()
{
Update();
}

protected override Dictionary<string, IGuideRoot> GetViewGuidesDic()
{
Dictionary<string, IGuideRoot> dic = new();
IGuideRoot guide = new DemoViewGuide();
dic.Add(guide.GetViewName(), guide);
return dic;
}
}

GameRoot中调用GuideMgr.InitGuide

GuideDataMgr

用来持久化Guide的数据,为了模块的独立性,所以单独实现一套数据持久化的方法。

这里使用了PlayerPrefs来存储引导的进度数据,可以根据实际情况调整。

注意:GuideGroupGroup这种套娃引导组不需要传入自己的Queue<IGuideGroup>内的Group的构造函数的Key,因为这样一个套娃组对应的存档会出现多份,而一个套娃组只需要一份存档就可以,整个组代表的是一次完整的引导。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GuideDataMgr 
{
protected static GuideDataMgr m_Instance;
public static GuideDataMgr Instance => m_Instance ??= new GuideDataMgr();
public void SaveData(int key, bool value = true)
{
UnityEngine.PlayerPrefs.SetInt(key.ToString(), value ? 1 : 0);
}
public bool GetData(int key)
{
int result = UnityEngine.PlayerPrefs.GetInt(key.ToString(), 0);
return result == 1;
}
}

Demo举例

在Demo文件夹内新建Demo

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
using System.Collections.Generic;

public class DemoViewGuide : GuideBase
{
protected override int GuideID { get; }
protected override Queue<IGuideGroup> GetGroups()
{
Queue<IGuideGroup> groups = new();
groups.Enqueue(new DemoGuideGroupGroupA(GuideID));
groups.Enqueue(new DemoGuideBehaviourGroupA(GuideID));
return groups;
}

public override string GetViewName()
{
return Pathes.START_VIEW;
}
}

/// <summary>
/// Demo引导行为组的组A, 实现起来和GuideBase很像
/// </summary>
public class DemoGuideGroupGroupA : GuideGroupGroupBase
{
public DemoGuideGroupGroupA(int parentId) : base(parentId)
{
}

protected override bool IsTrigger { get; }

protected override int GroupID { get; }

protected override Queue<IGuideGroup> GetGuideItems()
{
Queue<IGuideGroup> guideGroups = new();
guideGroups.Enqueue(new DemoGuideBehaviourGroupA());//这里使用默认值-1,不存储引导进度
guideGroups.Enqueue(new DemoGuideBehaviourGroupB());//这里使用默认值-1,不存储引导进度
return guideGroups;

}
}

/// <summary>
/// Demo引导行为组A
/// </summary>
public class DemoGuideBehaviourGroupA : GuideBehaviourGroupBase
{
public DemoGuideBehaviourGroupA(int parentId = -1) : base(parentId)//设为-1,默认不存储引导进度
{
}

protected override bool IsTrigger
{
get
{
//比如,这里的条件是金币大于20
return true;
}
}

protected override int GroupID { get; }

protected override Queue<IGuideBehaviour> GetGuideItems()
{
Queue<IGuideBehaviour> behaviours = new();
behaviours.Enqueue(new ClickButtonA());
return behaviours;
}
}
/// <summary>
/// Demo引导行为组B
/// </summary>
public class DemoGuideBehaviourGroupB : GuideBehaviourGroupBase
{
public DemoGuideBehaviourGroupB(int parentId = -1) : base(parentId)//设为-1,默认不存储引导进度
{
}

protected override bool IsTrigger
{
get
{
//比如,这里的条件是玩家的等级大于20
return true;
}
}
protected override int GroupID { get; }
protected override Queue<IGuideBehaviour> GetGuideItems()
{
Queue<IGuideBehaviour> behaviours = new();
behaviours.Enqueue(new ClickButtonA());
return behaviours;
}
}
public class ClickButtonA : GuideBehaviourBase
{
protected override void OnEnterLogic()
{
//初始化按钮,绑定按钮事件
//点击之后,执行退出逻辑OnExitLogic??
}

protected override void OnExitLogic()
{
//显示下一个界面
}
}