【CSND】Unity游戏开发学习、素材资源群
加入我们一起学习吧:1022366216
备注来源:csdn
这段时间在兼职以及复习内容~
联系小小老师~可以获取学习游戏开发大礼包!
群内还有更多素材资源领取!!
所有Lua面试题~
1.lua深拷贝和浅拷贝的区别?如何实现深拷贝? √
2. lua中ipairs和pairs的区别? √
3. lua中的userdata是什么?有什么作用?
4. 解释下lua中的元表元方法? √
5. 说说lua中如何实现面向对象? √
6. 如何实现一个lua table的迭代器?
7. Lua和C交互原理?√
8. C#与Lua交互原理? √
9. 说说Lua中的闭包? √
10. 请深入说明Lua的数据结构和内存占用? √
11. lua是如何实现热更新的? √
12. 简单描述一下Lua的GC原理?
13. AB包资源加载的方法有哪些
14. Resource.UnloadAsset前提条件?(错误分类,应该放在Unity中)
15. __index和__newindex元方法的区别?√
16. Lua如何实现C#构造函数new方法
1、lua深拷贝和浅拷贝的区别?如何实现深拷贝?
如何实现浅拷贝
使用 = 运算符进行浅拷贝
分2种情况
1.拷贝对象是string、number、bool基本类型。拷贝的过程就是复制黏贴!修改新拷贝出来的对象,不会影响原先对象的值,两者互不干涉
2.拷贝对象是table表,拷贝出来的对象和原先对象时同一个对象,占用同一个对象,只是一个人两个名字,类似C#引用地址,指向同一个堆里的数据~,两者任意改变都会影响对方.
如何实现深拷贝
复制对象的基本类型,也复制源对象中的对象
常常需用对Table表进行深拷贝,赋值一个全新的一模一样的对象,但不是同一个表
Lua没有实现,封装一个函数,递归拷贝table中所有元素,以及设置metetable元表
如果key和value都不包含table属性,那么每次在泛型for内调用的Func就直接由if判断返回具体的key和value
如果有包含多重table属性,那么这段if判断就是用来解开下一层table的,最后层层递归返回。
核心逻辑:使用递归遍历表中的所有元素。
1.先看copy方法中的代码,如果这个类型不是表的话,就没有遍历的必要,可以直接作为返回值赋值;
2.当前传入的变量是表,就新建一个表来存储老表中的数据,下面就是遍历老表,并分别将k,v赋值给新建的这个表,完成赋值后,将老表的元表赋值给新表。
3.在对k,v进行赋值时,同样要调用copy方法来判断一下是不是表,如果是表就要创建一个新表来接收表中的数据,以此类推并接近无限递归。
local numTest1=5
local numTest2=numTest1 --使用 == 进行浅拷贝
local numTest2=10 --修改numTest2,不会改变numTest1
print(numTest1)
--答案 5
print(numTest2)
--答案 10
local tab={}
tab["好好学习"]="游戏开发"
tab["热更"]="Xlua"
for key, value in pairs(tab) do
print(key.."对应"..value)
end
local temp = tab
tab["好好学习"]="热更"
tab["热更"]="好好学习"
for key, value in pairs(tab) do
print(key.."对应"..value)
end
--输出答案,tab和temp都发生了改变
--热更对应Xlua
--好好学习对应游戏开发
t={name="asd",hp=100,table1={table={na="aaaaaaaa"}}};
--实现深拷贝的函数
function copy_Table(obj)
function copy(obj)
if type(obj) ~= "table" then --对应代码梳理“1” (代码梳理在下面)
return obj;
end
local newTable={}; --对应代码梳理“2”
for k,v in pairs(obj) do
newTable[copy(k)]=copy(v); --对应代码梳理“3”
end
return setmetatable(newTable,getmetatable(obj));
end
return copy(obj)
end
a=copy_Table(t);
for k,v in pairs(a) do
print(k,v);
end
--1.先看copy方法中的代码,如果这个类型不是表的话,就没有遍历的必要,可以直接作为返回值赋值;
--2.当前传入的变量是表,就新建一个表来存储老表中的数据,下面就是遍历老表,并分别将k,v赋值给新建的这个表,完成赋值后,将老表的元表赋值给新表。
--3.在对k,v进行赋值时,同样要调用copy方法来判断一下是不是表,如果是表就要创建一个新表来接收表中的数据,以此类推并接近无限递归。
链接: 参考博文:Lua实现和理解深拷贝.
【面试题】2、lua中ipairs和pairs的区别?
第一种情况:Table的组成:哈希表(键值对,链表解决冲突),数组(数字,表,)
1、根据元素列表可以分为:哈希表{[1]=1,[3]=3,[5]=5.[6]=6} , 数组{2,4}
2、将数组中的元素放入哈希表,将会发生变化 [1]=2,[2]=4,[1]的键值对发生冲突,重新匹配,得到新的哈希表元素{[1]=2,[2]=4,[3]=3,[5]=5,[6]=6}
第二种情况:数字和表混合
先哈希表(键值对,然后在数值的方式进行匹配)
table在存储值的时候是按照顺序的,但是在存储键值对的时候是按照键的哈希值存储的,为nil也会分配一个key
local t = {[1]=1,2,[3]=3,4,[5]=5,[6]=6}
print('ipairs')
for index, value in ipairs(t) do
print(value)
end
print('pairs')
for key, value in pairs(t) do
print(value)
end
--答案是ipairs [2 4 3] , pairs [2 4 3 6 5] 无序
local testTab ={1,2,3,4,5};-- '纯表'
local testTab1 = {a = 1, b = 2, c =3};
local testTab2 = {"zi",a = 5,b = 10, c = 15,"miao","chumo"};-- '杂表1'
local testTab3 = {"zi",a = 5,b = 10, c = 15,"miao",nil,"chumo"};-- '杂表2'
--testTab3答案是ipairs [zi,miao] , pairs [zi,miao,chumo,5,10,15]
总结:
所以当ipairs遍历table时,从键值对索引值[1]开始连续递增,当键值对索引值[ ]断开或遇到nil时退出,所以上面的例子中ipairs遍历出的结果是2,4,3。
而pairs遍历时,会遍历表中的所有键值对,先按照索引值输出数组,在输出其它键值对,且元素是根据哈希算法来排序的,得到的不一定是连续的,所以pairs遍历出的结果是2,4,3,6,5。
如何解决既要顺序遍历,也要遇到nil也能遍历完全???
1.现获取table的长度,包含其中的nil,maxSize
for idx=1, maxSize do
if pTable[idx] ~= nil then --不等于
-- 做相应的处理...
end
end
链接: 参考博文.
3、解释下lua中的元表和元方法?
元表metetable : 允许该表table行为,行为关联元方法,类似一种“操作指南”,包含各种操作行为的解决方案!!!
元方法:当表执行某些操作失败的时候,操作指南里的元方法指导你的行为~~
A = {}
B = {}
setmetatable(
A,
{
__index = B, --索引查询
__newindex = B, --索引更新
__add = B, --加法
__sub = B, --减
__mul = B, --乘
__div = B, --除
__mod = B, --取模
__pow = B, --乘幂
__unm = B, --取反
__concat = B, --连接
__len = B, --长度
__eq = B, --相等
__lt = B, --小于
__le = B, --小于等于
__call = B, --执行方法调用
__tostring = B, --字符串输出
__metatable = B --保护元表
}
)
Table是非常重要的数据结构,由数组和哈希表两种内部实现,既可以当做数组也可以当做字典。
当Table当做为C#的Dictionary时,很多元素都是由Key-value组成。
如果尝试table表中不存在的元素时,就会触发lua的查找机制,这个机制模拟了类似“类”的行为~!!!
只设置元表是不管用,需要同时设置元表和对应的元方法的代码
father = {
prop1=2
}
father.__index = father -- 把father的__index方法指向它本身
son = {
prop2=1
}
setmetatable(son, father) --把son的metatable设置为father
print (son.prop1)
--输出结果为2
Lua并不是直接在fahter中找到名为prop1的成员,而是先调用father的__index方法),如果__index方法为nil,则直接返回nil。如果__index指向了一张表(上面的例子中father的__index指向了自己本身),那么就会到__index方法所指向的这个表中去查找名为prop1的成员。
链接: 参考博文.
4、Lua如何实现面向对象?
面向对象特性:封装、继承、多态
Lua中table是非常强大的数据结构,利用table再结合元表metatable,可以方便的在Lua中模拟类,并实现面向对象编程中具有的特性:封装、继承、多态
1.利用Lua实现类
--类:属性,构造函数,成员,属性等等
--类的声明,属性,初始值
class = {x = 0, y = 0}
--设置元表的索引,要模拟类,这一步最重要
class.__index = class --表的元表索引是自己
--构造方法,习惯性命名new()
function class:new(x, y)
local t = {} --初始化t,如果没有这一句,会导致类所建立的对象一旦发生改变,其它对象都会改变
setmetatable(t, class) --设置t的元表为class , 把class设为t的原型
t.x = x --属性值初始化
t.y = y
return t --返回自身
end
--这里定义类的其它方法,self标识是非常重要的核心点,冒号:隐藏了self参数
function class:test()
print(self.x, self.y)
end
function class:plus()
self.x = self.x + 1
self.y = self.y + 1
end
2.利用lua实现继承
将元表设置为父类,在实现lua索引查找机制,要注意构造函数新表和设定元表
require("Class")
--类的继承
SubClass = {z = 0}
--声明新的属性Z
--两步完成类Class的继承
setmetatable(SubClass, Class)
--设置类型是class
SubClass.__index = SubClass --设置表的索引为自身
--构造方法,习惯性命名new()
function SubClass:new(x, y, z)
local t = {} --初始化对象自身
t = Class:new(x, y) --将对象自身设定为父类,相当于C#里的base
setmetatable(t, SubClass) --将对象自身元表设定为SubClass
t.z = z --新属性赋值
return t
end
--定义一个新的方法
function SubClass:go()
self.x = self.x + 10
end
--重定义父类的方法,相当于override
function SubClass:test()
print(self.x, self.y, self.z)
end
2.利用lua实现多态(重写)
require("Class")
require("SubClass")
local a = Class:new() -- 首先实例化父类,并调用父类方法
a:plus()
a:test()
a=SubClass:new() -- 然后实例化子类对象
a:plus() --重写
a:go() --新增
a:test() --重写
链接: 扩展博文:Lua封装.
5、table和元表metatable的一些重要知识点有哪些?
(1)、table 是 Lua 的一种数据结构,用于帮助我们创建不同的数据类型,如:数组、字典等;
(2)、table 是一个关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil,所有索引值都需要用 “[“和”]” 括起来;如果是字符串,还可以去掉引号和中括号; 即如果没有[]括起,则认为是字符串索引,Lua table 是不固定大小的,你可以根据自己需要进行扩容;
(3)、table 的默认初始索引一般以 1 开始,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编;
(4)、table 的变量只是一个地址引用,对 table 的操作不会产生数据影响;
(5)、table 不会固定长度大小,有新数据插入时长度会自动增长;
(6)、table 里保存数据可以是任何类型,包括function和table;
(7)、table所有元素之间,总是用逗号 “,” 隔开;
元表请看第三题:3、解释下lua中的元表和元方法?
7. Lua和C交互原理?
C和Lua之间的差异
1.Lua有垃圾回收机制,C需要显示释放内存
2.Lua是动态类型,弱类型语言【运行时确认】,C是静态类型,强类型语言。【编译时确认】
C与Lua的通信使用了虚拟栈结构!!!
以下是简单的虚拟栈概念!
将2个数据压入虚拟栈
当使用正数索引时,表示从栈底开始,一直到栈顶 ,使用负数索引时表示从栈顶开始,一直到栈底。
通过指定索引来出栈和入栈
C#调用Lua是依靠C作为中间语言,通过C#调用C,C再调用Lua实现的 而框架中的tolua.dll等也就是借助LuaInterface封装的C语言动态库
Lua调用C
调用之前需要注册,将函数地址告知Lua
LuaFramework的框架中Lua要调用Unity自带的API或者我们自己写的脚本之前要先生成对应的XXXWrap文件,就是如上面例子一样,需要在lua里进行注册。
更进一步需要了解Lua的源码!!
链接: LuaFramework_UGUI.
链接: 参考博文1.
链接: 参考博文2.
9、 C#与Lua交互过程和原理?
Wrap文件:每一个Wrap文件都是对一个C#类的包装。
C# Call Lua交互过程
C#文件先调用Lua的解析器底层的dll库(C语言编写),再由DLL文件执行相应的Lua文件
Lua Call C# 交互过程
1.Wrap方式:首先生成C#源文件对应的Wrap文件,Lua文件会调用生成的Wrap文件,再由Wrap文件去调用C#文件。
2.反射方式:当索引系统API、DLL库或者第三方库,如果无法将代码具体实现进行代码生成,可通过反射来获取,执行效率较低。
C#与Lua交互原理:虚拟栈!!!
交互通过虚拟栈实现,栈的索引分为正数和负数,如果索引是正数,则1表示栈底,如果索引是负数,则-1表示在栈顶
C# Call Lua交互原理
C#先将数据放入栈中,然后Lua去栈中获取数据,然后返回数据对应的值到栈顶,再由栈顶返回至C#
Lua Call C#交互原理
C#源文件生成Wrap文件、或C#源文件生成C模块,将Wrap文件和C模块注册到Lua的解析器中,最后再由Lua去调用这个模块的函数~
从代码文件方面解释:
lua调用C#过程:
lua->wrap->C#
先生成Wrap文件(中间文件/适配文件),wrap文件把字段方法,注册到lua虚拟机中(解释器luajit),然后lua通过wrap就可以调C#了、或者在config文件中添加相应类型也可以
C#调用Lua过程:
C#生成Bridge文件,Bridge调dll文件(dll是用C写的库),先调用lua中dll文件,由dll文件执行lua代码
C#->Bridge->dll->Lua 或 C#->dll->Lua
从内存方面解释:说白了就是对栈进行操作
C# Call Lua:C#把请求或数据放在栈顶,然后lua从栈顶取出该数据,在lua中做出相应处理(查询,改变),然后把处理结果放回栈顶,最后C#再从栈顶取出lua处理完的数据,完成交互。
链接: 参考博文.
10. 说说lua中的闭包?
闭包=函数+引用环境
子函数可以使用父函数中的局部变量,这种行为可以理解为闭包!
1、闭包的数据隔离
不同实例上的两个不同闭包,闭包中的upvalue变量各自独立,从而实现数据隔离
2、闭包的数据共享
两个闭包共享一份变量upvalue,引用的是更外部函数的局部变量(即Upvlaue),变量是同一个,引用也指向同一个地方,从而实现对共享数据进行访问和修改。
3、利用闭包实现简单的迭代器
迭代器只是一个生成器,他自己本身不带循环。我们还需要在循环里面去调用它才行。
1)while…do循环,每次调用迭代器都会产生一个新的闭包,闭包内部包括了upvalue(t,i,n),闭包根据上一次的记录,返回下一个元素,实现迭代
2)for…in循环,只会产生一个闭包函数,后面每一次迭代都是使用该闭包函数。内部保存迭代函数、状态常量、控制变量。
链接: 参考博文.
11. 请深入说明Lua的数据结构和内存占用?
lua的数据结构的内存分配是动态的
常见的数据结构是string和table
链接: 参考博文.
12. lua是如何实现热更新的
Lua的模块加载机制
热更的核心就是替换Package.loaded表中的模块
导出函数require(mode_name)
查询全局缓存表package.loaded
通过package.searchers查找加载器
package.loaded
存储已经被加载的模块:当require一个mode_name模块得到的结果不为假时,require返回这个存储的值。require从package.loader中获得的值仅仅是对那张表(模块)的引用,改变这个值并不会改变require使用的表(模块)。
package.preload
保存一些特殊模块的加载器:这里面的值仅仅是对那张表(模块)的引用,改变这个值并不会改变require使用的表(模块)。
package.searchers
require查找加载器的表:这个表内的每一项都是一个查找器函数。当加载一个模块时,require按次序调用这些查找器,传入modname作为唯一参数。此方法会返回一个函数(模块的加载器)和一个传给这个加载器的参数。或返回一个描述为什么没有找到这个模块的字符串或者nil。
链接: 参考博文.
== 12. 简单描述一下Lua的GC原理?并且防止内存泄露 ==
1、Lua的GC垃圾回收机制算法
Lua的GC使用了标记清除算法Mark and Sweep
标记:每一次执行GC前,从根节点开始遍历每一个相关节点,进行标记
清除:标记完成后,遍历对象链表,然后对需要执行清除标记的对象,进行清除
使用三色法:白,灰,黑,作为对象的三种状态
新白:可以回收的对象;新创建的对象,初始状态是新白,但不会被清除
旧白:可以回收的对象;lua只会清除旧白,GC后,会更新新白
灰色:等待回收的对象:该对象已被GC访问过,但该对象引用的其它对象还未标记
黑色:不可回收的对象
简单流程:
1.根对象开始标记,将白色对象重置为灰色对象,加入灰色链表
2.如果灰色链表不为空,取出一个对象,重置为黑色,并遍历相关引用的对象,重置为黑色
3.如果灰色链表为空,清除一次灰色链表
4.根据不同类型对象分布回收,类型的存储表
5.判断是否遍历到链表尾
6.判断对象是否为白色
7.将对象重置为白色
8.释放资源
总结
Lua通过借助grey链表,依次利用reallymarkobject对对象进行了颜色的标记,之后通过遍历alloc链表,依次利用sweeplist清除需要回收的对象。
2、Lua中的GC优化,防止内存泄露
1.Xlua可以打标签[GCOptimize] ,C#复杂类型struct类型是引用传递到Lua,对满足条件的struct,在Lua一侧做到无GC
条件:struct允许嵌套其它struct,但它以及它嵌套的struct只能包含这几种基本类型:byte、sbyte、short、ushort、int、uint、long、ulong、float、double;例如UnityEngine定义的大多数值类型:Vector系列,Quaternion,Color。。。均满足条件,或者用户自定义的一些struct
2.游戏逻辑层:实体管理器如果是本身持有了实体,那么就不应该有create/remove接口。所有实体资源,主要是目前的玩家逻辑数据, 必须直接绑定在角色上,确保角色的销毁会引发实体资源的销毁;全局资源性的数据,可以考虑放在weak table中
3.可以对lua中的模块进行分代, 不同的数据使用不同的保存,封装和清除策略,保证在最大效率的情况下准确的完成垃圾收集。
4.Collectgarbage方法就是开放给Lua开发人员的API,用于监听Lua的内存使用情况(collectgarbage(“count”)),或者需要定期调用collectgarbage(“collect”),又或者collectgarbage(“step”))进行显式回收。
1.如果系统性能还能够承受的话,建议不要直接引用对象,可以多做 一层间接层。2.lua里面的弱引用是非常有用的。3.比较大的物理内存是必要的, 这可以为大家查证问题争取足够多的时间:) 4.可以把查找泄漏的部分写入到关机 逻辑里面,每次关机的时候自动查找泄漏,然后出具报告。Lua的内存监测和回收
1、Lua如何调用C#
三种方式
第一种:执行字符串;使用LuaEnv.DoString(“文件名称”),专门用来做执行lua的主入口Main.lua。
第二种:如果Resource文件下的Lua文件,使用Lua的Require函数即可。在Main.lua文件里Require其它lua文件
第三种:如果Lua文件是下载的,使用自定义Loader可满足。
public delegate byte[] CustomLoader(ref string filepath);
public void LuaEnv.AddLoader(CustomLoader loader)
C#里通过AddLoader可以注册个回调,该回调参数是字符串,Lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。
链接: C#调用Xlua文件.
3、资源如何打包?依赖项列表如何生成?
1.查找指定文件夹ABResource里的资源文件
——Directory.GetFile(资源路径)
——新建AssetBundleBuild对象
——获取资源名称,并赋值对应AB名称
——获取各个资源的依赖项:通过UnityEditor.AssetDataBase类获取各个资源的依赖项
2.使用Unity自带的BuildPipeline进行构建AB包
——BuildPipeLine.BuildAssetBundles(输出AB包路径)
——File.WriteAllLines(将依赖项写入文件里)
4.如何解析版本文件?如何加载AB包资源?具体流程是怎么样的?
1.解析版本文件列表
——File.ReadAllLines(读取文件列表资源路径URL)
——获取资源名称,获取AB包名称,获取依赖项,字典容器存储
——获取Lua文件
2.加载资源
——异步加载资源AB包,AssetBundleRequest请求,AssetBundle.LoadFromFileAsync
——先检查依赖项,再异步加载AB包依赖项
——加载成功后都有对应的回调方法,将资源作为参数传入
5、热更新方案有哪些?以及具体热更流程
1、整包:存放在上SteamingAssets里
——策略:完整更新资源放在包里
——优点:首次更新少
——缺点:安装包下载时间长,首次安装久
2、分包
——策略:少部分资源放在包里,大部分更新资源存放在更新资源器中
——优点:安装包小,安装时间短,下载快
——缺点:首次更新下载解压缩包时间旧
3、适用性
——海外游戏大部分是使用分包策略,平台规定
——国内游戏大部分是使用整包策略
4、文件可读写路径
——Application.streamingAssestsPath 只读目录
——Application.persistentDatapath 可读写目录
——资源服务器地址URL
5、【从资源服务器】下载单个文件或多个文件
——NetWorking.UnityWebRequest获取URL , HTTP GET , 连接资源服务器
——获取到downloadHander的文件数据Data,完成后会回调方法,将文件Data作为参数传出
6、检查是否初次安装
6、网络客户端C# 和 Lua
8、简述Lua实现面向对象的原理???
总结:对象标识、状态、类体系、继承、私有性
1.表table就是一个对象,对象具有了标识self,状态等相关操作
2. 使用参数self表示方法的该接受者是对象本身,是面向对象的核心点,冒号操作符可以隐藏该self参数
3. 类(Class):每个对象都有一个原型,原型(lua类体系)可以组织多个对象间共享行为
4. setmetatable(A,{__index=B}) 把B设为A的原型
5. 继承(Inheritance):Lua中类也是对象,可以从其他类(对象)中获取方法和没有的字段
6. 继承特性:可以重新定义(修改实现)在基类继承的任意方法
7. 多重继承:一个函数function用作__Index元方法,实现多重继承,还需要对父类列表进行查找方法,但多继承复杂性,性能不如单继承,优化,将继承的方法赋值到子类当中
8. 私有性(很少用)基本思想:两个表表示一个对象,第一个表保存对象的状态在方法的闭包中,第二个表用来保存对象的操作(或接口),用来访问对象本身。使第一个表完成内容私有性。
代码举例~~
--类
A={}
B={}
setmetatable(A,{__index=B})
--单继承
function Account:new(o) -- 传入self为Account
o=o or {}
self.__index = self --直接把表Account当做元表
setmetatable(o,self)
return o
end
--多重继承
local function serach(k,plist) --在父类列表中查找k方法
for i = 1, #plsit do
-- body
local v = plist[i][k] --第i个方法K
if v then return v end --V存在
end
end
setmetatable(c,{__index=function(t,k)
local v = search(k,parents) --serach方法(k是第K个,parents是父类列表)
t[k]=v --将方法保存进t数组里
return v
end})
--私有性
function newAccount(initBalance)
local self = {balance = initBalance} --self表示局部表,第一个表用来保存內部状态,私有性
local withdraw = function(v)
self.balance = self.balance - v
end
local deposit = function(v)
self.balance = self.balance + v
end
local getbalance = function()
return self.balance
end
return
{
withdraw = withdraw,
deposit = deposit,
getbalance = getbalance
} --返回外部對象,方法名進行映射
end
acc1=newAccount(100)
acc1.deposit(10)
print(acc1.getbalance()) --答案是110
9、简述Lua有哪8个类型?简述用途?
nil 空——可以表示无效值,全局变量(默认赋值为nil),赋值nil ,使其被删除
number 整数 ——双精度(double)类型的实浮点数
table 表 —— 数组+哈希表,table的创建通过构造表达式,通常是 { } 创建空表,关联数组:索引可以是数字或者字符串,索引数字一般是从1开始
string 字符——使用一组双引号或者单引号,…字符串拼接,#字符串长度,值类型
userdata 自定义——用户自定义,任意存储在变量中的C数据结构
function 函数——Lua编写函数
bool 布尔——true或者false
thread线程——独立线路,用于执行协同程序
14. Resource.UnloadAsset前提条件?(错误分类,应该放在Unity中)
Resource资源加载简单,只要Resource.Load读取Resouce文件夹下的资源
Resource.UnloadAsset(obj):卸载 不是 GameObject类型的资源,会将已加载资源及其克隆体一起卸载
可以卸载Resources.Load的资源,还包括AssetBundle.Load的资源,但有个前提
Resources.UnloadUnusedAssets同样可以卸载由AssetBundle.Load加载的资源,只是前提是其对应的AssetBundle已经调用Unload(false),且并没有被引用。
Destroy(obj):仅用于卸载GameObject类型的资源的克隆体
DestroyImmediately(obj):卸载GameObject类型的资源,会将内存中已加载资源及其克隆体卸载,但该方法只能用在非编辑模式下,否则会报错提示改为DestroyImmediately(obj, true),然而编辑模式下使用该函数会连文件夹里的原始Asset一并删除。
官方推荐的卸载资源方法是:
public static AsyncOperation UnloadUnusedAssets()
15. __index和__newindex元方法的区别
访问不存在的数据,由__index提供最终结果
__index元方法可以是一个函数,Lua语言就会以【表】和【不存在键】为参数调用该函数
__index元方法也可以是一个表,Lua语言就访问这个元表
Lua实现面向对象:继承
元表带有元方法__index,并且可以使用__index来实现单继承
prototype={x=0,y=0,width=100,height=200} --元表
local mt = {} --空表
function new(o)
setmetatable(o,mt) --把mt设为元表
return o
end
mt.__index=function(_,key) --元表元方法赋值一个函数,不存在键和表
return prototype[key]
end
w=new{x=10,y=20}
print(w.width)--100
mt.__index=prototype --元表
__newindex用于表的更新,__index用于表的查询
对表中不存在的值进行赋值的时候,解释器会查找__newindex
__newindex元方法如果是一个表,Lua语言就对这个元表的字段进行赋值
如果绕过元方法可以使用 rawset(t,k,v)等价于t[k]=v
参考Lua程序设计第20表相关的元方法
16. Lua如何实现C#构造函数new方法
new传进的参数当做一个本地表
元表本身Self作为元表,设置元方法,将其作为参数表o的元表,并返回出去
从而实现C# new构造函数
调用new的时候,其实访问的是元表内容
function Class:(o) -- 传入self为Account
o = o or {}
self.__index = self --直接把表Class当做元表
setmetatable(o, self)
return o
end
local object = Class:new(o) --object变量可以访问元表