Unity 制作一个分数统计系统
作者:CoderZ1010 发布时间:2021-11-30 03:01:52
标签: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()}");
}
}
来源:https://blog.csdn.net/qq_42139931/article/details/121654343


猜你喜欢
- 之前使用Retrofit都是将JSON串转化为POJO对象,针对不同的业务协议,定义相应的接口和参数列表。但是此种方式一般用在自己内部协议基
- 前言本文主要给大家介绍了关于Spring Boot应用事件监听的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧1.
- Spring是一个非常流行的Java Web开发框架,它提供了强大的依赖注入、面向切面编程、声明式事务管理等功能,为开发者提供了高效、快速地
- 1.引言在开发过程中,我们经常会遇到需要显示或隐藏View视图的情况,如果在隐藏或显示View的过程中加上动画,能让交互更加的友好和动感,本
- 1、背景当我们的hadoop集群运行了一段时间之后,各个DataNode上的数据分布并不一定是均匀分布的。比如说: 我们向现有集群中添加了一
- 本文实例讲述了c#图像截取的实现方法。分享给大家供大家参考。具体如下:图像截取的相关代码如下: public Form1()&nb
- 1.引言在开发中,拖放是一种比较常见的手势操作,使用它能够让应用的交互更加地便捷和友好,本文将简要介绍如何为Android中的View添加拖
- 一、问题重现1.配置文件spring: #DataSource数据源 datasource: &nbs
- 需要添加引用,System.Configuration;写系统配置文件: Configuration cfa =
- 什么是响应式简单来说当数据发生变化时,对数据有依赖的代码会重新执行。例如在Vue中,当我们的数据发生改变,界面上对该数据的引用组件会重新渲染
- 第一步:引入jar包 <dependency> <gro
- 本文是利用ZXing.Net在WinForm中生成条形码,二维码的小例子,仅供学习分享使用,如有不足之处,还请指正。什么是ZXing.Net
- 现在再回过头理解,结合自己的体会, 选用最佳的方式描述这些算法,以方便理解它们的工作原理和程序设计技巧。本文适合做java面试准备的材料阅读
- 因为我本人很喜欢在不同的页面之间跳转时加点好玩的动画,今天无意间看到一个动画效果感觉不错,几种效果图如下:既然好玩就写在博客中,直接说就是:
- 前言java8借鉴了第三方日期库joda很多的优点java.time包类名描述Instant时间戳Duration持续时间,时间差Local
- 一、继承引言继承关系可以对不同模块的依赖版本做统一管理,因为子模块中的依赖基本都继承于父模块,父模块中指定哪个版本,子模块就继承哪个版本,可
- Maven打包时指定启动类使用Maven打包的时候, 有时候需要指定启动类, 可如下操作!测试项目(结构如下):代码: com.xxx.Ma
- 新建多国语言包要在android studio项目中新建多国语言包,有两种方式,一种是手动建,一种是用使用android studio辅助建
- 概念里氏替换原则是任何基类出现的地方,子类一定可以替换它;是建立在基于抽象、多态、继承的基础复用的基石,该原则能够保证系统具有良好的拓展性,
- [LeetCode] 169. Majority Element 求大多数Given an array nums of