了解AB包
什么是AB包
AssetBundle简称AB包
它是特定于平台的资产压缩包,有点类似压缩文件
资产包括:模型、贴图、预设体、音效、材质球等等
AB包的作用
相对Resources下的资源AB包更好管理资源
1.能够减小包体大小:压缩资源、减少初始包大小
2.能够实现热更新:资源热更新、脚本热更新
AssetBundle Browser使用
其他选项作用:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ABTest : MonoBehaviour
{
public Image img;
void Start()
{
//关于AB包的依赖一个资源身上用到了别的AB包中的资源 这个时候 如果只加载自己的AB包
//通过它创建对象 会出现资源丢失的情况
//这种时候 需要把依赖包 一起加载了 才能正常
//第一步加载AB包
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath+'/' + "model");
//加载依赖包
//AssetBundle ab2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + '/' + "texture");
//往往不会主动加载依赖包,因为一个资源包可能依赖很多其他的资源包,自己一个一个加很麻烦
//利用主包来得到依赖信息
//加载主包
AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + '/' + "PC");
//加载主包中的固定文件
AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//从固定文件中得到依赖信息
string[] strs = abManifest.GetAllDependencies("model");
for(int i = 0 ; i < strs.Length; i++)
{
AssetBundle.LoadFromFile(Application.streamingAssetsPath + '/' + strs[i]);
Debug.Log(Application.streamingAssetsPath + '/' + strs[i]);
}
//第二步加载AB包中的资源
//只用名字加载会出现同名不同类型资源分不清的问题
//GameObject obj = ab.LoadAsset<GameObject>("Cube");
GameObject obj = ab.LoadAsset("Cube", typeof(GameObject)) as GameObject;//lua不支持泛型故建议使用这种
Instantiate(obj);
//卸载当前AB包,true为同时删除该包加载出来的资源
//ab.Unload(false);
//AB包不能重复加载 否则会报错
//GameObject obj1 = ab.LoadAsset("Sphere",typeof(GameObject)) as GameObject;
//Instantiate (obj1);
//异步加载->协程
//StartCoroutine(LoadABRes("texture", "CPU-Cache"));
}
IEnumerator LoadABRes(string ABname,string resName)
{
//加载AB包
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + '/' + ABname);
yield return abcr;
//加载资源
AssetBundleRequest abq = abcr.assetBundle.LoadAssetAsync(resName, typeof(Sprite));
yield return abq;
img.sprite = abq.asset as Sprite;
}
// Update is called once per frame
void Update()
{
//卸载所有加载的AB包 参数若为true 会把通过AB包加载出来的资源也卸载
if(Input.GetKeyDown(KeyCode.Space)){
AssetBundle.UnloadAllAssetBundles(true);
}
}
}
Lua语法
输出和注释
--末尾不用写分号(单行注释)
print("Hello World")
--[[
多行
注释一
]]
--[[
多行
注释二
]]--
--[[
多行
注释三
--]]
变量
lua中所有的变量申明都不需要申明变量类型,它会自动判断类型,可以随便赋值
nil:类似于C#中的null
number:所有数值都是number
string:用双引号或者单引号包裹(没有char)
bool:true/false
可以通过type()函数得到变量类型,type的返回值是string
a = nil
print(a)
print(type(a))
a=1
print(a)
print(type(a))
a= 1.2
print(a)
print(type(a))
a="123123"
print(a)
print(type(a))
a='123'
print(a)
print(type(a))
a=true
print(a)
a=false
print(a)
print(type(a))
输出如下:
lua中使用没有申明的变量(没赋值过的)不会报错,默认值是nil
print(b)
字符串
获取字符串长度
s="123456"
print(#s)
--一个汉字字符占三个长度 英文字符占一个长度
s1="一"
print(#s1)
多行打印
--lua中支持转义字符
print("123\n123")
s = [[1
2
3
4
]]
print(s)
字符串拼接
--字符串拼接通过..
print("123".."456")
s1 = 1
s2 = "2"
print(s1..s2)
s2=2
print(s1..s2)
print(string.format("abcde123%d","4"))
字符串转换
--别的类型转字符串
a = true
print(tostring(a))
a = 123
print(tostring(a))
常用方法
a = "abcDEF"
--大写转小写
print(string.upper(a))
--小写转大写
print(string.lower(a))
--翻转字符
print(string.reverse(a))
--字符串索引查找(索引从1开始)
print(string.find(a,"abc"))
--截取字符串
print(string.sub(a,3))
print(string.sub(a,3,5))
--字符串重复
print(string.rep(a,1))
--字符串修改
print(string.gsub("abcde123","%a","4"))
print(string.gsub("abcde123","a","4"))
print(string.gsub("abcde123","%A","4"))
--字符转ASCII码
a = string.byte("Lua",1)
print(a)
--ASCII码转字符
print(string.char(a))
运算符
算术运算符
+ - * / %
没有自增自减(++ --)没有复合运算符(+= -= /= *= %=)
print("add"..1+2)
a = 1
b = 2
print(a+b)
--字符串自动转换成number
print("123.4"+1)
--幂运算^
print(2^5)
条件运算符
不等于符号:~=
print(1>3)
print(1>=3)
print(1<3)
print(1<=3)
print(1==3)
print(1~=3)
逻辑运算符
与:and,或:or
非: not
逻辑运算支持短路:and前有false and后的不会执行,or前有true or后的不会执行
print(true and false)
print(true and true)
print(false or true)
print(false or false)
print(not true)
print(true and print("123"))
位运算符
不支持位运算,需要自己实现或者使用库
三目运算符
不支持三目运算
条件分支语句
a = 9
if a > 5 then
print("111")
end
if a < 5 then
print("111")
else
print("222")
end
if a < 5 then
print("111")
elseif a == 6 then
print("6")
elseif a == 6 then
print("7")
else
print("other")
end
--lua中没有switch语句,要自己实现
循环语句
while语句
a = 0
while a < 5 do
print(a)
a = a + 1
end
do while语句
a = 0
repeat
print(a)
a = a + 1
until a > 5 --满足条件跳出 结束条件
for语句
for i = 2 , 5 do--默认递增 i会+1
print(i)
end
for i = 1,5,2 do
print(i)
end
for i = 5, 1,-1 do
print(i)
end
函数
无参无返回值
--function 函数名()
--end
--a = function()
--end
function F1()
print("F1")
end
F1()
F2 = function()
print("F2")
end
F2()
有参
function F3(a)
print(a)
end
F3(1)
F3("123")
F3(true)
F3()
F3(1,2,3)
--传入参数不匹配,会自动补空nil 或者丢弃多余参数
有返回值
function F4( a )
return a , "123" , true
end
--返回多个值时 在前面申明多个变量来接取即可
--如果变量不够 接取对应前几个参数
--如果变量多了 多的几个值为nil
temp = F4("123")
print(temp)
temp , temp1 , temp2 , temp3= F4("123")
print(temp)
print(temp1)
print(temp2)
print(temp3)
函数类型
统一是function类型
F5 = function()
print("123")
return "123"
end
print(type(F5))
函数重载
lua中函数不支持重载,默认使用最近的一次申明的函数
function F6( str )
print(str)
end
function F6()
print("111")
end
F6("222")
输出为111
变长参数
function F7( ... )
arg = {...}
for i = 1 , #arg do
print(arg[i])
end
end
F7(1,"123",true,4,5,6,7)
函数嵌套
function F8()
return function ()
print("123")
end
end
f9 = F8()
f9()
--闭包
function F9(x)
a = 1
return function ( y )
return x + y +a
end
end
--父函数的参数在子函数中仍可使用
f10 = F9(10)
print(f10(5))
复杂数据类型——表
所有复杂类型都是table(表 )
数组及其遍历
a = {1,2,3,4,5,"123",true,nil,nil}
--末尾nil不计入数组长度
for i = 1,#a,1 do
print(a[i])
end
--中间的nil计入长度
a = {1,2,nil,4,5,"123",true,nil}
print(#a)
for i = 1,#a,1 do
print(a[i])
end
二维数组
a = {{1,2,3},{4,5,6}}
print(a[1][1])
print(a[1][2])
print(a[1][3])
二维数组遍历
for i = 1,#a do
b = a[i]
for j = 1,#a[i] do
print(b[j])
end
end
自定义索引
a = {[0] = 0,1,2,[-1] = -1,5}
print(#a)
for i = 1,#a do
print(a[i])
end
print(a[0])
print(a[-1])
由于 使用#来得到长度遍历 有时会出现bug故更多使用迭代器遍历
迭代器遍历
ipairs遍历
a = {[0] = 1 , 2, [-1] = 3, 4, 5,[5] = 6}
--ipairs遍历 从1开始往后遍历,小于0的键找不到
--只能找到连续索引的键如果中间断序了 就无法遍历后面的内容
for i,k in ipairs(a) do
print(i..":"..k)
end
pairs遍历
--pairs遍历能够找到所有的键
for i,k in pairs(a) do
print(i..":"..k)
end
for i in pairs(a) do
print("key:"..i)
end
复杂数据类型——字典
a = {["name"] = "111",["age"] = 11,["1"] = 5}
--可以通过中括号中填键来访问值
print(a["name"])
print(a["age"])
print(a["1"])
--还可以用类似成员变量的形式来得到值,但不能是数字
print(a.name)
print(a.age)
--修改
a.name = "222"
a["age"]=100
print(a.name)
print(a.age)
--新增
a["sex"] = false
print(a.sex)
--删除
a["sex"] = nil
print(a["sex"])
--遍历要用 pairs
for k,v in pairs(a) do
--print可以传入多个参数
print(k,v)
end
for k in pairs(a) do
--print可以传入多个参数
print(k)
print(a[k])
end
复杂数据类型——类和结构体
--lua中默认没有面向对象,需要我们自己来实现
--lua中类的表现是类中的有很多静态变量和静态函数,直接用类名来调用
Student = {
age = 11,
sex = true,
f = function ()
print("f1")
end,
f2 = function ()
print("f2")
end
}
print(Student.age)
Student.f()
--申明表后在表外也可以申明新的变量和方法
Student.name = "aaa"
Student.f3 = function ()
print("f3")
end
function Student.f4()
print("f4")
end
Student.f3()
print(Student.name)
Student.f4()
想要在表内部函数中调用表本身属性或者方法要指明是哪个表的
故使用 表名.属性 表名.方法 来访问,也可以通过传入参数的形式进行访问
Student = {
age = 11,
sex = true,
f = function ()
print(Student.sex)
print("f1")
end,
f2 = function (t)
print(t.sex)
print("f2")
end
}
Student.f2(Student)
Student.f()
Student:f2()
function Student:f5()
--lua中关键字self表示冒号申明函数传入的第一个参数
print(self.age)
end
Student:f5()
Student.f5(Student)
lua中.和:的区别: 冒号调用方法会默认把调用者作为第一个参数传入
表的公共操作
t1 = {{age = 1,name = "123"},{age = 2, name = "345"}}
t2 = {name = "name",sex = true}
--插入
print(#t1)
table.insert(t1,t2)
print(#t1)
print(t1[3].sex)
--删除指定元素
--没有第二个参数默认移除最后一个索引的内容
table.remove(t1)
print(#t1)
print(t1[1].name)
print(t1[2].name)
print(t1[3])
--第二个参数为要移除内容的索引
table.remove(t1,1)
print(t1[1].name)
print(#t1)
--排序
t2 = {5,2,7,9,5}
--默认升序
table.sort(t2)
for _,v in pairs(t2) do
print(v)
end
--降序排序
table.sort(t2,function(a,b)
if a>b then
return true
end
end)
for _,v in pairs(t2) do
print(v)
end
--拼接
tb = {"123","456","789","10101"}
--用于拼接表中元素,返回值是一个字符串,有一定限制,bool和nil不能拼接
str = table.concat( tb, ", ")
print(str)
多脚本执行
全局变量和本地变量
--全局变量
a = 1
b = "123"
for i = 1,2 do
c = "aaa"
end
print(c)
--局部变量
for i = 1,2 do
local d = "aaa"
print(d)
end
print(d)
fun = function ()
tt = "123123"
end
print(tt)
fun()
print(tt)
多脚本执行
--关键字 require("脚本名")
require("Test")--同一目录下
print(x)--别的脚本的局部变量不可访问,但是可以通过return的方式返回该局部变量使用
Test
print("Test")
testA = "123"
local x = "456"
local的x在另一个脚本中无法访问
脚本卸载
--如果是require加载执行的脚本 加载一次过后不会再被执行
require("Test")
--package.loaded["Test"]返回bool值,意为改脚本是否被执行
print(package.loaded["Test"])
--卸载已执行过的脚本
package.loaded["Test"] = nil--false也可
print(package.loaded["Test"])
require("Test")
大G表
for k,v in pairs(_G) do
print(k,v)
end
--局部变量不会存入大G表中
特殊用法
多变量赋值
a,b,c=1,2,"123"
print(a)
print(b)
print(c)
--多变量赋值 如果后面的值不够 会自动补空
a,b,c = 1,2
print(a)
print(b)
print(c)--ni1
--多变量赋值 如果后面的值多了 会自动省略
a,b,c=1,2,3,4,5,6
print(a)
print(b)
print(c)
多返回值
见函数有返回值内容
and or
--逻辑与 逻辑或
--and or 他们不仅可以连接 boolean 任何东西都可以用来连接
--在lua中 只有 ni1 和 false 才认为是假
--"短路"见逻辑运算符中的解释
--
print( 1 and 2)
print(0 and 1)
print( nil and 1)
print(false and 2)
print(true and 3)
print(true or 1)
print( false or 1)
print( nil or 2)
用and or 来模拟三目运算符
x = 1
y = 2
res = (x>y) and x or y
print(res)
协程
创建
fun = function()
print("123")
end
--该方法创建协程类型本质是一个线程
co = coroutine.create(fun)
print(co)
print(type(co))
--该方法创建协程本质是一个函数
co2 = coroutine.wrap(fun)
print(co2)
print(type(co2))
运行
--第一种创建方式对应的运行方式
coroutine.resume(co)
--第二种穿件方式对应的运行方式
co2()
挂起
fun2 = function ()
local i = 1
while true do
print(i)
i = i + 1
--挂起函数
coroutine.yield(i)
end
end
co3 = coroutine.create(fun2)
--返回值第一个为协程是否启动成功,第二个为yield返回的值
isOk,tempI = coroutine.resume(co3)
print(isOk,tempI)
isOk,tempI = coroutine.resume(co3)
print(isOk,tempI)
--这种方式的协程调用直接返回yield返回的值
co4 = coroutine.wrap(fun2)
print("111"..co4())
print("111"..co4())
状态
--coroutine.status(协程对象)
--dead 结束
--suspend 挂起
--running 进行中
print(coroutine.status(co3))
print(coroutine.status(co))
--这个函数可以得到当前正在运行的协程的线程号
print(coroutine.running(co3))
在协程函数中print(coroutine.status(co3))才能显示running
元表
元表概念
任何表变量都可以作为另一个表变量的元表
任何表变量都可以有自己的元表(父表)
当子表中进行一些特定的操作时,会执行元表中的内容
设置元表
meta = {}
myTable = {}
--设置元表函数
--第一个参数子表
--第二个参数元表
setmetatable(myTable,meta)
特定操作
__tostring
meta2 = {
--当子表要被当做字符串使用时会默认调用这个元表中的tostring方法
__tostring = function ()
return "aaaa"
end
}
myTable2 = {}
--设置元表函数
--第一个参数子表
--第二个参数元表
setmetatable(myTable2,meta2)
print(myTable2)
meta3 = {
--默认传入调用者本身
__tostring = function (t)
return t.name
end
}
myTable3 = {
--不会使用子表的tostring
__tostring = function()
print("123")
end,
name = "name"
}
setmetatable(myTable3,meta3)
print(myTable3)
__call
meta4 = {
__tostring = function (t)
return t.name
end,
--当子表被当做 一个函数使用时会默认调用__call函数
--当希望传入参数时,默认第一个参数是调用者自己类似于 冒号调用方法
__call = function(a,b)
print(a)
print(b)
print("call")
end
}
myTable4 = {
name = "name"
}
setmetatable(myTable4,meta4)
myTable4(1)
运算符重载
meta5 = {
--相当于运算符重载 当子表用到+运算符时会调用该方法
__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 1
end,
--除法 __div
__div = function (t1,t2)
return 2
end,
--除法 __mod
__mod = function (t1,t2)
return 3
end,
--阶乘 __pow
__pow = function (t1,t2)
return 4
end,
-- == __eq
__eq= function (t1,t2)
return true
end,
-- < __lt
__lt= function (t1,t2)
return true
end,
--<= __le
__le= function (t1,t2)
return false
end,
-- ..(连接运算符) __concat
__concat =function (t1,t2)
return "12345"
end,
--没有大于
}
myTable5 = {age =1}
myTable6 = {age =2}
setmetatable(myTable5,meta5)
print(myTable5+myTable6)
print(myTable5-myTable6)
print(myTable5*myTable6)
print(myTable5/myTable6)
print(myTable5%myTable6)
print(myTable5^myTable6)
--如果要用 条件运算符来比较两个对象
--这两个对象的元表必须一致 才能准确调用方法
setmetatable(myTable6,meta5)
print(myTable5==myTable6)
print(myTable5<myTable6)
print(myTable5<=myTable6)
print(myTable5..myTable6)
__index和_newIndex
--__index会层层向上找到含有该值的表,没找到则返回nil
meta7Father = {
age = 1
}
meta7Father.__index = meta7Father
meta7 = {
--meta7.__index = meta7 这样写最后输出的是nil故最好写在外面
age = 5
}
meta7.__index = meta7
--也可以直接指向别的表meta7.__index = {age =3}
--建议将__index写在外面,写在里面会导致指向自己时出现bug
myTable7 = {
}
setmetatable(meta7,meta7Father)
setmetatable(myTable7,meta7)
--__index 当子表中 找不到一个属性时
--会到元表中 __index指定的表去找索引
--得到元表
print(getmetatable(myTable7))
--仅在当前表中寻找是否含有该键
print(rawget(myTable7,"age"))
print(myTable7.age)
--如果子表中找不到要赋值的索引
--那么会一层一层向上找 找到含有该索引的表中进行赋值
meta8 = {
}
meta8.__newindex = {age = 6}
myTable8 = {
}
setmetatable(myTable8,meta8)
myTable8.age = 1
print(myTable8.age)
--只改变当前表中的值
rawset(myTable8,"age",5)
print(myTable8.age)
print(meta8.__newindex.age)
面向对象
封装
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()
--在子表(空表)中申明了id属性
myObj.id = 2
myObj:Test()
-- function myObj:Test()
-- print("test")
-- end
-- myObj:Test()
继承
function Object:subClass(className)
_G[className] = {}
--写继承相关的规则
--用到元表
self.__index = self
local obj =_G[className]
setmetatable(obj,self)
end
Object:subClass("Person")
print(Person)
print(Person.id)
local p1 = Person:new()
print(p1.id)
p1.id = 100
print(p1.id)
p1:Test()
Object:subClass("Monster")
local m1 = Monster:new()
print(m1.id)
m1.id = 200
print(m1.id)
m1:Test()
多态
在继承时加一个base属性
function Object:subClass(className)
_G[className] = {}
--写继承相关的规则
--用到元表
self.__index = self
local obj =_G[className]
--子类 定义一个base属性 base代表父类
obj.base = self
setmetatable(obj,self)
end
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()
--base指的是GameObject表(类)
--这种调用方式相当于把基类表作为第一个参数传入方法中,改变基类的值
--所以我们要避免把基类表传入到方法中 这样就相当于是公用一张表的属性
--self.base:Move()
--若要使用父类函数 ,不要使用冒号调用要通过.调用传入自己 作为第一个参数
self.base.Move(self)
end
local p1 = Player:new()
p1:Move()
p1:Move()
local p2 = Player:new()
p2:Move()
总结
Object = {}
--实例化对象方法
function Object:new()
local obj = {}
--给空对象设置元表以及元表的__index
self.__index=self
setmetatable(obj,self)
return obj
end
--继承
function Object:subClass(className)
--根据名字生成一张表 就是一个类
_G[className] = {}
local obj = _G[className]
--设置自己的父类
obj.base = self
--给子类设置元表以及元表的__index
self.__index = self
setmetatable(obj,self)
end
--申明一个新的类
Object:subClass("GameObject")
--成员变量
GameObject.posX = 0
GameObject.posY = 0
--成员函数
--成员方法
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
end
local obj = GameObject:new()
print(obj.posX)
obj:Move()
print(obj.posX)
local obj2 = GameObject:new()
print(obj2.posX)
obj2:Move()
print(obj2.posX)
--申明新的类Player继承GameObject
GameObject:subClass("Player")
--多态 重写GameObject的Move方法
function Player:Move()
--利用base调用父类方法将自己作为第一个参数传入
self.base.Move(self)
end
--实例化Player对象
local p1 = Player:new()
print(p1.posX)
p1:Move()
print(p1.posX)
local p2 = Player:new()
print(p2.posX)
p2:Move()
print(p2.posX)
注意赋值操作会直接在当前对象中创建新的值,而不是到元表中去找,
冒号函数默认就是冒号前的对象的函数,调用时也不会再向上寻找
自带库
时间相关
--系统时间
print(os.time())
--自己传入参数 的到时间
print(os.time({year =2024,month = 3,day = 2}))
local nowTime =os.date("*t")
for k,v in pairs(nowTime) do
print(k,v)
end
print(nowTime.hour)
数学运算
--绝对值
print(math.abs(-11))
--弧度转角度
print(math.deg(math.pi))
--三角函数 传弧度
print(math.cos(math.pi))
--向下向上取整
print(math.floor(2.6))
print(math.ceil(5.2))
--最大最小值
print(math.max(2,6))
print(math.min(5,2))
--小数分离 分成整数部分和小数
print(math.modf(1.2))
--幂运算
print(math.pow(2,5))
--随机数
--先设置随机数种子
math.randomseed(os.time())
print(math.random(100))
print(math.random(100))
--开方
print(math.sqrt(4))
路径相关
print(package.path)
package.path = package.path .. ";C:\\"
print(package.path)
Lua垃圾回收
test = {id = 1,name = "123"}
--垃圾回收关键字
--collectgarbage
--获取当前lua占用内存数 K字节 用返回值*1024 就可以得到具体的内存占用字节数
print(collectgarbage("count"))
--lua中的机制和C#中垃圾回收机制很类似 解除羁绊 就是变垃圾
test = nil
--进行垃圾回收类似于C#的GC
collectgarbage("collect")
print(collectgarbage("count"))
--lua中有自动定时进行GC的方法
--Unity中热更新开发尽量不要取用自动垃圾回收