阅读 254

Unity 制作一个分数统计系统

项目中经常遇到分数统计的需求,例如操作正确则计分,相反则不计分失去该项分数,为了应对需求需要一个分数统计系统。本文主要介绍了通过Unity实现这样的一个计分系统,快来跟随小编一起学习吧

项目中经常遇到分数统计的需求,例如我们执行了某项操作或做了某个题目,操作正确则计分,相反则不计分失去该项分数,为了应对需求需要一个分数统计系统。

首先定义一个分数信息的数据结构,使用Serializable特性使其可序列化:

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;
using UnityEngine;
  
namespace SK.Framework
{
    /// <summary>
    /// 分数信息
    /// </summary>
    [Serializable]
    public class ScoreInfo
    {
        /// <summary>
        /// ID
        /// </summary>
        public int id;
        /// <summary>
        /// 描述
        /// </summary>
        [TextArea]
        public string description;
        /// <summary>
        /// 分值
        /// </summary>
        public float value;
    }
}

ScoreInfo类可序列化后,创建ScoreProfile类继承ScriptableObject使其作为可通过菜单创建的Asset资产:

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;
  
namespace SK.Framework
{
    /// <summary>
    /// 分数配置文件
    /// </summary>
    [CreateAssetMenu]
    public class ScoreProfile : ScriptableObject
    {
        public ScoreInfo[] scores = new ScoreInfo[0];
    }
}

使用ScoreIDConstant类编写所有分数项ID常量,创建ScoreID特性并使用PropertyDrawer使其可在面板选择:

1
2
3
4
5
6
7
namespace SK.Framework
{
    public sealed class ScoreIDConstant
    {
        public const int INVALID = -1;
    }
}
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
using UnityEngine;
  
#if UNITY_EDITOR
using UnityEditor;
using System;
using System.Reflection;
using System.Collections;
#endif
  
namespace SK.Framework
{
    public class ScoreIDAttribute : PropertyAttribute { }
  
#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(ScoreIDAttribute))]
    public class ScoreIDPropertyAttributeDrawer : PropertyDrawer
    {
        private int[] scoreIDArray;
        private GUIContent[] scoreIDConstArray;
  
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return base.GetPropertyHeight(property, label);
        }
  
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            if (scoreIDConstArray == null)
            {
                ArrayList constants = new ArrayList();
                FieldInfo[] fieldInfos = typeof(ScoreIDConstant).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                for (int i = 0; i < fieldInfos.Length; i++)
                {
                    var fi = fieldInfos[i];
                    if (fi.IsLiteral && !fi.IsInitOnly) constants.Add(fi);
                }
                FieldInfo[] fieldInfoArray = (FieldInfo[])constants.ToArray(typeof(FieldInfo));
                scoreIDArray = new int[fieldInfoArray.Length];
                scoreIDConstArray = new GUIContent[fieldInfoArray.Length];
                for (int i = 0; i < fieldInfoArray.Length; i++)
                {
                    scoreIDConstArray[i] = new GUIContent(fieldInfoArray[i].Name);
                    scoreIDArray[i] = (int)fieldInfoArray[i].GetValue(null);
                }
            }
            var index = Array.IndexOf(scoreIDArray, property.intValue);
            index = Mathf.Clamp(index, 0, scoreIDArray.Length);
            index = EditorGUI.Popup(position, label, index, scoreIDConstArray);
            property.intValue = scoreIDArray[index];
        }
    }
#endif
}

有了ScoreID特性后,用于ScoreInfo中的id字段:

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
using System;
using UnityEngine;
  
namespace SK.Framework
{
    /// <summary>
    /// 分数信息
    /// </summary>
    [Serializable]
    public class ScoreInfo
    {
        /// <summary>
        /// ID
        /// </summary>
        [ScoreID]
        public int id;
        /// <summary>
        /// 描述
        /// </summary>
        [TextArea]
        public string description;
        /// <summary>
        /// 分值
        /// </summary>
        public float value;
    }
}

数据可配置后,创建分数项Score类,声明以下字段:Flag表示该分数项的标识,注册分数项时返回该标识,用于后续获取或取消该分数项分值;Description即分数项的描述;Value表示该分数项的分值;IsObtained用于标记该分数项的分值是否已经获得。

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
namespace SK.Framework
{
    /// <summary>
    /// 分数项
    /// </summary>
    public class Score
    {
        /// <summary>
        /// 标识
        /// </summary>
        public string Flag { get; private set; }
        /// <summary>
        /// 描述
        /// </summary>
        public string Description { get; private set; }
        /// <summary>
        /// 分值
        /// </summary>
        public float Value { get; private set; }
        /// <summary>
        /// 是否已经获得分值
        /// </summary>
        public bool IsObtained { get; set; }
  
        public Score(string flag, string description, float value)
        {
            Flag = flag;
            Description = description;
            Value = value;
        }
    }
}

为了实现一个分数组合,例如某项操作,通过A操作方式可获得5分,通过B操作方式可获得3分,它们之间是互斥的,即获得了前者的5分,就不会获得后者的3分,创建ScoreGroup类:

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
using System.Collections.Generic;
  
namespace SK.Framework
{
    /// <summary>
    /// 分数组合
    /// </summary>
    public class ScoreGroup
    {
        /// <summary>
        /// 组合描述
        /// </summary>
        public string Description { get; private set; }
        /// <summary>
        /// 计分模式
        /// Additive表示组合内分值进行累加
        /// MutuallyExclusive表示组内各分数项互斥 获得其中一项分值 则取消其它项分值
        /// </summary>
        public ValueMode ValueMode { get; private set; }
  
        public List<Score> Scores { get; private set; }
  
        public ScoreGroup(string description, ValueMode valueMode, params Score[] scores)
        {
            Description = description;
            ValueMode = valueMode;
            Scores = new List<Score>(scores);
        }
  
        public bool Obtain(string flag)
        {
            var target = Scores.Find(m => m.Flag == flag);
            if (target != null)
            {
                switch (ValueMode)
                {
                    case ValueMode.Additive: target.IsObtained = true; break;
                    case ValueMode.MutuallyExclusive:
                        for (int i = 0; i < Scores.Count; i++)
                        {
                            Scores[i].IsObtained = Scores[i] == target;
                        }
                        break;
                    default: break;
                }
                if (ScoreMaster.DebugMode)
                {
                    ScoreMaster.LogInfo($"获取分数组合 [{Description}] 中标识为 [{flag}] 的分值 [{target.Description}]");
                }
                return true;
            }
            if (ScoreMaster.DebugMode)
            {
                ScoreMaster.LogError($"分数组合 [{Description}] 中不存在标识为 [{flag}] 的分数项.");
            }
            return false;
        }
        public bool Cancle(string flag)
        {
            var target = Scores.Find(m => m.Flag == flag);
            if (target != null)
            {
                if (ScoreMaster.DebugMode)
                {
                    ScoreMaster.LogInfo($"取消分数组合 [{Description}] 中标识为 [{flag}] 的分数项分值 [{target.Description}]");
                }
                target.IsObtained = false;
                return true;
            }
            if (ScoreMaster.DebugMode)
            {
                ScoreMaster.LogError($"分数组合 [{Description}] 中不存在标识为 [{flag}] 的分数项.");
            }
            return false;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace SK.Framework
{
    /// <summary>
    /// 计分方式
    /// </summary>
    public enum ValueMode
    {
        /// <summary>
        /// 累加的
        /// </summary>
        Additive,
        /// <summary>
        /// 互斥的
        /// </summary>
        MutuallyExclusive,
    }
}

最终编写分数管理类,封装Create、Obtain、Cancle、GetSum函数,分别用于创建分数组合、获取分数、取消分数、获取总分,实现Editor类使分数信息在Inspector面板可视化:

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
using System;
using UnityEngine;
using System.Collections.Generic;
  
#if UNITY_EDITOR
using UnityEditor;
using System.Reflection;
#endif
  
namespace SK.Framework
{
    public class ScoreMaster : MonoBehaviour
    {
        #region NonPublic Variables
        private static ScoreMaster instance;
        [SerializeField] private ScoreProfile profile;
        private readonly Dictionary<string, ScoreGroup> groups = new Dictionary<string, ScoreGroup>();
        #endregion
  
        #region Public Properties
        public static ScoreMaster Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = FindObjectOfType<ScoreMaster>();
                }
                if (instance == null)
                {
                    instance = new GameObject("[SKFramework.Score]").AddComponent<ScoreMaster>();
                    instance.profile = Resources.Load<ScoreProfile>("Score Profile");
                    if (instance.profile == null && DebugMode)
                    {
                        LogError("加载分数信息配置表失败.");
                    }
                }
                return instance;
            }
        }
        #endregion
  
        #region NonPublic Methods
        private string[] CreateScore(string description, ValueMode valueMode, params int[] idArray)
        {
            Score[] scores = new Score[idArray.Length];
            string[] flags = new string[idArray.Length];
            for (int i = 0; i < idArray.Length; i++)
            {
                var info = Array.Find(profile.scores, m => m.id == idArray[i]);
                if (info != null)
                {
                    var flag = Guid.NewGuid().ToString();
                    flags[i] = flag;
                    scores[i] = new Score(flag, info.description, info.value);
                    if (DebugMode) LogInfo($"创建分数ID为 [{idArray[i]}] 的分数项 [{info.description}] flag: {flag}");
                }
                else if (DebugMode)
                {
                    LogError($"配置中不存在ID为 [{idArray[i]}] 的分数信息.");
                }
            }
            ScoreGroup group = new ScoreGroup(description, valueMode, scores);
            groups.Add(description, group);
            if (DebugMode)
            {
                LogInfo($"创建分数组合 [{description}] 计分模式[{valueMode}]");
            }
            return flags;
        }
        private bool ObtainValue(string groupDescription, string flag)
        {
            if (groups.TryGetValue(groupDescription, out ScoreGroup target))
            {
                return target.Obtain(flag);
            }
            if (DebugMode)
            {
                LogError($"不存在分数组合 [{groupDescription}].");
            }
            return false;
        }
        private bool CancleValue(string groupDescription, string flag)
        {
            if (groups.TryGetValue(groupDescription, out ScoreGroup target))
            {
                return target.Cancle(flag);
            }
            if (DebugMode)
            {
                LogError($"不存在分数组合 [{groupDescription}].");
            }
            return false;
        }
        private float GetSumValue()
        {
            float retV = 0f;
            foreach (var kv in groups)
            {
                var scores = kv.Value.Scores;
                for (int i = 0; i < scores.Count; i++)
                {
                    var score = scores[i];
                    if (score.IsObtained)
                    {
                        retV += score.Value;
                    }
                }
            }
            return retV;
        }
        #endregion
  
        #region Public Methods
        /// <summary>
        /// 创建分数组合
        /// </summary>
        /// <param name="description">分数组合描述</param>
        /// <param name="valueMode">分数组计分方式</param>
        /// <param name="idArray">分数信息ID组合</param>
        /// <returns>返回分数项标识符组合</returns>
        public static string[] Create(string description, ValueMode valueMode, params int[] idArray)
        {
            return Instance.CreateScore(description, valueMode, idArray);
        }
        /// <summary>
        /// 获取分数组合中指定标识分数项的分值
        /// </summary>
        /// <param name="groupDescription">分数组合</param>
        /// <param name="flag">分数项标识</param>
        /// <returns>获取成功返回true 否则返回false</returns>
        public static bool Obtain(string groupDescription, string flag)
        {
            return Instance.ObtainValue(groupDescription, flag);
        }
        /// <summary>
        /// 取消分数组合中指定标识分数项的分值
        /// </summary>
        /// <param name="groupDescription">分数组合</param>
        /// <param name="flag">分数项标识</param>
        /// <returns></returns>
        public static bool Cancle(string groupDescription, string flag)
        {
            return Instance.CancleValue(groupDescription, flag);
        }
        /// <summary>
        /// 获取总分值
        /// </summary>
        /// <returns>总分值</returns>
        public static float GetSum()
        {
            return Instance.GetSumValue();
        }
        #endregion
  
        #region Debugger
        public static bool DebugMode = true;
  
        public static void LogInfo(string info)
        {
            Debug.Log($"<color=cyan><b>[SKFramework.Score.Info]</b></color> --> {info}");
        }
        public static void LogWarn(string warn)
        {
            Debug.Log($"<color=yellow><b>[SKFramework.Score.Warn]</b></color> --> {warn}");
        }
        public static void LogError(string error)
        {
            Debug.Log($"<color=red><b>[SKFramework.Score.Error]</b></color> --> {error}");
        }
        #endregion
    }
  
#if UNITY_EDITOR
    [CustomEditor(typeof(ScoreMaster))]
    public class ScoreMasterInspector : Editor
    {
        private SerializedProperty profile;
        private Dictionary<string, ScoreGroup> groups;
        private Dictionary<ScoreGroup, bool> groupFoldout;
  
        private void OnEnable()
        {
            profile = serializedObject.FindProperty("profile");
        }
  
        public override void OnInspectorGUI()
        {
            EditorGUILayout.PropertyField(profile);
            if (GUI.changed)
            {
                serializedObject.ApplyModifiedProperties();
                EditorUtility.SetDirty(target);
            }
  
            if (!Application.isPlaying) return;
            Color color = GUI.color;
            GUI.color = Color.cyan;
            OnRuntimeGUI();
            GUI.color = color;
        }
        private void OnRuntimeGUI()
        {
            if (groupFoldout == null)
            {
                groups = typeof(ScoreMaster).GetField("groups", BindingFlags.Instance | BindingFlags.NonPublic)
                    .GetValue(ScoreMaster.Instance) as Dictionary<string, ScoreGroup>;
                groupFoldout = new Dictionary<ScoreGroup, bool>();
            }
  
            foreach (var kv in groups)
            {
                if (!groupFoldout.ContainsKey(kv.Value))
                {
                    groupFoldout.Add(kv.Value, false);
                }
  
                ScoreGroup group = kv.Value;
                groupFoldout[group] = EditorGUILayout.Foldout(groupFoldout[group], group.Description);
                if (groupFoldout[group])
                {
                    GUILayout.Label($"计分模式: {(group.ValueMode == ValueMode.Additive ? "累加" : "互斥")}");
                    for (int i = 0; i < group.Scores.Count; i++)
                    {
                        Score score = group.Scores[i];
                        GUILayout.BeginVertical("Box");
                        GUI.color = score.IsObtained ? Color.green : Color.cyan;
                        GUILayout.Label($"描述: {score.Description}");
                        GUILayout.Label($"标识: {score.Flag}");
                        GUILayout.BeginHorizontal();
                        GUILayout.Label($"分值: {score.Value}   {(score.IsObtained ? "" : "")}");
                        GUI.color = Color.cyan;
                        GUILayout.FlexibleSpace();
                        GUI.color = Color.yellow;
                        if (GUILayout.Button("Obtain", "ButtonLeft", GUILayout.Width(50f)))
                        {
                            ScoreMaster.Obtain(group.Description, score.Flag);
                        }
                        if (GUILayout.Button("Cancle", "ButtonRight", GUILayout.Width(50f)))
                        {
                            ScoreMaster.Cancle(group.Description, score.Flag);
                        }
                        GUI.color = Color.cyan;
                        GUILayout.EndHorizontal();
                        GUILayout.EndVertical();
                    }
                }
            }
            GUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            GUILayout.Label($"总分: {ScoreMaster.GetSum()}", "LargeLabel");
            GUILayout.Space(50f);
            GUILayout.EndHorizontal();
        }
    }
#endif
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
namespace SK.Framework
{
    public sealed class ScoreIDConstant
    {
        public const int INVALID = -1;
  
        public const int TEST_A = 0;
        public const int TEST_B = 1;
        public const int TEST_C = 2;
        public const int TEST_D = 3;
    }
}

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
using UnityEngine;
using SK.Framework;
  
public class Foo : MonoBehaviour
{
    private string[] flags;
  
    private void Start()
    {
        flags = ScoreMaster.Create("测试", ValueMode.MutuallyExclusive,
            ScoreIDConstant.TEST_A, ScoreIDConstant.TEST_B,
            ScoreIDConstant.TEST_C, ScoreIDConstant.TEST_D);
    }
  
    private void OnGUI()
    {
        if (GUILayout.Button("A", GUILayout.Width(200f), GUILayout.Height(50f)))
        {
            ScoreMaster.Obtain("测试", flags[0]);
        }
        if (GUILayout.Button("B", GUILayout.Width(200f), GUILayout.Height(50f)))
        {
            ScoreMaster.Obtain("测试", flags[1]);
        }
        if (GUILayout.Button("C", GUILayout.Width(200f), GUILayout.Height(50f)))
        {
            ScoreMaster.Obtain("测试", flags[2]);
        }
        if (GUILayout.Button("D", GUILayout.Width(200f), GUILayout.Height(50f)))
        {
            ScoreMaster.Obtain("测试", flags[3]);
        }
        GUILayout.Label($"总分: {ScoreMaster.GetSum()}");
    }
}

 

以上就是Unity 制作一个分数统计系统的详细内容


文章分类
百科问答
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐