VS任务列表

在C#代码中输入类似的注释

1
2
//TODO: something to do
//todo: something to do

然后打开Visual Studio的“任务列表”,快捷键“CTRL + ,T”,或者“视图——任务列表”,就能够显示所有在代码中标记的需要做的任务。

选择英雄部分

我们希望选择一个英雄时,它的头像变亮,其他的头像变暗。

英雄头像选择

每一个英雄头像都是一个Button,它们都动态挂载HeroItem脚本。这个脚本负责头像明暗的逻辑,并且声明一个回调,这个回调调用父级的方法,从而让每个英雄头像正确地变亮变暗。

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
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using System;

public class HeroItem : MonoBehaviour
{
private Color defaultColor = Color.gray;
private Color selectedColor = Color.white;
private float tweenTime = 0.3f;
private Image image;
private Action<int> callBack;
void Start()
{
image = GetComponent<Image>();
GetComponent<Button>().onClick.AddListener(Selected);
Unselected();
}
void Selected()
{
image.DOKill();//清空所有正在tween的数据,防止频繁地点击
image.DOColor(selectedColor, tweenTime);
callBack?.Invoke(transform.GetSiblingIndex());
}
public void Unselected()
{
image.DOKill();
image.DOColor(defaultColor, tweenTime);
}
public void AddResetListener(Action<int> callback)
{
this.callBack = callback;
}
}

SelectHero

SelectHero是每一个HeroItem的父对象,负责给每一个Item动态挂载HeroItem脚本,以及监听每个Item的点击回调

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

public class SelectHero : MonoBehaviour
{
private List<HeroItem> heroItems;
void Start()
{
heroItems = new List<HeroItem>(transform.childCount);
HeroItem heroItem = null;
foreach (Transform trans in transform)
{
heroItem = trans.gameObject.AddComponent<HeroItem>();
heroItem.AddResetListener(ResetState);
heroItems.Add(heroItem);
}
}
private void ResetState(int index)
{
for (int i = 0; i < heroItems.Count; i++)
{
if (i == index) continue;
heroItems[i].Unselected();
}
}
}

数据持久化

IDataMemory

Scripts——Module——DataMemory文件夹内,新建IDataMemory文件

1
2
3
4
5
6
7
public interface IDataMemory 
{
T Get<T>(string key);
void Set<T>(string key, T value);
void Clear(string key);
void ClearAll();
}

PlayerPrefsMemory及优化

在DataMemory文件夹内新建PlayerPrefsMemory,做一个PlayerPrefs的实现,

优化前的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PlayerPrefsMemory : IDataMemory
{
public T Get<T>(string key)
{
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (typeof(T) == typeof(int))
{
int value = PlayerPrefs.GetInt(key, 0);//如果PlayerPrefs之中不包含key,会创建对应的key以及默认值0
return (T)converter.ConvertTo(value, type);
}
if (typeof(T) == typeof(string))
{
string value = PlayerPrefs.GetString(key, "");
return (T)converter.ConvertTo(value, type);
}
}
}

发现在Get方法中有重复的逻辑,产生重复的核心是PlayerPrefs.Get有三个重载

注意这里的TypeDescriptor.GetConverterAPI。Get方法返回的是泛型T,而PlayerPrefs只能返回string、float、int三种类型,我们只能通过converter来转换成符合的泛型T来返回。

优化思路:声明一个字典,提前把每一个Type对应的PlayerPrefs.Get方法对应好,在使用时直接将Type作为key从字典获取对应的方法即可。这个字典相当于一个配置表,这也是通过配置数据来减少逻辑修改的思想,配置数据是可以及时修改的,而核心的方法逻辑不能总是修改。

优化后的版本:

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

public class PlayerPrefsMemory : IDataMemory
{
private Dictionary<Type, Func<string, object>> dataGetter = new Dictionary<Type, Func<string, object>>()
{
{typeof(int), key => PlayerPrefs.GetInt(key,0) },
{typeof(string), key => PlayerPrefs.GetString(key,"") },
{typeof(float), key => PlayerPrefs.GetFloat(key,0f) },
};
private Dictionary<Type, Action<string, object>> dataSetter = new Dictionary<Type, Action<string, object>>()
{
{typeof(int), (key,value) => PlayerPrefs.SetInt(key,(int)value) },
{typeof(string), (key,value) => PlayerPrefs.SetString(key,(string)value) },
{typeof(float), (key,value) => PlayerPrefs.SetFloat(key,(float)value) },
};
public T Get<T>(string key)
{
Type type = typeof(T);
TypeConverter converter = TypeDescriptor.GetConverter(type);
if(dataGetter.ContainsKey(type))
{
return (T)converter.ConvertTo(dataGetter[type].Invoke(key), type);
}
else
{
Debug.LogError("当前数据存储中无此类型数据,类型名:" + type.Name);
return default;
}
}

public void Set<T>(string key, T value)
{
Type type = typeof(T);
if (dataSetter.ContainsKey(type))
{
dataSetter[type].Invoke(key, value);
}
else
{
Debug.LogErrorFormat("当前数据存储中无此类型数据,数据为key:{0} value:{1}", key, value);
}
}
public void Clear(string key)
{
PlayerPrefs.DeleteKey(key);
}

public void ClearAll()
{
PlayerPrefs.DeleteAll();
}
}

DataMgr

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
public class DataMgr : NormalSingleton<DataMgr>, IDataMemory
{
private IDataMemory m_data;
public DataMgr()
{
m_data = new PlayerPrefsMemory();
}
public void Clear(string key)
{
m_data.Clear(key);
}

public void ClearAll()
{
m_data.ClearAll();
}

public T Get<T>(string key)
{
return m_data.Get<T>(key);
}

public void Set<T>(string key, T value)
{
m_data.Set<T>(key, value);
}
}

针对接口编程,我们可以随时切换实现,比如直接实现一个JsonMemory,只需要实现相同的IDataMemory接口就好了

1
2
3
4
5
6
7
8
9
public class DataMgr : NormalSingleton<DataMgr>, IDataMemory
{
private IDataMemory m_data;
public DataMgr()
{
m_data = new JsonMemory();
}
//...
}