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