EC(ECS)思想技能编辑器

前言:根据不同的需求,在一个技能上添加不同的动画,声音,特效(这里编辑器拓展就不说了,还是主要说EC思想,代码中其他方法不用看)
1.首先还是EC思想,先从E(entity)开始,创建一个人物实体e(以下简称为"e"),e拥有不同的技能(技能1,技能2),每个技能的动画(特效,音效,以下以动画为例)不同,所以对应来添加这些动画,特效,音效.
public class Player : MonoBehaviour
{
    //玩家身上,的技能集合
    //技能名称为key,技能组件集合为value
    public Dictionary<string, List<SkillBase>> skillsList = new Dictionary<string, List<SkillBase>>(); 

    
    //动画部分
    RuntimeAnimatorController controller; //动画控制器

    public AnimatorOverrideController overrideController; //动画覆盖控制器

    //特效部分
    public Transform effectsparent; //特效图节点

    //音效
    AudioSource audioSource;   //音效

    //定义一个技能id
    string m_strSkillId;

    static string m_strPath;

    //Player

    Animator anim;
    public void InitData()
    {
        //动画控制器
        overrideController = new AnimatorOverrideController();
        controller = Resources.Load<RuntimeAnimatorController>("Player"); 
        overrideController.runtimeAnimatorController = controller;
        anim.runtimeAnimatorController = overrideController;

        audioSource = gameObject.AddComponent<AudioSource>(); //音效是添加上的

        effectsparent = transform.Find("effectsparent");  // 特效的父节点
       // gameObject.name = path;
        //加载技能配置表
        LoadAllSkill();
    }

    public void play()
    {
        //根据条件判断播放不同的组件  遍历   skillsList  item.player
        //_Anim.Play();
        //声音的
        //特效的
        //根据技能组件播放一遍。
        Debug.Log("skillsList" + " " + skillsList.Count);


        if(m_strSkillId != null && skillsList.ContainsKey(m_strSkillId))
        {
            foreach(var item in skillsList[m_strSkillId])
            {
                item.Play();
            }
        }

    }

    private Skill_Anim _Anim;
    private Skill_Audio _Aduio;
    private Skill_Effects _Effect;

    //在人物身上配置技能数据
    public void SetData(string skillName)
    {

        m_strSkillId = skillName;
        List<SkillXml> skillList = GameData.Instance.GetSkillsByRoleName("Teddy");
        //Debug.Log(skillList[0]);
        foreach (var item in skillList) //
        {
            if (item.name == skillName) //找到指定的技能配置,把技能配置转换成类对象。
            {
                foreach (var ite in item.skills)
                {
                    foreach (var it in ite.Value)
                    {
                        if (ite.Key.Equals("动画"))
                        {
                            //加载一个动画片段
                            AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>("Assets/GameDate/Anim/" + it + ".anim");
                            if (_Anim == null) _Anim = new Skill_Anim(this); //创建一个技能的类对象
                            _Anim.SetAnimClip(clip);
                            //skillsList[item.name].Add(_Anim);
                        }
                        else if (ite.Key.Equals("音效"))
                        {
                            //加载一个音效的资源
                            AudioClip clip = AssetDatabase.LoadAssetAtPath<AudioClip>("Assets/GameDate/Audio/" + it + ".mp3");
                            if (_Aduio == null) _Aduio = new Skill_Audio(this); //创建一个音效的对象。
                            _Aduio.SetAnimClip(clip);
                            //skillsList[item.name].Add(_Anim);
                        }
                        else if (ite.Key.Equals("特效")) //特效
                        {
                            GameObject clip = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/GameDate/Effect/Skill/" + it + ".prefab");
                            if(_Effect == null) _Effect = new Skill_Effects(this);
                            _Effect.SetGameClip(clip); //
                            //skillsList[item.name].Add(_Anim);
                        }
                    }
                }
            }
        }

    }


    public static Player Init(string path)
    {
      
        if (path != null)
        {
            m_strPath = path;
            Debug.Log("Init" + path);
            Debug.Log("strPath" + m_strPath);
            string str = "Assets/aaa/" + path + ".prefab";
            GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(str);
            if (obj != null)
            {
                //动画
                Player player = Instantiate(obj).GetComponent<Player>();
                player.overrideController = new AnimatorOverrideController();
                player.controller = Resources.Load<RuntimeAnimatorController>("Player");
                player.overrideController.runtimeAnimatorController = player.controller;
                player.anim.runtimeAnimatorController = player.overrideController;
                //添加音效组件
                player.audioSource = player.gameObject.AddComponent<AudioSource>();
                //特效的父节点
                player.effectsparent = player.transform.Find("effectsparent");
                player.gameObject.name = path;
                //加载技能配置表
                player.LoadAllSkill();
                return player;
            }
        }
        return null;
    }

    //加载玩家身上的技能配置表
    void LoadAllSkill()
    {
        Debug.Log("LoadAllSkill" + m_strPath);

        //根据角色的名字,来加载技能文件,
        if (File.Exists("Assets/" + "Teddy" + ".txt"))
        {
            string str = File.ReadAllText("Assets/" + "Teddy" + ".txt");
            List<SkillXml> skills = JsonConvert.DeserializeObject<List<SkillXml>>(str);
            foreach (var item in skills) //解析技能配置表,技能列表
            {
                //技能列表的组件
                skillsList.Add(item.name, new List<SkillBase>());
                foreach (var ite in item.skills) //解析技能组件
                {
                    foreach (var it in ite.Value)
                    {
                        if (ite.Key.Equals("动画")) //根据类型,创建对应的功能组件
                        {

                            AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>("Assets/GameDate/Anim/" + it + ".anim");
                            Skill_Anim _Anim = new Skill_Anim(this); //动画技能的组件
                            _Anim.SetAnimClip(clip);
                            skillsList[item.name].Add(_Anim);
                        }
                        else if (ite.Key.Equals("音效"))
                        {
                            AudioClip clip = AssetDatabase.LoadAssetAtPath<AudioClip>("Assets/GameDate/Audio/" + it + ".mp3");
                            Skill_Audio _Anim = new Skill_Audio(this);
                            _Anim.SetAnimClip(clip);
                            skillsList[item.name].Add(_Anim);
                        }
                        else if (ite.Key.Equals("特效"))
                        {
                            GameObject clip = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/GameDate/Effect/Skill/" + it + ".prefab");
                            Skill_Effects _Anim = new Skill_Effects(this);
                            _Anim.SetGameClip(clip);
                            skillsList[item.name].Add(_Anim);
                        }
                        else if(ite.Key.Equals("移动")) //移动的组件
                        {
                            
                            Skill_Move Amin = new Skill_Move(this); //玩家信息传入移动技能里面\
                            Amin.moveToPos(it); // 传递坐标数组

                            //加入技能字典
                            skillsList[item.name].Add(Amin);
                        }
                    }
                }
            }
        }
    }
    private void Awake()
    {
        anim = gameObject.GetComponent<Animator>();
    }
    public List<SkillBase> GetSkill(string skillName)
    {
        if (skillsList.ContainsKey(skillName))
        {
            return skillsList[skillName];
        }
        return null;
    }

    public List<SkillBase> AddNewSkill(string newSkillName)
    {
        if (skillsList.ContainsKey(newSkillName))
        {
            return skillsList[newSkillName];
        }
        skillsList.Add(newSkillName, new List<SkillBase>());
        return skillsList[newSkillName];
    }
    public void RevSkill(string newSkillName)
    {
        if (skillsList.ContainsKey(newSkillName))
        {
            skillsList.Remove(newSkillName);
        }
    }

    public void Destroy()
    {
        Destroy(gameObject);
    }

    //玩家销毁的时候的时候,保存技能按钮
    private void OnDestroy()
    {
       // return;
        List<SkillXml> skills = new List<SkillXml>();
        foreach (var item in skillsList)
        {
            SkillXml skillXml = new SkillXml();
            skillXml.name = item.Key;
            foreach (var ite in item.Value)
            {
                if (ite is Skill_Anim)
                {
                    if (!skillXml.skills.ContainsKey("动画"))
                    {
                        skillXml.skills.Add("动画", new List<string>());
                    }
                    skillXml.skills["动画"].Add(ite.name);
                }
                else if (ite is Skill_Audio)
                {
                    if (!skillXml.skills.ContainsKey("音效"))
                    {
                        skillXml.skills.Add("音效", new List<string>());
                    }
                    skillXml.skills["音效"].Add(ite.name);
                }
                else if (ite is Skill_Effects)
                {
                    if (!skillXml.skills.ContainsKey("特效"))
                    {
                        skillXml.skills.Add("特效", new List<string>());
                    }
                    skillXml.skills["特效"].Add(ite.name);
                }
                //保存移动技能
                else if(ite is Skill_Move)
                {
                    if(!skillXml.skills.ContainsKey("移动"))
                    {
                        skillXml.skills.Add("移动", new List<string>());
                    }
                   // 保存移动数据
                    skillXml.skills["移动"].Add(ite.name);
                }

            }
            skills.Add(skillXml);
        }
        string str = JsonConvert.SerializeObject(skills);
        File.WriteAllText("Assets/" + gameObject.name + ".txt", str); //json数据保存,
    }
}

//文件数据转内存数据
public class SkillXml
{
    public string name; //技能的名字
    //组成这个技能的,组件的集合
    public Dictionary<string, List<string>> skills = new Dictionary<string, List<string>>();
}
2.在e身上的肯定需要一个库(字典)来存放这些动画,特效,音效,就是所谓的"C"这些动画,特效,音效又需要统一管理起来,,所以他们需要继承一个"SkillBase"基类
//技能基类
public class SkillBase
{
    public string name = string.Empty;
    public virtual void Play() //播放
    {

    }
    public virtual void Init() //初始化
    {

    }
    public virtual void Stop() //停止
    {

    }

}

(这个库和这个基类在EC思想中很重要)

写完这个基类之后,以动画(这里用的是动画重写控制器)为例

public class Skill_Anim : SkillBase
{
    Player player;

    Animator anim;

    public AnimationClip animClip;
    AnimatorOverrideController controller;

    public Skill_Anim(Player _player)
    {
        player = _player;
        anim = player.gameObject.GetComponent<Animator>();
        controller = player.overrideController;
    }
    public override void Init()
    {
        controller["Start"] = animClip;
    }

    public void SetAnimClip(AnimationClip _animClip)
    {
        animClip = _animClip;
        name = animClip.name;
        controller["Start"] = animClip;
    }

    public override void Play()
    {
        base.Play();
        anim.StopPlayback();
        AnimatorStateInfo stateInfo = anim.GetCurrentAnimatorStateInfo(0);
        if (stateInfo.IsName("Idle1"))
        {
            anim.SetTrigger("Play");
        }
        

    }
    public override void Stop()
    {
        base.Play();
        anim.StartPlayback();
        

    }




}
3. 这里放一下编辑的代码(但是需要与我的unity的资源一样,比如有对应的文件夹)

        这个是选择人物窗口

public class SkillEditorWindow : EditorWindow
{
    class PlayerEditor
    {
        public int _characterIndex = 0;  //角色的id号
        public int _folderIndex = 0;     //文件的id
        public string characterName = string.Empty; 
        public string folderName = string.Empty;
        public string characterFilter = string.Empty;
        public List<string> characteList = new List<string>(); //文件下模型名字的集合
        public Player player = null; //玩家信息的业务逻辑
    }

    PlayerEditor m_player = new PlayerEditor();

    /// <summary>
    /// 文件名
    /// </summary>
    List<string> m_folderList = new List<string>();
    /// <summary>
    /// 所有预制体名
    /// </summary>
    List<string> m__characterList = new List<string>();

    /// <summary>
    /// 按文件名储存 预制体
    /// </summary>
    Dictionary<string, List<string>> m_folderPrefabs = new Dictionary<string, List<string>>();


    /// <summary>
    /// 技能详情窗口
    /// </summary>
    SkillWindow skillWindow;

    /// <summary>
    ///  储存 创建新的技能 的名字
    /// </summary>
    string newSkillName = string.Empty;


    /// <summary>
    /// 窗口初始化  (打开窗口)
    /// </summary>
    [MenuItem("Tools/技能编译器")]
    public static void Init()

    {
        
        //if (Application.isPlaying) //应用程序是否在运行状态,在运行状态,打开技能编辑器
        {
            SkillEditorWindow window = EditorWindow.GetWindow<SkillEditorWindow>("Editor");
            if (window != null)
            {
                window.Show();
            }
        }
        
    }

    // 先走这个方法,里面放的都是解析数据的方法

    private void OnEnable()
    {
        //查找文件夹
        DoSearchFolder(); //敌人,玩家
        //查找文件夹下面的角色名,
        DoSearchCharacter(); //角色,
    }

    /// <summary>
    /// 模型文件夹名称 , 比如敌人,玩家
    /// </summary>
    void DoSearchFolder()
    {
        //文件夹列表
        m_folderList.Clear();
        m_folderList.Add("all");//添加一个all,代表下面要显示敌人和玩家所有模型的名称
        //获取当前路径下的文件夹
        string[] folders = Directory.GetDirectories(GetCharacterPath());//存放model下面的文件夹的名称
        foreach (var item in folders)
        {
            m_folderList.Add(Path.GetFileName(item));
        }
    }

    /// <summary>
    /// 索引所有的 预制体
    /// </summary>
    void DoSearchCharacter()
    {
        //查找当前文件下,所有后缀名".prefab"为的名字,
        string[] files = Directory.GetFiles(GetCharacterPath(), "*.prefab", SearchOption.AllDirectories);
        m__characterList.Clear();
        foreach (var item in files)
        {
            m__characterList.Add(Path.GetFileNameWithoutExtension(item));//把所有模型(敌人,玩家)不带后缀名(.prefab)放入集合中
        }
        m__characterList.Sort();
        m__characterList.Insert(0, "null");//插入第一个null值,就是没选角色
        m_player.characteList.AddRange(m__characterList);//赋值给角色数据里的模型集合 

    }
    string GetCharacterPath()
    {
        return Application.dataPath + "/GameDate/Model";
    }
    private void OnGUI()
    {
        //枚举,选择类型:敌人类,还是玩家类
        int folderIndex = EditorGUILayout.Popup(m_player._folderIndex, m_folderList.ToArray()); //folderIndex返回已选择的,m_player._folderIndex初始的值(0),刚开始是"null"

        if (folderIndex != m_player._folderIndex) //判断选择的类型和之前的是否一致,一致不变
        {
            m_player._folderIndex = folderIndex;//让默认值等你选的值 
            m_player._characterIndex = -1;
            string folderName = m_folderList[m_player._folderIndex];

            //通过你选的角色类型,来展示不同的人物模型
            List<string> list;

            if (folderName.Equals("all"))//如果选的all就直接把所有的模型放到集合里
            {
                list = m__characterList;
            }
            else
            {
                if (!m_folderPrefabs.TryGetValue(folderName, out list))//如果选择敌人或者玩家,那就再多读一层 ,根据点的,来读玩家文件夹或者敌人文件夹,然后把里面的模型放入集合
                {
                    list = new List<string>();
                    string[] files = Directory.GetFiles(GetCharacterPath() + "/" + folderName, "*.prefab", SearchOption.AllDirectories);
                    foreach (var item in files)
                    {
                        list.Add(Path.GetFileNameWithoutExtension(item));
                    }
                    m_folderPrefabs[folderName] = list;
                }
                //SetPlayerCharacters(m_player, list);
            }
            m_player.characteList.Clear(); //角色的集合,当前选择的类型文件目录下的所有角色,加入到角色集合
            m_player.characteList.AddRange(list);//所选类型的模型放入到此文件夹
        }
        int characterIndex = EditorGUILayout.Popup(m_player._characterIndex, m_player.characteList.ToArray()); //选择模型的下拉列表,
        if (characterIndex != m_player._characterIndex)
        {
            m_player._characterIndex = characterIndex;
            if (m_player.characterName != m_player.characteList[m_player._characterIndex])
            {
                m_player.characterName = m_player.characteList[m_player._characterIndex];
                if (!string.IsNullOrEmpty(m_player.characterName))
                {
                    if (m_player.player != null) //m_player有一个player属性,如果创建过,就把之前的模型删掉
                    {
                        m_player.player.Destroy();  //调用玩家的销毁函数
                    }
                    m_player.player = Player.Init(m_player.characterName);//然后再创建新的角色
                }
            }

        }
        /*float speed = EditorGUILayout.Slider((m_player.player == null ? 0f : m_player.player.AnimSpeed), 0.1f, 5);
        if (m_player.player != null && m_player.player.AnimSpeed != speed)
        {
            m_player.player.AnimSpeed = speed;
        }*/
        newSkillName = GUILayout.TextField(newSkillName);  //输入新技能,
        if (GUILayout.Button("创建新的技能"))
        {
            if (!string.IsNullOrEmpty(newSkillName) && m_player.player != null)//如果输入了新技能,并且任务也创建了
            {
                List<SkillBase> skills = m_player.player.AddNewSkill(newSkillName);//掉用方法添加新技能

                OpenSkillWindow(newSkillName, skills);//打开技能窗口

                newSkillName = "";//然后将上面的输入框置空
            }
        }

        if (m_player.player != null)//如果选择玩家,
        {

            ScrollViewPos = GUILayout.BeginScrollView(ScrollViewPos, false, true);
            foreach (var item in m_player.player.skillsList) //遍历玩家身上的技能列表,
            {
                GUILayout.BeginHorizontal();
                if (GUILayout.Button(item.Key)) //根据的技能名字,创建一个按钮,技能名称为key,技能组件集合为value
                {
                    List<SkillBase> skills = m_player.player.GetSkill(item.Key);
                    foreach (var ite in skills)//循环技能组件
                    {
                        ite.Init();//给他们依次赋上各自的属性
                    }
                    OpenSkillWindow(item.Key, skills);//打开技能窗口(预览)
                }

                GUILayoutOption[] option = new GUILayoutOption[] {
                GUILayout.Width(60),
                GUILayout.Height(19)
                };

                if (GUILayout.Button("删除技能", option))
                {
                    m_player.player.RevSkill(item.Key);
                    break;
                }
                GUILayout.EndHorizontal();
            }

            GUILayout.EndScrollView();
        }


    }
    Vector2 ScrollViewPos = new Vector2(0, 0);

    //打开新的技能面板
    void OpenSkillWindow(string newSkillName, List<SkillBase> skills)
    {
        if (skills != null)
        {
            if (skillWindow == null)
            {
                skillWindow = EditorWindow.GetWindow<SkillWindow>(""); //打开技能面板的窗口
            }
            skillWindow.titleContent = new GUIContent(newSkillName); //设置第二个技能面板的标题

            skillWindow.SetInitSkill(skills, m_player.player); //给技能面板设置数据, 技能数据和玩家模型传值

            //if (skillWindow == null)
            //{
            //    skillWindow = EditorWindow.GetWindow<Skill2Window>(""); //打开技能面板的窗口
            //}
            //skillWindow.titleContent = new GUIContent(newSkillName); //设置第二个技能面板的标题

            //skillWindow.setSkillData(skills, m_player.player); //给技能面板设置数据, 技能数据和玩家模型传值
            skillWindow.Show(); //显示技能面板
            skillWindow.Repaint(); //技能面板重新绘制。
        }

    }
}

        人物窗口选择完

        点击"123"技能

        下面是技能窗口代码(注意看点击播放按钮)

public class SkillWindow : EditorWindow
{
    Player player;//玩家
    List<SkillBase> skills;//技能组件集合

    float currSpeed = 1;
    public void SetInitSkill(List<SkillBase> _skills, Player _player)
    {
        player = _player;
        // player.AnimSpeed = 1;
        currSpeed = 1;
        skills = _skills;
    }
    //技能类型列表
    string[] skillComponent = new string[] { "null", "动画", "声音", "特效", "移动" };
    int skillComponentIndex = 0;
    private void OnGUI()
    {
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("播放"))//这里的简单了,当你点击播放的时候,循环此技能的组件列表,都播放就行了
        {
            foreach (var item in skills)
            {
                item.Play();
            }
        }
        if (GUILayout.Button("停止"))
        {
            foreach (var item in skills)
            {
                item.Stop();
            }
        }
        GUILayout.EndHorizontal();
        GUILayout.Label("速度");//调整技能的组件的速度属性
        float speed = EditorGUILayout.Slider(currSpeed, 0, 5);
        if (speed != currSpeed)
        {
            currSpeed = speed;
            Time.timeScale = currSpeed;
        }

        GUILayout.BeginHorizontal();
        skillComponentIndex = EditorGUILayout.Popup(skillComponentIndex, skillComponent);
        if (GUILayout.Button("添加"))//添加一个新组件
        {
            switch (skillComponentIndex)
            {
                case 1:
                    skills.Add(new Skill_Anim(player));
                    break;
                case 2:
                    skills.Add(new Skill_Audio(player));
                    break;
                case 3:
                    skills.Add(new Skill_Effects(player));
                    break;
                case 4:
                    skills.Add(new Skill_Move(player));
                    break;
            }
        }
        GUILayout.EndHorizontal();

        ScrollViewPos = GUILayout.BeginScrollView(ScrollViewPos, false, true);
        foreach (var item in skills)
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label(item.name);
            if (GUILayout.Button("删除"))//循环技能组件,先展示删除
            {
                skills.Remove(item);
                break;
            }
            GUILayout.EndHorizontal();
            if (item is Skill_Anim)//如果动画,那么展示动画,
            {
                ShowSkill_Anim(item as Skill_Anim);
            }
            else if (item is Skill_Audio)
            {
                Skill_Audio(item as Skill_Audio);
            }
            else if (item is Skill_Effects)
            {
                Skill_Effects(item as Skill_Effects);
            }
            else if(item is Skill_Move) //显示移动坐标
            {
                Skill_Move(item as Skill_Move);
            }
            GUILayout.Space(0.5f);
        }
        GUILayout.EndScrollView();

    }
    Vector2 ScrollViewPos = new Vector2(0, 0);
    void ShowSkill_Anim(Skill_Anim _Anim)
    {
        AnimationClip animClip = EditorGUILayout.ObjectField(_Anim.animClip, typeof(AnimationClip), false) as AnimationClip;
        if (_Anim.animClip != animClip)
        {
            _Anim.SetAnimClip(animClip);//把这个animClip创过 通过方法给他赋值上去
        }
    }
 
    void Skill_Audio(Skill_Audio _Audio)
    {
        AudioClip audioClip = EditorGUILayout.ObjectField(_Audio.audioClip, typeof(AudioClip), false) as AudioClip;
        if (_Audio.audioClip != audioClip)
        {
            _Audio.SetAnimClip(audioClip);
        }
    }

    void Skill_Effects(Skill_Effects _Effects)
    {
        GameObject gameClip = EditorGUILayout.ObjectField(_Effects.gameClip, typeof(GameObject), false) as GameObject;
        if (_Effects.gameClip != gameClip)
        {
            _Effects.SetGameClip(gameClip);
        }
    }

    //显示移动的面板
    void Skill_Move(Skill_Move _move)
    {
        if(_move != null)
        {

            GUILayoutOption[] option = new GUILayoutOption[] {
            GUILayout.Width(60),
            GUILayout.Height(19)
            };

            _move.m_vPos =  EditorGUILayout.Vector3Field("移动目标", _move.m_vPos);


            编辑一个X坐标
            //EditorGUILayout.LabelField("X:", option);
            //_move.m_vPos.x = EditorGUILayout.FloatField(_move.m_vPos.x, option);

            编辑一个Y坐标
            //EditorGUILayout.LabelField("Y:", option);
            //_move.m_vPos.y = EditorGUILayout.FloatField(_move.m_vPos.y, option);

            编辑一个X坐标
            //EditorGUILayout.LabelField("Z:", option);
            //_move.m_vPos.z = EditorGUILayout.FloatField(_move.m_vPos.z, option);


        }
    }
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值