AssetBundle AB包
AB包就是用于特定平台的资产压缩包。它的作用是:拆分资源、缩包体;支持热更新。
Asset Bundle Browser 打包AB包
打包AB包可以用到Unity中提供的打包工具:Asset Bundle Browser。(需要在Window - Package Manager - Asset Bundle Browser中安装)
打包方法
- 在Project窗口中选择要打包的内容,在Inspector窗口下方的AssetBundle取一个包名
- 在AssetBundle的Build页签中选择Build Target打包平台、OutputPath输出路径、建议选择Clear Folders和Copy to StreamingAssets;高级设置中压缩方式选择LZ4 (用什么解压什么,内存占用低)
- Build
打包好后可以在与Asset同级的AssetBundle文件夹下找到打包好的文件。
使用方法
- 同步加载
1 2
| AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "model");
|
1
| ab.LoadAsset<GameObject>("Cube")
|
- 异步加载
1 2 3 4 5 6 7 8 9 10
| IEnumerator LoadABResourcesAsync(string ABName, string resName) { AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetPath + "/" + "model"); yield return abcr; AssetBundleRequest abq = abcr.assetBundle.LoadAsseyAsync(resName, typeof(Sprite)); yield return abq; img.sprite = abq.asset as Sprite; }
|
注意:同一个AB包不能重复加载
- 卸载AB包
1 2
| AssetBundle.UnloadAllAssetBundles(false);
|
另外,如果一个AB包中的资源用到了其他AB包中的资源,需要加载用到资源的那个AB包。其中,可以通过主包得到依赖信息:
- 加载主包
1
| AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "PC");
|
- 加载主包中的固定文件
1
| AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
|
- 从固定文件中得到依赖信息的名字
1
| string[] strs = abManifest.GetAllDependencies("model");
|
Lua语法
Lua 代码文件的后缀名为.lua。
注释
变量
在默认情况下,变量总是认为是全局的。用 local 显式声明为局部变量。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。
多变量赋值
如果后面的值不够,会自动补空;如果后面的值多了,会自动省略。
数据类型
| 数据类型 |
描述 |
nil |
这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean |
包含两个值:false和true。 |
number |
所有的数值都是number。表示双精度类型的实浮点数 |
string |
字符串由一对双引号或单引号来表示 |
function |
由 C 或 Lua 编写的函数 |
userdata |
表示任意存储在变量中的C数据结构 |
thread |
表示执行的独立线路,用于执行协同程序 |
table |
Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。 |
注意:Lua中的变量可以随便赋值。 |
|
我们可以使用 type 函数测试给定变量或者值的类型:
1 2 3 4 5 6 7
| print(type("Hello world")) print(type(10.4*3)) print(type(print)) print(type(type)) print(type(true)) print(type(nil)) print(type(type(X)))
|
nil 空
nil 类型表示一种没有任何有效值,它只有一个值 —— nil,例如打印一个没有赋值的变量,便会输出一个 nil 值:
对于全局变量和 table,nil 还有一个”删除”作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉
nil 作比较时应该加上双引号 ":
1 2 3 4 5 6
| type(X) nil type(X)==nil false type(X)=="nil" true
|
type(X)==nil 结果为 false 的原因是 type(X) 实质是返回的 "nil" 字符串,是一个 string 类型。
boolean 布尔
boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是 false,其他的都为 true,数字 0 也是 true
number 数字
Lua 默认只有一种 number 类型 —— double(双精度)类型
string 字符串
字符串由一对双引号或单引号来表示。
1 2
| string1 = "this is string1" string2 = 'this is string2'
|
也可以用 2 个方括号 “[[]]” 来表示”一块”字符串。
1 2 3 4 5 6 7 8 9
| html = [[ <html> <head></head> <body> <a href="http://www.runoob.com/">菜鸟教程</a> </body> </html> ]] print(html)
|
以下代码执行结果为:
1 2 3 4 5 6
| <html> <head></head> <body> <a href="http://www.runoob.com/">菜鸟教程</a> </body> </html>
|
字符串操作
注意:一个汉字占3个长度
也可以这样:
1
| print(string.format("我是小草, 我今年%d岁了", 18))
|
| 符号 |
含义 |
%d |
与数字拼接 |
%a |
与任何字符拼接 |
%s |
与字符配对 |
1 2
| str = "abCdefg" string.find(str, "Cde")
|
结果为:
从第3个位置向后截取。
还有一个重载:
截取这两个位置之间的字符。
1
| string.gsub(str, "Cd", "**")
|
1 2 3
| // 参数1:字符串 // 参数2:要转为ASCII码的字符 string.byte("Lua", 1)
|
结果为:L
function 函数
基本语法
基本语法:
或者这样写:
参数与返回值
注意:必须声明后才能调用。因为Lua是从上到下依次执行。
参数可以传任意类型。
1 2 3
| function F1(...) arg = {...} // 变长参数要用一个表存起来 end
|
1 2 3
| function F1(a) return a end
|
多返回值时,在前面申明多个变量来接取即可。如果变量不够,只接取对应位置的返回值;如果变量多了,直接赋值nil。
注意:Lua不支持重载,默认调用申明的最后一个函数
函数的嵌套
函数也支持嵌套:
1 2 3 4 5
| function F1() return function() print("F2调用") end end
|
函数的闭包
闭包的含义:在A函数里面返回一个B函数,改变了A函数中变量的生命周期。
函数的闭包:
1 2 3 4 5
| function F1(x) return function(y) return x + y end end
|
1 2
| f2 = F1(10) print(f2(5))
|
输出:
table 表
数组
Lua中数组的申明可以是不同类型:
1
| a = {1, 2, 3, "123", true}
|
注意:Lua中索引从1开始
打印长度时如果nil在最后,则被忽略
迭代器遍历
ipairs
通过这种遍历,是从1开始往后遍历的,小于等于0和索引中断的都不能遍历得到
1 2 3
| for i, k in ipairs(a) do print(i.."_"..k) end
|
其中,i是键,k是值,a是表
pairs
这种遍历可得到任意键
1 2 3
| for i, k in pairs(a) do print(i.."_"..k) end
|
其中,i是键,k是值,a是表
也可以单独遍历键
二维数组
1
| a = {{1, 2, 3},{4, 5, 6}}
|
得到里面的元素:
1 2 3 4 5 6
| for i = 1, #a do b = a[i] for j = 1, #b do print(b[j]) end end
|
自定义索引
1
| a = {[0] = 1, 2, 3, [-1] = 4, 5}
|
可以自定义索引,不过使用索引时只会用没有自定义索引的值。比如以上的这个,长度为3,a[1] = 2。
字典
1
| a = {["name"] = "小草", ["age"] = 18}
|
或者:
修改值直接用=赋新值即可
添加值直接用表名["新键名"]即可
删除
1 2 3
| for k, v in pairs(a) do
end
|
类和结构体
注意:Lua中默认没有面向对象,需要我们自己实现
1 2 3 4 5 6 7
| Student = { age = 1, sex = true, Up = function() print("我成长了") end }
|
想要在表内部函数中调用表本身的属性和方法,一定要指明表本身的属性或方法:
1 2 3 4 5 6 7 8
| Student = { age = 1, sex = true, Up = function(t) print(t.age) print("我成长了") end }
|
调用时:
上面这句话也等价于:
其中,:的含义是默认把调用者作为第一个参数传入方法中。
1 2 3
| Student.Speak = function() print("你好") end
|
另外,Lua中有一个关键字self表示默认传入的第一个参数。
1 2 3
| function Student:Speak() print(self.name .. ":你好") end
|
大G表
_G是一个总表,它将我们声明的所有全局变量都存储在其中
表的公共操作
接下来用这两张表实例:
1 2 3
| t1 = { {age = 1, name = "小草"}, {age = 2, name = "小花"} }
t2 = { name = "小树", sex = true }
|
第二个参数是可选参数,不传默认移除最后一个元素,传数字n表示删除下标位n的元素。
1 2 3 4
| table.sort(t2, function(a, b) if a > b then return true end)
|
第二个参数表示用于拼接的字符,返回值是一个字符串。注意:这个方法只适用于表中全是数字的情况。
元表
元表是一张普通 table,用来给另一个表(或用户数据)定义元方法,修改它的运算、访问、赋值行为,实现面向对象、重载运算符、自定义读写逻辑。
1 2 3 4 5 6 7 8 9
| meta = { __tostring = function() return "转为字符串" end } myTable = {} setmetatable(myTable, meta)
print(myTable)
|
当子表要被当做一个字符串使用时,默认调用元表中的__tostring方法
传入的参数默认是myTable这个表本身
1 2 3 4 5 6 7 8 9
| meta = { __call = function() print("作为函数") end } myTable = {} setmetatable(myTable, meta)
myTable()
|
当子表要被当做一个函数使用时,默认调用元表中的__call方法
传入的第一个参数默认是myTable这个表本身
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
| meta = { __add = function(t1, t2) return t1.age + t2.age end, __sub = function(t1, t2) return t1.age - t2.age end, __mul = function(t1, t2) return t1.age * t2.age end, __div = function(t1, t2) return t1.age / t2.age end, __mod = function(t1, t2) return t1.age % t2.age end, __pow = function(t1, t2) return t1.age ^ t2.age end, __eq = function(t1, t2) return 0 end, __lt = function(t1, t2) return 0 end, __le = function(t1, t2) return 0 end, __concat = function(t1, t2) return "" end } myTable = {age = 1} setmetatable(myTable, meta) myTable1 = {age = 2}
print(myTable + myTable1)
|
1 2 3 4 5 6 7 8
| meta = { age = 1 } meta.__index = {age = 2} myTable = {} setmetatable(myTable, meta)
print(myTable.age)
|
当子表中找不到某一个属性时,会到元表中__index指定的表去找索引(建议把__index写在外部来避免问题)
打印结果是
另外,如果想找自己身上的变量,可以用下面这个方法:
当赋值时,如果赋值一个不存在的索引,那么会把这个值赋值到newindex所指的表中,不会修改自己
1 2 3 4 5 6 7 8
| meta = {} meta.__newindex = {} myTable = {} setmetatable(myTable, meta) myTable.age = 1
print(myTable.age) print(meta.__newindex.age)
|
打印结果是
同样的,如果想设置自己身上的变量,可以用下面这个方法:
1
| rawset(myTable, "age", 6)
|
运算符
- 算术运算符
- 与
C#的区别:没有++、--、+=、-=、/=、*=、%=;有^幂运算
- 特点:字符串可以进行算术运算符操作,会自动转成
number
- 条件运算符
- 逻辑运算符
- 位运算符(不支持)
- 三目运算符(不支持)
注意:
and和or不仅可以连接boolean,任何东西都可以用来连接。在Lua中,只有nil和false才认为是假。
另外,还存在“短路”的使用。and有假则假;or有真则真。
在这个例子中,1为真,所以需要判断2是否为假,返回值就是2
在这个例子中,true为真,所以返回结果就是true
我们还可以用and和or来模拟三目运算符:
1 2 3
| x = 1 y = 2 (x > y) and x or y
|
选择结构
1 2 3 4 5
| if 条件 then print("满足条件") else print("不满足条件") end
|
另外,多分支写作elseif
Lua中没有switch-case结构
- 不支持三目运算符
循环结构
意思是i从1开始,默认+1,到5结束。
第三个数字是增量。
多脚本执行和卸载
执行
在一个脚本中相执行另一个脚本,可以在这个脚本中这样写:
可以在这个脚本中使用另一个脚本里面的全局变量。
要想使用里面的本地变量可以在要被加载的脚本中返回需要的变量。
注意:如果是require加载执行的脚本,加载一次后不会再被执行
下面这句代码返回值是boolean,可以通过这个判断脚本有没有被执行:
卸载
卸载:
1
| package.loaded["脚本名"] = nil
|
协程
创建、运行和挂起
创建协程有两种方式,不同的方式对应不同的运行方式:
create创建协程
1
| co = coroutine.create(函数)
|
运行结果就是123,相当于执行了这个函数。
第一个返回值:协程是否启动成功
第二个返回值:yield里面的返回值
wrap创建协程
1
| co2 = coroutine.wrap(函数)
|
返回值就是传入的参数
状态
得到协程的状态:
可能得到的返回值:
dead 结束
suspended 暂停
running 进行中
得到当前正在运行的协程的线程号:
面向对象
Lua中比较复杂的数据结构都是通过table来实现的。
封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Object = {} Object.id = 1
function Object : Test() print(self.id) end
function Object : new() local obj = {} self.__index = self setmetatable(obj, self) return obj end
local myObj = Object:new() print(myObj) print(myObj.id) myObj:Test()
|
其中,:会自动将调用这个函数的对象作为第一个参数传入。
继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Object = {} Object.id = 1
function Object : subClass(className) _G[className] = {} local obj = _G[className] self.__index = self setmetatable(obj, self) end
Object:subClass("Person")
local p1 = Person:new()
|
p1的元表是Person;Person的元表是Object。找不到的属性会依次往上找。
多态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Object = {}
Object : subClass("GameObject") GameObject.posX = 0 GameObject.posY = 0 function GameObject : Move() self.posX = self.posX + 1 self.posY = self.posY + 1 print(self.posX) print(self.posY) end GameObject : subClass("Player") function Player : Move() end
local p1 = Player:new() p1:Move()
|
不过实现多态有一个问题,那就是没有办法通过base.保留父类的方法,因此,我们需要实现base的功能:
我们可以在subClass()中加上:
1 2 3 4 5 6 7 8
| function Object : subClass(className) _G[className] = {} local obj = _G[className] self.__index = self obj.base = self setmetatable(obj, self) end
|
然后我们就可以使用了:
1 2 3
| function Player : Move() self.base.Move(self) end
|
自带库
时间戳
1
| os.time({year = 2026, month = 7, day = 17})
|
数学运算
1 2
| math.randomseed(os.time()) math.random(!00)
|
路径
垃圾回收
- 获取当前Lua占用内存数、K字节(用返回值
*1024就可以得到具体的内存占用字节数)
1
| collectgarbage("collect")
|