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()}");
-
}
-
}
文章来源: coderz.blog.csdn.net,作者:CoderZ1010,版权归原作者所有,如需转载,请联系作者。
原文链接:coderz.blog.csdn.net/article/details/121654343
- 点赞
- 收藏
- 关注作者
评论(0)