unityUnity3D 成神之路unity3d

Unity 之 Scriptable Objects 更直观的序

2018-07-31  本文已影响215人  su9257_海澜

在日常开发中总会在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

还有一篇是国内开发者的文章【貌似是冯乐乐,shader入门精要的作者】

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的用法如出一辙。《炉石传说》对应的配置文件就用过这玩应

最后总结一下优缺点

优点:

缺点:

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

上一篇 下一篇

猜你喜欢

热点阅读