PlaneProperty

PlaneProperty脚本是动态挂载在Property对象上的脚本,用来动态加载PropertyItem,显示当前战机的各种属性和加点情况。

属性加点部分也是需要配置的,我们修改InitPlane.json文件

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
{
"planes": [
{
"planeId": 0,
"level": 0,
"attackTime": 1,
"attack": {
"name": "攻击",
"value": 5,
"cost": 200,
"grouth": 10,
"maxValue": 500
},
"fireRate": {
"name": "攻速",
"value": 80,
"cost": 200,
"grouth": 1,
"maxValue": 500
},
"life": {
"name": "生命",
"value": 100,
"cost": 200,
"grouth": 50,
"maxValue": 1000
}
},
{
"planeId": 1,
"level": 0,
"attackTime": 1,
"attack": {
"name": "攻击",
"value": 10,
"cost": 200,
"grouth": 20,
"maxValue": 800
},
"fireRate": {
"name": "攻速",
"value": 120,
"cost": 200,
"grouth": 2,
"maxValue": 500
},
"life": {
"name": "生命",
"value": 120,
"cost": 200,
"grouth": 100,
"maxValue": 1500
}
},
{
"planeId": 2,
"level": 0,
"attackTime": 1,
"attack": {
"name": "攻击",
"value": 20,
"cost": 200,
"grouth": 50,
"maxValue": 1000
},
"fireRate": {
"name": "攻速",
"value": 140,
"cost": 200,
"grouth": 5,
"maxValue": 500
},
"life": {
"name": "生命",
"value": 150,
"cost": 200,
"grouth": 200,
"maxValue": 3000
}
},
{
"planeId": 3,
"level": 0,
"attackTime": 1,
"attack": {
"name": "攻击",
"value": 40,
"cost": 200,
"grouth": 80,
"maxValue": 1500
},
"fireRate": {
"name": "攻速",
"value": 180,
"cost": 200,
"grouth": 10,
"maxValue": 500
},
"life": {
"name": "生命",
"value": 200,
"cost": 200,
"grouth": 300,
"maxValue": 5000
}
}
]
}

value是初始值,cost是升级所需要的花费,grouth是每次升级的成长点数,maxValue是成长最大值。

攻速在这里是“attackTime/value”,attackTime表示基础攻速,attackTime = 1表示每1秒发射一颗子弹,攻速的Value值表示战机的基础攻速占真正发射子弹的时间间隔站的百分比,当攻速的Value = 80时,这个战机的真正攻速是1 / 80% = 1.25,也就是每1.25秒发射一颗子弹。

为了更好地反映配置,在PlaneProperty脚本中添加内部的枚举Property,每个枚举值和配置文件中的相对应,这样就可以快速通过修改配置文件和枚举值来实现不同的UI,这也是尽量修改配置不修改逻辑的思想。

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

public class PlaneProperty : ViewBase
{
public enum Property
{
attack,
fireRate,
life,
COUNT
}
private List<PropertyItem> _items;
protected override void InitChild()
{
_items = new((int)Property.COUNT);
for (Property i = 0; i < Property.COUNT; i++)
{
GameObject item = LoadMgr.Instance.LoadPrefab(Path.PROPERTY_ITEM, transform);
PropertyItem propertyItem = item.AddComponent<PropertyItem>();
propertyItem.Init(i.ToString());
_items.Add(propertyItem);
}
}
public void UpdataId(int id)
{
foreach (var item in _items)
{
item.UpdatePlaneId(id);
}
}
}

别忘了在StrengthenView内动态挂载此脚本

1
2
3
4
5
protected override void InitChild()
{
UIUtil.Get("Switchplayer").Go.AddComponent<SwitchPlayer>();
UIUtil.Get("Property").Go.AddComponent<PlaneProperty>();//+++
}

遍历枚举的三种方式

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

public class PlaneProperty : ViewBase
{
public enum Property
{
attack,
fireRate,
life,
count
}

protected override void InitChild()
{
LoadMgr.Instance.LoadPrefab(Path.PROPERTY_ITEM, transform);
foreach (string pName in System.Enum.GetNames(typeof(Property)))
{
Debug.Log(pName);
}
foreach (Property pItem in System.Enum.GetValues(typeof(Property)))
{
Debug.Log(pItem);
}
for (Property i = 0; i < Property.count; i++)
{
Debug.Log(i);
}
}
}

第一种方式,使用System.Enum.GetNames(System.Type enumType),这种方式会将枚举值转化为string

第二种方式,使用System.Enum.GetValues(typeof(Property),这种方式会返回一个Array类,我们可以强转成对应的Enum类型

第三种方式,使用for循环,因为枚举本质上还是int,所以可以使用枚举类型代替int来循环

PropertyItem

这个脚本是动态挂载在Item的UIPrefab上的。

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

public class PropertyItem : MonoBehaviour
{
public enum ItemKey
{
name,
value,
cost,
grouth,
maxValue,
COUNT
}
private static int _itemId = -1;
private string _key;
public void Init(string key)
{
_key = key;
_itemId++;
UpdatePos(_itemId);
}
private void UpdatePos(int itemId)
{
RectTransform rect = transform.GetRect();
float offsetY = rect.rect.height * itemId;
rect.anchoredPosition -= Vector2.up * offsetY;
}
public void UpdatePlaneId(int planeId)//注意这个Id是选择的战机的id,不是Item位置的id
{
UpdateData(planeId);
}
private void UpdateData(int planeId)
{
for (ItemKey i = 0; i < ItemKey.grouth; i++)
{
Transform trans = transform.Find(ConvertName(i));
if(trans != null)
{
string key = KeysUtil.GetPropertyKeys(planeId, _key + i.ToString());
trans.GetComponent<Text>().text = DataMgr.Instance.GetObject(key).ToString();
}
else
{
Debug.LogError("PropertyItem指定的子项名称错误,错误名称为:" + ConvertName(i));
}
}
}
private string ConvertName(ItemKey key)//将首字母改为大写,使用了C#的范围运算符
{
string first = key.ToString()[..1].ToUpper();
string others = key.ToString()[1..];
return first + others;
}
}

这个脚本也是用了静态变量来让自己的id自增的方法,和协程管理器的协程id分配方式一样。

每个Item自己设定在Rect中的位置,不使用Layout组件,配合id来自动设置。

每个Item配合ItemKey枚举,动态查找需要显示的数据。

PlayerPrefsMemory

在其中实现GetObject方法,来解决使用Get<T>方法时,泛型类型指定的不准确就不返回值的问题。同时我们也实现一下SetObject方法

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
public class PlayerPrefsMemory : IDataMemory
{
private readonly Dictionary<Type, Func<string, object>> dataGetter = new()
{
{typeof(int), key => PlayerPrefs.GetInt(key,(int)_defaultValues[typeof(int)]) },
{typeof(string), key => PlayerPrefs.GetString(key,(string)_defaultValues[typeof(string)]) },
{typeof(float), key => PlayerPrefs.GetFloat(key,(float)_defaultValues[typeof(float)]) },
};
//...
private static readonly Dictionary<Type, object> _defaultValues = new()
{
{typeof(int), default(int) },
{typeof(string), "" },//default(string)是null,而PlayerPrefs并不会把null作为字符串key的默认值,所以这里统一为""。
{typeof(float), default(float) },
};
//...
public object GetObject(string key)
{
if (Contains(key))
{
foreach (var item in dataGetter)
{
object value = item.Value(key);
if (!value.Equals(_defaultValues[item.Key]))
{
return value;
}
}
}
else
{
Debug.LogError("当前存档数据内不包含对应键值:" + key);
}
return null;
}
public void SetObject(string key, object value)
{
bool success = false;
foreach(var pair in dataSetter)
{
if(value.GetType().Equals(pair.Key))
{
pair.Value(key, value);
success = true;
}
}
if (!success)
{
Debug.LogErrorFormat("未找到当前值的类型,赋值失败:Key:{0} Value:{1}", key, value);
}
}
//...
}

IDataMemoryDataMgr也要添加GetObjectSetObject方法,这里省略。

静态拓展类ExtendUtil

在Utility文件夹内新建ExtendUtil脚本,用来简化获取RectTransform组件的方法,只需要调用transform.GetRect()即可。

1
2
3
4
5
6
7
8
9
using UnityEngine;

public static class ExtendUtil
{
public static RectTransform GetRect(this Transform target)
{
return target.GetComponent<RectTransform>();
}
}

静态拓展方法只能通过实例来调用,不能使用类名静态调用,也即是使用Transform.GetRect()是不行的。