Unity 制作一个分数统计系统
程序员文章站
2022-03-04 10:56:44
项目中经常遇到分数统计的需求,例如我们执行了某项操作或做了某个题目,操作正确则计分,相反则不计分失去该项分数,为了应对需求需要一个分数统计系统。首先定义一个分数信息的数据结构,使用serializab...
项目中经常遇到分数统计的需求,例如我们执行了某项操作或做了某个题目,操作正确则计分,相反则不计分失去该项分数,为了应对需求需要一个分数统计系统。
首先定义一个分数信息的数据结构,使用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的资料请关注其它相关文章!