C# 相关
请简述拆箱和装箱
装箱操作:值类型隐式转换为object类型或由此值类型实现的任何接口类型的过程。
1.在堆中开辟内存空间。
2.将值类型的数据复制到堆中。
3.返回堆中新分配对象的地址。
拆箱操作:object类型显示转换为值类型或从接口类型到实现该接口值类型的过程。
1.判断给定类型是否是装箱时的类型。
2.返回已装箱实例中属于原值类型字段的地址。
程序集的重要特性
程序集的一个重要特性是它们包含的元数据描述了对应代码中定义的类型和方法。
in、ref与out关键字
in 关键字用于按引用传递参数,但它保证参数在方法内部是只读的。适用于需要高效传递大数据结构(如大型结构体)而无需修改的场景。参数在传递给方法前必须初始化。方法内部不能修改参数的值。
ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。
new 关键字用法
-
new 运算符 :用于创建对象和调用构造函数。
-
new 修饰符 :用于向基类成员隐藏继承成员。
-
new 约束 :用于在泛型声明中约束可能用作类型参数的参数的类型。
抽象类(abstract class)和接口(interface)的区别
相同点:
-
不能直接实例化:抽象类和接口都不能被直接实例化,只能通过继承或实现来使用。
-
定义成员的蓝图:它们都可以定义方法、属性、事件等成员的签名,而不提供具体的实现(抽象类可以提供部分实现)。
不同点:
-
抽象类 使用继承(inheritance)。一个类只能继承一个抽象类(单继承),但是可以实现多个接口。接口 使用实现(implementation)。一个类可以实现多个接口。
-
抽象类 可以包含已实现的方法(即普通方法)和未实现的方法(抽象方法),抽象方法必须在继承的非抽象子类中实现。接口 只能包含未实现的方法签名(直到C# 8.0引入了默认接口方法,接口也可以有默认实现)。
-
抽象类可以包含构造函数,用于初始化类的状态。接口不能包含构造函数。
-
抽象类 可以包含字段,接口 不能包含字段。
-
抽象类 可以包含各种访问修饰符(public、protected、private等)的成员。接口 中的成员默认是public的,不能有其他访问修饰符。
-
抽象类 适用于表示具有共同特性和行为的类的集合,提供部分实现和共享代码。接口 适用于定义一组相关功能,不关心这些功能的实现细节,强调行为契约。
什么叫应用程序域?
在.NET中,应用程序域(AppDomain)是一个隔离的执行环境,用于加载和执行应用程序。它提供了一种轻量级的、基于软件的隔离机制,用于在同一个进程中运行多个应用程序。以下是应用程序域的详细解释:
隔离性:应用程序域提供了类似于进程的隔离性,但不需要为每个应用程序分配一个独立的操作系统进程。各个应用程序域之间是相互隔离的,无法直接访问彼此的内存。
安全性:应用程序域可以配置不同的安全权限,以限制代码的执行能力。可以在应用程序域级别设置代码访问安全(CAS)策略,确保只有具有适当权限的代码才能运行。
稳定性:如果一个应用程序域中的代码出现崩溃或未处理的异常,其他应用程序域不会受到影响。这提供了更高的可靠性和稳定性,尤其是在托管不受信任的代码时。
独立性:各个应用程序域可以独立地加载和卸载程序集(DLL),管理其生命周期。可以在运行时卸载不再需要的应用程序域,释放资源。
什么是强类型?
为所有变量指定数据类型称为“强类型”。C#是强类型语言。
什么是CTS,什么是CLR,什么是CLS?
CTS:通用系统类型 Common Type System。所有.NET语言共享这一类型系统,实现它们之间无缝的互操作。该方案还提供了语言之间的继承性。
CLR:公共语言运行库 Common Language Runtime。是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。
公共语言规范 Common Language Specification。可以保证C#组件与其他语言组件间的互操作性。
什么是委托,什么是事件?
委托是一种引用类型,它定义了一个方法签名,可以指向任何具有相同签名的方法。委托允许将方法作为参数进行传递,并在运行时调用这些方法。委托类似于 C++ 函数指针,但它是类型安全的(因为它在编译时强制确保所引用的方法与委托的签名匹配)。委托常用于实现回调机制,在特定条件下调用指定的方法。
一个委托实例可以引用多个方法,这些方法在调用时会按顺序执行。(多播委托)
事件是基于委托的一种机制,提供了一种发布-订阅模式,用于在对象之间进行消息传递。事件是一个委托的封装,它使得订阅者(event subscriber)可以响应发布者(event publisher)触发的事件。
事件通过委托来实现,但提供了更严格的访问控制,只允许添加或移除事件处理方法。
功能和用途:委托是一种类型安全的方法指针,用于回调、方法参数和多播调用。事件是一种封装了委托的机制,主要用于发布-订阅模式,通知订阅者特定的事件发生。
访问控制:委托可以直接调用和分配方法。事件只允许添加或移除事件处理方法,不能在外部直接调用。
定义和使用:委托是独立定义,直接实例化和调用。事件是在类中定义,使用event关键字,并通过委托来实现。
值类型和引用类型的区别
- 值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高。
- 引用类型分配在托管堆上,引用类型的变量通常包含一个指向实例的指针,变量通过该指针来引用实例。
- 一个是值COPY,一个是地址COPY。
进程和线程和协程的区别
其实很简单,首先需要理解进程和线程是怎么一回事:进程是Windows系统中的一个基本概念,他包含着运行一个程序所需要的基本资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。
再谈谈线程和协程的区别。一般应用一个应用程序只使用线程这一“资源”。
需要明确,Unity只使用了一个线程,但是,我们需要”同时做很多事“,那Unity作为单线程,该如何去做,协程,就来了,协程是一种”伪线程“。 协同程序(coroutine)即协作式程序,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协成处于休眠状态。协程实际上是在一个线程中,只不过每个协程对CPU进行分时,协程可以访问和使用unity的所有方法和component。
同一时间只能执行某个协程,协程适合对某个任务进行分时处理。控制代码在特定的时间执行。
协程不是线程,也不是异步执行,跟Update一样,在主线程中执行。不用考虑同步和锁的问题。协程是一个分部组件,遇到条件(yield return)会挂起,直到条件满足才会被唤起执行后面的语句。
-
进程是系统进行资源分配和调度的单位。
-
线程是CPU调度和分派的单位。
-
一个进程可以有多个线程,这些线程共享这个进程的资源。
在Unity中,协程(Coroutine)是一种特殊的方法,用于在多个帧上执行代码,而不是一次性完成。这种特性使得协程特别适合处理需要逐帧执行的任务,例如动画、等待一定时间后执行某些操作、异步操作等。
在Unity中,协程通常使用IEnumerator接口和yield关键字来实现。协程方法必须返回IEnumerator,并且可以通过yield return语句来暂停执行。
协程的基本特性:
- 异步执行:协程允许你编写异步代码,以避免阻塞主线程。
- 逐帧执行:协程可以在每一帧暂停和恢复执行,使其非常适合处理需要跨越多帧的任务。
- 可中断和停止:协程可以在任意时刻中断或停止,提供灵活的控制机制。
Unity提供了多种类型的yield return语句,用于控制协程的执行:
// 暂停协程的执行直到下一帧。
yield return null;
// 暂停协程的执行直到下一帧。
yield return new WaitForSeconds(float seconds);
// 暂停协程的执行直到当前帧结束。
yield return new WaitForEndOfFrame();
// 暂停协程的执行直到下一次物理更新。
yield return new WaitForFixedUpdate();
协程是怎么实现的
在Unity中,协程(Coroutine)的实现依赖于C#的IEnumerator接口和编译器生成的状态机。它通过协程的特殊调度机制,实现了在多个帧之间暂停和恢复执行的方法。以下是协程实现的关键步骤和原理:
协程的基础
-
IEnumerator接口:协程方法必须返回IEnumerator,这意味着它可以被编译器识别并转换为一个状态机。
-
yield return关键字:在协程中使用yield return来暂停执行。每次遇到yield return,协程会返回控制权给调用者,等待下次调用时恢复执行。
当你在Unity中定义一个协程时,编译器会将协程方法转换为一个状态机。这个状态机是一个实现了IEnumerator接口的类,它跟踪协程的当前状态和执行位置。
Unity的MonoBehaviour类提供了StartCoroutine方法,用于启动协程。Unity内部有一个协程调度系统,会在每一帧检查所有活动的协程,并调用它们的MoveNext方法。
启动协程:当你调用StartCoroutine(MyCoroutine())时,Unity会创建状态机实例,然后将状态机注册到内部的协程调度系统。
调度协程:每一帧,Unity的协程调度系统会遍历所有活动的协程。调用它们的MoveNext方法。根据MoveNext的返回值和Current属性决定下一步操作。例如,当MoveNext返回true时,调度系统会根据Current的类型(例如WaitForSeconds)来决定何时再次调用MoveNext。
启动一个线程是用run()还是start()?
启动一个线程是调用start()方法
构造器Constructor是否可被override(重写)?
构造器Constructor不能被继承,因此不能重写override,但可以被重载Overloade。
在C#中using和new这两个关键字有什么意义。
using 关键字有两个主要用途:
- 作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。
- 作为语句,用于定义一个范围,在此范围的末尾将释放对象。
new 关键字:新建实例或者隐藏父类方法。
const和readonly有什么区别?
- const 字段只能在该字段的声明中初始化。
- 不允许在常数声明中使用 static 修饰符。
- readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。
什么是虚函数?什么是抽象函数?
-
虚函数:没有实现的,可由子类继承并重写的函数。
-
抽象函数:规定其非虚子类必须实现的函数,必须被重写。
在c#中所有可序列化的类都被标记为什么?
[Serializable]
public static const int A=1;
这段代码有错误么?是什么?
const不能用static修饰。
请简述string字符串不可变性的原因?
在很多语言当中如C#中字符串string被设计成具有不可变性的。从系统优化方面考虑这样的设计好处主要由三点。
首先,它允许在一个字符串上执行各种操作,而不实际地更改字符串。
字符串不可变,还因为这在操作或访问一个字符串时不会发成线程同步问题。
除此之外,CLR可通过一个String对象共享多个完全一致的String内容。这样能减少系统中的字符串数量,这正是字符串留用技术的目的。
1.字符串常量池的需要。字符串常量池是内存堆中一个特殊的存储区域,当创建一个string对象时,假设此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
2.允许string对象缓存HashCode。字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存,这是一种性能优化手段,因为这不必每次都去计算新的哈希码。
3.安全性。string被许多的类库用来当做参数,例如网络连接URL,文件路径Path,还有反射机制所需要的String参数等,假若string可变的,将会引起各种安全隐患。
综上,string不可变的原因包括设计考虑,效率优化问题,以及安全性三大方面。
override 与 overload的区别。
override:是指方法的重写,C#中可重写abstract、vritual 、override三类方法,重写是子类重写父类的以上三种方法,是动态绑定的,调用速度比方法隐藏慢,但灵活性好,重写本质是对方法表的修改
overload:是指方法的重载,一个类中的多个同名方法,参数列表不同,主要指参数个数或类型不同,重载的目的是为了让实现相似功能的方法用一个名称表示,方便程序员编写和理解代码。可读性好。
重写和重载都是多态的一种体现。
方法名称相同,但参数列表不同称为方法重载。用于在不同条件下解决同一类型的问题。
对比数组与集合。
Example:数组是Array,集合是List
特性 | 数组(Array) | 集合(Collection) |
---|---|---|
大小 | 固定大小 | 动态大小 |
类型安全 | 类型安全 | 类型安全(使用泛型集合) |
性能 | 访问元素速度快(O(1)) | 一般情况下访问速度较快,但略低于数组 |
内存 | 连续分配,节省内存 | 非连续分配,稍高的内存开销 |
灵活性 | 不灵活,不能动态改变大小 | 非常灵活,可以动态增加或移除元素 |
功能 | 基本功能 | 丰富的功能,如排序、搜索、过滤等 |
多维支持 | 支持多维数组 | 通常为一维,但可以嵌套使用多种集合类型 |
典型用途 | 固定大小的数据存储(如矩阵) | 需要动态管理数据集合的场景 |
数组:
- 当你知道数据的大小是固定的,并且需要快速访问时,使用数组。
- 数组适用于矩阵运算、固定长度的数据缓存等场景。
集合:
- 当你需要灵活的增删元素时,使用集合。
- 集合适用于管理动态数据,如游戏中的对象列表、动态输入数据等。
反射的实现原理
是指在程序运行时动态的获取类的信息以及操作类的成员和属性的机制。它允许程序在运行时检查和修改对象的结构、行为和属性,而不是在编译时确定这些信息。但是这样会带来一定的性能开销,通常比静态代码更慢,因为涉及到动态解析和调用,因此在性能需求较高的情况下,应该尽量避免过度使用反射。
原理主要涉及以下方面
元数据:在.NET 平台下,每个程序集都包含元数据,其中包含了有关程序集、类型、成员和其他信息的描述。这些元数据可以在运行时被反射机制所访问和利用
类型加载和装载:当程序需要使用某个类型时,CLR(通用语言执行平台(Common Language Runtime,简称 CLR)是微软为他们的.NET 的虚拟机所选用的名称。它是微软对通用语言架构(CLI)的实现版本,它定义了一个代码执行的环境。CLR 执行一种称为通用中间语言的字节码,这个是微软的通用中间语言实现版本)会负责加载和装载该类型的元数据信息。这些元数据包含了类型的结果、成员和方法等信息。
Type 类和 MemberInfo 类:在.Net 中,Type 类和 MemberInfo 类是反射的核心,它们提供了许多方法和属性,可以用于动态的获取和操作类型信息和成员信息。通过这些类,可以获取类的名称、属性、方法、字段等信息,也可以调用对象的方法和属性
动态调用:反射允许程序在运行时动态的调用方法、属性和构造函数,而不需要在编译时确定调用哪个方法或属性
Func 和 Action
Func: 是一个泛型委托类型,它表示一个具有指定返回类型的方法。Func 委托可以接受 0 到 16 个输入参数,并返回一个指定类型的结果。例如,Func<int, string> 表示一个接受一个整数参数并返回一个字符串结果的方法。
Func<int, int, int> add = (x, y) => x + y; //前两个为传的参数,最后一个为返回值
int result = add(3, 5); // 结果为 8
12
Action: 也是一个委托类型,但它表示一个没有返回值的方法。与 Func 不同,Action 委托可以接受 0 到 16 个输入参数,但不返回任何值。
Action<string> logMessage = message => Console.WriteLine(message);
logMessage("Hello, world!"); // 打印 "Hello, world!"
12
什么是 lambda 表达式
一个用来代替委托实例的匿名方法(本质是一个方法,只是没有名字,任何使用委托变量的地方都可以使用匿名方法赋值),从而让代码更加简洁。被 Lambda 表达式引用的外部变量叫做被捕获的变量;捕获了外部变量的 Lambda 表达式叫做闭包;被捕获的变量是在委托被实际调用的时候才被计算,而不是在捕获的时候
设计模式相关
请说出面向对象的设计原则,并分别简述它们的含义。
- 单一职责原则:一个类,最好只做一件事,只有一个引起它的变化。
- 开放-封闭原则:对于扩展是开放的,对于更改是封闭的。
- 里氏替换原则:子类必须能够替换其基类。
- 依赖倒置原则:设计应该依赖于抽象而不是具体实现。
- 接口隔离原则:使用多个小的专门的接口而不要使用一个大的总接口。
- 最少知识原则:如果两个类不彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
- 合成/聚合复用原则:尽量使用合成/聚合,尽量不要使用类继承。
请描述你所了解的设计模式,并说明在你的项目中哪里使用过?
单例: 对象池,游戏管理器。
观察者模式:
描述:定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。
使用场景:游戏事件系统、UI通知系统、任务或成就系统
工厂模式:
描述:定义一个接口或基类,让子类决定实例化哪一个类。工厂方法把类的实例化延迟到子类。
使用场景:创建不同类型的敌人、生成各种类型的武器或道具
单例模式的懒汉模式和饿汉模式
懒汉模式指的是单例实例在第一次被使用时才被创建。这种方式可以延迟实例的创建,直到确实需要它时才初始化,从而节省资源。
优点是只有在需要时才创建实例,节省资源,避免在程序启动时创建不必要的对象。
缺点是可能导致线程安全问题,在多线程环境中,第一次创建实例时可能会引发竞态条件,需要额外的同步机制来确保线程安全。
饿汉模式指的是单例实例在类加载时就被创建。这种方式在类加载时就完成了实例化,避免了线程安全问题。
优点是线程安全:由于实例在类加载时就已经创建,因此不需要额外的同步机制。而且实现简单,没有复杂的延迟加载逻辑。
缺点是资源浪费,即使单例实例可能永远不会被使用,也会在类加载时创建,浪费资源。以及提前加载:可能会导致程序启动变慢,尤其是如果单例实例的初始化开销较大时。
数据结构相关
说出你所了解的数据结构,并说明它们的特点。
列表:特点:有序,内存空间连续,读取速度快,大小可变,通过位置索引取元素。缺点:输入,删除的速度慢。
类库已实现:List、ArrayList
字典:特点:键值对的集合,通过键查找值,查找方便,无序
类库已实现:Dictionary、Hashtable
栈:特点:后进前出 LIFO(Last In First Out)支持操作:入栈,出栈,读栈顶。
类库已实现:Stack、Stack
队列:特点:前进前出 FIFO(First In First Out)。支持操作:入队,出队,读队首
类库已实现:Queue。
链表:特点:节点内存空间不连续,插入,删除速度快,读取速度慢。
类库已实现:LinkedList
数组:特点:内存空间连续,长度固定,读取速度快
类库已实现:Array。
树:特点:可以将对象组织为层次结构方便快速访问。
实现:可使用设计模式中的组合模式实现树结构
Lua 相关
Lua 如何创建对象
在Lua中,可以通过表(table)来模拟面向对象编程的概念。Lua本身并没有内建的类(class)系统,但你可以使用表来模拟对象,使用元表(metatable)来模拟继承。以下是一个简单的示例,展示了如何在Lua中创建对象。
-- 定义一个“类”
Person = {}
-- 为“类”添加方法
function Person:new(name, age)
-- 创建一个新的表作为对象
local obj = {}
-- 将“类”的引用设置为对象的metatable
setmetatable(obj, self)
-- 初始化对象的属性
self.__index = self
obj.name = name
obj.age = age
return obj
end
function Person:speak()
print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end
-- 创建对象
person1 = Person:new("Alice", 30)
person2 = Person:new("Bob", 25)
-- 调用对象的方法
person1:speak() -- 输出:My name is Alice and I am 30 years old.
person2:speak() -- 输出:My name is Bob and I am 25 years old.
Lua 中用多种方式创建对象
使用简单的表作为对象
-- 创建一个对象
person = {
name = "Alice",
age = 30,
speak = function(self)
print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end
}
-- 调用对象的方法
person:speak() -- 输出:My name is Alice and I am 30 years old.
使用原型模式
-- 原型对象
PersonPrototype = {
speak = function(self)
print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end
}
-- 创建对象的函数
function createPerson(name, age)
local person = {}
setmetatable(person, { __index = PersonPrototype })
person.name = name
person.age = age
return person
end
-- 创建对象
person = createPerson("Alice", 30)
-- 调用对象的方法
person:speak() -- 输出:My name is Alice and I am 30 years old.
使用工厂模式
-- “类”定义
Person = {}
Person.__index = Person
-- 创建对象的工厂方法
function Person.create(name, age)
local obj = setmetatable({}, Person)
obj.name = name
obj.age = age
return obj
end
-- 为“类”添加方法
function Person:speak()
print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end
-- 使用工厂方法创建对象
person = Person.create("Alice", 30)
-- 调用对象的方法
person:speak() -- 输出:My name is Alice and I am 30 years old.
使用构造函数模式
-- 构造函数
function Person(name, age)
local obj = {}
obj.name = name
obj.age = age
obj.speak = function(self)
print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end
return obj
end
-- 创建对象
person = Person("Alice", 30)
-- 调用对象的方法
person:speak() -- 输出:My name is Alice and I am 30 years old.
Unity 相关
Animation 和 Animator 的区别在哪里
Animation:
- 基本功能:
Animation
组件用于播放简单的动画片段(clips)。它可以在没有复杂状态机的情况下直接播放、停止或控制动画片段。 - 动画片段管理:
Animation
组件可以直接管理多个动画片段,你可以通过代码或Inspector界面直接控制这些动画片段。 - 使用场景:
Animation
更适合处理简单的动画需求,比如仅需要播放几个动画片段而不需要复杂的过渡和状态管理的情况。 - 控制方式:使用代码控制较为简单,如通过
Play()
、Stop()
、CrossFade()
等方法控制动画。 - 过渡和状态管理:
Animation
组件没有内置的状态机和过渡功能,所有过渡效果需要手动编写。
Animator:
Animator
组件使用Mecanim动画系统,可以管理复杂的动画状态机(Animator Controller)。它支持动画片段之间的过渡、混合、层次化动画控制等高级功能。
- 动画片段管理:
Animator
通过Animator Controller来管理动画片段,并可以在一个图形化的状态机中定义动画片段之间的过渡条件和触发器。 - 控制方式更加丰富,可以使用参数(Triggers、Bools、Ints、Floats)和条件来控制动画状态的切换。
Animator
组件提供了强大的状态机和过渡管理功能,可以轻松定义和控制动画之间的过渡。
特性 | Animation | Animator |
---|---|---|
复杂性 | 适用于简单动画 | 适用于复杂动画和状态管理 |
状态管理 | 无状态机和过渡管理 | 支持状态机和过渡管理 |
控制方式 | 直接通过代码控制动画片段 | 通过Animator Controller和参数控制 |
动画片段管理 | 直接管理多个动画片段 | 通过Animator Controller管理动画片段 |
使用场景 | 简单动画需求,如UI动画 | 复杂动画需求,如角色动画系统 |
如何开启和关闭协程
StartCoroutine("Test");
StopCoroutine("Test");
RawImage和Image的区别
- RawImage可以显示任何类型的Texture,而Image只能显示Sprite
- RawImage支持UV Rect,Image支持Simple(简单)/Sliced(切片)/Tiled(平铺)/Filled(填充)
AssetBundle 冗余是为什么,怎么解决
资源冗余的原因:
- 重复引用的资源:当多个
AssetBundle
引用同一个资源(例如材质、纹理等)时,资源可能会被打包多次,导致冗余。 - 未正确配置资源依赖:如果在打包
AssetBundle
时未正确配置资源依赖,某些共享资源可能会被多个AssetBundle
单独打包。 - 不必要的资源:包含了一些不必要的资源或者未使用的资源。
如何解决冗余:
- 精细的资源分包策略:
- 尽量将共享资源单独打包到一个独立的
AssetBundle
中,以避免重复打包。 - 将常用资源或基础资源单独打包为一个
AssetBundle
,其他AssetBundle
引用这些共享资源。
- 尽量将共享资源单独打包到一个独立的
- 检查和优化冗余资源
-
- 使用
AssetBundle
分析工具(如 Unity 提供的AssetBundle Browser
)检查AssetBundle
的内容,找出重复的资源。
- 使用
- 利用
BuildPipeline.GetAssetBundleDependencies
函数来检查AssetBundle
之间的依赖关系。
-
- 清理未使用的资源,确保打包前清理未使用的资源,避免将不必要的资源打包到
AssetBundle
中。
Animation 和 Animator 的区别在哪里
Animation:
- 基本功能:
Animation
组件用于播放简单的动画片段(clips)。它可以在没有复杂状态机的情况下直接播放、停止或控制动画片段。 - 动画片段管理:
Animation
组件可以直接管理多个动画片段,你可以通过代码或Inspector界面直接控制这些动画片段。 - 使用场景:
Animation
更适合处理简单的动画需求,比如仅需要播放几个动画片段而不需要复杂的过渡和状态管理的情况。 - 控制方式:使用代码控制较为简单,如通过
Play()
、Stop()
、CrossFade()
等方法控制动画。 - 过渡和状态管理:
Animation
组件没有内置的状态机和过渡功能,所有过渡效果需要手动编写。
Animator:
Animator
组件使用Mecanim动画系统,可以管理复杂的动画状态机(Animator Controller)。它支持动画片段之间的过渡、混合、层次化动画控制等高级功能。
- 动画片段管理:
Animator
通过Animator Controller来管理动画片段,并可以在一个图形化的状态机中定义动画片段之间的过渡条件和触发器。 - 控制方式更加丰富,可以使用参数(Triggers、Bools、Ints、Floats)和条件来控制动画状态的切换。
Animator
组件提供了强大的状态机和过渡管理功能,可以轻松定义和控制动画之间的过渡。
特性 | Animation | Animator |
---|---|---|
复杂性 | 适用于简单动画 | 适用于复杂动画和状态管理 |
状态管理 | 无状态机和过渡管理 | 支持状态机和过渡管理 |
控制方式 | 直接通过代码控制动画片段 | 通过Animator Controller和参数控制 |
动画片段管理 | 直接管理多个动画片段 | 通过Animator Controller管理动画片段 |
使用场景 | 简单动画需求,如UI动画 | 复杂动画需求,如角色动画系统 |
3D物体如何显示在UI中的一个Image的前面
可以建立两个Canvas,一个负责存储不能被遮挡的UI界面,另一个负责组织需要被遮挡的UI组件。然后将需要被遮挡的Canvs的Render Mode设置为 Screen Space -Camera。这样UI组件就会显示在3D物体的后面。如果有更多的需求,还可以调整物体的Z轴。
说说帧同步和状态同步
UGUI 的画布有几种模式?分别是什么?
-
Screen Space-Overlay:画布位于屏幕空间的最上方,覆盖在其他所有内容之上。这种模式常用于 UI 元素不需要与场景中的 3D 物体交互的情况,例如游戏菜单、提示信息等。
-
Screen Space-Camera:画布位于相机的前方,但不与 3D 场景中的其他物体交互。可以通过设置画布的 Render Camera 属性来指定相机。这种模式通常用于需要与 3D 场景中的物体进行交互,但仍然在屏幕空间内的 UI,比如角色头顶的血条、NPC 对话框等。
-
WorldCamera:画布位于 3D 世界中的某个位置,可以与 3D 场景中的其他物体进行交互。这种模式允许 UI 元素随着场景中的物体移动、旋转和缩放,适用于需要与 3D 环境完全融合的 UI,比如游戏中的虚拟现实界面、HUD(头顶显示)等。
UGUI优化
Unity中的UGUI(Unity GUI)是Unity引擎提供的一套用户界面系统,用于创建游戏内的交互界面。优化UGUI可以提高游戏的性能和用户体验。以下是一些优化UGUI的方法,并提供相应的代码示例:
-
减少Canvas的数量:尽量将UI元素组织在同一个Canvas下,避免创建过多的Canvas。每个Canvas都会有一个额外的Draw Call,减少Canvas数量可以减少Draw Call。
-
避免UI元素的动态创建和销毁:
- 尽可能在场景加载时创建所有需要的UI元素,并通过激活和隐藏来控制显示。
- 使用对象池(Object Pooling)来管理频繁创建和销毁的UI元素。
- 使用Atlas(图集):
- 将多个小图片合并成一个大图片(Atlas),并通过Sprite切片来引用。
- 这样可以减少Draw Call的数量,因为多个Sprite可以从同一个Texture中渲染。
- 优化Text的使用:
- 尽量避免使用过多的动态文本(如频繁更新的计分板)。
- 使用BMFont或TextMeshPro等高性能文本解决方案。
- 避免UI元素的深度重叠:
- 确保UI元素在Canvas的层级中不会不必要地重叠,以减少Overdraw。
- 关闭不必要的UI组件:
- 如Image的Raycast Target,如果不需要处理点击事件,可以关闭以减少性能开销。
- 使用Mask组件谨慎:
- Mask组件会增加额外的渲染开销,只在必要时使用。
- 减少UI动画的复杂度:
- 避免使用过多的动画和复杂的动画效果。
- 使用DOTween等高效的动画库。
- 代码层面的优化:
- 避免在Update中频繁调用UI元素的属性或方法。
- 使用Cached组件引用来减少GetComponent的调用。
- 对于频繁更新的UI元素,使用Coroutine或InvokeRepeating代替Update中的检查。
UGUI 如何打图集和使用图集
创建图集的流程
graph LR Project --> Create --> SpriteAtlas可以将文件夹,纹理或精灵分配给Sprite Atlas。可以将整个文件夹分配给Sprite Atlas资产,该文件夹中的所有纹理(包括子文件夹)都将被打包。
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(a.AssetPath);
if (atlas == null)
continue;
var s = atlas.GetSprite(spriteName);
if (s != null)
{
GetComponent<SpriteRenderer>.sprite = s;
}
为什么图集可以减少 Draw Call
图集它是一种将多个纹理合并为一个组合纹理的资源。Unity 可以调用此单个纹理来发出单个绘制调用而不是发出多个绘制调用,能够以较小的性能开销一次性访问压缩的纹理
什么是 Draw Call
Draw Call就是一个命令(也是CPU调用图形编程接口,比如DirectX或OpenGL),一个由CPU发起,命令GPU执行的命令,就是CPU告诉GPU可以对某个模型进行渲染处理的传话人。一般来说一个独立的模型会产生一个Draw Call,但是如果经过一些特殊的处理,比如所动态合批,静态合批等等操作,就会降低Draw Call。
那么为什么drawcall会影响CPU性能呢?
CPU将Draw Call发往GPU过程中并不是很直接的就交给GPU,而是先将这些数据存到一个命令缓冲区内,然后再后面的几何阶段由GPU进行数据的读取。GPU的渲染能力是很强的,渲染300个和3000个三角网格通常没有什么区别,因此渲染速度往往快于CPU提交命令的速度。Draw Call的数量太多,CPU就会把大量时间花费在提交Draw Call命令上,造成CPU的过载。
为什么图片的大小要设成2的N次方
因为大部分游戏引擎底层的渲染方式都是基于OpenGL的,OpenGL载入纹理图片时,所用内存会自动扩张到2的N次方(500 x 500 → 512 x 512)。
- 因为转化过程比较慢,由运行程序转换十分耗时,所以Unity3D提前将资源转化为符合标准的图片,这样可以提升转化速度
- 节省内存。一张图片的大小为10x10像素,OpenGL会按照16x16的规格将图片载入到内存中;如果图片大小为64x65,那么就会按照64x128载入了,这就造成了内存的无必要开销。
- 减少包体。打成图集后的合成的大图会比之前所有的散图所占用的物理存储更小。这样从通过减小图片资源物理存储大小起到压缩游戏安装包的作用。
Button怎样接受用户点击并调用函数,具体方法名称是什么?
在Unity编辑器中:
- 在Hierarchy视图中,创建一个空的GameObject,命名为GameManager。
- 将ButtonHandler.cs脚本附加到GameManager对象上。
- 选择Hierarchy视图中的Button对象。
- 在Inspector视图中,找到Button (Script)组件。
- 在Button组件的底部,有一个On Click ()事件列表。
- 点击+按钮,添加一个新的事件项。
- 将GameManager对象拖到新添加的事件项的Object字段中。
- 在右侧的下拉菜单中,选择
ButtonHandler -> OnButtonClick
。
通过Unity持久化存储PlayerPrefs,如何实现存储、获取或删除一个整数数据。
// 存储
PlayerPrefs.SetInt("MyKey", 1);
// 获取
int myKey = PlayerPrefs.GetInt("MyKey");
// 删除
PlayerPrefs.DeleteKey("MyKey");
PlayerPrefs.DeleteAll();
一个角色要用到unity中寻路系统,应该添加哪个组件?
NavMeshAgent
NavMeshObstacle组件的作用?
寻路网格动态碰撞组件,用于运动的物体阻碍寻路物体效果。
射线中RaycastHit代表什么?
在Unity中,RaycastHit 是一个结构体,用于存储射线投射(Raycasting)命中物体的信息。当你使用物理引擎进行射线投射时(如通过 Physics.Raycast 方法),如果射线与任何物体碰撞,则返回一个 RaycastHit 实例,包含有关该碰撞的信息。
通过什么可以区分射线碰到的游戏对象?
通过LayerMask,RaycastHit返回的碰撞的标签。
RaycastHit 结构体包含许多有用的属性,用于获取关于射线命中物体的详细信息。以下是一些常用属性:
collider:被射线命中的碰撞体(Collider)。
point:射线与物体碰撞的世界坐标。
normal:碰撞点处表面的法线。
distance:从射线起点到碰撞点的距离。
transform:被射线命中的物体的变换(Transform)。
rigidbody:被射线命中的物体的刚体(Rigidbody),如果存在的话。
public class RaycastExample : MonoBehaviour
{
void Update()
{
// 创建一条从相机位置出发的射线,方向为相机的前方
Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
RaycastHit hit;
// 如果射线命中任何物体
if (Physics.Raycast(ray, out hit))
{
// 输出命中物体的名称
Debug.Log("Hit: " + hit.collider.name);
// 输出命中点的世界坐标
Debug.Log("Hit Point: " + hit.point);
// 输出碰撞点的法线
Debug.Log("Hit Normal: " + hit.normal);
// 输出从射线起点到碰撞点的距离
Debug.Log("Hit Distance: " + hit.distance);
// 输出命中物体的变换
Debug.Log("Hit Transform: " + hit.transform.name);
// 如果命中物体有刚体,输出刚体信息
if (hit.rigidbody != null)
{
Debug.Log("Hit Rigidbody: " + hit.rigidbody.name);
}
}
}
}
MeshCollider和其他Collider的一个主要不同点?
MeshCollider是网格碰撞器,对于复杂网状模型上的碰撞检测,比其他的碰撞检测精确的多,但是相对其他的碰撞检测计算也增多了,所以一般使用网格碰撞也不会在面数比较高的模型上添加,而会做出两个模型,一个超简模能表示物体的形状用于做碰撞检测,一个用于显示。
MeshCollider有Mesh属性
Unity3d中的碰撞器和触发器的区别?
碰撞器物体不能互相进入到对方内部,触发器可以。
触发器角色控制器可以使用,碰撞器中不能使用。
触发器没有物理属性了,碰撞器可以有力存在。
碰撞器调用OnCollisionEnter/Stay/Exit函数,触发器调用OnTriggerEnter/Stay/Exit函数。
物体发生碰撞的必要条件。
两个物体都必须带有碰撞器(Collider),其中一个物体还必须带有Rigidbody刚体。
注:动的物体,你希望发生碰撞效果的物体要带能接受物理引擎的操作,最常见的就是挂载Rigidbody组件。
CharacterController和Rigidbody的区别?
在Unity游戏引擎中, CharacterController 和 Rigidbody 是两种不同的组件,它们用于实现物理行为和角色控制,但它们之间有一些关键的区别:
物理引擎集成:
Rigidbody 是Unity物理引擎的核心组件,它允许物体受到重力、碰撞和其他力的影响。 Rigidbody 可以模拟真实的物理行为,如碰撞响应和运动。
CharacterController 是一个更高级的组件,它提供了一个简化的接口来控制角色移动和跳跃,但它不直接使用Unity的物理引擎。它通过检测碰撞来模拟物理行为,但不会像 Rigidbody 那样精确模拟物理碰撞。
用途区别:
Rigidbody 通常用于需要精确物理模拟的物体,如车辆、环境物体等
CharacterController 则更适合于玩家控制的角色,因为它提供了一个更直接的控制方式,可以轻松实现跳跃、爬梯等动作。
性能区别:
Rigidbody 由于其物理模拟的复杂性,可能会消耗更多的计算资源。
CharacterController 由于其简化的物理模拟,通常在性能上更加高效。
控制方式:
使用 Rigidbody 时,你可以通过设置力、速度、加速度等来控制物体。
使用 CharacterController 时,你可以通过调用其方法(如 Move 、 SimpleMove 、 IsGrounded 等)来控制角色的移动和跳跃。
碰撞检测:
Rigidbody 会参与Unity的碰撞检测系统,并且可以与其他 Rigidbody 或静态碰撞器进行交互。
CharacterController 有自己的碰撞检测机制,它使用胶囊形状来检测碰撞,并且可以设置大小和中心。
适用场景:
如果你需要一个物体在物理世界中自然地移动和响应, Rigidbody 是更好的选择。
如果你需要对角色的移动进行精细控制,并且希望简化物理交互, CharacterController 是更合适的选择。
总的来说,选择使用 CharacterController 还是 Rigidbody 取决于你的游戏设计需求和性能考虑。如果你需要更精确的物理模拟,选择 Rigidbody ;如果你需要更直接和高效的控制,选择 CharacterController 。
CharacterController自带胶囊碰撞器,里面包含有刚体的属性;
Rigidbody就是刚体,使物体带有刚体的特征。
当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?
穿透(碰撞检测失败)。避免的方法是把刚体的实时碰撞检测打开Collision。Detection修改为Continuous Dynamic
在unity3d中物体发生碰撞的整个过程中,有几个阶段,分别列出对应的阶段函数?
主要是三个阶段:
-
Collider.OnCollisionEnter 进入碰撞,当collider/rigidbody开始触动另一个rigidbody/collider时OnCollisionEnter被调用。
-
Collider.OnCollisionStay:逗留碰撞,每个collider/rigidbody触动rigidbody/collider,将在每帧调用OnCollisionStay。通俗的说,一个碰撞器或刚体触动另一个刚体或碰撞器,在每帧都会调用OnCollisionStay,直到它们之间离开不接触。
-
Collider.OnCollisionExit 退出碰撞,当 collider/rigidbody停止触动另一个 rigidbody/collider时,OnCollisionExit被调用。
反向旋转动画的方法是什么?
反转动画,将动画的播放速度调到-1。
写出Animation的五个方法。
AddClip 添加剪辑、Blend 混合、Play 播放、Stop 停止、Sample 采样 、CrossFade淡入淡出切换动画、IsPlaying是否正在播放某个动画
Itween插件的作用是什么,Itween作用于世界坐标还是局部坐标,请列举出3个其常用方法?
ITween是补间动画的一个插件,主要作用就是给出开始、结束的值、时间,此插件实现各种动画,晃动,旋转,移动,褪色,上色,音量控制等等。
-
MoveTo 物体移动
-
ColorTo:随着时间改变对象的颜色组
-
LookTo:随时间旋转物体让其脸部朝向所提供的Vector3或Transform位置
DoTween常用方法
DOMove(Vector3 to, float duration, bool snapping)
DORotate(Vector3 to, float duration, RotateMode mode)
DOScale(float/Vector3 to, float duration)
什么是法线贴图 、CG动画 ?
法线贴图:是一种特殊的纹理,可以应用在3D表面,让低模呈现出更明显的凹凸效果。一般应用在CG动画、美术效果要求较高的单机游戏
CG动画:游戏中的CG动画其实是用3D模拟引擎制作的游戏短片,一般画面效果比较真实。
Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?
Unity支持多线程,如果同时要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。
-
虽然支持多线程,但是仅能从主线程中访问Unity3D的组件、对象和Unity3D系统调用,所以如果使用的话需要把组件中的数值传到开启的新线程中。
-
C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象
Unity3D的协程和C#线程之间的区别是什么?
多线程程序同时运行多个线程,除主线程之外的线程无法访问Unity3D的对象、组件、方法,而在任一指定时刻只有一个协程在运行。
什么是协同程序?
在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。使用户可以编写更灵活的程序来控制运动、序列以及对象的行为。
在场景中放置多个Camera并同时处于活动状态会发生什么?
游戏界面可以看到很多摄像机的混合。
Unity摄像机有几种工作方式,分别是什么?
正交和透视。
正交无法看到一个物体距离自己有多远,或者远近的变化,物体也不会随着距离而收缩,所以一般做2D游戏或者是UI时会使用正交摄像机。
透视一般看物体会随着距离有大小的变化,一般3D游戏里经常使用这种摄像机。
unity当需要频繁创建一个物体对象时,怎样减少内存?
动态加载再实例化,如果自己不主动清理内存的话,再次加载不会增加内存的,会自动去取之前已经加载好的assets,如果这一个assets你都嫌多的话,那你只能减资源了,比如,模型面数,纹理尺寸等。
MeshRender中material和shader的区别?
MeshRender是模型渲染的组件,有此组件物体才能显示出来。
Material是材质球,实际就是shader的实例,并进行赋值,贴图、纹理、颜色等。
Shader是着色器,实际上是一段程序,还可以用来实现一些仅靠贴图不容易实现的效果,如玻璃。
Shader大致分为:表面着色器、顶点和片元着色器、固定功能着色器
Unity3d提供了几种光源,分别是什么?
4种,平行光(Directionl light) ,点光源(Point Light) ,聚光灯(Spot Light),区域光源(Area Light)
物理更新一般在哪个系统函数里?
FixedUpdate,每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。
OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生?
graph TD Awake --> OnEnable --> StartAwake用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如GameObject.FindWithTag这样的函数搜索它们。每个游戏物体上的Awke以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息。
Start在脚本的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。
假设有一物体,一个脚本挂了两次,请指出Awake的执行顺序。
ExcutionOrder有关,不设定顺序的时候初始化Start的顺序不确定。
移动摄像机的动作放在哪个系统函数中,为什么放在这个函数中?
LateUpdate,它是在Update结束后才调用。一般来说摄像机的跟随,都是在所有Update操作完成后进行的,以免出现摄像机已经推进了,但是还有角色未刷新的空帧出现。
请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义?
当物体是否可见切换之时。可以用于只需要在物体可见时才进行的计算。
当renderer(渲染器)在任何相机上都不可见时调用:OnBecameInvisible
当renderer(渲染器)
Unity中的寻路系统属于那种寻路方式?
网格寻路。
动态加载资源的方式?区别是什么?
Resources.Load() 和 AssetBundle
区别:
-
Resources资源的加载是动态加载内部的,AssetBundle 是动态加载外部的。
-
Resources是动态内部调用,Resources在编辑环境下是project窗口的一个文件夹,调用里面的资源,可以用Resources类,比如Resources.Load,打包后这个文件夹是不存在的,会统一生成Assets资源。
-
AssetBundle 是外部调用,要用AssetBundle 首先要先把资源打包为.assetbundle文件,再动态的去加载这个文件,本地或者网络服务器都可以。
如何销毁一个UnityEngine.Object及其子类
Destory()
获取、增加、删除组件的命令分别是什么?
GetComponent();
AddComponent();
Material和Physic Material区别?
PhysicMaterial 物理材质:主要是控制物体的摩擦,弹力等物理属性。
Material材质:主要是控制一个物体的颜色,质感等显示。
Unity3d的物理引擎中,有几种施加力的方式,分别描述出来。
Rigidbody.Addforce () // 这种力可以是持续性的,也可以是瞬时的
Rigidbody rb = GetComponent<Rigidbody>();
rb.AddForce(Vector3.forward * forceAmount);
Rigidbody.AddTorque () // 在物体上施加扭矩(旋转力)
Rigidbody rb = GetComponent<Rigidbody>();
rb.AddTorque(Vector3.up * torqueAmount);
// 直接改变 Rigidbody 的 velocity 属性可以施加速度,但是不会产生物理效果,而是改变物理的速度
Rigidbody rb = GetComponent<Rigidbody>();
rb.velocity = new Vector3(velocityX, velocityY, velocityZ);
Rigidbody.MovePosition () // 直接设置物体的位置,会受到物理引擎的影响,可以直接穿过碰撞器
Rigidbody rb = GetComponent<Rigidbody>();
rb.MovePosition(rb.position + Vector3.forward * speed * Time.deltaTime);
什么是导航网格( NavMesh)
Unity内一种用于实现自动寻路的网格
什么是 FSM 有限状态机?
将对象的行为抽象为一系列状态,对象在不同的状态之间转换,从而实现不同的行为和动作
通常一个 FSM 由以下要素组成
- 状态:对象可能处于的各种状态,每个状态都有对应的行为和过渡条件
- 转换:状态之间的转换条件
- 行为:对象在特定状态下执行的行为或动作
- 状态机:管理状态和状态之间的转换
什么是渲染管道?
是指在图形渲染过程中,图形数据从输入到最终输出的一系列处理步骤和阶段,即将场景中的模型、材质、光照等信息转换成最终的图像输出到屏幕上
共分为四个阶段
- 应用阶段:在这个阶段,cpu 将决定传递 GPU 什么样的数据,并告诉 GPU 这些数据的渲染状态
- 几何阶段:又分为几个常见的渲染阶段。放入显存和 DrawCall、顶点着色器、曲面细分着色器、几何着色器、投影、裁剪、屏幕映射
- 光栅化阶段:因为在前面几个阶段只是得到了顶点信息,还不能被显示在屏幕上的像素,所以还要进行处理。图元组装、三角形遍历、片元着色器、逐片元操作
- 输出到屏幕
有两个物体,如何让一个物体永远比另一个物体先渲染?
- 调整物体距离相机的距离,越近的越先渲染
- 调整物体的渲染顺序(Sorting Layer)
当场景中有大量的碰撞或复杂物理场景时,该怎么去优化?
- 如有可能的话,减少刚体数量
- 优化碰撞体,让碰撞体的形状尽可能的简单,避免使用过于复杂的 MeshCollider,而是使用更简单的形状
- 批量处理,如有多个相似的物体需要进行模拟,可以尝试合并成一个复合刚体
- 使用不同的层级,避免不必要的碰撞检测
- 利用物理引擎设置,可以通过更改物理迭代次数和时间步长
- 避免频繁的状态改变,例如频繁的启用 / 禁用,切换成动 / 静态
- 使用空间分割算法,如四叉树和八叉树(详情可见下面的算法介绍),来进行碰撞检测。这
- 算法可以将空间切割成更小的区域,每个区域包含一定数量的物体。这样,在进行碰撞检测时,可以只对相邻区域中的物体进行检测,从而减少计算量。
操作系统
线程之间是怎么通信的
- 共享内存
- 消息队列
进程之间是怎么通信的
- 管道通信
- 消息队列
- 共享内存
3D 数学
向量的点乘、叉乘以及归一化的意义?
-
点乘计算两个向量之间的夹角,还可表示某一方向的投影。
-
叉乘得到的是法向量。
-
标准化向量:用在只关系方向,不关心大小的时候。
计算机网络
请简述TCP与UDP的区别。
- TCP是面向连接的,在传输数据前需要建立连接(通过三次握手)。UDP是无连接,不需要建立连接即可发送数据。
- TCP是可靠传输,确保数据包的有序和完整,具有错误检测、丢包重传、流量控制等机制。UDP不保证可靠传输,数据包可能丢失、重复或乱序,没有重传机制。
- TCP面向流,数据以字节流的形式传输,没有明确的消息边界。UDP面向报文,数据以独立的报文段(Datagram)形式传输,有明确的消息边界。
- TCP首部开销较大,通常为20字节。UDP首部开销较小,只有8字节。
- TCP适用于需要保证数据完整性和可靠性的场景,如文件传输、电子邮件、网页浏览等。UDP适用于对传输速度和实时性要求高的场景,如视频流、音频流、在线游戏、实时通信等。
为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务段的LISTEN状态下的SOCKET当收到SYN报文的建立请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。
但在关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你未必会马上关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发的。
标签:动画,面试题,调用,协程,后续,物体,Unity,使用,方法 From: https://www.cnblogs.com/soryu-ryouji/p/18261824