欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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];
    }
}

Unity 制作一个分数统计系统

使用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;
    }
}

Unity 制作一个分数统计系统

数据可配置后,创建分数项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;
    }
}

Unity 制作一个分数统计系统

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 制作一个分数统计系统 

以上就是unity 制作一个分数统计系统的详细内容,更多关于unity的资料请关注其它相关文章!