Unity 之 Scriptable Objects 更直观的序列化

在日常开发中总会在Inspector面板中填写一些序列化信息,如:名称、血量、或者秘钥序列号等(图一)。但看似方便,还是有些缺点的,例如填写的序列化信息不能共享,如果在prefab上对应的脚本丢失,填写的信息也随之丢失。如果需要填写的信息特别多,这绝对是个灾难。

vuforia的SDK 7版本相对于6版本最直观的改进就是原来的配置文件改成了Scriptable Objects。

图一

而且在【UWA DAY 2018 张鑫大佬 提到过】,使用Scriptable Objects可显著的减少内存的占用,其中的示例是这样的:每个prefab上有1000个public int 变量,实例化100个Prefab ,在红米2中的数据,常规情况下为: 耗时38.44ms 堆内存391.8KB,然后我们使用 Scriptable Objects后对应的数据为,耗时:10.76ms,堆内存1.6KB,耗时占用为原来的 28%,堆内存占用为原来的 0.4%(效果还是很可观的)。


有说的错误或者不准确的地方欢迎留言指正


下面笔者就和大家聊一聊 Scriptable Objects

首先放出官方连接

https://unity3d.com/cn/learn/tutorials/modules/beginner/live-training-archive/scriptable-objects?playlist=17117
https://unity3d.com/cn/learn/tutorials/topics/scripting/ability-system-scriptable-objects?playlist=17117
https://unity3d.com/cn/learn/tutorials/topics/scripting/character-select-system-scriptable-objects?playlist=17117

还有一篇是国内开发者的文章

https://blog.csdn.net/candycat1992/article/details/52181814

如想更深入了解的Scriptable Objects 可以去Unity在YouTube 的官方网站(自备梯子),里面有几个视频讲解的很详细,不过语速过快,笔者本人已经放弃


要是使用 Scriptable Objects ,首先需要准备一个模板,然后根据指定的模板创建Scriptable Objects。

#region using
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
#endregion

public class PlayerDataScriptableObject : ScriptableObject
{
    [Header("玩家 ID")]
    public int playerId;
    [Header("玩家 分数")]
    public float playerScore;
    [Header("玩家 名称")]
    public string playerName;
    [Header("玩家 地址")]
    public string playerAddress;
    [Header("玩家 随机出生点")]
    public Vector3[] playerSpawnPoint;
    [Header("玩家 描述列表")]
    public List<Description> playerDescription = new List<Description>();

}

[Serializable]
public class Description
{
    public string descriptionOne;
    public string descriptionTwo;
}

注意:PlayerDataScriptableObject 继承ScriptableObject,非传统的MonoBehaviour。Description类需要添加[Serializable]特性,这个PlayerDataScriptableObject 是无法直接拖拽到Prefab等Object物体上的

模板已经创建好,下面我们介绍两种创建 ScriptableObject的方式

第一种

在Editor文件夹中创建ScriptableObjectUtility脚本

using UnityEngine;
using UnityEditor;
using System.IO;

public static class ScriptableObjectUtility
{
    public static void CreateAsset<T>() where T : ScriptableObject
    {
        T asset = ScriptableObject.CreateInstance<T>();

        string path = AssetDatabase.GetAssetPath(Selection.activeObject);
        if (path == "")
        {
            path = "Assets";
        }
        else if (Path.GetExtension(path) != "")
        {
            path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
        }

        string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/New " + typeof(T).ToString() + ".asset");

        AssetDatabase.CreateAsset(asset, assetPathAndName);

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = asset;
    }
}

public class CreateClassAsset
{
    [MenuItem("Assets/Create/Su9257/ScriptableObject")]
    public static void CreateAsset()
    {
        ScriptableObjectUtility.CreateAsset<PlayerDataScriptableObject>();
    }

}
第二种 相对比较快捷

直接在对应的模板类上添加CreateAssetMenu特性

注意:创建的模板类名称要与对应的脚本名称保持一致


上面我们已经创建完对应的ScriptableObject文件,接下来是我们如何使用

创建测试脚本PlayGame 放到任意场景物体上
public class PlayGame : MonoBehaviour
{

    public PlayerDataScriptableObject playerDataScriptableObject;

    void Start()
    {

    }


    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            ++playerDataScriptableObject.playerScore;
        }
    }
}

然后我们运行,会发现这个.asset序列化文件的好处,在运行模式下产生的数据不会因为退出运行而消失,同一个模板类产生的两个.asset文件不会产生数据干扰,相互独立

什么?不想拖拖拽拽?有办法!

把对应的.asset放到Resources文件夹下调用Start方法中的code就可以了(Assetbundle的加载目前还没测试)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayGame : MonoBehaviour
{

    public PlayerDataScriptableObject playerDataScriptableObject;

    void Start()
    {
        playerDataScriptableObject = (Resources.Load<PlayerDataScriptableObject>("New PlayerDataScriptableObject"));

        //克隆的,运行结束时数据也随之消失,换句话就是和原来的New PlayerDataScriptableObject的序列化文件没什么关系了
        //playerDataScriptableObject = Instantiate(Resources.Load<PlayerDataScriptableObject>("New PlayerDataScriptableObject"));
    }


    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            ++playerDataScriptableObject.playerScore;
        }
    }
}

这时有一个疑问?这玩应难道仅仅就服务于属性、字段?答案当然不是,ScriptableObject官方文档中都有对应额回调函数,起码也能支持咱们自己写的函数吧~

然后我们把原来的模板稍加改动,改成抽象类然后添加一个抽象方法Attack;,当然,我们的创建方法也需要稍加改动一下。然后调用Attack

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "TestPlayerDataOne", menuName = "Su9257/PlayerDataOne", order = 100)]
public class PlayerDataOne : PlayerDataScriptableObject {
    public override void Attack()
    {
        Debug.Log($"玩家{playerName}进行了攻击");
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayGame : MonoBehaviour
{

    public PlayerDataScriptableObject playerDataScriptableObject;
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            ++playerDataScriptableObject.playerScore;
        }

        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            playerDataScriptableObject.Attack();
        }
    }
}

在实际游戏开发中,我们会设置游戏的难度,简单、困难之类的,里面需要配置的东西非常的多,根据这个特点,我们可以根据不同的难度级别配置不同的 .asset序列化文件,用法和这个Attack的用法如出一辙。《炉石传说》对应的配置文件就用过这玩应

最后总结一下优缺点

优点:

  • 可以保存为assets文件
  • 可以在运行时保存
  • 可以引用而不是像MonoBehaviour那样复制
  • 性能非常快
  • 适合插槽式设计,即插即用

缺点:

  • 需要额外的编辑脚本
  • 无法在Unity外编辑

最后,欢迎大家留言补充,非常感谢!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容