Resources资源加载
同步和异步
同步就是我等你啊,异步就是我不等你啊。
同步:排队等饭的苦逼打工人:你去餐厅点了一份炒饭,必须 站在柜台前干等,直到厨师做好饭递给你,中间不能做其他事。
异步:边等外卖边刷剧的聪明人:你在外卖平台下单后,不用干等,直接回家刷剧。外卖到了,骑手打电话通知你取餐。
Resources同步加载
加载预制体对象
// Resource.Load的本质,只是将这个对象数据配置在内存中,但是并没有创建
GameObject go = Resources.Load<GameObject>("预制体文件资源");
Instantiate(go);
加载各种资源
Resources.Load<AudioClip>("音频文件路径");
Resources.Load<TextAsset>("文本文件路径");
Resources.Load<Sprite>("图片文件路径");
Resources.Load<RuntimeAnimatorController>("状态机文件资源");
Resources异步加载
配合协程进行异步加载
public void AsyncLoad()
{
// 使用协程加载资源
StartCoroutine(Load());
}
private IEnumerator Load()
{
ResourceRequest rr = Resources.LoadAsync<TextAsset>("文本资源路径");
while(!rr.isDone)
{
yield return null;
}
//asset 是资源对象 加载完毕过后 就能够得到它
string content = (rr.asset as TextAsset).text;
print(content);
}
Resources资源卸载
重复加载载不会浪费内存
当一个资源被加载后,将会把它放入内存进行缓存。如果再一次使用,则会从内存中查找。
但是会浪费性能(每次加载都会去内存中查找并取出,始终伴随一些性能消耗)
卸载指定资源
仅适用于卸载非GameObject和Component的资源,如纹理、材质、音频等。
必须保证该资源未被任何活跃对象引用,否则调用无效。
因为精准卸载指定资源,所以会立刻生效。无显著性能开销。
Resouces.UnLoadAsset(指定资源);
卸载当前场景未被引用的资源
遍历所有加载的资源,然后检查是否还有被任何对象引用,如果没有,则卸载它们。
因为会遍历所有资源,所以开销很大。不建议使用。
资源加载后会被缓存到内存中,即使不再使用,但它可能依然在内存中,如果游戏或应用中有大量的资源加载和卸载操作,可能会导致内存占用过高。
而Resources.UnloadUnusedAssets()就是删除缓存在内存中的资源,从而减少内存的占用。
// 使用示例
public class ResourceManager : MonoBehaviour
{
void Start()
{
// 在游戏启动时或某个特定时刻调用
StartCoroutine(UnloadUnusedAssetsRoutine());
}
IEnumerator UnloadUnusedAssetsRoutine()
{
yield return Resources.UnloadUnusedAssets(); // 卸载未使用的资源
Debug.Log("Unused assets have been unloaded.");
}
}
AB包
AB包的定义
AB可以对Unity中的资源(场景、模型、贴图等)进行打包为独立文件,供运行时按需进行加载。
主资源包文件
实际打包的资源(如预制体、贴图、音频等)
清单文件
AB包内的资源列表和对应资源的Hash值
主清单文件
全局记录所有AB包的依赖关系
AB包的作用
减少初始安装包体积,支持动态加载,实现热更新
Asset Bundle Browser
常用参数
BuildTarget:目标平台
OutPutPath:目标输出路径
如果添的是:ABPackage/PC
实际上就是:项目根目录/Assets/ABPackage/PC
Clear Folders:是否清空文件夹,重新打包
Copy To StreamingAssets:是否拷贝到StreamingAssets文件夹中
Compression:压缩方式
NoCompression: 不压缩、解压快,包大(不推荐)
LZMA:压缩最小,解压慢。缺点:用一个资源压解压所有
LZ4:压缩(相对于LZMA大),用什么解压什么,内存占用低(建议)
构建AB的步骤
打AB包
通过BuildPipeline.BuildAssetBundles构建AB包到指定目录下
加载和卸载AB资源
先加载AB包 AssetBundle.LoadFromFile
在从AB包加载对应的资源 ab.LoadAsset
构建AB包信息API
BuildPipeline.BuildAssetBundles
将项目资源打包为AB包,返回一个记录了所有生成AssetBundle的元数据信息
参数一:生成AB包文件的输出路径(路径存在且具有写入权限)
参数二:构建选项BuildAssetBundleOptions
None:默认选项LZMA压缩,增量构建。(增量构建:如果资源修改,只会构建修改的资源,原来已经构建的资源不会重新构建。LZMA压缩:有着很高的压缩比)
参数三:BuildTarget指定目标平台(不同平台的 AssetBundle 不兼容)
// 示例代码
namespace DYFramework.Examples.Editor
{
public class AbBuild
{
[MenuItem("DYFramework/Example/1. 构建AB包")]
static void Build()
{
if (!Directory.Exists(Application.streamingAssetsPath))
{
Directory.CreateDirectory(Application.streamingAssetsPath);
}
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath,
BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
}
}
}
AB包的加载
本地同步加载
// 从本地路径加载AB包
AssetBundle localBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/characters.assetbundle");
// 加载资源
GameObject playerPrefab = localBundle.LoadAsset<GameObject>("Player");
Instantiate(playerPrefab);
远程异步加载
IEnumerator LoadRemoteBundle() {
string url = "http://your-server.com/characters.assetbundle";
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success) {
AssetBundle remoteBundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject enemyPrefab = remoteBundle.LoadAsset<GameObject>("Enemy");
Instantiate(enemyPrefab);
} else {
Debug.LogError("加载失败: " + request.error);
}
}
带缓存的加载
AB的卸载
1. 当为true时,卸载AssetBundle及其所有已加载资源。如果场景中有对象引用了这些资源,会导致引用丢失(如材质变粉、模型消失)
2. 当为false是,仅卸载AssetBundle文件的内存镜像,不卸载已加载的资源。资源实例会留在内存中,需要手动管理。
// 示例代码
AssetBundle bundle = AssetBundle.LoadFromFile("characters.assetbundle");
GameObject obj = bundle.LoadAsset<GameObject>("Player");
Instantiate(obj);
// 卸载AB包及所有资源(即使资源已被实例化)
bundle.Unload(true);
// 此时obj可能变为"Missing"(若场景中仍有引用)
bundle.Unload(false);
// 已实例化的GameObject仍存在,但无法通过bundle再次加载
AB包的依赖
依赖加载的定义
当不同的AB包之间共享资源时,比如材质、纹理或脚本,这些共享资源会被打包到一个公共的AB包中,而其他使用这些资源的AB包则会依赖这个公共包。例如,如果“角色”AB包和“场景”AB包都使用了同一个材质,那么这个材质可能被打包到“shared_materials”AB包中,这样角色和场景AB包在加载时都需要先加载这个共享包。如果不处理依赖关系,可能会导致资源丢失,比如角色模型显示为紫色(材质丢失)。
当打包AB包时,Unity会自动分析资源之间的引用关系,并生成依赖信息。这些信息存储在AB包的清单文件(.manifest)中,以及主清单文件(AssetBundleManifest)中。主清单文件记录了所有AB包之间的依赖关系,可以通过它来查询某个AB包依赖的其他包。
依赖加载的流程
1. 加载主清单文件
AssetBundle manifestBundle = AssetBundle.LoadFromFile("AssetBundles/AssetBundles");
AssetBundleManifest manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
2. 获得依赖链
string targetBundle = "characters";
string[] dependencies = manifest.GetAllDependencies(targetBundle); // 获得characters的AB包中所有依赖包
3. 按顺序加载依赖包
foreach (string depBundle in dependencies) {
AssetBundle.LoadFromFile($"AssetBundles/{depBundle}");
}
4. 加载目标依赖包
AssetBundle mainBundle = AssetBundle.LoadFromFile("AssetBundles/characters");
GameObject playerPrefab = mainBundle.LoadAsset<GameObject>("Player");
AB包资源加载管理器策略
详细见DYFramework框架
Addressables
Addressables的定义
将资源(如预制体、贴图、场景)标记为“可寻址”(Addressable),通过逻辑标签(Label)或唯一Key动态加载,无需手动管理AB包的依赖和路径。
简化资源加载/卸载流程,自动化处理依赖关系,支持本地和远程资源分发。
Addressable 的核心组件
Lua语法
安装Lua脚本,并测试成功运行
下载地址:https://kkgithub.com/rjpcomputing/luaforwindows
下载后,无脑点点点
基本语法
第一个Hello world
print("hello world")
注释
--这是注释内容
数据类型
type
通过type可以得到对于的类型
print(type(nil))
print(type("hello world"))
print(type(123))
print(type(true))
输出
PS G:\系统默认\桌面\lua> lua .\hello.lua
nil
string
number
boolean
type的返回值时string类型的
print(type(type(nil)))
空
nil相对于一个null
-- nil 空
a = nil
使用没有赋值的变量不会报错,返回nil
print(b)
输出:nil
nil做比较时,应该加双引号
type(X) == nil
输出:false
type(X) == "nil"
输出:true
boolean类型
0为false,1为true
a = true
number类型
number可以理解为int、float、double的整合
print(type(2))
print(type(2.2))
print(type(0.2))
print(type(2e+1))
print(type(0.2e-1))
print(type(7.8263692594256e-06))
特殊的数字类型的值
print(type(2e+1))
-- 其实就是 2 × 10^1 = 20
print(type(0.2e-1))
-- 其实就是 0.2 × 10^(-1) = 0.02
print(type(7.8263692594256e-06))
-- 其实就是7.8263692594256 × 10 ^ (-6)
table表
table可以是一个空表,也可以往里面添加数据
-- 创建一个空的 table
a = {}
-- 直接初始表
b = {"apple", "pear", "orange", "grape"}
table是一个关联数组,数组的索引可以是数字,也可以是字符串
a = {}
a["key"] = "value"
-- 输出:
table: 0xe8ec650
value
b = {}
b[10] = 22
b[10] = b[10] + 10
print(b[10])
-- 输出:32
table的默认索引是从1开始的
a = {"apple", "pear", "orange", "grape"}
print(a[1])
print(a[2])
print(a[3])
print(a[4])
-- 输出
apple
pear
orange
grape
变量
全局变量
-- 全局变量a
a = 10
-- 全局变量b
function joke()
b = 5
end
局部变量
变量前加local都是局部变量
-- 局部变量
local a = 5
赋值语句
lua可以对多个变量赋值
a,b = 10, 2*x
会先计算右边的值,再进行赋值操作
-- 交换x和y
x,y = y,x
当变量个数与值的个数不一致时
-- 变量个数 > 值的个数,按变量个数补nil
a,b,c = 1,2
输出:
1,2,nil
-- 变量个数 < 值的个数,多余的值会被忽略
a,b = 1,2,3
输出:
1,2
循环
while循环
-- 先去判断条件,如果条件到了执行statements语句,如果条件没有达到,则直接退出
while(condition)
do
statements
end
for循环
-- var是循环变量,表示当前的值
=- exp1表示循环变量的初始值
-- exp2表示循环的终止条件
-- var的增量,默认为1
for var=exp1,exp2,exp3 do
<执行体>
end
简单递增循环
for i = 1,5,1 do
print(i)
end
输出:
1
2
3
4
5
步长可以省略
for i = 1,5 do
print(i)
end
输出:
1
2
3
4
5
非整数步长
for i = 0, 2, 0.5 do
print(i)
end
输出
0
0.5
1
1.5
2
流程控制
if语句
if(布尔表达式)
then
--[ 在布尔表达式为 true 时执行的语句 --]
end
if-else语句
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
if-elseif-else语句
if( 布尔表达式 1)
then
--[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2)
then
--[ 在布尔表达式 2 为 true 时执行该语句块 --]
elseif( 布尔表达式 3)
then
--[ 在布尔表达式 3 为 true 时执行该语句块 --]
else
--[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
函数
基本语法
function 函数名(参数1, 参数2, ...)
-- 函数体
return 返回值 -- 可选
end
function可以理解为第一类值,函数可以存储在变量中
-- 无参
function greet()
print("Hello, Lua!")
end
greet() -- 调用函数,输出: Hello, Lua!
-- 有参
function add(a, b)
return a + b
end
print(add(3, 5)) -- 输出: 8
函数可以返回多个值
function getCoordinates()
return 10, 20
end
x, y = getCoordinates()
print(x, y)
-- 输出: 10 20
函数可以使用匿名函数
myFunction = function(name)
print("Hello, " .. name)
end
myFunction("Alice")
-- 输出: Hello, Alice
函数参数
函数可以作为参数传递
function apply(func, value)
return func(value)
end
function double(x)
return x * 2
end
print(apply(double, 5)) -- 输出: 10
函数可以接收可变的参数
function prints(...)
print(...)
end
prints(1,2,3,4,5,6)
输出:
1 2 3 4 5 6
-- 获得传入参数的个数
function prints(...)
local n = select('#', ...)
print(n)
end
prints(1,2,3,4,5,6,9,91)
输出;8
-- 返回从起点 n 开始到结束位置的所有参数列表
function prints(...)
print(select(2, ...))
end
prints(1,2,3,4,5,6,9,91)
输出:
2 3 4 5 6 9 91
特殊的变量名 _
-- _ 可以理解为一个占位符,不需要使用的变量
function sum(a,b,c)
return a + b,c
end
local sum,_ = sum(1,2,5)
print(sum)
print(_)
输出
3
5
运算符
一元运算符
逻辑运算符
or
从左到右依次计算操作数
如果某个操作数的值为真值(只要不是false和nil都是真值),则立刻返回该操作值,并停止后续计算
如果所有操作数都为假值,则返回最后一个操作数的值
print(a or b)
输出:
nil
print(a or 1)
输出:
1
print(3 or 1)
输出:
3
其他运算符
连接两个字符串
print("hello" .. "world")
输出:
helloworld
返回字符串或表的长度
print(#"hello" .. "world")
输出:
5world
字符串
单引号和双引号都是字符串
a = "hello"
a = 'wolrd'
使用[[]]来表示一块字符串
html = [[
<html>
<head></head>
<body>
hello world
</body>
</html>
]]
print(html)
输出
<html>
<head></head>
<body>
hello world
</body>
</html>
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字
print("2" + 6)
-- 输出:8
print("2" + "6")
-- 输出:8
print("-2e2" * "6")
-- 输出:-1200
print("error" + 1)
-- 直接报错
字符串连接使用的是 ..
print("a" .. 'b')
-- 输出:ab
使用#计算字符串的长度
len = "hello world"
print(#len)
-- 输出:11
数组
迭代器
泛型for迭代器
基本语法
-- k,v是变量列表,t表达式列表
for k,v paris(t) do
print(k,v)
end
-- 例子
array = {"Google", "Runoob"}
for k, v in pairs(array) do
print(k, v)
end
输出:
1 Google
2 Runoob
泛型for的执行过程
初始化阶段:泛型for需要三个参数(迭代函数、状态值、初始值),再循环开始之前,lua会将这三个参数分别赋值给内部变量(f-迭代函数、s-状态值、var-初始值)
每次迭代的执行,lua都会调用迭代函数f(s,var),迭代函数会返回一个新的值,作为当前循环的变量
如果迭代函数返回nil,则循环终止
以上步骤会不断重复
-- 迭代函数等价于这段
local f, s, var = function(state), state, initial
while true do
local var = f(s, var)
if var == nil then break end
-- 循环体
end
迭代函数ipars
-- 用于遍历数组的部分表
local t = {10, 20, 30}
for key, value in ipairs(t) do
print(key, value)
end
输出
1 10
2 20
3 30
执行过程分析
ipars返回三个值迭代函数f=next_ipairs、状态值s=t、初始值var=0
第一次迭代:调用f(s,var)即next_ipairs(t,0),返回键1和值10
第二次迭代:调用f(s,var)即next_ipairs(t,1),返回键2和值20
第三次迭代:调用f(s,var)即next_ipairs(t,2),返回键3和值30
第四次迭代:调用f(s,var)即next_ipairs(t,3),返回nil,循环终止
无状态迭代器
每次调用迭代器函数时,它只根据当前输入计算下一个值,而不需要维护额外的状态信息
多状态迭代器
-- 自定义无状态迭代器
function count_iterator(start, step)
return function(_, current)
current = current + (step or 1)
if current > 3 then -- 假设上限为 100
return nil
end
return current, current * 2 -- 返回当前值和它的两倍
end, nil, start - (step or 1) -- 状态值和初始值
end
for i, v in count_iterator(1, 2) do
print(i, v)
end
-- 分析
循环开始之前,将迭代函数、状态值、初始值赋值给内部变量
即 迭代函数为匿名函数、状态值为nil、初始值为 -1
第一次循环,调用匿名函数,返回1,2
表
模块与包
元表
协同程序
-- lua中一个汉字占3个字符长度
a = "helloworld"
print(#a)
a = "中文"
print(#a)
输出
PS G:\系统默认\桌面\lua> lua .\hello.lua
10
6
a = [[hello
world]]
print(a)
输出
PS G:\系统默认\桌面\lua> lua .\hello.lua
hello
world
a = "hello\nworld"
print(a)
输出
PS G:\系统默认\桌面\lua> lua .\hello.lua
hello
world
xLua
toLua
元
评论区