【unity进阶知识6】Resources的使用,如何封装一个Resources资源管理器

embedded/2024/10/5 21:12:01/

文章目录

  • 一、Unity资源加载的几种方式
    • 1、Inspector窗口拖拽
    • 2、Resources
    • 3、AssetBundle
    • 4、Addressables(可寻址资源系统)
    • 5、AssetDatabase
  • 二、准备
  • 三、同步加载Resources资源
    • 1、Resources.Load同步加载单个资源
      • 1.1、基本加载
      • 1.2、加载指定类型的资源
      • 1.3、使用泛型加载指定类型的资源
    • 2、Resources.LoadAll同步加载多个资源
      • 2.1、基本加载
      • 2.2、加载指定类型的资源
      • 2.3、使用泛型加载指定类型的资源
  • 四、异步加载Resources文件夹中的资源
  • 五、封装一个Resources资源管理器
    • 1、封装异步加载资源
    • 2、封装同步加载资源
    • 3、封装卸载资源方法
  • 六、最终代码
  • 完结

一、Unity资源加载的几种方式

1、Inspector窗口拖拽

  • 在脚本中用public声明变量,然后在Inspector窗口把要加载的资源拖拽给该脚本的变量。
  • 不建议在大型项目使用。在公司的项目也不要用。如果你是独立游戏开发者,则可以用。
  • 不支持热更新。

2、Resources

  • 用Resources.Load方法、Resources.LoadAsync方法、Resources.LoadAll方法来加载资源。
  • 可以在商业项目使用,包括公司的项目。但是Resources文件夹中可以存放的资源有限,大概只能存储2G左右的资源,因此要谨慎使用。
  • 不支持热更新。

3、AssetBundle

  • 用AssetBundle.LoadFromXXX方法来加载资源。
  • 商业项目常用的资源加载方案,如果你在公司做项目,则推荐用这种方式来加载资源。
  • 效率比Resources加载高,占用内存小,正式发布游戏后,资源所占用的空间也小。
  • 支持热更新。

4、Addressables(可寻址资源系统)

  • 可以理解为高级的AssetBundle,资源管理由Unity内部自动完成。
  • 但是目前还在发展中,可能有bug。主流的商业游戏都是使用AssetBundle来做资源加载的。
  • 支持热更新。

5、AssetDatabase

  • 用AssetDatabase.LoadAssetAtPath方法来加载资源。
  • 仅限于编辑器模式,主要用于在编辑器模式下用代码更改本地文件。
  • 游戏运行时不会用这种方案加载资源。
  • 不支持热更新。

二、准备

使用前必须在项目中创建一个名叫Resources的文件夹,这个名字是固定的。
在这里插入图片描述

三、同步加载Resources资源

1、Resources.Load同步加载单个资源

1.1、基本加载

Resources.Load(string 要加载的资源的路径)

返回值是Object型。

如果有多个相同路径的资源,则只会返回找到的第一个资源。

调用案例

GameObject go = Resources.Load("Prefabs/Cube") as GameObject;

1.2、加载指定类型的资源

Resources.Load(string 要加载的资源的路径, System.Type 要加载的资源的类型的Type对象)

返回值是Object型。

如果有多个相同类型且相同路径的资源,则只会返回找到的第一个资源。

调用案例

GameObject go = Resources.Load("Prefabs/Cube", typeof(GameObject)) as GameObject;

1.3、使用泛型加载指定类型的资源

Resources.Load<要加载的资源的类型>(string 要加载的资源的路径)

返回值是T型。

返回值是要加载的资源的类型。如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。

调用案例

GameObject go = Resources.Load<GameObject>("Prefabs/Cube");

2、Resources.LoadAll同步加载多个资源

2.1、基本加载

Resources.LoadAll(string 要加载的资源的文件夹路径或文件路径)

返回值是Object[]型。

同步加载Resources文件夹中指定路径的文件夹中的所有资源,包括其中子孙文件夹中的所有资源,然后返回到一个Object[]型数组。

如果该路径是一个文件,则只会加载该文件,并返回到一个Object[]型数组。如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。

调用案例

Object[] gos = Resources.LoadAll("Prefabs");
for (int i = 0; i < gos.Length; i++)
{Instantiate(gos[i] as GameObject);
}

2.2、加载指定类型的资源

Resources.LoadAll(string 要加载的资源的文件夹路径或文件路径,System.Type 要加载的资源的类型的Type对象)

返回值是Object[]型。

同步加载Resources文件夹中指定路径的文件夹中的指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个Object[]型数组。

如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个Object[]型数组。如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。

调用案例

Object[] gos = Resources.LoadAll("Prefabs", typeof(GameObject));
for (int i = 0; i < gos.Length; i++)
{Instantiate(gos[i] as GameObject);
}

2.3、使用泛型加载指定类型的资源

Resources.LoadAll<泛型T类型>(string 要加载的资源的文件夹路径或文件路径)

返回值是T[]型。

同步加载Resources文件夹中指定路径的文件夹中的指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个T[]型数组。

如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个T[]型数组。如果没有加载到任何资源,则返回一个没有任何元素的T[]型数组。

调用案例

GameObject[] gos = Resources.LoadAll<GameObject>("Prefabs");
for (int i = 0; i < gos.Length; i++)
{Instantiate(gos[i]);
}

四、异步加载Resources文件夹中的资源

Resources.LoadAsync异步加载单个资源方法

使用泛型加载指定类型的资源

Resources.LoadAsync<泛型T类型>(string 要加载的资源的路径)

返回值是ResourceRequest类型。

一般配合协程来使用。在协程中可以使用yield return来等待资源加载。

如果有多个相同路径的资源,则只会加载找到的第一个资源。

调用案例

void Start()
{StartCoroutine(LoadAsyncCoroutine());
}IEnumerator LoadAsyncCoroutine(){//开始异步加载资源ResourceRequest rr = Resources.LoadAsync<GameObject>("Prefabs/Cube");//等待资源加载完毕yield return rr;//加载完成执行逻辑Instantiate(rr.asset);
}

五、封装一个Resources资源管理器

1、封装异步加载资源

注意这个脚本依赖Mono管理器:【unity进阶知识2】Mono管理器

public class ResourcesManager : Singleton<ResourcesManager>
{/// <summary>/// 异步加载Resources文件夹中指定类型的资源/// </summary>public void LoadAsync<T>(string path, UnityAction<T> callback = null) where T : Object{MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, callback));}IEnumerator LoadAsyncCoroutine<T>(string path, UnityAction<T> callback = null) where T : Object{ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);yield return resourceRequest;callback?.Invoke(resourceRequest.asset as T);}
}

调用,效果和前面一样

ResourcesManager.Instance.LoadAsync<GameObject>("Prefabs/Cube", Callback);void Callback(GameObject obj){Instantiate(obj);
}

或者

ResourcesManager.Instance.LoadAsync<GameObject>("Prefabs/Cube", (obj)=>{Instantiate(obj);
});

2、封装同步加载资源

同步加载封装虽然是一样的,但是好处是自己封装可以统一管理和进行自定义注释

/// <summary>
/// <para>同步加载Resources文件夹中指定类型的资源。</para>
/// <para>如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。</para>
/// </summary>
/// <typeparam name="T">要加载的资源的类型</typeparam>
/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
public T Load<T>(string path) where T : Object
{return Resources.Load<T>(path);
}

调用

GameObject[] gos = ResourcesManager.Instance.LoadAll<GameObject>("Prefabs");
for (int i = 0; i < gos.Length; i++)
{Instantiate(gos[i]);
}

3、封装卸载资源方法

通过Resources加载的资源,就算销毁了,但是它还是占在内存里面,没有释放,所以我们需要卸载资源,比如切换场景的时候

/// <summary>
/// <para>异步卸载所有用Resources方式加载到内存中且当前没有被任何地方使用的资源。</para>
/// <para>例如要卸载某一个用Resources方式加载的预制体,则必须确保场景中所有这个预制体创建的物体都被销毁了,且这个预制体资源没有赋值给任何脚本中的任何变量。</para>
/// <para>如果有,可以把该变量也赋值为null,这样本方法才能成功释放它。</para>
/// </summary>
/// <param name="callback">资源卸载完毕后执行的逻辑</param>
public void UnloadUnusedAssets(UnityAction callback = null)
{MonoManager.Instance.StartCoroutine(UnLoadUnusedAssetsCoroutine(callback));
}
IEnumerator UnLoadUnusedAssetsCoroutine(UnityAction callback = null)
{//异步操作对象,记录了异步操作的数据。AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();//等待资源卸载while (asyncOperation.progress < 1)yield return null;//资源卸载完毕后执行的逻辑callback?.Invoke();
}

调用

ResourcesManager.Instance.UnloadUnusedAssets(() =>
{Debug.Log("异步卸载所有资源成功");
});

六、最终代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XYFrame;/// <summary>
/// Resources资源加载管理器
/// 这个脚本依赖Mono管理器。
/// 要加载的资源必须放到项目中名叫Resources的文件夹中。项目中可以有多个名叫Resources的文件夹,但如此一来,必须避免资源的路径相同。
/// </summary>
public class ResourcesManager : Singleton<ResourcesManager>
{#region 同步加载单个资源/// <summary>/// <para>同步加载Resources文件夹中的资源。</para>/// <para>如果有多个相同路径的资源,则只会返回找到的第一个资源。</para>/// </summary>/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>public Object Load(string path){return Resources.Load(path);}/// <summary>/// <para>同步加载Resources文件夹中指定类型的资源。</para>/// <para>如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。</para>/// </summary>/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>/// <param name="systemTypeInstance">要加载的资源的类型的Type对象。例如typeof(GameObject)表示要加载的资源的类型是GameObject型。</param>/// <returns></returns>public Object Load(string path, System.Type systemTypeInstance){return Resources.Load(path, systemTypeInstance);}/// <summary>/// <para>同步加载Resources文件夹中指定类型的资源。</para>/// <para>如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。</para>/// </summary>/// <typeparam name="T">要加载的资源的类型</typeparam>/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>public T Load<T>(string path) where T : Object{return Resources.Load<T>(path);}#endregion#region 同步加载多个资源/// <summary>/// <para>同步加载Resources文件夹中指定路径的文件夹中的所有资源,包括其中子孙文件夹中的所有资源,然后返回到一个Object[]型数组。</para>/// <para>如果该路径是一个文件,则只会加载该文件,并返回到一个Object[]型数组。</para>/// <para>如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。</para>/// </summary>/// <param name="path">要加载的文件夹或文件的路径。例如"Prefabs"表示Resources文件夹中的Prefabs文件夹。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>public Object[] LoadAll(string path){return Resources.LoadAll(path);}/// <summary>/// <para>同步加载Resources文件夹中指定路径的文件夹中指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个Object[]型数组。</para>/// <para>如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个Object[]型数组。</para>/// <para>如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。</para>/// </summary>/// <param name="path">要加载的文件夹或文件的路径。例如"Prefabs"表示Resources文件夹中的Prefabs文件夹。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>/// <param name="systemTypeInstance">要加载的资源的类型的Type对象。例如typeof(GameObject)表示要加载的资源的类型是GameObject型。</param>public Object[] LoadAll(string path, System.Type systemTypeInstance){return Resources.LoadAll(path, systemTypeInstance);}/// <summary>/// <para>同步加载Resources文件夹中指定路径的文件夹中指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个Object[]型数组。</para>/// <para>如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个Object[]型数组。</para>/// <para>如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。</para>/// </summary>/// <typeparam name="T">要加载的资源的类型</typeparam>/// <param name="path">要加载的文件夹或文件的路径。例如"Prefabs"表示Resources文件夹中的Prefabs文件夹。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>public T[] LoadAll<T>(string path) where T : Object{return Resources.LoadAll<T>(path);}/// <summary>/// <para>同步把Resources文件夹中指定路径的文件夹及其所有子孙文件夹中所有指定类型的资源添加到一个新建的字典中,并返回该字典。</para>/// <para>应保证Prefabs文件夹中以及它的子孙文件夹中没有重名的资源,如果有重名的,则只会添加找到的第一个资源进字典,其它重名的资源不会进到字典中。</para>/// </summary>/// <typeparam name="T">要加载的资源类型</typeparam>/// <param name="path">资源的路径。例如"Folder/Res"表示Resources文件夹中的Folder文件夹中的Res文件夹</param>public Dictionary<string, T> LoadAllIntoDictionary<T>(string path) where T : Object{Dictionary<string, T> dic = new Dictionary<string, T>();T[] temp = Resources.LoadAll<T>(path);for (int i = 0; i < temp.Length; i++){if (!dic.ContainsKey(temp[i].name))//字典不存在该键,才添加进去。这样可以防止字典的键名重复而报错。{dic.Add(temp[i].name, temp[i]);}else//如果字典已经存在该键,则跳过这个资源,并输出警告,不将它加入到字典中{Debug.LogWarning(string.Format("Resources/{0}的子孙文件夹的资源{1}与已经添加到字典中的资源重名,因此无法将它添加到字典中,请确保加载的资源的名字是唯一的。", path, temp[i].name));}}return dic;}/// <summary>/// <para>同步把Resources文件夹中指定路径及其所有子孙文件夹中所有指定类型的资源添加到指定的字典中</para>/// </summary>/// <typeparam name="T">要加载的资源类型</typeparam>/// <param name="path">资源的路径。例如"Folder/Res"表示Resources文件夹中的Folder文件夹中的Res文件夹</param>/// <param name="dictionary">指定的字典</param>public void LoadAllIntoDictionary<T>(string path, Dictionary<string, T> dictionary) where T : Object{T[] temp = Resources.LoadAll<T>(path);for (int i = 0; i < temp.Length; i++){if (!dictionary.ContainsKey(temp[i].name))//字典不存在该键,才添加进去。这样可以防止字典的键名重复而报错。{dictionary.Add(temp[i].name, temp[i]);}else//如果字典已经存在该键,则跳过这个资源,并输出警告,不将它加入到字典中{Debug.LogWarning(string.Format("Resources/{0}的子孙文件夹的资源{1}与已经添加到字典中的资源重名,因此无法将它添加到字典中,请确保加载的资源的名字是唯一的,并且传入参数的字典中不包含该名字的资源。", path, temp[i].name));}}}#endregion#region 异步加载单个资源/// <summary>/// <para>异步加载Resources文件夹中的资源。</para>/// <para>如果有多个相同路径的资源,则只会加载找到的第一个资源。</para>/// </summary>/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>/// <param name="callback">资源加载完毕后要执行的逻辑。参数表示加载的资源。</param>public void LoadAsync(string path, UnityAction<Object> callback = null){MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, callback));}IEnumerator LoadAsyncCoroutine(string path, UnityAction<Object> callback = null){ResourceRequest resourceRequest = Resources.LoadAsync(path);yield return resourceRequest;callback?.Invoke(resourceRequest.asset);}/// <summary>/// <para>异步加载Resources文件夹中指定类型的资源。</para>/// <para>如果有多个相同类型,且相同路径的资源,则只会加载找到的第一个资源。</para>/// </summary>/// <param name="path"></param>/// <param name="type">要加载的资源的类型的Type对象。例如typeof(GameObject)表示要加载的资源的类型是GameObject型。</param>/// <param name="callback">资源加载完毕后要执行的逻辑。参数表示加载的资源。</param>public void LoadAsync(string path, System.Type type, UnityAction<Object> callback = null){MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, type, callback));}IEnumerator LoadAsyncCoroutine(string path, System.Type type, UnityAction<Object> callback = null){ResourceRequest resourceRequest = Resources.LoadAsync(path, type);yield return resourceRequest;callback?.Invoke(resourceRequest.asset);}/// <summary>/// <para>异步加载Resources文件夹中指定类型的资源。</para>/// <para>如果有多个相同类型,且相同路径的资源,则只会加载找到的第一个资源。</para>/// </summary>/// <typeparam name="T">加载的资源的类型</typeparam>/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>/// <param name="callback">资源加载完毕后要执行的逻辑</param>public void LoadAsync<T>(string path, UnityAction<T> callback = null) where T : Object{MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, callback));}IEnumerator LoadAsyncCoroutine<T>(string path, UnityAction<T> callback = null) where T : Object{ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);yield return resourceRequest;callback?.Invoke(resourceRequest.asset as T);}#endregion#region 卸载资源/// <summary>/// <para>异步卸载所有用Resources方式加载到内存中且当前没有被任何地方使用的资源。</para>/// <para>例如要卸载某一个用Resources方式加载的预制体,则必须确保场景中所有这个预制体创建的物体都被销毁了,且这个预制体资源没有赋值给任何脚本中的任何变量。</para>/// <para>如果有,可以把该变量也赋值为null,这样本方法才能成功释放它。</para>/// </summary>/// <param name="callback">资源卸载完毕后执行的逻辑</param>public void UnloadUnusedAssets(UnityAction callback = null){MonoManager.Instance.StartCoroutine(UnLoadUnusedAssetsCoroutine(callback));}IEnumerator UnLoadUnusedAssetsCoroutine(UnityAction callback = null){//异步操作对象,记录了异步操作的数据。AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();//等待资源卸载while (asyncOperation.progress < 1)yield return CoroutineTool.WaitForFrame();//资源卸载完毕后执行的逻辑callback?.Invoke();}/// <summary>/// <para>同步卸载指定的资源。</para>/// <para>只能卸载非GameObject类型和Component类型,例如Mesh、Texture、Material、Shader。如果卸载了不让卸载的资源,则会报错。</para>/// <para>如果随后加载的任何场景或资源引用了该资源,将导致从磁盘中加载该资源的新实例。此新实例将与先前卸载的对象相互独立。</para>/// </summary>/// <param name="assetToUnload">要卸载的资源</param>public void UnloadAsset(Object assetToUnload){Resources.UnloadAsset(assetToUnload);}#endregion
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.ppmy.cn/embedded/123545.html

相关文章

(9)MATLAB瑞利衰落信道仿真2

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、瑞利衰落信道二、瑞利衰落信道建模三、仿真结果二、高斯随机变量和瑞利随机变量后续 前言 本文首先给出瑞利衰落信道模型&#xff0c;并根据瑞利衰落变量估…

C++教程(三):c++常用的配置文件类型

目录 1. INI 文件 2. JSON 文件 3. YAML 文件 4. XML 文件 5. TOML 文件 6. 二进制配置文件&#xff08;Protocol Buffers, MessagePack, Avro 等&#xff09; 总结 在 C 项目中&#xff0c;常用的配置文件类型有多种选择&#xff0c;具体选择取决于项目的复杂性、可读性…

linux启用 IPv4 转发

1. 启用 IPv4 转发 要解决这个问题&#xff0c;可以通过以下步骤启用 IPv4 转发&#xff1a; 临时启用 IPv4 转发&#xff08;重启后失效&#xff09;&#xff1a; echo 1 > /proc/sys/net/ipv4/ip_forward永久启用 IPv4 转发&#xff1a; 打开 /etc/sysctl.conf 文件&…

常用排序算法(下)

目录 2.5 冒泡排序 2.6 快速排序 2.6 1 快速排序思路 详细步骤 2.6 2 快速排序递归实现 2.6 3快速排序非递归&#xff1a; 快排非递归的优势 非递归思路 1. 初始化栈 2. 将整个数组的起始和结束索引入栈 3. 循环处理栈中的子数组边界 4. 单趟排序 5. 处理分区后的子…

2、Spring Boot 3.x 集成 Feign

一、前言 本篇主要是围绕着两个点&#xff0c;1、集成 Feign&#xff0c;2、分离feign接口层&#xff0c;独立服务&#xff1b; 还有一点就是上篇文章的服务 iot-channel、system-server 服务名称调整成为了 chain-iot-channel、chain-system二、搭建 chain-common 服务 pom.…

5G NR 协议规范表(对应3GPP 协议编号)

文章目录 5G NR 协议规范表&#xff08;对应3GPP 协议编号&#xff09;5G 架构相关协议5G 新空口相关协议无线接入网相关协议终端相关协议 5G NR 协议规范表&#xff08;对应3GPP 协议编号&#xff09; 5G 架构相关协议 5G 新空口相关协议 无线接入网相关协议 终端相关协议

深入理解Web浏览器与服务器的连接过程

目录 1. 域名解析&#xff1a;找到地址 2. TCP连接&#xff1a;建立通信 3. HTTP请求&#xff1a;点菜 4. 服务器处理请求&#xff1a;厨房做菜 5. HTTP响应&#xff1a;上菜 6. 客户端接收响应&#xff1a;品尝美食 7. 关闭TCP连接&#xff1a;吃完离开 8. 持久连接&a…

腾讯一面-LRU缓存

为了设计一个满足LRU&#xff08;最近最少使用&#xff09;缓存约束的数据结构&#xff0c;我们可以使用哈希表&#xff08;HashMap&#xff09;来存储键值对&#xff0c;以便在O(1)时间复杂度内访问任意键。同时&#xff0c;我们还需要一个双向链表&#xff08;Doubly Linked …