Unity 制作一個分數統計系統
項目中經常遇到分數統計的需求,例如我們執行瞭某項操作或做瞭某個題目,操作正確則計分,相反則不計分失去該項分數,為瞭應對需求需要一個分數統計系統。
首先定義一個分數信息的數據結構,使用Serializable特性使其可序列化:
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資產:
using UnityEngine; namespace SK.Framework { /// <summary> /// 分數配置文件 /// </summary> [CreateAssetMenu] public class ScoreProfile : ScriptableObject { public ScoreInfo[] scores = new ScoreInfo[0]; } }
使用ScoreIDConstant類編寫所有分數項ID常量,創建ScoreID特性並使用PropertyDrawer使其可在面板選擇:
namespace SK.Framework { public sealed class ScoreIDConstant { public const int INVALID = -1; } }
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字段:
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用於標記該分數項的分值是否已經獲得。
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類:
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; } } }
namespace SK.Framework { /// <summary> /// 計分方式 /// </summary> public enum ValueMode { /// <summary> /// 累加的 /// </summary> Additive, /// <summary> /// 互斥的 /// </summary> MutuallyExclusive, } }
最終編寫分數管理類,封裝Create、Obtain、Cancle、GetSum函數,分別用於創建分數組合、獲取分數、取消分數、獲取總分,實現Editor類使分數信息在Inspector面板可視化:
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 }
測試:
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; } }
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 制作一個分數統計系統的詳細內容,更多關於Unity的資料請關註WalkonNet其它相關文章!