草庐IT

Unity 之 Addressable可寻址系统 -- 代码加载介绍 -- 进阶(一)

陈言必行 2024-07-02 原文

Unity 之 可寻址系统 -- 代码加载介绍 -- 进阶(一)

概述:本片文章为大家介绍可寻址系统使用代码动态加载物体的多种形式。

一,可寻址系统代码加载

准备工作,创建几个预制体分别为:Cube,Capsule,Sphere,并将预制体设置为可寻址系统的资源,然后将Cube的地址修改为Cube,如下图:

1.1 回调形式

using UnityEngine;
// 引用命名空间
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class LoadManager : MonoBehaviour
{
    void Start()
    {
        // 回调形式
        LoadGameObject();
        LoadGameObjectCallBack();
        InstantiateGameObject();
    }
  
    # region 回调形式
    
    /// <summary>
    /// 加载物体 
    /// 逻辑简单且不需复用,直接使用Lambda表达式的形式
    /// </summary>
    void LoadGameObject()
    {
        // 参数:"Cube" 为可寻址系统的地址
        Addressables.LoadAssetAsync<GameObject>("Cube").Completed += (obj) =>
        {
            GameObject go = obj.Result;

            Instantiate(go, Vector3.zero, Quaternion.identity);
        };
    }
    
    /// <summary>
    /// 加载物体
    /// </summary>
    void LoadGameObjectCallBack()
    {
        Addressables.LoadAssetAsync<GameObject>("Assets/Prefab/Sphere.prefab").Completed += LoadCallBack;
    }

    /// <summary>
    /// 加载物体的回调函数
    /// </summary>
    void LoadCallBack(AsyncOperationHandle<GameObject> handle)
    {
        GameObject go = handle.Result;
        Instantiate(go, Vector3.right * 2, Quaternion.identity);
    }
    
    /// <summary>
    /// 加载并实例化物体
    /// </summary>
    void InstantiateGameObject()
    {
        Addressables.InstantiateAsync("Assets/Prefab/Capsule.prefab").Completed += (obj) =>
        {
            // 已经实例化后的物体
            GameObject go = obj.Result;
            go.transform.position = Vector3.left * 2;
        };
    }
    
    #endregion   
}

运行结果如下:
成功加载三个预制


1.2 异步等待

若你不习惯回调的形式,可寻址系统也给了我们加载异步等待的形式直接去加载物体,示例代码如下:

using UnityEngine;
// 引用命名空间
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class LoadManager : MonoBehaviour
{
    void Start()
    {
        // 异步形式
        AsyncLoadCube();
        AsyncInstantiateCube();
    }
    
    #region 异步形式
    
    private async void AsyncLoadCube()
    {
        // 虽然这里使用了Task,但并没有使用多线程
        GameObject prefabObj = await Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Cube.prefab").Task;
        // 实例化
        GameObject cubeObj = Instantiate(prefabObj);
        cubeObj.transform.position = Vector3.zero;
    }
    
    private async void AsyncInstantiateCube()
    {
         // 直接使用InstantiateAsync方法
         GameObject cubeObj = await Addressables.InstantiateAsync("Assets/Prefabs/Cube.prefab").Task;
         cubeObj.transform.position = Vector3.right * 2;
    }
    #endregion
}

运行结果如下:


1.3 面板赋值

在代码中声明AssetReference类型的变量,将上面准备好的Cube拖拽上来进行赋值。若此时拖拽上来的没有未加入可寻址系统的资源,他会自动变成一个可选寻址资源。

准备场景如下:

示例代码如下:

using UnityEngine;
// 引用命名空间
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class LoadManager : MonoBehaviour
{
    // 可寻址资源的弱引用
    public AssetReference cubeRef;
    
    void Start()
    {
        // 面板引用
        RefLoadCube();
    }  

    #region 面板形式

    void RefLoadCube()
    {
        cubeRef.LoadAssetAsync<GameObject>().Completed += (obj) =>
        {
            // 加载完成结果
            GameObject cubePrefab = obj.Result;
            // 实例化
            GameObject cubeObj = Instantiate(cubePrefab);
            // 修改位置
            cubeObj.transform.position = Vector3.zero;
        };
    }
    #endregion
}

运行结果如下:

PS:
如果我们声明的不是AssetReference类型,而是我们常用的GameObject类型,那么场景就直接依赖了Cube预制体,打包时Cube预制体就会被打到场景中。现在这里用的是AssetReference 类型,它是一个弱引用,场景并不会真的依赖Cube预制体。


1.4 同步加载

在1.17.4版本之后的可寻址系统,通过在 AsyncOperationHandleWaitForCompletion 方法来实现同步加载。 WaitForCompletion 的作用是会让系统阻拦代码执行,直到资源加载完成。

同步加载GameObject的基本用法,代码如下:

using UnityEngine;
using UnityEngine.AddressableAssets; // 引用命名空间
using UnityEngine.ResourceManagement.AsyncOperations;

public class LoadManager : MonoBehaviour
{
    // Asset的弱引用
    public AssetReference cubeRef;
    
    void Start()
    {
        Addressables.InitializeAsync();
        // 同步加载
        InstantiatePrefab();
    }

    void InstantiatePrefab()
    {
        // 实例化加载到的游戏物体
        Instantiate(LoadPrefab(), transform);
    }
    
    // 强制同步加载GameObject的基本用法
    GameObject LoadPrefab()
    {
        var op = Addressables.LoadAssetAsync<GameObject>("Cube");
        GameObject go = op.WaitForCompletion();
        return go;
    }
}

多数情况下同步加载的性能和异步加载基本一致,但偶尔也会出现更快或更慢的情况。

WaitForCompletion 运算必须在引擎的任务队列中依次完成,如果小型运算的前面有一些大型运算,则只有前面的完成,系统才能完成队列后方的运算。这种情况下会出现加载慢的现象。


二,可寻址系统分标签加载

2.1 场景搭建

创建一个RawImage和一个Button摆放位置如下图:

找两张图片将其放入到工程中,并将其设置为可寻址资源,然后把地址都修改为Logo,最后分别为其创建标签Black,White 设置如下:

PS:这里创建的地址Logo和标签Black,White是下面代码中要用到的,若有变化,则代码中的值需要对应修改。


2.2 代码示例

创建如下代码并将其挂载到上面创建的RawImage物体上,并初始化如下:

下面代码,分别使用了AssetLabelReference面板赋值的形式和寻址+标签两种形式进行了分标签加载代码的示例。根据实际情况决定使用哪一种形式即可。

using System.Collections.Generic;
using UnityEngine;
// 引用命名空间
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;

public class LoadByLabelManager : MonoBehaviour
{
    // 可寻址系统标签的引用
    public AssetLabelReference prefabsLabel;

    public Button loadWhiteLogoBtn;

    private RawImage _rawImage;

    void Start()
    {
        _rawImage = GetComponent<RawImage>();
        
        loadWhiteLogoBtn.onClick.AddListener(() =>
        {
            LoadTextureByKeyLabel("Logo", "Black");
        });
        
        LoadGameObjectByLabel();
    }

    /// <summary>
    /// 根据标签加载
    /// </summary>
    void LoadGameObjectByLabel()
    {
        Addressables.LoadAssetsAsync<Texture2D>(prefabsLabel, (texture) =>
        {
            // 每加载完成一个,就回调一次。 标签下有几个就会执行几次
            Debug.Log("加载完成一个资源: " + texture.name);
            _rawImage.texture = texture;
            _rawImage.SetNativeSize();
        });
    }

    /// <summary>
    /// 根据地址和标签加载
    /// </summary>
    /// <param name="key">可寻址资源地址</param>
    /// <param name="label">资源标签</param>
    void LoadTextureByKeyLabel(string key, string label)
    {
        Addressables.LoadAssetsAsync<Texture2D>(new List<string> {key, label},
            null, Addressables.MergeMode.Intersection).Completed += TextureLoaded;        
    }

    void TextureLoaded(AsyncOperationHandle<IList<Texture2D>> texture)
    {
        _rawImage.texture = texture.Result[0];
        _rawImage.SetNativeSize();
    }
}

根据寻址和标签加载的代码中Addressables.MergeMode:用于合并请求结果 --> 可以理解为,可寻址系统依次从key和label加载两组结果,合并结果有四个枚举值:若查询结果分别为:A,B,C 和 B,C,D

  1. None和UseFirst 都会取第一组结果 --> A,B,C
  2. Union 取两组结果的并集 --> A,B,C,D
  3. Intersection 取两组结果的交集 --> B,C

2.3 效果展示

运行效果:


三,代码加载可寻址的解释

代码加载逻辑基本上这几个,写法也都在上面。逻辑和使用AB包加载逻辑基本一致,只是写法略有不同,所以稍微有经验的同学,应该可以很快上手。只需要熟悉熟悉写法即可。

上面代码加载用的都是key加载的,我们也可以使用它的绝对路径加载,比如上面的Cube,可以这样写:

Addressables.LoadAssetAsync<GameObject>("Cube")

Addressables.LoadAssetAsync<GameObject>("Assets/Prefab/Cube.prefab")

使用key的方式加载的好处在于不管我们之后如何修改路径或者是修改预制体名称,都不会影响到上面代码的加载逻辑,这就是可寻址。只要key不修改就不用修改代码,若AddressablesGroups中存在相同的key,则代码会返回列表中靠上的资源。

若资源不在本地而在服务器上,可寻址系统会帮助我们先下载等待下载完成在执行后面的加载逻辑,所以代码上也不需要有任何的修改。


除了我们常用的资源类型(预制,贴图,音频,配置文件)这些可以使用可寻址代码加载,场景也可以使用可寻址代码加载代码Addressables.LoadSceneAsync("Assets/Scenes/Game.unity");

在后面介绍资源更新的文章中会介绍一个检测更新的脚本,用以游戏开始前下载我们的需要的资源,这样就不会再等到使用时现下载,也就解决了资源过大或者网络稳定的情况下,现下载卡顿的问题。


TODO:上一篇 --> Unity 之 Addressable可寻址系统 – 可寻址系统使用介绍 – 入门(三)

TODO:下一篇 --> Unity 之 Addressable可寻址系统 – 资源加载和释放 – 进阶(二)

有关Unity 之 Addressable可寻址系统 -- 代码加载介绍 -- 进阶(一)的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  5. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  6. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  7. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  8. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  9. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  10. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

随机推荐