热更新流程简述

获取对比文件:从远程服务器下载一个用于对比的文件,该文件记录了当前可用资源的版本号或哈希值等信息。

对比本地资源和远程资源:将下载的对比文件与本地保存的对比文件进行比较,以确定本地资源和远程资源之间的差异。根据比较结果,确定需要更新的资源和需要移除的资源。

资源更新和移除:下载远程服务器上与本地资源不同的更新文件,并覆盖本地对应的资源文件,或者直接将更新的资源文件添加到本地资源目录。根据对比结果,删除本地不再需要的资源文件。

更新对比文件:将下载的远程对比文件替换本地保存的对比文件,以保持本地对比文件与服务器端一致,为下一次热更新做准备。

在 Unity 中 AssetBundle 的压缩方式有不压缩、LZMA、LZ4 三种,请问 LZMA 和 LZ4 有什么区别?

🟡 Lua 面向对象的三大特性

Lua 如何实现面向对象的三大特性

封装:可以通过 table 进行实现。在 Lua 中,我们可以将对象的属性和方法放入一个 table 中,然后对该 table 进行操作,从而达到封装的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object = {}
setmetatable(Object, Object)
Object.__index = Object

function Object:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end

function Object:toString()
return tostring(self)
end

object1 = Object:new()
print(object1:toString())

继承:可以通过元表(metatables)和 **index 元方法来模拟。我们可以将子类的元表设置为父类,然后将父类的 **index 指向父类自身。这样,当子类对象找不到对应的属性或方法时,就会去父类中查找,从而实现继承关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Object:subClass(className)
_G[className] = {}
_G[className].base = self
setmetatable(_G[className], self)
self.__index = self
end

Object:subClass("Animal")
function Animal:new(animalName)
local obj = Animal.base.new(self)
obj.animalName = animalName
return obj
end
function Animal:Speak()
print("动物"..self.animalName.."开始叫")
end

多态:在 Lua 中可以通过子类自己实现同名方法并且使用冒号 : 来调用。当调用同名方法时,Lua 会根据对象的类型来决定调用哪个方法,从而实现多态的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Animal:subClass("Dog")
function Dog:Speak()
self.base.Speak(self)
print("狗"..self.animalName.."开始旺旺叫")
end

dog1 = Dog:new("Spike")
dog1:Speak()

Animal:subClass("Cat")
function Cat:Speak()
self.base.Speak(self)
print("猫"..self.animalName.."开始喵喵叫")
end

cat1 = Cat:new("Tom")
cat1:Speak()

🟡 Windows 和 Android 平台上的热更新实现

如果不考虑 IOS 平台,只在 Windows 和 Android 平台上发布游戏,如何在不使用第三方热更新方案的前提下实现热更新功能?

使用热更 DLL 文件,将需要更新的游戏逻辑打包成 DLL 文件。

在游戏启动时,通过代码加载这些 DLL 文件,而不是直接编译进游戏。

利用 C# 的反射功能,动态加载并执行热更 DLL 包中的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
// 加载热更 DLL
Assembly hotfixAssembly = Assembly.LoadFile("path/to/Hotfix.dll");

// 获取热更类
Type hotfixType = hotfixAssembly.GetType("HotfixNamespace.HotfixClass");

// 创建实例
object hotfixInstance = Activator.CreateInstance(hotfixType);

// 执行方法
MethodInfo method = hotfixType.GetMethod("HotfixMethod");
method.Invoke(hotfixInstance, null);

请说出 Lua 中常用的数据类型(至少说出 6 种)

Lua 中常用的数据类型包括以下几种:

nil:表示无效值或空值。
boolean:布尔类型,有两个值:true 和 false。
number:数字类型,在 Lua 5.3 之前是双精度浮点数,在 Lua 5.3 之后支持整数和浮点数。
string:字符串类型,用于存储文本。
table:表类型,用于表示关联数组,可以用作数组、字典等。
function:函数类型,可以存储并调用函数。

此外,还有两种较为高级的类型:
userdata:用户数据类型,用于表示 C 数据结构。
thread:线程类型,用于表示协同程序。

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
-- nil类型
local a = nil

-- boolean类型
local b = true
local c = false

-- number类型
local d = 123 -- 整数
local e = 45.67 -- 浮点数

-- string类型
local f = "Hello, Lua!"

-- table类型
local g = { key1 = "value1", key2 = "value2" }
g[3] = "value3"

-- function类型
local function h()
print("This is a function")
end

-- userdata类型
-- 通常由C代码创建,这里只是一个示例
local i = newproxy(true)

-- thread类型
local j = coroutine.create(function()
print("This is a coroutine")
end)

-- 打印变量类型
print(type(a)) -- 输出: nil
print(type(b)) -- 输出: boolean
print(type(d)) -- 输出: number
print(type(f)) -- 输出: string
print(type(g)) -- 输出: table
print(type(h)) -- 输出: function
print(type(i)) -- 输出: userdata
print(type(j)) -- 输出: thread

Lua 中 pairs 和 ipairs 的区别

正常情况下,ipairs 和 pairs 在遍历数组时没有任何区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local tbl1 = { "apple", "pear", "orange", "grape" }
print("=============ipairs的执行结果=============")
for i, v in ipairs(tbl1) do
print(i, '=', v)
end
print("=============pairs的执行结果==============")
for i, v in pairs(tbl1) do
print(i, '=', v)
end
=============ipairs的执行结果=============
1 = apple
2 = pear
3 = orange
4 = grape
=============pairs的执行结果==============
1 = apple
2 = pear
3 = orange
4 = grape

当我们使用自定义的键值时,通过 pairs 和 ipairs 的输出结果会有所不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local tbl2 = {}
tbl2[1] = "1"
tbl2[2] = "2"
tbl2[3] = "3"
tbl2[5] = "5"
print("=============ipairs的执行结果=============")
for i, v in ipairs(tbl2) do
print(i, '=', v)
end
print("=============pairs的执行结果=============")
for i, v in pairs(tbl2) do
print(i, '=', v)
end
print('tbl2的长度为:', #tbl2) -- 长度输出为3,实际上数组中的长度是4
=============ipairs的执行结果=============
1 = 1
2 = 2
3 = 3
=============pairs的执行结果=============
1 = 1
2 = 2
3 = 3
5 = 5
tbl2的长度为: 3

从以上可以发现,ipairs 会依据键值从 1 开始加 1 递增遍历相应的 table 值。而 pairs 则能够遍历表中全部的键值,并且还能够返回 nil。ipairs 不能返回 nil,仅能返回数字 0,遇到 nil 则循环退出。它仅能遍历到表中出现的第一个不是整数的键值。

当我们获取 table 的长度时,无论是使用#还是 table.getn,都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。要正确求得 table 的长度,可以参考以下代码:

1
2
3
4
5
6
7
8
9
10
function tableLength(tbl)
local count = 0
for _ in pairs(tbl) do
count = count + 1
end
return count
end

local tbl2 = {1, 2, 3, nil, 5}
print("Table长度为:", tableLength(tbl2)) -- 输出5

Lua 中常用的元方法有哪些?至少说出 3 个原方法

Lua 中的元方法(metamethod)是一种特殊的函数,可以在特定操作发生时被调用,以实现自定义行为。以下是一些常用的元方法及其说明:

__index:
当试图访问一个表中不存在的字段时,会调用该方法。
用于实现表的继承或默认值。

__newindex:
当试图给一个表中不存在的字段赋值时,会调用该方法。
用于控制对表的更新操作。

__tostring:
当试图将一个表转换为字符串时,会调用该方法。
用于自定义表的字符串表示形式。

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
-- 定义一个元表
local metatable = {}

-- 定义 __index 元方法
metatable.__index = function(table, key)
return "默认值"
end

-- 定义 __newindex 元方法
metatable.__newindex = function(table, key, value)
rawset(table, key, value)
print("设置新键值对: " .. key .. " = " .. value)
end

-- 定义 __tostring 元方法
metatable.__tostring = function(table)
return "这是一个自定义表"
end

-- 创建一个表并设置其元表
local myTable = setmetatable({}, metatable)

-- 测试 __index 元方法
print(myTable.nonExistentKey) -- 输出: 默认值

-- 测试 __newindex 元方法
myTable.newKey = "新值" -- 输出: 设置新键值对: newKey = 新值

-- 测试 __tostring 元方法
print(myTable) -- 输出: 这是一个自定义表

Lua 中元表的作用

在 Lua 中,元表(Metatable)是用于改变或扩展表(table)行为的一种机制。为一个表设置元表后,允许该表的行为关联元方法,从而实现定制化的操作。元表的使用可以让我们定义自定义的运算符行为、实现面向对象编程等。

元表的基本用法
元表可以通过设置特定的元方法来实现定制行为。下面是一个简单的示例,展示如何使用元表来实现两个表的相加操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 创建两个表
local table1 = {value = 10}
local table2 = {value = 20}

-- 定义一个元方法,用于实现表的相加操作
local metatable = {
__add = function(t1, t2)
return {value = t1.value + t2.value}
end
}

-- 为两个表设置相同的元表
setmetatable(table1, metatable)
setmetatable(table2, metatable)

-- 执行相加操作
local result = table1 + table2

-- 打印结果
print(result.value) -- 输出: 30

Lua 中 __index 和 __newindex 有什么作用

在 Lua 中, __index 和 __newindex 是元表(metatable)中用于控制表(table)行为的重要字段。它们主要用于对表进行自定义操作,如查找和更新不存在的键值对。以下是它们的详细说明:

__index 用于查找

__index 用于查找。如果访问一个表中不存在的字段,Lua 会在元表中查找__index 方法,并调用它来提供最终结果。

1
2
3
4
5
6
7
8
9
10
11
12
local myTable = {}  -- 创建一个空表

local myMetatable = {
__index = function(table, key)
return key .. " not found" -- 当访问不存在的键时,返回提示信息
end
}

setmetatable(myTable, myMetatable) -- 为表设置元表

print(myTable.name) -- 输出: name not found
print(myTable.age) -- 输出: age not found

__newindex 用于更新

__newindex 用于更新。如果对一个表中不存在的字段进行赋值操作,Lua 会调用元表中的__newindex 方法来处理赋值操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local myTable = {}  -- 创建一个空表

local myMetatable = {
__newindex = function(table, key, value)
print("Setting " .. key .. " to " .. value)
rawset(table, key, value) -- 使用 rawset 直接设置键值对,避免递归调用
end
}

setmetatable(myTable, myMetatable) -- 为表设置元表

myTable.name = "Lua" -- 输出: Setting name to Lua
myTable.age = 25 -- 输出: Setting age to 25

print(myTable.name) -- 输出: Lua
print(myTable.age) -- 输出: 25

在上述代码中,当对 myTable 中不存在的键进行赋值时,__newindex 方法会打印赋值信息并将值设置到表中。

总结
__index 用于查找:当访问一个表中不存在的键时,由__index 提供最终结果。
__newindex 用于更新:当对一个表中不存在的键进行赋值时,由__newindex 处理赋值操作。

Unity 热更新解决方案中,Lua 和 ILRuntime 方案的本质是什么?

lua 语言中的 upvalue 是什么?