Unity Addressable Asset System 文档

简介

Unity可寻址资源系统

可寻址资源系统提供了一种简单的方法通过“地址”加载资源。简化资源包的创建和部署的管理开销。
可寻址资源系统允许你从任何地方通过异步加载的方式加载资源包。无论你是通过"直接引用",或者传统的Assetbundle形式,亦或是通过"Resource"文件夹管理,可寻址资源系统提供了一种简单的方法来使你的游戏更加灵活多变。

什么是一个资源(Asset)

Asset资源是你用来创建游戏或应用程序的内容。 资产的常见例子包括预制件Prefabs、纹理Textures、材质Materials、音频剪辑AudioClips和动画Animations。

什么是一个可寻址的资源

一个被做成可寻址的资源,可以从任何地方通过"唯一地址"取访问它,无论它是打包驻留在本地的资源,还是发布到远程服务器上的资源,可寻址资源系统都可以定位并返回它,你可以通过地址加载单个可寻址资源,或者通过自定义的组标签加载多个可寻址资源

为什么要使用可寻址资源

传统的游戏资源的结构,使得有效加载是一个非常具有挑战性的事情.但是可寻址资源系统可以大幅缩短迭代周期,让你节省大量的时间取用于设计,编码,和测试你的应用

  • 迭代时间: 按地址引用内容是非常有效的,优化内容将不再需要更改代码。
  • 依赖项管理: 当你请求一个内容时,系统会自动返回所请求内容的所有依赖项,在将内容返回给您之前先加载所有网格、着色器、动画等等。
  • 内存管理: 寻址系统能卸载资源也能加载资源,而且会自动计算引用,并提供一个强大分析器来帮助您发现潜在的内存问题。
  • 内容打包: 系统会自动映射和理清的复杂的依赖关系,并进行有效的打包,即使移动或重命名资源后也是如此. 您可以很容易地为本地和远程部署准备资源,以支持追加或减少应用程序的下载内容的大小。

概览

可寻址资产系统由两个包组成(在Package Managed中打开):

  • Addressable Assets package (primary package)
  • Scriptable Build Pipeline package (dependency)

当您安装Addressable Assets package,Scriptable Build Pipeline package将同时安装。

常见概念
  • Address : 运行时检索资源位置的标识符
  • AddressableAssetData directory : 存储您的可寻址资源元数据在项目的资产目录
  • Asset group : 资源组,可用于构建时处理一组可寻址资产
  • Asset group schema : 定义一组数据可以分配到一个组,在生成过程中使用
  • AssetReference : 这个做法就像是一个直接引用的对象(类似传统的拖拽到面板上绑定),但会延迟初始化。该AssetReference对象存储GUID作为寻址,可以按需加载。
  • Asynchronous loading : 异步加载,允许在整个应用过程中中定位资源及其依赖的位置,而且是在不改变游戏代码的前提下。异步加载是寻址资产系统的基础.
  • Build script : 构建脚本,运行资产组处理器进行资源打包,并对资源管理器的提供资源的地址和与可寻址标识之间的映射关系。
  • Label : 标签,为一个资源提供额外的可寻址表示符

开始使用

安装可寻址资源系统的包

重要:可寻址资源系统要求Unity的版本为2018.3及以上
通过Window > Package Manager 打开找到Addressables即可安装

将一个资源标记为可寻址

Unity中有两种方式可以将一个资源标记为可寻址的

  1. 在Inspector监视面板中
    Project window项目窗口中,选择需要标记的资源,然后在Inspector面板中,勾选上Address的复选框,并输入一个名称标记该资源

    Inspector中标记.png

  2. 在单独的可寻址资源管理窗口中标记
    选择Window > Asset Management > Addressables打开Addressables窗口。接下来,将所需的资产从Project window窗口拖拽到Addressables窗口中的资产组之一。

    Addressables中标记.png

指定地址

默认地址为该资源在你项目中的路径(例如,Assets/images/myImage.png),若要通过 Addressables window 改变该资源的地址,用鼠标右键单击该资源,并选择重命名。 当你第一次开始使用可寻址的资源,系统会保存的一些编辑时和运行时的数据资源在Assets/AddressableAssetsData文件中,该文件应该被添加到您的版本控制的允许列表中。

打包构建可寻址内容

资源管理系统需要你在使用之前打包构建所有的可寻址资源,该步骤不是自动的,你可以通过编辑器来构建或者通过的API来构建

  • 通过编辑器构建:在Addressables window, 然后选择 Build > Build Player Content
  • 通过API来构建:AddressableAssetSettings.BuildPlayerContent()
使用可寻址资源
通过address加载或者实例化资源

您可以在运行时加载或实例化一个寻址资源,并加载资源的所有依赖项到内存中(包括可用的绑定数据),允许你在需要的时候使用它,但这不会直接将其添加到场景中,若要将资源添加到您的场景你还需要instantiate实例化。但是你也可以直接通过Addressables的Instantiate接口立即加载并实例化添加到您的场景。

通过address访问可寻址资源,需要先应用命名空间UnityEngine.AddressableAssets,然后调用如下方法:

//这将使用指定的地址加载资产。
Addressables.LoadAssetAsync<GameObject>("AssetAddress");

或者

//这将直接实例化指定地址的资源并添加到场景中
Addressables.InstantiateAsync("AssetAddress");

Note: LoadAssetAsyncInstantiateAsync 是异步操作方式. 你可以通过回调函数当加载完成时后进行后续的操作 (具体请查看文档 Async operation handling)

using System.Collections;
using System.Collections.Generic;
using UnityEngine.AddressableAssets;
using UnityEngine;

public class AddressablesExample : MonoBehaviour {
    
    GameObject myGameObject;
    void Start()
    {
        Addressables.LoadAssetAsync<GameObject>("AddressTest").Completed += OnLoadDone;
    }

    private void OnLoadDone(UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<GameObject> obj)
    {
        // In a production environment, you should add exception handling to catch scenarios such as a null result.
        myGameObject = obj.Result;
    }
}

子资源和组件

子资源和组件对资源加载来说是一个特殊情况

组件 : 你不能通过Addressables直接加载一个组件,你必须先实例化一个GameObject,然后获取这个GameObject的组件(通用做法是获取GameObject后通过GetComponent来获取),这里我们对Addressables进行了扩展以支持组件加载,具体方法参见our ComponentReference sample.

子资源 : 系统支持子资源的加载,但是需要特殊的语法,举个潜在的例子,子资源包括,图集中的大量图片,或者FBX模型文件中的大量动画片段,如何直接加载这些图篇,参考 our sprite loading sample

//加载所有的子资源:
Addressables.LoadAssetAsync<IList<Sprite>>("MySpriteSheetAddress");

//加载单个的子资源
Addressables.LoadAssetAsync<Sprite>("MySpriteSheetAddress[MySpriteName]");

资产中可用的名称是在主Addressables组编辑器窗口中可见。此外,你可以使用一个AssetReference访问资源的子对象,参考下方

使用AssetReference引用类

AssetReference类提供一种方法来访问寻址的资源,而且无需知道他们的地址。要使用AssetReference类访问一个可寻址的资源:

  1. 在你的Scene Hierarchy界面或者Project窗口中选择一个资源
  2. 在Hierarchy中,为其添加组件(Add Component ),组件的类型为任何可序列化的组件都支持AssetReference变量引用(例如,游戏脚本、 ScriptableObject 或其他可序列化类)
  3. 在脚本中增加一个公开public的AssetReference变量
  4. 最后在Inspector窗口中,选择一个可寻址的资源关联起来,无论是将资源拖拽引用过来,或者是通过下拉菜单从预定义的可寻址资源中选择一个,都是可以的


    Addressable.png

加载或实例化一个AssetRefAsseterence资源,可通过如下方法:

public class Z_Test : MonoBehaviour
{
    public AssetReference assetReference;
    // Start is called before the first frame update
    void Start()
    {
        assetReference.LoadAssetAsync<GameObject>();
    }
}

或者

public class Z_Test : MonoBehaviour
{
    public AssetReference assetReference;
    // Start is called before the first frame update
    void Start()
    {
        assetReference.InstantiateAsync(这里有2个重载,自己填参数);
    }
}

和一般的资源寻址一样,LoadAssetAsyncInstantiateAsync都是异步操作,你可以提供加载完成时的回调
子资源,如果AssetRefAsseterence中包含了子资源,(例如Sprite或FBX),你可以在该资源的Inspector面板中,AssetReference组件上看到多出了一个下拉框,下拉框中选择你需要的是,引用资源本身,还是子类资源.第一个下拉框选择的是资源本身,第二个下拉框,如果你选择"<none>"的话, 那么资源引用还是指向资源该资源本身,此时可以获取到所有的子类资源(例如assetReference.LoadAssetAsync<IList<Sprite>>()),但是如果第二个下拉框你选择了某一个子资源,那么AssetRefAsseterence引用仅指向该子资源

构建方面需要考虑的因素
在StreamingAssets中的本地数据

可寻址资源系统需要一些文件取帮助识别一个资源在什么地方以及怎么取加载它,当你构建Addressables数据(即在Addressables窗口中Build Play Content)时,这些文件会在StreamingAssets文件夹中生成,StreamingAssets是Unity中的一个特殊文件夹,它包括了所有的资源寻址信息.当你构建Addressables数据时候,系统级的文件会被生成在库中,然后当你构建整个App时,系统会拷贝这些所需要的文件到StreamingAssets文件夹中,构建,然后再从库中删除它们,通过这种方式,你可以为多个平台构建每个平台各自关联需要的数据.

除了特定的Addressables数据,任何为本地使用构建数据的组也将使用于特定平台的位置,为了验证这项工作,去设置profile配置文件变量中的构建路径和加载路径分别是[UnityEngine.AddressableAssets.Addressables.BuildPath]

{UnityEngine.AddressableAssets.Addressables.RuntimePath}
你可以在AddressableAssetSettings文件的Inspector面板中指定这些设置(默认情况下,这个对象是位于项目的资产/AddressableAssetsData目录)

预先下载

通过调用Addressables.DownloadDependenciesAsync()方法可以加载指定的address或label的依赖资源,通常情况下,这是一个assetbundle

通过AsyncOperationHandle的句柄和可以获取PercentComplete属性来帮助监视和显示当前的下载进度,你可以让应用等待直到所有内容加载完毕

如果你想征求用户同意后再下载,那么需要先获取到下载资源的大小告诉用户,通过
Addressables.GetDownloadSize()返回需要下载内容的大小
注意:这需要考虑到之前下载的bundles是否仍然有缓存

虽然提前为应用程序下载资源是有利的,但在某些情况下,您可能会选择不这样做。 例如:

  • 如果你的应用程序有大量的在线内容,你通常希望用户只与其中的一部分进行交互。
  • 你有一个应用程序必须连网下载,如果你的应用程序的所有内容都是小的bundle,你可以根据需要选择下载,即按需下载

您可以使用预加载功能来显示下载已经启动,然后继续,而不是使用完成百分比值等待内容加载。 此实现将需要一个'加载或等待的屏幕'(即UI或交互效果)来处理资源仍在加载时的等待情况

为多平台构建

可寻址资源系统在你构建可寻址资源内容时,其资源是依赖于平台的,因而你必须为你每个需要支持的平台进行重新构建

默认情况下,建立Addressables应用数据时,您指定平台的数据存储在Addressables的特定平台的目录构建路径中.
运行时路径负责处理这些平台文件夹指向适用的应用程序数据。
注意:当你在编辑器模式下使用Addressables BuildScriptPackedPlayMode,Addressables 将尝试加载数据到你当前选中的编辑器平台(win或ios或mac等等).因此,如果你当前构建的目标数据不是您当前编辑器平台,那么兼容性可能会出现问题.要了解更多详细信息,参考 Play mode scripts.

可寻址资源的开发周期

可寻址资源系统最重要的优点之一就是将 准备内容 构建内容 和加载内容 的关联进行解耦,事实上,在之前它们是紧密联系在一起的

传统的资源管理器

如果你之前是使用 Resources文件夹来存放资源的,那么它挥别内置到应用程序中,你必须通过Resources.Load的方式来加载内容,需要提供路径的资源.如果资源存放在别处,你会使用直接应用或者AssetBundle来加载,如果你使用AssetBundle,你需要再次进行路径,打包以及组织加载策略,如果AssetBundle在远程服务器上,或者对其他的bundle具有依赖性,那么你必须通过写代码来管理下载,加载和卸载所有的Bundles

可寻址资源管理器

给资源一个'address'名称作为寻址方式,你就可以使用该地址来加载他,不管它在你的项目中是如何构建的,即使你更改这个资源的路径名或者文件名都不会出现问题,你还可以从Resources文件夹中移动该资源或者从构建路径,或者一些其他的生成位置(包括远程的),而不用去改变你的加载代码

资源组模式(Asset group schemas)

模式定义了一组数据,你可以增加模式到资源组中,附加到组的模式可以定义如何构建改组中的内容.举个例子,当你构建打包模式时,将附加有BundledAssetGroupSchema模式的组用作资产的源,您可以将模式集组合到用于定义新组的模板中
你也可以通过AddressableAssetsSettings添加资源组模式

构建脚本

构建脚本是一个继承了[IDataBuilder] 接口的[ScriptableObject]可编辑脚本资源.
用户可以自己创建构建脚本然后在AddressableAssetSettings的Inspector面板中添加它们.
通过Addressables窗口,即(Window > AssetManagement > Addressables)中选择Play Mode Script,然后选择下拉选项,当前,已实现三个脚本来支持完整的应用程序构建,并提供三个用于在编辑器中进行迭代的播放模式脚本。

播放模式脚本

Addressable Assets Package默认自带了三个构建脚本,这些脚本创建播放模式数据以帮助您加速应用程序开发。

  • Fast mode
    快速模式(BuildScriptFastMode)使您可以在游戏流程中快速运行游戏。快速模式直接通过资产数据库加载资产,无需分析或创建AssetBundle即可进行快速迭代。

  • Virtual mode
    虚拟模式(BuildScriptVirtualMode)可以分析内容的布局和依赖性,而无需创建AssetBundle。资源通过ResourceManager从资源数据库加载,就好像它们是通过Bundles加载一样。查看游戏过程中Bundles何时加载或卸载,通过Addressable Profiler window(Window > Asset Management > Addressable Profiler)
    虚拟模式可帮助您模拟负载策略并调整内容组以找到生产版本的适当平衡。

  • Packed Play mode
    打包播放模式(BuildScriptPackedPlayMode)使用已构建的Bundles。此模式与已部署的应用程序构建最匹配,但是需要你进行一步单独的数据构建过程.如果你不修改资源,则此模式是最快的,因为它在进入"Play"模式时是不会处理任何数据的,你必须通过Addressables window (Window > Asset Management > Addressables) 然后选择Build > Build Player Content,或者可以使用如下代码进行代码打包:

AddressableAssetSettings.BuildPlayerContent()
选择编译脚本

要改变播放模式脚本,请通过Addressables window (Window > Asset Management > Addressables) ,选择Play Mode Script的下拉框来选择,在开发和部署期间,每种模式都有特定的使用时间和地点。下表说明了开发周期的各个阶段,在这些阶段中,特定的模式很有用。

播放模式

分析和调试

默认情况下,可寻址资源只有warnings日志和error错误日志,你可以开启更加详细的detailed logging,通过Edit > Project Settings > Player,然后到Other Settings > Configuration,增加预定义宏ADDRESSABLES_LOG_ALL

你可以通过不勾选AddressableAssetSettings面板中的Log Runtime Exceptions来禁用异常捕捉,你可以通过继承ResourceManager.ExceptionHandler属性实现自己的异常捕捉机制,但是这必须在Addressables运行时初始化成功后

初始化

您可以将对象附加到“可寻址资源”设置,然后在运行时进行初始化过程。
CacheInitializationSettings对象控制Unity在运行时的缓存API,创建自己的初始化对象,创建一个可编程对象ScriptableObject 继承 IObjectInitializationDataProvider接口,这是系统的编辑器组件,负责创建ObjectInitializationData在运行时序列化数据

内容更新工作流

Unity建议将游戏内容分为两类:

  • Static静态内容,你可以不用考虑更新
  • Dynamic动态内容,你期望更新的内容

在这种结构中,静态内容随应用程序一起提供(或在安装后立即下载),并且驻留在很少的Bundles。动态内容驻留在线上服务器,最好放在较小的包中,以最大程度地减少每次更新所需的数据量。可寻址资源系统的目标之一是使这种结构易于使用和修改,而无需更改脚本。

但是,"可寻址资源系统"也允许适当的改变和调整"Static"内容,当你不想要发布整个新的应用构建时。

它是怎样工作的

Addressables 使用内容目录将地址映射到每个资产,以及指定从什么地方加载它,为了提供修改映射的支持,你的原始应用必须知道该目录的在线副本,需要打开这个,在AddressableAssetSettings的inspector的面板中打开Build Remote Catalog选项,这可以确保从指定的路径拷贝和加载该目录副本,应用发布后,此加载路径就无法更改。内容更新过程将创建目录的新版本(具有相同的文件名),以覆盖先前指定的加载路径上的文件。

构建应用程序会生成一个唯一的应用程序内容版本字符串,该字符串标识每个应用程序应加载的内容目录。给定的服务器可以包含应用程序多个版本的目录,而不会发生冲突。我们将所需的数据存储在addressables_content_state.bin文件中。包括版本字符串,以及标记为StaticContent的组中包含的任何资产的哈希信息。默认情况下,此文件与AddressableAssetSettings.asset文件位于同一文件夹中。

addressables_content_state.bin文件包含Addressables系统中每个StaticContent资源组的哈希和依赖项信息。构建到StreamingAssets文件夹的所有组都应标记为StaticContent,尽管大型远程组也可以从该名称中受益。在下一步(准备进行内容更新)期间,此哈希信息确定任何StaticContent组是否包含已更改的资源,以及是否需要将这些资产移至其他位置。

准备更新内容

如果您在任何 StaticContent 组中修改了资源,则需要运行 Prepare For Content Update 命令。 这将把任何已修改的资产从静态组中移出,并将它们移动到新组中。 产生新的资源组合:

  1. 打开Addressables window (Window > Asset Management > Addressables)
  2. Addressables window窗口中,选择Build菜单,然后选择Prepare For Content Update
  3. Build Data File对话框打开时,选择addressables_content_state.bin文件 (默认情况下,位置在Assets/AddressableAssetsData)

此数据用于确定自上次构建应用程序以来已修改了哪些资产或依赖项。系统将这些资产移至新的组,以准备内容更新构建。

注意:如果所有更改都限于非静态组,则此命令将不执行任何操作。

重要信息:在运行准备操作之前,Unity建议分支您的版本控制系统。准备操作以适合于更新内容的方式重新排列资产组。分支机构确保下次发送新播放器时,您可以返回到首选的内容安排。

用于内容更新的构建
  1. 打开Addressables window (Window > Asset Management > Addressables)
  2. Addressables window窗口中,选择Build菜单,然后选择Build For Content Update
  3. Build Data File对话框打开时,选择Build文件夹下已经存在需要被当前替换的应用构建文件,构建文件夹中必须包含 addressables_content_state.bin 文件

构建生成内容目录时,会有一个hash文件和assetbundles

生成的内容目录与所选应用程序构建中的目录名称相同,将覆盖旧的目录和哈希文件。应用程序加载哈希文件以确定是否有新的目录可用。系统从应用程序附带的或已经下载的现有捆绑包中加载未修改的资源

系统通过使用addressables_content_state.bin文件中的内容版本信息和位置信息去创建AssetBundles,不包含更新的Assetbundle则与之前的同名,不发生变化,如果Assetbundle包含更新的内容,则生成包含更新内容的新Assetbundle,使用一个新的文件,以便它可以与原来的文件共存,新文件名的Assetbundle必须复制到承载内容的位置(新的内容追加到老文件的承载位置)

系统还为静态内容构建资源包,但是你不需要将它们上传到内容托管位置,因为没有 Addressables 资源引用它们。

内容更新例子

在这个例子中,一个已发布的应用程序知道以下组:


由于这个版本是实时的,有些玩家在他们的设备上有 Local static,并且有可能在本地缓存这两个远程捆绑包中的一个或者两个。
如果你修改了每个组中的一个资产(AssetA,AssetL 和 AssetX) ,然后运行Prepare For Content Update,在本地 Addressable 设置中的结果现在是:

请注意,prepare 操作实际上编辑了静态组,这可能看起来有悖直觉。 但是,关键是系统构建了上面的布局,但是丢弃了任何静态组的构建结果。 因此,从玩家的角度来看,你最终会得到以下结果:

Local_Static已经在播放器设备上,不能更改。 这个旧版本的 AssetA 不再被引用。 相反,它被作为死数据粘在播放器设备上。

Remote_Static没有改变。 如果它还没有缓存在播放器的设备上,它会在请求 AssetM 或 AssetN 时下载。 像 AssetA 一样,这个旧版本的 AssetL 不再被引用。

Remotenonstatic 包现在已经旧了。 你可以从服务器上删除它,但是无论如何,从现在开始它都不会被下载。 如果被缓存,它最终会离开缓存。 像 AssetA 和 AssetL 一样,这个旧版本的 AssetX 不再被引用。

旧的 Remote nonstatic 包被替换为新版本,其特征是它的散列文件。 Assetx 的修改版本使用这个新包进行更新。

content_update_group由接下来的过程中修改过的引用资产组成。

请注意,上面的例子有以下含义:

  1. 任何已更改的本地资产将永远不会在用户的设备上使用。
  2. 如果用户已经缓存了一个nonstatic非静态包,那么他们将需要重新下载该包,包括未更改的资产(例如,在这个实例中,AssetYAssetZ)。 理想情况下,用户没有缓存包,在这种情况下,他们只需要下载新的 Remote nonstatic 包。
  3. 如果用户已经缓存了 Remote Static远程静态包,那么他们只需要下载更新的资产(在这个实例中,是content update group中的AssetL )。 在这种情况下,这是理想的。 如果用户没有缓存该捆绑包,他们必须通过content update group下载新 AssetL,以及通过未触及的 Remote static 捆绑包下载现已失效的 AssetL。 无论初始缓存状态如何,在某个时刻,用户的设备上都会有不存在的 AssetL,尽管它从未被访问过,但它会无限期地缓存下去

资源的主机服务

概览

资源主机服务是通过Unity Editor环境为可寻址资源配置资源打包到本地或者远程服务器的综合服务,当测试打包的内容时,资源的主机服务的设计目的是为了提高迭代速度,也可以用来向本地和远程网络连接的客户端提供内容。

打包模式测试和迭代

从编辑播放模式测试转移到平台应用程序构建测试给开发过程带来了复杂性和时间成本,主机服务提供可扩展的编辑器内嵌的内容传递服务,这些服务直接映射到 Addressables 组配置,使用自定义 Addressables 配置文件,您可以快速配置应用程序,从 Unity 编辑器本身加载所有内容。这包括部署和构建到移动设备或任何其他平台,这些移动设备或平台具有对开发系统的网络访问权限。

Turn-Key(打开,配置) 内容服务

您可以将 Asset Hosting Services 部署到服务器环境中,方法是以批处理模式(headless)运行,为面向内部网和因特网的 Unity 应用程序客户机托管内容。

配置

本文详细介绍了项目资产托管服务的初始设置。 虽然安装指南侧重于编辑器工作流,但是您可以通过设置 AddressableAssetSettings 类的 HostingServicesManager 属性使用 API 来配置托管服务。

配置新的主机服务

使用 Hosting window 添加、配置和启用新的 Hosting 服务。 在编辑器中,选择 Window > Asset Management > Hosting Services,或者单击 Addressables window菜单中的 Hosting 按钮以访问 Hosting window。

要添加新的托管服务,请单击Add Service按钮。

在出现的Add Service对话框中,可以选择预定义的服务类型或定义自定义服务类型。 若要使用预定义的服务类型,请从Service Type下拉选项中选择。 使用Descriptive Name字段为服务输入名称。

注意: 有关实现自定义托管服务类型的更多信息,请参见关于自定义服务的部分。

新添加的服务出现在Hosting windowHosting Services 部分,默认为禁用状态。 要启动服务,请单击Enable Service按钮。

Http 主机服务启动时会自动分配一个端口号。 端口号在 Unity 会话之间保存和重用。 要选择不同的端口,可以在 Port 字段中分配特定的端口号,或者使用 Reset 按钮随机分配不同的端口。
注意: 如果重置端口号,则必须执行完整的应用程序构建,以生成并嵌入正确的 URL。
现在已经启用了 HTTP 主机服务,可以为每个资产组的 BuildPath 中指定的目录提供内容。

Profile文件配置

在开发过程中使用 Hosting Services 时,Unity 建议创建一个Profile配置文件,将所有资产组配置为使用专门为此目的创建的目录或从 Hosting Service 加载内容。

Addressables window窗口中选择(Window > Asset Management > Addressables),选择Profiles > Inspect Profile Settings,您还可以通过 addresssableassetsettings 访问这些设置。

接下来,创建一个新的Profile文件,如下面的例子,将profile的命名为"Editor Hosted"


修改加载路径字段,改为从主机服务加载。 Httphostingservice 是一个指定服务器本地 IP 地址和端口的URL。 在 Hosting 窗口中,您可以使用名为 PrivateIpAddress 和 HostingServicePort 的配置文件变量来构造 URL (例如,http: / / [ PrivateIpAddress ] : [ HostingServicePort ])。

此外,您应该修改所有生成路径变量,以指向 Project 的 Assets 文件夹外的一个公共目录。

验证每个组的配置是否正确。 确保它们各自的配置文件profile的 BuildPathLoadPath 路径都被正确设置 ,这些配置文件profile 信息是针对对应的主机服务所作出的修改。 在这个示例中,您可以看到如何展开 LoadPath 中的配置文件变量,以构建正确的基础 URL,从而从 Hosted Services 加载。

最后,从 Addressables window 选择新的配置文件,新建一个构建,并部署到目标设备。 Unity 编辑器现在通过 HttpHostingService 服务处理来自应用程序的所有加载请求。 现在不需要重新部署就可以对内容进行添加和更改。 重新构建可寻址内容,并重新启动已部署的应用程序以刷新内容。

批处理模式

您还可以使用批处理模式从Unity编辑器启动主机服务。 要做到这一点,从命令行启动 Unity,使用以下选项:
-batchMode -executeMethod UnityEditor.AddressableAssets.HostingServicesManager.BatchMode

这将从默认的addresssableassetsettings 对象加载 Hosting Services 配置,并启动所有已配置的服务。

要使用可选的 addresssableassetsettings 配置,请创建自己的静态方法入口点,通过
UnityEditor.AddressableAssets.HostingServicesManager.BatchMode(AddressableAssetSettings settings)重载。

自定义服务

主机服务设计为可扩展的,允许您实现自己的自定义逻辑,以便从 可寻址资源系统 加载请求服务内容。 例如:

  • 支持使用非 http 协议下载内容的自定义 IResourceProvider。
  • 管理一个外部内容服务进程,提供 CDN 匹配的解决方案(例如 Apache HTTP Server 文档)。
实现自定义服务

Hostingservicesmanager 可以管理任何实现 IHostingService 接口的类(有关方法参数和返回值的详细信息,请参阅 API documentation.

创建一个新的自定义服务

  1. 按照上面配置新的主机服务部分中概述的步骤来访问添加服务对话框。
  2. 选择 Custom,然后将适用的脚本拖放到字段中,或者从对象选择器中选择它。 该对话框验证所选脚本是否实现 IHostingService 接口。
  3. 完成添加服务,点击添加按钮。

下一步,您的自定义服务将出现在服务类型下拉选项中。

内存管理

镜像加载和卸载

当使用 Addressable Assets 时,确保正确内存管理的主要方法是镜像正确地加载和卸载。 这取决于您的资产类型和加载方法。 在任何情况下,释放方法都可以接受已加载的资产或是由加载返回的操作句柄。 例如,在场景创建期间(如下所述) ,加载将返回一个 AsyncOperationHandle<Sceneinstance>,您可以通过这个返回的句柄释放这个 AsyncOperationHandle sceneinstance,或者通过handle.Result来释放 。 结果(在本例中为 SceneInstance)。

资源加载

通过Addressables.LoadAssetAsync 或者 Addressables.LoadAssetsAsync 来加载资源.
这会将资源加载到内存中,而不会将其实例化。 每次执行加载调用时,它都会为每个加载的资源的引用计数ref-count加1,如果对同个资产调用 LoadAssetAsync 三次,则会得到 AsyncOperationHandle 结构的三个引用实例但是它们都指向相同的底层,该操作对相应资源的的参考计数为3。如果加载成功,那么生成的AsyncOperationHandle结构将包含.Result属性.你也您可以使用 Unity 的内置实例化方法来实例化加载资源,但是该方法不会增加资源的引用数ref-count。

若要卸载资源,请使用 Addressables.Release 方法,该方法将 ref-count 递减。 当给定资源的 ref-count 为零时,就可以卸载该资源,并减少任何依赖项的ref-count引用。

注意: 资产可能会也可能不会立即卸载,这取决于现有的依赖项。 有关更多信息,请阅读关于何时清除内存的部分。 when memory is cleared

场景加载

要加载一个场景,请使用Addressables.LoadSceneAsync. 您可以使用此方法在Single单模式下加载场景,该模式将关闭所有打开的场景,或者在Additive追加模式下加载场景(更多信息,请参阅场景模式加载文档 Scene mode loading)。

若要卸载一个场景,请使用Addressables.UnloadSceneAsync。 或者在Single模式下打开一个新的场景,那么会自动卸载其他场景.你可以使用 Addressables 的接口或者 SceneManager.LoadSceneSceneManager.LoadSceneAsync, 打开一个新场景并会自动关闭当前场景,引用计数ref-count也会适当地递减。

游戏对象的实例化

要加载和实例化一个游戏对象资产,使用 Addressables.InstantiateAsync 实例化。 这实例化位于指定location参数的预置, Addressables系统将加载预置及其依赖项,并递增所有相关资源的引用计数ref-count。

在同一地址上三次调用 InstantiateAsync 将导致所有依赖资产的 ref-count 为3。 但是,与三次调用 LoadAssetAsync 不同的是,每次InstantiateAsync异步调用都返回一个 AsyncOperationHandle,它指向一个唯一的操作。 这是因为每个实例化 InstantiateAsync的结果都是唯一的实例。 Instanateasync 和其他加载调用之间的另一个区别是可选的 trackHandle 参数(追踪handle)。 当设置为 false 时,在释放实例时,必须保留 AsyncOperationHandle。 这样更有效率,但是需要更多的开发工作。

要销毁毁一个实例化的游戏对象,请使用 Addressables.ReleaseInstance,或关闭包含实例化对象的场景。 你可以使用 Addressables 的接口或者 SceneManager.LoadSceneSceneManager.LoadSceneAsync, 打开一个新场景并会自动关闭当前场景。 如上所述,如果将 trackHandle 设置为false,则只能调用 Addressables.ReleaseInstance,而不是实际的游戏对象GameObject。

注意: 如果您调用 Addressables.ReleaseInstance。 在一个没有使用 Addressables API 创建,或者创建时将 trackHandle设置未false了,系统会返回 false,以指示该方法无法释放指定的实例。 在这种情况下,实例不会被销毁

Addressables.InstantiateAsync有一些相关的开销,所以如果你需要每帧实例化相同的对象几百次,考虑通过 Addressables API 加载,然后通过其他方法实例化,你可以调用 Addressables. LoadAssetAsync,然后对返回结果调用 GameObject.Instantiate(),这允许灵活地以同步方式调用 Instanate,缺点是 Addressables 系统不知道您创建了多少实例,如果管理不当,可能会导致内存问题.例如,引用纹理的预置将不再具有可供引用的有效加载纹理,从而导致内存泄漏问题(或更糟)。 这些类型的问题可能很难跟踪,因为您可能不会立即触发内存卸载(请参阅下面关于清除内存的小节clearing memory

数据加载

你不需要使用AsyncOperationHandle.Result来释放物体,但仍然需要它们本身才能被释放(这段我也不清楚它在说什么)
举个例子可以是Addressables.LoadResourceLocationsAsyncAddressables.GetDownloadSizeAsync
你可以访问这些数据,直到它们被释放.请使用Addressables.Release操作来释放

后台交互

不返回 AsyncOperationHandle 中任何内容的操作。 结果字段具有一个可选参数,用于在完成时自动释放操作句柄,如果在操作完成后不再需要这些操作句柄之一,请将 autoReleaseHandle 参数设置为 true,以确保清除操作句柄,如果需要在操作句柄完成后检查操作句柄的状态,则希望 autoReleaseHandle 为 false。 这些接口的例子是 Addressables.DownloadDependenciesAsyncAddressables.UnloadScene

Addressable Profiler配置文件

使用 Addressable Profiler 窗口监视所有 Addressables 系统操作的引用计数。 访问“编辑器”中的窗口,
Window > Asset Management > Addressable Profiler

重要:为了查看探查器中的数据,必须在 Addresssableassetsettings 对象的检视面板中启用 Send Profiler Events 设置。

分析器提供了以下信息:

  • 白色垂直线表示发生加载请求的帧。
  • 蓝色背景表示当前加载的资源。
  • 图中绿色部分表示资产的当前引用计数。
内存何时清空

不再被引用的资源(在探查器的蓝色部分的末尾显示)并不一定意味着已经卸载了该资源。 一个常见的适用场景涉及一个资源包中的多个资产。 例如:

  • 在一个资产组合'stuff'中有三个资产(树tree、坦克tank,和牛cow)。
  • 当加载树tree时,分析器将显示一个树tree引用计数,一个stuff引用计数。
  • 稍后,当 tank 加载时,分析器将同时显示一个 tree 和 tank 的参考计数,以及两个 stuff 的参考计数
  • 如果释放 tree,它的 ref-count 变为零,并且蓝条消失。

在这个示例中,此时实际上并没有卸载树资源。 您可以加载资源包或其部分内容,但不能部分卸载资产包.
在stuff本身完全卸载之前,其中的任何资源都不会卸载。 但是有一个例外Resources.UnloadUnusedAssets,
在上述场景中执行此方法将导致 tree 卸载。 因为 Addressables 系统无法知道这些事件,profiler图形监视器仅反映 Addressables ref-counts (不完全反映内存所拥有的内容)。 注意,如果您选择使用Resources.UnloadUnusedAssets来手动释放资源,这是一个非常慢的操作,并且应该只在不会显示任何故障的屏幕上调用(即最好是在加载时调用,不然可能存在游戏过程中的掉帧卡帧)。

异步处理句柄

一些Addressables API方法会返回一个AsyncOperationHandle句柄,句柄最主要的目的是允许访问当前异步的的状态和曹祖哦的结果,句柄信息知道你调用Addressables.Release或者Addressables.ReleaseInstance都是有效的.获取更多的相关信息,参见memory management.

当操作完成之后,AsyncOperationHandle.Status属性将会返回AsyncOperationStatus.Succeeded或者AsyncOperationStatus.Failed,如果操作是成功的,那么将会得到一个AsyncOperationHandle.Result结果属性
你可以定期检查操作状态,或者注册一个完成时的回调事件AsyncOperationHandle.Complete,当你不再需要提供返回AsyncOperationHandle,你应该使用Addressables.Release方法来释放

有类型(确定类型)的句柄 对比 无类型句柄

大多数 Addressables API 方法返回一个一般的 AsyncOperationHandle<T>泛型结构,允许 AsyncOperationHandle.Completed 事件的类型安全,以及确切的AsyncOperationHandle.Result对象的类型,还有一个非通用的 AsyncOperationHandle 结构,你可以根据需要在两个句柄之间进行转换

请注意,如果尝试将非泛型句柄强制转换为不正确类型的泛型句柄,则会发生运行时异常。 例如

AsyncOperationHandle<Texture2D> textureHandle = Addressables.LoadAssetAsync<Texture2D>("mytexture");

// Convert the AsyncOperationHandle<Texture2D> to an AsyncOperationHandle:
AsyncOperationHandle nonGenericHandle = textureHandle;

// Convert the AsyncOperationHandle to an AsyncOperationHandle<Texture2D>:
AsyncOperationHandle<Texture2D> textureHandle2 = nonGenericHandle.Convert<Texture2D>();

// This will throw and exception because Texture2D is required:
AsyncOperationHandle<Texture> textureHandle3 = nonGenericHandle.Convert<Texture>();

AsyncOperationHandle 使用案例
void Start() {
    //注册完成时的回调函数
    AsyncOperationHandle<Texture2D> textureHandle = Addressables.LoadAsset<Texture2D>("mytexture");
    textureHandle.Completed += TextureHandle_Completed;
}

private void TextureHandle_Completed(AsyncOperationHandle<Texture2D> handle) {
    if (handle.Status == AsyncOperationStatus.Succeeded) {
        Texture2D result = handle.Result;
        // The texture is ready for use.
    }
}

Asyncoperationhandle 继承自 IEnumerator,因此可以在协同程序中生成它

public IEnumerator Start() {
    AsyncOperationHandle<Texture2D> handle = Addressables.LoadAssetAsync<Texture2D>("mytexture");
    yield return handle;
    if (handle.Status == AsyncOperationStatus.Succeeded) {
        Texture2D texture = handle.Result;
        // The texture is ready for use.
        // ...
        // Release the asset after its use:
        Addressables.Release(handle);
    }
}

Addressables 还支持异步操作await,通过AsyncOperationHandle.Task属性:

public async Start() {
    AsyncOperationHandle<Texture2D> handle = Addressables.LoadAssetAsync<Texture2D>("mytexture");
    await handle.Task;
    // The task is complete. Be sure to check the Status is successful before storing the Result.
}

注意:
用 SceneManager.LoadSceneAsync加载场景并将allowSceneActivation属性设置为false时,或使用 Addressables.LoadSceneAsync并将activateOnLoad 设置为false时.可能会导致后续的异步操作被阻塞无法完成,详情请参见allowSceneActivation

自定义操作

IResourceProvider API 允许您通过以数据驱动的方式定义位置和依赖关系来扩展加载过程。 在某些情况下,您可能希望创建自定义操作。Iresourceprovider API 是构建这些自定义操作的基础

创建一个自定义操作

通过从 AsyncOperationBase 类派生并重写所需的虚方法来创建自定义操作。 可以将派生的操作传递给 ResourceManager.StartOperation启动,并接收 一个AsyncOperationHandle 的返回结构。 以这种方式启动的操作在 ResourceManager 中注册,并显示在 Addressables Profiler

执行操作

Resourcemanager 调用 AsyncOperationBase。 在依赖性操作完成后,再执行自定义操作方法。

完成句柄

自定义操作完成后,调用 AsyncOperationBase.Complete。您可以在 Execute 方法中调用这个函数,也可以将其推迟到调用外部。 Asyncoperationbase. Complete 通知 ResourceManager 操作已经完成,并将调用关联的 AsyncOperationHandle.Completed 事件已完成活动。

终止操作

当你在释放AsyncOperationHandle时,Resourcemanager会为你的自定义操作调用 AsyncOperationBase.Destroy方法,你应该在此释放与自定义操作关联的任何内存或资源。

Addressables分析器

Addressables分析器一个收集你的项目的地址布局信息的工具。 在某些情况下,分析人员可能会采取适当的行动来清理项目的状态。 在其他情况下,分析仅仅是一个信息工具,它可以让你对你的 Addressables 布局做出更明智的决定。

使用分析器

在编辑器中,打开 Addressables Window **(Window Asset Management Addressables) **,然后单击菜单中的 Analyze 按钮以打开 Addressables Window

分析窗口显示一个分析规则列表,以及以下操作:

  • Analyze Selected Rules 分析选择规则
  • Fix Selected Rules 修复选择规则
  • Clear Selected Rules 清理选择规则
分析操作

分析操作是规则信息的收集步骤。运行此操作可以收集有关生成、依赖映射等的数据, 每个规则负责收集所需的数据,并将其作为 AnalyzeResult 对象返回。

在分析步骤期间,不应采取任何措施修改任何数据或项目状态。 根据在此步骤中收集的数据,修复操作可能是适当的操作过程。 但是,有些规则只包含分析步骤,因为没有明确的合适的操作基于收集到的基础信息。
例子
Check Scene to Addressable Duplicate Dependencies
Check Resources to Addressable Duplicate Dependencies

纯信息性规则且不包含修复操作的规则被归类为不可修复规则。 那些确实具有修复操作的规则被归类为可修复规则。

清除步骤

此操作将删除分析收集的任何数据,并相应地更新TreeView

修复操作

对于可修复规则,您可以选择运行修复操作。 这将使用在分析步骤中收集的数据来执行任何必要的修改并解决问题

例子:
Check Duplicate Group Dependencies

可以采取合理的适当行动来解决分析中发现的问题

提供分析规则
可修复的规则

检查重复的组依赖项
该规则通过使用 BundledAssetGroupSchemas 扫描所有组并投影资产组布局来检查可能重复的资产。 这实际上需要触发一个完整的构建,因此这种检查非常耗时,而且需要大量的性能。

问题: 重复的资源产生于共享依赖关系的不同组中的资源,例如共享不同 Addressable 组中存在的资产的两个预制Prefab。 这些材料(以及它的任何附属物)将被拉入包含预制Prefabs的两个组。 为了防止这种情况,材料必须被标记为可寻址的,无论是在一个Prefab,或在自己的空间,从而把材料和它的依赖关系在一个单独的可寻址组

解决方案: 如果此检查发现任何问题,在此规则上运行修复操作将创建一个新的 Addressable 组,用于移动所有依赖的资产。

异常: 如果您有一个包含多个对象的资源,那么不同的组可能只提取该资源的一部分,而不是实际的重复。 一个多网格的 FBX 就是这样的例子。 如果一个网格在“ GroupA”中,另一个网格在“ GroupB”中,那么这个规则将认为 FBX 是共享的,如果您运行修复操作,则将它提取到它自己的组中。 在这种情况下,运行修复操作实际上是有害的,因为两组都没有完整的 FBX 资源。

还要注意,重复的资源可能并不总是一个问题。 如果同一组用户永远不会请求资源(例如,区域特定的资源) ,那么可能希望重复依赖,或者至少是无关紧要的。 每个项目都是唯一的,因此修复重复的资资源依赖关系应该根据具体情况进行评估。

不可修复的规则

检查资源以寻址重复依赖项
此规则检测是否有任何资源或资源依赖项在构建的Addressable数据和驻留在Resources文件夹中的资源之间重复。

问题: 这些重复意味着数据将同时包含在应用程序构建和 Addressables 构建中。

解决方案: 这个规则是不可修复的,因为没有合适的操作可以自动解决。 它纯粹提供信息,提醒您存在重复和冗余,如果有的话,你必须自行决定如何进行和采取什么行动。 可能的手动修复的一个示例是将有问题的资产移出 Resources 文件夹,并使其可寻址。

构建捆绑布局
此规则将显示如何在 Addressable 构建中显式标记为 Addressable 的资源。 对于这些显式资源,我们还显示了构建隐式引用了哪些资源,因此将引入哪些资源。
此规则收集的数据不表示任何特定问题。 它是纯粹的信息。

检查精灵地图集,以寻址重复依赖
给定一个可寻址的精灵地图集,这个规则将检测是否有任何精灵在地图集被标记为可寻址的任何其他地方。

问题: 这些重复意味着精灵数据将在可寻址构建的多个区域中重复。
解决方案:它是纯粹的信息,提醒你冗余。 如果有的话,你必须决定如何进行和采取什么行动。 一个可能的手动修复的例子是从 Addressables 中移除重复的精灵,让你的资产从你的 Addressable 精灵地图集中引用一个精灵,而不是直接引用精灵。

拓展分析

每个唯一的项目可能需要额外的分析规则以外的预先包装。
可寻址资产系统允许您创建自己的自定义规则类

AnalyzeRule objects
创建 AnalyzeRule 类的一个新的子类,并重写以下属性:

  • canFix 告诉分析该规则是否被认为是可修复的。
  • ruleName 是您将在Analyze window分析窗口中看到的此规则的显示名称。
    你还需要重写以下方法:
  • List<AnalyzeResult> RefreshAnalysis(AddressableAssetSettings settings)
  • void FixIssues(AddressableAssetSettings settings)
  • void ClearAnalysis().
    Note: 如果您的规则被指定为不可修复,那么您不必重写 FixIssues方法。

引用分析
这是你的分析操作。 在此方法中,执行所需的任何计算,并缓存潜在修复所需的所有数据。 返回值是一个 List<Analyzeresult> 列表。 在收集数据之后,为分析中的每个条目创建一个新的 AnalyzeResult,其中第一个参数的数据为字符串,第二个参数的数据为 MessageType (可以选择将消息类型指定为警告或错误)。

返回您创建的对象列表。 如果需要在 TreeView 中为特定的 AnalyzeResult对象创建子元素,则可以使用 kDelimiter 描述父项和任何子项。 包括父项和子项之间的分隔符。

修复问题
这是您的修复操作。 如果需要对分析步骤采取适当的行动,请在这里执行

清除分析
这是你的清除分析行为。 在分析步骤中缓存的任何数据都可以在此函数中清理或删除。 Treeview 将会更新以反映数据的缺乏。

追加自定义规则到GUI
自定义规则必须使用 1AnalyzeWindow.RegisterNewRule<RuleType>()1 向 GUI 类注册自己。 为了在Analyze window.中显示。 例如:

class MyRule : AnalyzeRule {}
[InitializeOnLoad]
class RegisterMyRule
{
    static RegisterMyRule()
    {
        AnalyzeWindow.RegisterNewRule<MyRule>();
    }
}

(旧项目)更新移植到可寻址资源系统

本文详细介绍了如何修改现有项目以利用可寻址资产。 有三种传统的资产参考方法:

  • Direct References 直接引用类型 : 将资源直接添加到应用程序自动加载的组件或场景中
  • Resource Folders Resource 文件夹 : 将资源放在Resource文件夹中通过它们的名字加载
  • Asset Bundles 资源打包成资源包 : 打包成AssetBundle然后通过文件路径加载它们及其依赖项
Direct References直接引用法的移植:

要从这种方法迁移,请遵循以下步骤:

  1. 用AssetReference 替换对对象的直接引用
public GameObject directRefMember;
//替换为
public AssetReference AssetRefMember;
  1. 将资源拖到适当的组件的 Inspector 上,就像直接引用一样。
  2. 如果希望基于对象而不是字符串名称加载资产,请直接从您在设置中创建的 AssetReference 对象中实例化该资产.
    AssetRefMember.LoadAssetAsync<GameObject>();
    AssetRefMember.InstantiateAsync(pos, rot);

注意: 可寻址资产系统异步加载资产。 在更新对资产引用的直接引用时,还必须更新代码以异步操作。

Resource folders法的移植:

当您将 Resources文件夹中的资产标记为Addressable时,系统会自动将该资产从Resources文件夹移动到 Project 中名为 Resources_moved 的新文件夹中。 已移动资产的默认地址是旧路径,省略了文件夹名称。 加载代码的变化为:

Resources.LoadAsync<GameObject>("desert/tank.prefab"); 
//替换为
Addressables.LoadAssetAsync<GameObject>("desert/tank.prefab");.
Asset Bundles法的移植:

当您打开 Addressables window时,Unity 提供将所有资源包转换为 Addressable 资产组。
这是迁移代码最简单的方法。 如果选择手动转换资源,请单击忽略按钮。
然后,使用前面描述的直接引用或资源文件夹方法。
资产地址的默认路径是其文件路径。 如果使用路径作为资源的地址,则装载资源的方式与从捆绑包装载资产的方式相同。 可寻址资源系统处理包及其所有依赖项的加载。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,154评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,252评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,107评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,985评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,905评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,256评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,978评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,611评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,891评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,910评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,736评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,516评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,995评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,132评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,447评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,034评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,242评论 2 339

推荐阅读更多精彩内容