Unity 实现一个分数系统

举报
CoderZ1010 发表于 2022/09/25 04:40:31 2022/09/25
【摘要】 项目中经常遇到分数统计的需求,例如我们执行了某项操作或做了某个题目,操作正确则计分,相反则不计分失去该项分数,为了应对需求需要一个分数统计系统。 首先定义一个分数信息的数据结构,使用Serializable特性使其可序列化: using System;using UnityEngine; namespace SK.Framework...

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

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


  
  1. using System;
  2. using UnityEngine;
  3. namespace SK.Framework
  4. {
  5. /// <summary>
  6. /// 分数信息
  7. /// </summary>
  8. [Serializable]
  9. public class ScoreInfo
  10. {
  11. /// <summary>
  12. /// ID
  13. /// </summary>
  14. public int id;
  15. /// <summary>
  16. /// 描述
  17. /// </summary>
  18. [TextArea]
  19. public string description;
  20. /// <summary>
  21. /// 分值
  22. /// </summary>
  23. public float value;
  24. }
  25. }

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


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

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


  
  1. namespace SK.Framework
  2. {
  3. public sealed class ScoreIDConstant
  4. {
  5. public const int INVALID = -1;
  6. }
  7. }

  
  1. using UnityEngine;
  2. #if UNITY_EDITOR
  3. using UnityEditor;
  4. using System;
  5. using System.Reflection;
  6. using System.Collections;
  7. #endif
  8. namespace SK.Framework
  9. {
  10. public class ScoreIDAttribute : PropertyAttribute { }
  11. #if UNITY_EDITOR
  12. [CustomPropertyDrawer(typeof(ScoreIDAttribute))]
  13. public class ScoreIDPropertyAttributeDrawer : PropertyDrawer
  14. {
  15. private int[] scoreIDArray;
  16. private GUIContent[] scoreIDConstArray;
  17. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  18. {
  19. return base.GetPropertyHeight(property, label);
  20. }
  21. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
  22. {
  23. if (scoreIDConstArray == null)
  24. {
  25. ArrayList constants = new ArrayList();
  26. FieldInfo[] fieldInfos = typeof(ScoreIDConstant).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
  27. for (int i = 0; i < fieldInfos.Length; i++)
  28. {
  29. var fi = fieldInfos[i];
  30. if (fi.IsLiteral && !fi.IsInitOnly) constants.Add(fi);
  31. }
  32. FieldInfo[] fieldInfoArray = (FieldInfo[])constants.ToArray(typeof(FieldInfo));
  33. scoreIDArray = new int[fieldInfoArray.Length];
  34. scoreIDConstArray = new GUIContent[fieldInfoArray.Length];
  35. for (int i = 0; i < fieldInfoArray.Length; i++)
  36. {
  37. scoreIDConstArray[i] = new GUIContent(fieldInfoArray[i].Name);
  38. scoreIDArray[i] = (int)fieldInfoArray[i].GetValue(null);
  39. }
  40. }
  41. var index = Array.IndexOf(scoreIDArray, property.intValue);
  42. index = Mathf.Clamp(index, 0, scoreIDArray.Length);
  43. index = EditorGUI.Popup(position, label, index, scoreIDConstArray);
  44. property.intValue = scoreIDArray[index];
  45. }
  46. }
  47. #endif
  48. }

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


  
  1. using System;
  2. using UnityEngine;
  3. namespace SK.Framework
  4. {
  5. /// <summary>
  6. /// 分数信息
  7. /// </summary>
  8. [Serializable]
  9. public class ScoreInfo
  10. {
  11. /// <summary>
  12. /// ID
  13. /// </summary>
  14. [ScoreID]
  15. public int id;
  16. /// <summary>
  17. /// 描述
  18. /// </summary>
  19. [TextArea]
  20. public string description;
  21. /// <summary>
  22. /// 分值
  23. /// </summary>
  24. public float value;
  25. }
  26. }

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


  
  1. namespace SK.Framework
  2. {
  3. /// <summary>
  4. /// 分数项
  5. /// </summary>
  6. public class Score
  7. {
  8. /// <summary>
  9. /// 标识
  10. /// </summary>
  11. public string Flag { get; private set; }
  12. /// <summary>
  13. /// 描述
  14. /// </summary>
  15. public string Description { get; private set; }
  16. /// <summary>
  17. /// 分值
  18. /// </summary>
  19. public float Value { get; private set; }
  20. /// <summary>
  21. /// 是否已经获得分值
  22. /// </summary>
  23. public bool IsObtained { get; set; }
  24. public Score(string flag, string description, float value)
  25. {
  26. Flag = flag;
  27. Description = description;
  28. Value = value;
  29. }
  30. }
  31. }

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


  
  1. using System.Collections.Generic;
  2. namespace SK.Framework
  3. {
  4. /// <summary>
  5. /// 分数组合
  6. /// </summary>
  7. public class ScoreGroup
  8. {
  9. /// <summary>
  10. /// 组合描述
  11. /// </summary>
  12. public string Description { get; private set; }
  13. /// <summary>
  14. /// 计分模式
  15. /// Additive表示组合内分值进行累加
  16. /// MutuallyExclusive表示组内各分数项互斥 获得其中一项分值 则取消其它项分值
  17. /// </summary>
  18. public ValueMode ValueMode { get; private set; }
  19. public List<Score> Scores { get; private set; }
  20. public ScoreGroup(string description, ValueMode valueMode, params Score[] scores)
  21. {
  22. Description = description;
  23. ValueMode = valueMode;
  24. Scores = new List<Score>(scores);
  25. }
  26. public bool Obtain(string flag)
  27. {
  28. var target = Scores.Find(m => m.Flag == flag);
  29. if (target != null)
  30. {
  31. switch (ValueMode)
  32. {
  33. case ValueMode.Additive: target.IsObtained = true; break;
  34. case ValueMode.MutuallyExclusive:
  35. for (int i = 0; i < Scores.Count; i++)
  36. {
  37. Scores[i].IsObtained = Scores[i] == target;
  38. }
  39. break;
  40. default: break;
  41. }
  42. if (ScoreMaster.DebugMode)
  43. {
  44. ScoreMaster.LogInfo($"获取分数组合 [{Description}] 中标识为 [{flag}] 的分值 [{target.Description}]");
  45. }
  46. return true;
  47. }
  48. if (ScoreMaster.DebugMode)
  49. {
  50. ScoreMaster.LogError($"分数组合 [{Description}] 中不存在标识为 [{flag}] 的分数项.");
  51. }
  52. return false;
  53. }
  54. public bool Cancle(string flag)
  55. {
  56. var target = Scores.Find(m => m.Flag == flag);
  57. if (target != null)
  58. {
  59. if (ScoreMaster.DebugMode)
  60. {
  61. ScoreMaster.LogInfo($"取消分数组合 [{Description}] 中标识为 [{flag}] 的分数项分值 [{target.Description}]");
  62. }
  63. target.IsObtained = false;
  64. return true;
  65. }
  66. if (ScoreMaster.DebugMode)
  67. {
  68. ScoreMaster.LogError($"分数组合 [{Description}] 中不存在标识为 [{flag}] 的分数项.");
  69. }
  70. return false;
  71. }
  72. }
  73. }

  
  1. namespace SK.Framework
  2. {
  3. /// <summary>
  4. /// 计分方式
  5. /// </summary>
  6. public enum ValueMode
  7. {
  8. /// <summary>
  9. /// 累加的
  10. /// </summary>
  11. Additive,
  12. /// <summary>
  13. /// 互斥的
  14. /// </summary>
  15. MutuallyExclusive,
  16. }
  17. }

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


  
  1. using System;
  2. using UnityEngine;
  3. using System.Collections.Generic;
  4. #if UNITY_EDITOR
  5. using UnityEditor;
  6. using System.Reflection;
  7. #endif
  8. namespace SK.Framework
  9. {
  10. public class ScoreMaster : MonoBehaviour
  11. {
  12. #region NonPublic Variables
  13. private static ScoreMaster instance;
  14. [SerializeField] private ScoreProfile profile;
  15. private readonly Dictionary<string, ScoreGroup> groups = new Dictionary<string, ScoreGroup>();
  16. #endregion
  17. #region Public Properties
  18. public static ScoreMaster Instance
  19. {
  20. get
  21. {
  22. if (instance == null)
  23. {
  24. instance = FindObjectOfType<ScoreMaster>();
  25. }
  26. if (instance == null)
  27. {
  28. instance = new GameObject("[SKFramework.Score]").AddComponent<ScoreMaster>();
  29. instance.profile = Resources.Load<ScoreProfile>("Score Profile");
  30. if (instance.profile == null && DebugMode)
  31. {
  32. LogError("加载分数信息配置表失败.");
  33. }
  34. }
  35. return instance;
  36. }
  37. }
  38. #endregion
  39. #region NonPublic Methods
  40. private string[] CreateScore(string description, ValueMode valueMode, params int[] idArray)
  41. {
  42. Score[] scores = new Score[idArray.Length];
  43. string[] flags = new string[idArray.Length];
  44. for (int i = 0; i < idArray.Length; i++)
  45. {
  46. var info = Array.Find(profile.scores, m => m.id == idArray[i]);
  47. if (info != null)
  48. {
  49. var flag = Guid.NewGuid().ToString();
  50. flags[i] = flag;
  51. scores[i] = new Score(flag, info.description, info.value);
  52. if (DebugMode) LogInfo($"创建分数ID为 [{idArray[i]}] 的分数项 [{info.description}] flag: {flag}");
  53. }
  54. else if (DebugMode)
  55. {
  56. LogError($"配置中不存在ID为 [{idArray[i]}] 的分数信息.");
  57. }
  58. }
  59. ScoreGroup group = new ScoreGroup(description, valueMode, scores);
  60. groups.Add(description, group);
  61. if (DebugMode)
  62. {
  63. LogInfo($"创建分数组合 [{description}] 计分模式[{valueMode}]");
  64. }
  65. return flags;
  66. }
  67. private bool ObtainValue(string groupDescription, string flag)
  68. {
  69. if (groups.TryGetValue(groupDescription, out ScoreGroup target))
  70. {
  71. return target.Obtain(flag);
  72. }
  73. if (DebugMode)
  74. {
  75. LogError($"不存在分数组合 [{groupDescription}].");
  76. }
  77. return false;
  78. }
  79. private bool CancleValue(string groupDescription, string flag)
  80. {
  81. if (groups.TryGetValue(groupDescription, out ScoreGroup target))
  82. {
  83. return target.Cancle(flag);
  84. }
  85. if (DebugMode)
  86. {
  87. LogError($"不存在分数组合 [{groupDescription}].");
  88. }
  89. return false;
  90. }
  91. private float GetSumValue()
  92. {
  93. float retV = 0f;
  94. foreach (var kv in groups)
  95. {
  96. var scores = kv.Value.Scores;
  97. for (int i = 0; i < scores.Count; i++)
  98. {
  99. var score = scores[i];
  100. if (score.IsObtained)
  101. {
  102. retV += score.Value;
  103. }
  104. }
  105. }
  106. return retV;
  107. }
  108. #endregion
  109. #region Public Methods
  110. /// <summary>
  111. /// 创建分数组合
  112. /// </summary>
  113. /// <param name="description">分数组合描述</param>
  114. /// <param name="valueMode">分数组计分方式</param>
  115. /// <param name="idArray">分数信息ID组合</param>
  116. /// <returns>返回分数项标识符组合</returns>
  117. public static string[] Create(string description, ValueMode valueMode, params int[] idArray)
  118. {
  119. return Instance.CreateScore(description, valueMode, idArray);
  120. }
  121. /// <summary>
  122. /// 获取分数组合中指定标识分数项的分值
  123. /// </summary>
  124. /// <param name="groupDescription">分数组合</param>
  125. /// <param name="flag">分数项标识</param>
  126. /// <returns>获取成功返回true 否则返回false</returns>
  127. public static bool Obtain(string groupDescription, string flag)
  128. {
  129. return Instance.ObtainValue(groupDescription, flag);
  130. }
  131. /// <summary>
  132. /// 取消分数组合中指定标识分数项的分值
  133. /// </summary>
  134. /// <param name="groupDescription">分数组合</param>
  135. /// <param name="flag">分数项标识</param>
  136. /// <returns></returns>
  137. public static bool Cancle(string groupDescription, string flag)
  138. {
  139. return Instance.CancleValue(groupDescription, flag);
  140. }
  141. /// <summary>
  142. /// 获取总分值
  143. /// </summary>
  144. /// <returns>总分值</returns>
  145. public static float GetSum()
  146. {
  147. return Instance.GetSumValue();
  148. }
  149. #endregion
  150. #region Debugger
  151. public static bool DebugMode = true;
  152. public static void LogInfo(string info)
  153. {
  154. Debug.Log($"<color=cyan><b>[SKFramework.Score.Info]</b></color> --> {info}");
  155. }
  156. public static void LogWarn(string warn)
  157. {
  158. Debug.Log($"<color=yellow><b>[SKFramework.Score.Warn]</b></color> --> {warn}");
  159. }
  160. public static void LogError(string error)
  161. {
  162. Debug.Log($"<color=red><b>[SKFramework.Score.Error]</b></color> --> {error}");
  163. }
  164. #endregion
  165. }
  166. #if UNITY_EDITOR
  167. [CustomEditor(typeof(ScoreMaster))]
  168. public class ScoreMasterInspector : Editor
  169. {
  170. private SerializedProperty profile;
  171. private Dictionary<string, ScoreGroup> groups;
  172. private Dictionary<ScoreGroup, bool> groupFoldout;
  173. private void OnEnable()
  174. {
  175. profile = serializedObject.FindProperty("profile");
  176. }
  177. public override void OnInspectorGUI()
  178. {
  179. EditorGUILayout.PropertyField(profile);
  180. if (GUI.changed)
  181. {
  182. serializedObject.ApplyModifiedProperties();
  183. EditorUtility.SetDirty(target);
  184. }
  185. if (!Application.isPlaying) return;
  186. Color color = GUI.color;
  187. GUI.color = Color.cyan;
  188. OnRuntimeGUI();
  189. GUI.color = color;
  190. }
  191. private void OnRuntimeGUI()
  192. {
  193. if (groupFoldout == null)
  194. {
  195. groups = typeof(ScoreMaster).GetField("groups", BindingFlags.Instance | BindingFlags.NonPublic)
  196. .GetValue(ScoreMaster.Instance) as Dictionary<string, ScoreGroup>;
  197. groupFoldout = new Dictionary<ScoreGroup, bool>();
  198. }
  199. foreach (var kv in groups)
  200. {
  201. if (!groupFoldout.ContainsKey(kv.Value))
  202. {
  203. groupFoldout.Add(kv.Value, false);
  204. }
  205. ScoreGroup group = kv.Value;
  206. groupFoldout[group] = EditorGUILayout.Foldout(groupFoldout[group], group.Description);
  207. if (groupFoldout[group])
  208. {
  209. GUILayout.Label($"计分模式: {(group.ValueMode == ValueMode.Additive ? "累加" : "互斥")}");
  210. for (int i = 0; i < group.Scores.Count; i++)
  211. {
  212. Score score = group.Scores[i];
  213. GUILayout.BeginVertical("Box");
  214. GUI.color = score.IsObtained ? Color.green : Color.cyan;
  215. GUILayout.Label($"描述: {score.Description}");
  216. GUILayout.Label($"标识: {score.Flag}");
  217. GUILayout.BeginHorizontal();
  218. GUILayout.Label($"分值: {score.Value} {(score.IsObtained ? "√" : "")}");
  219. GUI.color = Color.cyan;
  220. GUILayout.FlexibleSpace();
  221. GUI.color = Color.yellow;
  222. if (GUILayout.Button("Obtain", "ButtonLeft", GUILayout.Width(50f)))
  223. {
  224. ScoreMaster.Obtain(group.Description, score.Flag);
  225. }
  226. if (GUILayout.Button("Cancle", "ButtonRight", GUILayout.Width(50f)))
  227. {
  228. ScoreMaster.Cancle(group.Description, score.Flag);
  229. }
  230. GUI.color = Color.cyan;
  231. GUILayout.EndHorizontal();
  232. GUILayout.EndVertical();
  233. }
  234. }
  235. }
  236. GUILayout.BeginHorizontal();
  237. GUILayout.FlexibleSpace();
  238. GUILayout.Label($"总分: {ScoreMaster.GetSum()}", "LargeLabel");
  239. GUILayout.Space(50f);
  240. GUILayout.EndHorizontal();
  241. }
  242. }
  243. #endif
  244. }

测试:


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


  
  1. using UnityEngine;
  2. using SK.Framework;
  3. public class Foo : MonoBehaviour
  4. {
  5. private string[] flags;
  6. private void Start()
  7. {
  8. flags = ScoreMaster.Create("测试", ValueMode.MutuallyExclusive,
  9. ScoreIDConstant.TEST_A, ScoreIDConstant.TEST_B,
  10. ScoreIDConstant.TEST_C, ScoreIDConstant.TEST_D);
  11. }
  12. private void OnGUI()
  13. {
  14. if (GUILayout.Button("A", GUILayout.Width(200f), GUILayout.Height(50f)))
  15. {
  16. ScoreMaster.Obtain("测试", flags[0]);
  17. }
  18. if (GUILayout.Button("B", GUILayout.Width(200f), GUILayout.Height(50f)))
  19. {
  20. ScoreMaster.Obtain("测试", flags[1]);
  21. }
  22. if (GUILayout.Button("C", GUILayout.Width(200f), GUILayout.Height(50f)))
  23. {
  24. ScoreMaster.Obtain("测试", flags[2]);
  25. }
  26. if (GUILayout.Button("D", GUILayout.Width(200f), GUILayout.Height(50f)))
  27. {
  28. ScoreMaster.Obtain("测试", flags[3]);
  29. }
  30. GUILayout.Label($"总分: {ScoreMaster.GetSum()}");
  31. }
  32. }

 

 

文章来源: coderz.blog.csdn.net,作者:CoderZ1010,版权归原作者所有,如需转载,请联系作者。

原文链接:coderz.blog.csdn.net/article/details/121654343

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。