撕夜
用心爱你你不懂
十年
时间都去哪儿了
其实你不懂我的心
关于你们之间的故事
假如爱有天意
xlua 代码扩展
在使用xlua的过程中,可能需要自定义xlua,比如所Astar寻路算法,如果在lua中实现的话,效率是非常低的,所以可以考虑将实现方法放到C/C++,这样运行效率可以大大提升。
这里参照xlua官方文档,总结了一下。
首先下载xlua源码。
下载完后可以用Unity打开这个项目,跑一下01_Helloworld案例,后面扩展xlua也将在这个案例上修改。
然后添加扩展代码,扩展代码是在和Assets同目录的build目录下。
这里我们沿用官方文档的例子,扩展lua-rapidjson。可以在github上搜索,路径的话就是xpol/lua-rapidjson
下载lua-rapidjson源码
在xlua的build目录下新建文件夹lua-rapidjson,然后在lua-rapidjson文件夹下新建文件夹include和src。在官方文档中说的是新建include和source,其实名字是什么都可以,后面配置的时候对应上就行。
然后将lua-rapidjson源码中的
lua-rapidjson/rapidjson/include
目录全部拷贝到上面新建的include目录下。再将lua-rapidjson源码中的
lua-rapidjson/src
目录全部拷贝到上面新建的src目录下。上面两步就准备好了扩展的源码。
然后是将上面的源码关联到xlua的cmake上。
在xlua的build目录下找到CMakeLists.txt文件。在这个文件里面配置。配置内容如下。下面这个
lua-rapidjson/include
和ua-rapidjson/src/
就是上面建的源文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24MARK_AS_ADVANCED(XLUA_PROJECT_DIR)
# 下面是新增部分
#begin lua-rapidjson
set (RAPIDJSON_SRC
lua-rapidjson/src/Document.cpp
lua-rapidjson/src/Schema.cpp
lua-rapidjson/src/rapidjson.cpp
lua-rapidjson/src/values.cpp
)
set_property(
SOURCE ${RAPIDJSON_SRC}
APPEND
PROPERTY COMPILE_DEFINITIONS
LUA_LIB
)
list(APPEND THIRDPART_INC lua-rapidjson/include)
set (THIRDPART_SRC ${THIRDPART_SRC} ${RAPIDJSON_SRC})
#end lua-rapidjson
# 上面是新增部分
if (NOT LUA_VERSION)
set(LUA_VERSION "5.3.5")
endif()配置好后,就可以尝试重新编译xlua源码。在xlua的build目录下找到make_win_lua54.bat文件,双击,会运行控制台命令,看看如果没有报错就说明成功了。
成功后会导出plugin_lua54,这里就是Unity Plugins下要用到的dll。将生成的Plugins拷贝到Unity下替换原本的Plugins。当然拷贝要关闭Unity才行。
替换好Plugins后,重新打开Unity。还是打开01_Helloworld案例。这里将在01_Helloworld案例中测试新增的lua-rapidjson
首选可以新建一个脚本目录
XLuaExt
,用来存放Xlua扩展脚本。当然也可以不建,后面的扩展代码直接写在LuaDLL.cs
脚本中,不过后面更新xlua时就会被覆盖。在
XLuaExt
目录下新建LuaDLL.Rapidjson.cs
,并且在脚本中添加如下内容。这样C#脚本就关联上了xlua中的扩展。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15using System.Runtime.InteropServices;
namespace XLua.LuaDLL
{
public partial class Lua
{
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_rapidjson(System.IntPtr L);
[MonoPInvokeCallback(typeof(LuaDLL.lua_CSFunction))]
public static int LoadRapidJson(System.IntPtr L)
{
return luaopen_rapidjson(L);
}
}
}然后打开01_Helloworld案例的
Helloworld.cs
脚本,修改这个脚本成如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35using UnityEngine;
using XLua;
namespace XLuaTest
{
public class Helloworld : MonoBehaviour
{
// Use this for initialization
void Start()
{
LuaEnv luaenv = new LuaEnv();
luaenv.AddBuildin("rapidjson", XLua.LuaDLL.Lua.LoadRapidJson);
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
luaenv.DoString(@"
local rapidjson = require('rapidjson')
local t = rapidjson.decode('{""a"":123}')
print(t.a)
t.a = 456
local s = rapidjson.encode(t)
print('json', s)
");
luaenv.Dispose();
}
// Update is called once per frame
void Update()
{
}
}
}然后运行01_Helloworld案例。可以看到
LUA: json {"a":456}
打印日志。
创建最简单的TS项目
创建文件夹
TSProjectDemo
1
[TSProjectDemo]/
进入文件夹,打开cmd控制台
在控制台输入命令
npm init -y
,会创建package.json
文件,1
2[TSProjectDemo]/
package.json1
2
3
4
5
6
7
8
9
10
11
12{
"name": "TSProjectDemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}在控制台可以执行
npm run test
来执行上面的test
脚本在目录下再创建一个
Src
子目录,ts脚本后面都放这里面1
2
3[TSProjectDemo]/
package.json
[Src]/在控制台输入
tsc --init
,会创建tsconfig.json
1
2
3
4[TSProjectDemo]/
package.json
tsconfig.json
[Src]/修改为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"compilerOptions":{
"target": "ES6",
"module": "NodeNext",
"esModuleInterop": true,
"strict": false,
"outDir": "output",
"moduleResolution": "NodeNext"
},
"include": [
// 表示编译Src目录下的所有ts文件
"./Src/**/*.ts"
],
"exclude": [
"node_modules"
]
}进Src目录,创建一个测试脚本
main.ts
1
2
3
4
5[TSProjectDemo]/
package.json
tsconfig.json
[Src]/
main.ts1
2
3// main.ts
const message : string = "test log .... "
console.log(message)回到根目录,然后运行命令
tsc
,会编译出js脚本1
2
3
4
5
6
7[TSProjectDemo]/
package.json
tsconfig.json
[Src]/
main.ts
[output]/
main.js运行命令
node ./output/mian.js
可以执行代码也可以修改
package.json
中的test脚本,这样就可以使用npm run test
运行1
2
3
4
5
6
7
8
9
10
11
12{
"name": "TSProjectDemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "node ./output/mian.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}上面是最简单的ts工程了,但是我们开发的时候可能需要外部库,比如nodejs相关的。
比如
require
指令,或者fs
文件库。这时候我们要安装npm install --save-dev @types/node
安装完成后,脚本里面可以引入
fs
包,import和require两种方式都可以1
2
3
4
5// main.ts
import * as fs from "fs"
const fsLib = require("fs")
const messa : string = "test log .... "
console.log(messa)假如我想读写xml文件,可以安装
npm install xml2js
,xml2js
可以用来读写xml,实现js对象和xml数据进行转换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64// main.ts
import { rejects } from "assert";
import * as fs from "fs";
import { resolve } from "path";
import * as xml2js from "xml2js";
function readXmlFile(filePath: string):Promise<any>{
return new Promise((resolve, rejects)=>{
fs.readFile(filePath, 'utf-8', (err, data)=>{
if(err){
rejects(err);
}
else{
xml2js.parseString(data, (err, result)=>{
if(err){
rejects(err);
}
else{
resolve(result);
}
});
}
});
});
}
function writeXmlFile(filePath:string, data:any):Promise<void>{
return new Promise((resolve, reject)=>{
const buildOption : xml2js.BuilderOptions = {
xmldec:{version:"", encoding:"utf-8",standalone:undefined}
}
const builder = new xml2js.Builder(buildOption);
const xml = builder.buildObject(data);
fs.writeFile(filePath, xml, (err)=>{
if(err){
reject(err);
}
else{
resolve();
}
});
});
}
async function main() {
try{
// 读xml文件
const xmlData = await readXmlFile("jj.xml");
console.log(xmlData);
// xmlData 是一个js对象,根节点是component, $表示xml属性
xmlData.component.$.attribute = "new value --- 100"
// js对象转换为xml
await writeXmlFile("jj2.xml", xmlData)
console.log("xml file write")
}
catch(err){
console.error("err: ", err);
}
}
main()上面的
xml2js
库是没有代码提示的,我们可以额外安装npm install --save-dev @types/xml2js
, 这样在vscode中就可以看到代码提示了上面安装后,
package.json
文件会变成1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
"name": "TSProjectDemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "node ./output/main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.12.7",
"@types/xml2js": "^0.4.14"
},
"dependencies": {
"xml2js": "^0.6.2"
}
}
基于上面的流程,我们就可以开发一些工具了
Unity SRP Misc
渲染指定物体
在SRP里面一个非常重要的函数是ScriptableRenderContext.DrawRenderers
,通过它可以帮我们完成整个场景的渲染。但是这个函数用起来很方便,因为它封装了很多细节。但是在游戏UI中经常会用RenderTexture
来混合UI和模型,就需要将个别物体渲染到RenderTexture
。如果我们只想渲染某一两个物体时,操作起来就比较麻烦,因为首先我们需要将需要渲染的物体ScriptableRenderContext.Cull
出来,这个剔除操作是针对整个场景的,必然有额外的消耗。特别是我们已经知道要渲染哪个物体的情况下,我们可能希望跳过剔除这一步。但是跳过剔除,ScriptableRenderContext.DrawRenderers
函数就用不了了。
这时候我们可以使用CommandBuffer.DrawRanderer
函数,针对单个物体进行渲染。具体操作就是获取所有物体的Render
组件、材质,使用CommandBuffer
渲染。示例代码如下:
1 | public class RenderData{ |
这里有几个点需要注意:
- 首先是
Camera.SetupCurrent(camera)
,如果没有这行代码的话,粒子系统是不会渲染的参考。 ScriptableRenderContext.DrawRenderers
支持SRPBatch
,但是其他绘制函数不一定支持,比如这里的CommandBuffer.DrawRanderer
,这是当前方法的一个弊端。但是Unity2022版本增加了BatchRendererGroup据说是可以实现SRPBatch
,参考这里,但是WebGL目前还不支持。BatchRendererGroup
的原理是在SRPBatch
上扩展的自定义合批方法。- 这里也有对剔除的探讨,但是好像并没有什么有价值的东西,暂且留个入口。
- 另外,
Animator
组件和ParticleSystem
组件上都带有CullingMode
属性,一般来说当物体不可见时,我们希望动画和粒子系统都停止,从而减少性能消耗。CullingMode
就是用来控制物体不可见时的行为。当使用ScriptableRenderContext.DrawRenderers
绘制时,会自动标记物体是否可见的状态,所以通常设置为Automatic
,也就是说不可见时停止,可见时照常运行。但是使用CommandBuffer.DrawRanderer
绘制时,并不会标记这些可见状态,所以需要强制设置为Always Simulate
,当然有些粒子系统设置为Automatic
也能正常运行,这个应该是Unity的bug。 - 当然,我非要用
ScriptableRenderContext.DrawRenderers
渲染某一个物体也不是不行,就是要把剔除流程走一遍。如果我们需要将多个物体分别渲染到多个纹理上呢?是不是就的把整个流程走多遍?有一种做法,可以只走一边剔除流程,就可以渲染多个模型到多个贴图上。FilteringSettings.renderingLayerMask
这个参数就是可以用来控制本次渲染渲染指定渲染层的模型,我们可以为不同的模型设置不同的层,渲染多张纹理的时候把FilteringSettings.renderingLayerMask
设置到对应的层。渲染层总共有32层,也就是最多可分32个独立渲染的物体。我之前想过渲染一次设置一次模型的渲染层,这样看起来就可以渲染无数多个独立渲染的物体,但是实际上这是行不通的,因为整个指令是CommandBuffer
缓存执行,也就是说动态修改物体的渲染层是无效的,CommandBuffer
指令只知道最后一次设置的渲染层。
Shader Tools
源码
1 | // ShaderOpenTools.cs |
参考
VSCode Command line
VSCode settings
VSCode GlobPattern:可以用于设置VSCode不显示.meta
文件,在Settings
界面,搜索file:exclude
,然后增加**/*.meta
。
使用配置文件
上面是通过路径打开VSCode文件,更实用的一种方法是直接通过配置文件打开。通过配置文件打开有几个好处,就是可以在同一个VSCode里面打开多个文件路径,并且保存设置。配置文件格式如下,文件名为shaderworkspace.code-workspace
:
1 | { |
然后前面打开VSCode工程的命令可以改成:
1 | if(line > 0) |
当然,上面命令有效的前提是shaderworkspace.code-workspace
文件在当前工作目录下。
资源网站
Unity AssetBundles
接着上一篇Unity资源逆向解析,这里再来谈一谈AssetBundles。
AssetBundles 使用
使用AssetBundles
总体上分成两步,第一步是创建AssetBundles
资源,第二部是使用AssetBundles
资源。
AssetBundles 创建
Unity提供内置的BuildPipeline管线和可编程的Scriptable Build Pipeline,这里仅以内置的BuildPipeline
管线为例进行讲解。
首先内置的BuildPipeline
管线提供了BuildPipeline.BuildAssetBundles()
函数来帮助创建资源,有两种调用方式。
方式一:
public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
方式二:
public static AssetBundleManifest BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
方式一是针对项目中所有被标记为AssetBundles
的资源,根据其标记的AssetBundles
名称对这些资源进行打包的。
方式二是忽略AssetBundles
的资源标记,而是通过纯代码的形式,向BuildAssetBundles
传入所需要构建的资源,以及AssetBundles
资源名称。
如果想实现自动化构建资源的话,偏向于使用方式二,可以完全通过脚本进行控制。
但是这里为了方便,采用方式一进行构建资源。
因此资源构建代码如下:
1 | using UnityEngine; |
然后要手动编辑哪些资源需要被打包成AssetBundles
,根据官方文档,可以对Assets目录下的文件、以及文件夹进行标记,如果标记的是文件夹,那么相当于标记了文件夹下所有子文件。当然文件的标记优先级高于文件夹的优先级。
这里我的目录结构如下:
1 | 资源 AssetBundleName 资源依赖 |
上面第一列是资源目录结构,第二列是针对资源的AssetBundles
资源名称,第三列表示的是该资源所依赖的其他资源。
一切准备就绪后,在Assets菜单下点击Build AssetBundles
开始构建资源。
在assetBundleDirectory
目录下得到如下资源:
1 | [assetBundleDirectory] |
其中以manifest为后缀的文件是给人读的,展示了一些基本信息。加载AssetBundles
时只需要读取无后缀的文件就行。而assetBundleDirectory
这个资源文件记录了其他所有资源的manifest文件信息。
上面打包出来的资源文件可以使用Unity资源逆向解析中提到的解包工具打开。
AssetBundles 加载
本文加载的基本思路是读取assetBundleDirectory
资源文件中的信息,然后去构建一个资源目录表,然后在使用资源时,首先访问资源目录表,看看该资源是否已经加载,如果没有加载的话就对该资源进行加载,加载时也会判断资源依赖,待所有依赖资源以及该资源都加载成功后,触发资源加载成功回调函数。
代码如下:
1 | using System.Collections.Generic; |
AssetBundles 内存分析
这里以实例化mogucolor.prefab
为例。总共有以下几个步骤:
- 调用Init函数初始化
AssetBundleManifest
- LoadAsset异步加载
mogucolor
,因为其直接以及间接依赖的资源有texture
、mogu
、mogumaterial
、mogushader
,所以一共会有5个AssetBundle会被加载 - 然后调用
AssetBundle.LoadAsset<GameObject>("mogucolor)
,提取其中的mogucolor.prefab
资源供Unity后面使用。 - 最后使用
GameObject.Inistatiate()
函数将mogucolor.prefab
实例化出来。
在Profiler监控窗口中,选中Momory
,然后在下方选择Detailed
。执行实例化加载流程,然后每一步点击Task Sample Editor
进行性能数据采样,你会发现下方有以下5大类:
- Other
- Assets
- Not Saved
- Builtin Resources
- Scene Momory
通过记录发现,每一步各个类型数据下都各有变化,以下是变化情况:
1 | 第一步:调用Init函数初始化`AssetBundleManifest` |
可以发现,第一和第二步,主要是增加了NotSaved里面的AssetBundle。第三步增加的是Assets里面的资源数据。第四步增加的是场景里面的数据,至于NotSaved和Builtin Resources数据也被动增加,主要是因为新入场景的物体可能会导致渲染流程发生改变,所以内置的一些材质或者RenderTexture会有所增加。
至于Builtin Resources里面的数据,从分析上来看主要是GUI相关的的一些内置数据,以及异常Shader等。
AssetBundles 与 Shader
Shader也是一种资源,和模型、贴图资源一样,可以打包进AssetBundle。但是Shader又是一种程序,只不过是运行在GPU上的,因此为了节约性能,需要对Shader进行裁剪,将不需要的变体剔除掉。而我们自己使用AssetBundles对Shader进行打包时,也会进行剔除。以下是官方文档中的原文,介绍了如何根据需要将需要的变体打包。
1 | Interactions between Shaders and AssetBundles |
从文档总结就是,假设我们有一个Shader定义了多个关键字,如果我们只想打包某几个关键字相关的变体时,我们可以采用以下几种方式:
- 创建与变体相对应的材质,然后将这些材质一起打包到这个Shader的AssetBundle中
- 创建变体集,如ShaderVar.shadervariants,以shadervariants为后缀的资源,在这个变体集中定义变体,然后将变体集一起打包到这个Shader的AssetBundle中
- 使用Graphics Settings,(暂时不知道怎么使用)。
有一个很重要的点是,一个Shader可以打包进多个AssetBundle,各个AssetBundle中所生成的变体是相互独立的,最终运行时也是相互独立看待。
Shader变体分析
既然谈到Shader变体,这里就再多说几句,在Unity中Shader变体主要是受Shader关键字控制,当然根据官网描述,一个shader里面拥有多个Pass,也是形成变体的因素。
这里主要是谈一下关键字,首选关键字的增加,对于变体的数量影响很大,最极端的情况是变体的数量是关键字数量的指数级。所以我们需要控制关键字的数量,尽量减少关键字的使用。
假设当前关键字的数量已经是最优的了,我们还可以控制关键字的搭配来控制变体的数量。上一节提到了变体与关键字的关系,就是通过材质和shadervariants等形式,来限定哪些变体生成。
但是目前Unity在关键字上还有一个限制,就是关键字数量不超过256,其中Unity内部已经占用了一部分,所以留给我们用来定义关键字的数量根本够不到256。所以在定义关键字时是需要精打细算的。
在Unity2018版本以及之前的版本,关键字只有global全局关键字,也就是说,所有Shader定义过的关键字数量之和不得超过256,这个限制就非常大,如果使用一些第三方资源,有可能会突破这个限制,最终导致项目崩溃等问题。
Unity2018之后的版本,关键字分为了local和global两种,其中global还是延续了全局关键字的逻辑,也就是所有shader共享的,但是local关键字是从属于当前定义的shader。关键字256的数量限制还在,只不过是local和global两种关键字的总和。怎么理解呢?
举个例子,现在只有两个Shader,Shader1和Shader2,Shader1中定义了100个global关键字,50个local关键字,Shader2中定义了90个global关键字,49个local关键字,这些关键字没有重复的。那么在Shader1看来,gloabl关键字有190个,local关键字有50个,两者之和没有超过256。那么在Shader2看来,gloabl关键字有190个,local关键字有49个,两者之和没有超过256。但是将所有关键字都加起来其实是超过256的。但是这两个Shader的变体组合是有效的。也就是说新的关键字规则将一部分全局关键字的份额分配给了局部关键字,具体来说就是192个全局关键字,和64个局部关键字。
关于关键字的上限,以及因为这个上限导致的问题在这里有很多讨论,其中网友发现问题最多的是使用HDRP时,很容易超出限制,以及使用AssetStore里面的资源也会很容易超出限制。所以网友希望能够直接提高上限,或者给个上限可选项,但是Unity客服表示不太愿意这样做。这个问题提出了好几年了,关键字分为了local和global两种也是基于这个问题给出的一个解决方案。
从程序优化的角度来讲,控制关键字的数量是非常有必要的,因此我们首先需要知道有哪些关键字被使用的了,Unity本身并没有提供这个功能(我目前没看到,并且从上面的讨论链接里面,Unity客服也表示这个很难实现)。但是广大的开发网友开发了一些工具可以用来辅助分析关键字。这里我知道的有两个:
- Shader Control :Shader Control 是一款功能强大的编辑器插件,让你可以完全控制着色器的编译和关键字的使用以及它们在游戏中的影响(广告是这么说的,我穷没用过)
- Shader Keywords Tool UNITY3D :免费开源的,用了一下下,可用。
另外这里以及这里详细的介绍了变体相关的东西,包括变体数量的计算,以及如何控制变体的数量。而且Unity还提供scriptable shader variants stripping
自定义变体剔除的方法,只要是实现IPreprocessShaders,这个回调接口在打包项目和打包Assetbundle资源时都会调用。
Create Parent For Transforms
扩展命令
1 | using UnityEngine; |
Unity Package Menu Item
Package扩展命令
1 | using System; |
Unity资源逆向解析
Asset Ripper
支持打开打包后的资源,对于图片等资源可以直接预览,但是Shader只能查看到名字属性等基本信息,里面的代码变体等信息都是假的。
下载链接
UABE(AssetsBundleExtractor)
支持解析打包后的资源,但是好像只能罗列出资源信息,不能像Asset Ripper
一样完美的将资源解包出来。但是支持修改打包后的资源,这是其他工具所不具备的。
下载链接
社区
AssetStudio
和Asset Ripper
一样,支持打开打包后的资源,但是Shader信息展示的更多一点,可以看到变体信息,但是代码块部分仍然看不了。
下载链接
关于Unity Resources
Unity项目运行时可以通过Resources和AssetsBundle方式读取资源。推荐是使用AssetsBundle,但是Resource资源的组织方式影响着包体的大小,所以即便项目中没有使用到Resources资源读取方式,也还是需要有所了解。
- 首先Resources资源读取方式,需要将资源放置在Assets目录及其子目录下的Resources目录。只有在这个文件夹下的资源才可以使用Resources的方式读取。
- 打包项目工程时,Resources下的资源会直接打到项目App,如Apk。
- Resources资源在App中,是存储在
resources.assets
、resources.assets.resS
、sharedassets0.assets
、sharedassets0.assets.resS
文件中。 - 关于
*.assets
和*.assets.resS
两个后缀文件,同名文件中的*.assets存储的是序列化文件。而*.assets.resS
里面存储的是二进制数据源,如图片、音效等。 *.assets
文件类似于Prefab
,存储着资源的所有序列化信息,以及对二进制数据源的引用,比如说图片,而*.assets.resS
里面便是*.assets
里面所引用的实际的图片、音效等数据。- Resources目录下的资源是打包到
resources*
中还是打包到sharedassets*
文件中,是有一定的规律。未被引用的资源是打包到resources*
中,比如新放入的图片还没有使用。而被引用的资源是被打包到sharedassets*
,比如被其他Prefab引用的图片。
除了我们自己创建的Resources资源,Unity在打包时还会拷贝一些默认资源,比如内置的着色器,按钮图集等等。这些资源是被统一打包到unity default resources
文件中(实际操作是将这个文件直接拷贝到包内,这个资源文件是平台预先就打包好的)。unity default resources
资源实际上是*.assets
和*.assets.resS
文件的合体,也就是包含序列化数据和二进制源数据。整个文件有好几兆,但是实际项目中我们可能不会使用这些内置资源,所以无疑会导致包体变大,资源空间浪费。而且在苹果IOS查重时,这个文件还可能被标记。所以可以适当的修改这个文件。
以上这些Resources资源文件,都是可以使用上面的解包工具打开,可以查看到这些文件中具体包含了哪些资源。