Unity3D 面试题收录

Unity3D 客户端面试题收录(持续更新~)

面试题收录

本文收录一些对于 Unity3D 客户端可能遇到的面试题(持续更新~),答案仅出于个人理解,如有偏差,希望指正。

Unity3D


MonoBehaviour 生命周期的各个回调函数

  • Awake():初始化,早于 Start() 调用。
  • Start():依赖其他对象时的初始化。
  • FixedUpdate():每个固定时间步长调用,处理物理更新。
  • Update():每帧调用,处理大多数游戏逻辑。
  • LateUpdate():每帧调用,在 Update() 之后,通常用于相机跟随。
  • OnEnable()OnDisable():在组件启用和禁用时调用。
  • OnDestroy():在对象销毁时调用。

Prefab 是什么,有何作用

Prefab(预制体) 是一种存储和复用游戏对象(GameObject)及其组件的模板。它允许你创建一个游戏对象的原型,并可以在场景中反复实例化这个对象。


Unity 常用的资源文件夹有哪些

  • Resources:任何放在 Resources 文件夹中的资源可以通过 Resources.Load() 方法在运行时加载。该文件夹的内容会被 Unity 打包进最终的游戏构建中。
  • StreamingAssets:该文件夹用于存放在运行时需要直接访问的文件(如视频、配置文件等)。在构建时,Unity 不会对其内容进行压缩或转换,保持原始格式。访问这些文件时需要使用文件路径。
  • Editor:该文件夹用于存放编辑器扩展脚本。这些脚本只能在 Unity 编辑器中使用,而不会包含在最终的游戏构建中。
  • Plugins:用于存放插件(如 DLL 文件或第三方库)。在 Unity 中,放在这个文件夹下的库会被自动识别为插件。

C#


封装、继承、多态三大面向对象特性

封装是将对象的属性和行为封装在一起,通过访问控制机制(如 privateprotectedpublic 等修饰符)来隐藏对象的内部实现细节。外部只能通过公开的方法(即公共接口)与对象进行交互,而不能直接访问对象的内部数据。这种机制提高了代码的安全性和可维护性。

继承是通过定义一个类来基于已有类扩展其功能的能力。通过继承,子类可以继承父类的属性和方法,并可以重写或扩展这些功能。继承支持代码的复用和逻辑的扩展。

多态允许不同类型的对象以统一的接口进行操作。C# 中的多态性主要体现在方法的重载(编译时多态)和方法的重写(运行时多态)。通过多态,父类的引用可以指向子类的对象,执行子类重写的方法。


值类型和引用类型的区别

值类型

  • 存储位置:栈
  • 数据存储:直接存储数据
  • 赋值行为:复制数据(相互独立)
  • 常见类型:intfloatstructenum
  • 性能:快,分配和回收速度高

引用类型

  • 存储位置:堆(数据)、栈(引用)
  • 数据存储:存储引用(指向堆中的数据)
  • 赋值行为:复制引用(指向同一数据,互相影响)
  • 常见类型:classinterfacearraystring
  • 性能:相对较慢(涉及垃圾回收)

浅拷贝和深拷贝的区别

浅拷贝

  • 复制内容:仅复制对象的直接字段,对于引用类型字段,只复制引用,不复制引用指向的对象。
  • 实现方式:可以通过 MemberwiseClone() 方法实现。
  • 效果:原对象和新对象中的引用类型字段指向同一个内存地址,因此修改其中一个的引用类型字段会影响另一个。值类型字段则互不影响。

深拷贝

  • 复制内容:递归复制所有引用类型的对象,生成一个完全独立的新对象。
  • 实现方式:可以使用自定义拷贝方法、序列化/反序列化、第三方库等方式来实现。
  • 效果:原对象和新对象完全独立,修改其中一个不会影响另一个。

注意:如果直接使用 = 将一个对象赋值给另一个对象变量,并不是严格意义上的浅拷贝,而是赋值引用,让两个变量指向同一个对象实例,修改其中一个的属性会影响另一个。

使用 MemberwiseClone() 进行浅拷贝,会创建一个新对象实例,值类型字段会被复制,引用类型字段会共享引用。


重载和重写的区别

重载是一种在同一个类中定义多个同名方法,但参数不同。它提供编译时的多态性,允许方法在同名的情况下处理不同的输入。

重写是子类通过 override 关键字重新定义父类中的虚方法。它提供运行时的多态性,允许子类提供特定的实现,取代父类的行为。


委托和事件的区别

委托是 C# 中的一种类型,类似于函数指针。它允许将方法作为参数进行传递和调用。通过委托,你可以存储对方法的引用,并在需要时执行这些方法。

事件是基于委托的封装,适用于发布-订阅场景。事件只能在声明它的类内部触发,外部类不能直接调用事件,只能使用 +=-= 进行注册和取消订阅,防止了事件的误用。


Lua


pairs 和 ipairs 的区别

pairs 用于遍历所有键值对,适用于不规则的表或键为字符串的关联表,遍历顺序不固定。

ipairs 用于遍历数组形式的表,适用于数值键为连续索引的场合(从 1 开始),并在遇到第一个 nil 时停止。


如何判断一个 table 为空

在 Lua 中判断一个 table 是否为空并没有内置的函数,但可以通过遍历 table 来判断其中是否有任何元素。

一个常见的方法是使用 next() 函数来检查 table 中是否存在任何键值对。

1
2
3
function isTableEmpty(t)
return next(t) == nil
end

next(t):返回 t 中的第一个键值对。如果 t 中没有任何元素,则返回 nil

注意:不能直接通过 #t(长度运算符)来判断表是否为空,因为 #t 只对具有 连续整数键 的数组部分有效。

也不能简单判断 t == nil,因为要判断的是 table 里面是否有元素,而不是 t 变量是否有引用。


if 0 的结果

在 Lua 中,只有 falsenil 被认为是 ,其他所有值(包括 0 和空字符串 "")都被认为是


loadfile、dofile、require 的区别

loadfile

  • 编译但不执行代码。
  • 返回编译好的函数,需要手动调用来执行。

dofile

  • 立即加载并执行代码。
  • 不缓存结果,每次调用都重新加载和执行文件。

require

  • 专为模块系统设计。
  • 仅加载和执行一次模块,后续调用返回缓存结果。
  • 支持基于 package.path 和 package.cpath 查找模块。

如何实现面向对象

Lua 并不直接支持面向对象编程,我们可以通过 tablemetatable 来模拟面向对象。

通常会用一个 table 表示类,把它的 __index 元方法指向自己,定义一个 new 构造函数,在构造函数中创建一个新的 table 作为对象,通过 setmetatable 为对象设置元表,最后返回实例对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Person = {}  -- 定义一个类 Person
Person.__index = Person -- 设置 __index 元方法

-- 类的构造函数
function Person:new(name, age)
local obj = {} -- 创建一个新表来表示对象
setmetatable(obj, Person) -- 设置 metatable,表示 obj 是 Person 类的实例
obj.name = name or "Unknown"
obj.age = age or 0
return obj
end

-- 定义一个方法
function Person:speak()
print("name = " .. self.name .. ", age = " .. self.age)
end

-- 创建对象
local p1 = Person:new("Alice", 10)
local p2 = Person:new("Bob", 20)

p1:speak() -- 输出:name = Alice, age = 10
p2:speak() -- 输出:name = Bob, age = 20

这里之所以把类的 __index 元方法指向自己,是为了实现 Lua 的查找机制。当我们试图访问一个对象上不存在的字段或方法时,Lua 会查找该对象的元表(metatable),并尝试调用其 __index 元方法。


Unity3D 面试题收录
http://weikunou.github.io/2024/10/20/unity-interview-questions/
作者
Awake
发布于
2024年10月20日
许可协议