头图

您好!在我的博客当中,我将持续挑选一些优质的国外技术文章进行翻译,如果文章内容翻译有误,欢迎在评论区指正,感谢:)
以下是原文链接:Database System in Unity using Resources and ScriptableObjects – The Knights of Unity
<!-- more -->

1.1 引言

基本情况介绍
在进行 Unity 开发时,经常会有一个问题:如何在 Unity 当中存储数据?

一个很有可能的回答是:创建一个数据库,然后尽可能使这个数据库易于被开发者或者设计师进行使用。
有很多种技术可以解决这个问题,这篇文章的目的不是将每种技术进行讲解,这篇文章关注的问题是:如何在 Unity 当中更友好的进行数据存储。

对于设计师或开发者而言,本文的技术可以帮助自动完成的数据的读写,以及自动处理资源的实例化、更改等处理流程。使用本文所用的技术,可以在不借助任何其他的插件的前提下轻松完成数据的存储。并且本文另一个需要关注的点是:本文讲解的进行数据存储的解决方案,全部都存储在集合中,可以轻松的使用用户定义键(user-defined key)来进行访问。

解决方案使用范围
这套解决方案可以使用在很多不同的情况。我个人已经使用它作为游戏的数据库系统,并为一个 RPG 游戏开发了其物体(items)以及技能 (skills)系统。

因此对于 Unity 开发而言,这是一个非常棒、可以简单并且稳定的存储数据,特别是在多人开发当中。这些数据对象可以被索引,并且可以被轻松的从 ScriptableObjects 导出成 json 数据。

警告:当把数据序列化成 json 格式时,需要注意现在你放入到 ScriptableObjects 的类当中的字段是否可以被序列化。Unity/CSharp 中并不是所有的类型都支持序列化

1.2 实现

项目结构
以下是项目中涉及的脚本:

  • Scripts

    • Data

      • ItemData.cs
    • Managers

      • DataManager.cs
      • ItemDataManager.cs
    • Tests

      • InventoryTestContext.cs

    这里有四个主要的脚本我们接下来会进行使用,我们将会在后续的内容当中详细讲解这些脚本的内容。

以下是当前项目所使用工程的 Unitypack 链接:
UnityDatabasePackage_0.1.unitypackage - Google 云端硬盘

1.2.1 ScriptableObjects

ScriptableObjects 系统可以帮助你在 Unity 中存储数据到资源文件夹当中。这些资源可以通过代码来进行读取以及修改,尽管并不建议你通过代码在游戏运行时修改 ScriptableObjects 数据。ScriptableObjects 系统的目的是帮助你存储数据,而不是保存临时存在的变量;
ScriptableObjects 的最大好处是它可以被分配至 Unity 编辑器(Unity Editor)并且暴露这些变量。

警告:在配合 Unity 编辑器进行使用时,使用代码接口在游戏运行时改变 ScriptableObjects 中的字段可能会在编辑器中导致奇怪的行为。它的数据将会在我们停止了游戏之后依然被保留,但我们并不希望数据被运行时编辑器中更改的数据搅乱,我们希望这些数据可以回归到启动游戏前的状态。

1.2.2 创建对象数据:Scriptable Object

Item.cs 脚本
以下是对应的完整代码:

using System.Globalization;
using UnityEngine;

namespace DatabaseSystem.ScriptableObjects 
{
    [CreateAssetMenu(menuName = "Demo/Items/Item Data")]
    public class ItemData : ScriptableObject
    {
        #region 变量
        [SerializeField] private int id = default;
        [SerializeField] private string displayName = default;
        [SerializeField] private int levelRequired = default;
        [SerializeField] private Sprite icon = default;

        public int Id 
        {
            get { return id; }
            set { id = value; }
        }

        public string DisplayName 
        {
            set { displayName = value; }
            get { return displayName; }
        }

        public int LevelRequired
        { 
            get { return levelRequired; } 
            set {  levelRequired = value; } 
        }
        public Sprite Icon 
        {
            get { return icon; }
            set { icon = value; } 
        }
        #endregion
    }
}

Scriptable Object 可以通过 class 来被添加添加,它需要继承 ScriptableObject 类并添加 [CreateAssetMenu(menuName = "Demo/Items/Item Data")] 这个属性来实现。
基于这个 menuName 参数,我们可以在编辑器的选项中创建 ScriptableObject 物体。在我们添加了这个脚本后,当 Unity 完成重新编译脚本后,这个添加数据对象的选项将会出现在 Project 窗口右键后的选项中,如下图所示:
image.png
现在我们可以随便往里面填入一些数据,然后这个物体对象就以及可以被使用了!
image.png
接下来就可以从资源中读取数据,并初始化数据库资源文件夹以充当数据库容器。
image.png

1.2.3 编写 ItemDataManager 代码

补充:这里是译者对原文的补充,原文的教程当中跳过了 DataManager.cs 的部分,在这附上未讲解的 DataManager.cs 代码
在编写 ItemDataManager 代码之前。需要先编写一个用于被 ItemDataManager 继承的代码。以下代码中编写的 DataManager 类本质就是对 CSharp 中的 Dictionary 字典类进行了一层封装,结合 T、D 两种使用到的类型对字典提供的读取、增加、修改接口分别进行了一层抽象,使其在面对更多的自定义数据类型时,更具有可扩展性。

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

namespace DatabaseSystem.Managers
{
    // T 表示key类型,D 表示ItemDat的类型
    public abstract class DataManager<T, D> : MonoBehaviour
    {
        #region Variables
        protected Dictionary<T, D> dataDictionary = default;
        #endregion

        #region Public Methods
        public Dictionary<T, D> GetAllDataObjects()
        {
            return dataDictionary.ToDictionary(k => k.Key, v => v.Value);
        }
        public D GetDataObject(T id)
        {
            if(dataDictionary.ContainsKey(id))
            {
                return dataDictionary[id];
            }

            return default;
        }
        #endregion

        #region Protected Methods
        protected virtual bool TryPutDataItem(T id, D data)
        {
            if (dataDictionary.ContainsKey(id))
            {
                return false;
            }

            dataDictionary.Add(id, data);
            return true;
        }
        protected virtual bool TryRemoveItem(T id, D data)
        {
            if (!dataDictionary.ContainsKey(id))
            {
                return false;
            }

            dataDictionary.Remove(id);
            return true;
        }
        #endregion
    }
}

ItemDataManager 代码编写
现在,如果我们要创建一个数据容器,我们需要在创建的脚本中包含一个存储所有单个数据对象的集合。所有的 ScriptableObject 都在此物体文件夹下,我们可以在实例化时加载它。

using DatabaseSystem.ScriptableObjects;
using System.Collections.Generic;
using UnityEngine;

namespace DatabaseSystem.Managers
{
    public class ItemsDataManager : DataManager<int, ItemData>
    {
        #region Variables
        [SerializeField] private string resourcesItemsFolder = default;
        #endregion

        #region Private Methods
        private void LoadFromResources()
        {
            dataDictionary = new Dictionary<int, ItemData>();
            ItemData[] itemsFromResources = Resources.LoadAll<ItemData>(resourcesItemsFolder);
            foreach (var itemData in itemsFromResources)
            {
                TryPutDataItem(itemData.Id, itemData);
            }
        }
        #endregion

        #region Public Methods
        public void Initialize()
        {
            LoadFromResources();
        }
        #endregion
    }
}

这个管理类继承自 DataManager 类,并传入两个泛型参数分别用于索引以及表示数据类型。它包含用于存储以及删除 items 的方法,并在此过程中伴随着逻辑检查(logical checks)。

为了从 Resources 文件夹读取物体数据,我们需要使用这个 Resources.LoadAll<T>(string) 方法。这个方法将会返回一个物体的数据,并且在稍微我们会为其分配一个可被索引的字典容器。

1.2.4 在数据清单中使用数据库

现在我们可以在测试的数据清单中使用此数据库。我们只需要创建两个空物体并为其分配 ItemDataManager.cs 以及 InventoryTestContext.cs 两个组件。如下图所示是 ItemDataManager.cs ;
image.png
这个 InventoryTestContext 组件需要引用这个 ItemDataManager 组件去进行实例化以及包含数据。在实例化结束后,它便可以去使用数据对象。
image.png

InventoryTestContext.cs 脚本
image.png

在我们启动游戏后,这些脚本将从引用的 itemsDataManager 中打印所有的物品信息,如下图所示:
image.png

1.3 总结

通过这篇文章你可以看到,只需要一点点努力便可以在 Unity 中创建一个可扩展的、结构化的数据库。
通过 Resources 存储的数据对象可以被动态的进行加载,并且可以伴随着脚本被编译、执行,其中的变量可被自动进行实例化。这种解决方案可以被使用于任意类型的数据,让你的开发变得更简单!


博尔赫斯的面孔
1 声望0 粉丝

向着黄昏与自由迁徙