U3D面试题目整理

1.如何优化内存,CPU,GPU开销

参考Unity优化(上)Unity优化(中)Unity优化(下)

2.协同程序实现原理

协程不是多线程,协程还是在主线程里面。协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足),但也有写特例。

通过设置MonoBehaviour脚本的enabled对协程是没有影响的,但如果gameObject.SetActive(false) 则已经启动的协程则完全停止了,即使在Inspector把gameObject 激活还是没有继续执行。也就说协程虽然是在MonoBehvaviour启动的(StartCoroutine)但是协程函数的地位完全是跟MonoBehaviour是一个层次的,不受MonoBehaviour的状态影响,但跟MonoBehaviour脚本一样受gameObject 控制,也应该是和MonoBehaviour脚本一样每帧“轮询” yield 的条件是否满足。
注:WaitForSends()受Time.timeScale影响,当Time.timeScale = 0f时,yieldreturn new WaitForSecond(X)将不会满足。

3.Sprite与Texture

Sprite

Sprite可用在Image组件上,可以用来制作动画;

Sprite有图集概念,导入图片后使用SpriteEditor切割,一个图集一个DrawCall,使用图集内Sprite整个图集都会载入内存里(带透明通道与不带透明通道的,不可以在一个图集。勾选Mipmap与没勾选的,不可以在一个图集);

将Sprite直接拖入场景,会自动添加SpriteRenderer组件,可作为游戏对象使用;

Sprite制作帧动画;

Texture

Texture可用在Raw Image组件上,可以用来制作动画;

Texture没有图集概念,每一张Texture都有一个DrawCall。原画或者背景图可使用Texture;

Texture不可直接放入场景中;

用于3D模型贴图。

4.Image与Sprite

Unity2D:Sprite和Image的区别_a435931517的专栏-CSDN博客

Unity2D:Sprite和UI Image的区别_宁静致远-CSDN博客

5.进程与线程

进程:进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。

线程:线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

区别:

1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

6.值类型与引用类型

值类型在线程栈分配空间,引用类型在托管堆分配空间

值类型转为引用类型称成为装箱,引用类型转为值类型称为拆箱。

7.帧同步与状态同步

什么是帧同步:帧同步常被RTS(即时战略)游戏常采用。在游戏中同步的是玩家的操作指令,操作指令包含当前的帧索引。一般的流程是客户端上传操作到服务器, 服务器收到后并不计算游戏行为, 而是转发到所有客户端。这里最重要的概念就是 相同的输入 + 相同的时机 = 相同的输出。

实现帧同步的流程一般是:

同步随机数种子。(一般游戏中都设计随机数的使用, 通过同步随机数种子,可以保持随机数一致性)

客户端上传操作指令。(指令包括游戏操作和当前帧索引)

服务器广播所有客户端的操作。(如果没有操作, 也要广播空指令来驱动游戏帧前进)。

因为帧同步的特性, 我们可以很方便的做出战斗回放:服务器记录所有操作, 客户端请求到操作文件再执行一次即可。

帧同步的特性导致客户端的逻辑实现和表现实现必须完全分离。Unity中的一些方法接口(如 Invoke, Update、动画系统等)是不可靠的,所有要自己实现一套物理引擎、数学库,做到逻辑和表现分离。 这样即使Unity的渲染是不同步的,但是逻辑跑出来是同步的。

我曾经参与过一个飞机类弹幕游戏的项目,它的同步方案就是帧同步, 可以完美的播放回放, 并实现服务器上加速验算。
状态同步

什么是状态同步:同步的是游戏中的各种状态。一般的流程是客户端上传操作到服务器,服务器收到后计算游戏行为的结果,然后以广播的方式下发游戏中各种状态,客户端收到状态后再根据状态显示内容。状态同步最广泛的应用应该是在回合制游戏中。

状态同步其实是一种不严谨的同步。它的思想中,不同玩家屏幕上的表现的一致性并不是重要指标, 只要每次操作的结果相同即可。所以状态同步对网络延迟的要求并不高。像玩RPG游戏,200-300ms的延迟也可以接受。 但是在RTS游戏中,50ms的延迟也会很受伤。
举个移动的例子,在状态同步中, 客户端甲上操作要求从A点移动到B点,但在客户端乙上, 甲对象从A移动到C,然后从C点移动到了B。这是因为, 客户端乙收到A的移动状态时, 已经经过了一个延迟。这个过程中,需要客户端乙本地做一些平滑的处理,最终达到移动到B点的结果。

所以国产RPG游戏中,动画的特效一般做的比较绚丽(大), 攻击的时候给人感觉是击中了。放技能之前一般也有一个动画前摇,同时将攻击请求提交给服务器。等服务器结果返回时,动画也播放完毕了,之后就是统一的伤害效果和结算。

选择:对于单位比较多的即时策略游戏,帧同步是很好的选择。反过来,如果玩家比较多,状态同步更合适,安全性更高。 

转载:帧同步与状态同步的区别 - little飞 - 博客园

8.接口和抽象类

接口:接口是指对协定进行定义的引用类型,其他类型实现接口,以保证它们支持某些操作。接口指定必须由类提供的成员或实现它的其他接口。与类相似,接口可以包含方法、属性、索引器和事件作为成员。

1、接口存在的意义是为了实现多态;
2、接口中只能包含方法(属性、事件、索引);
3、接口的成员不能有任何实现;
4、接口不能被实例化;静态类、抽象类也不可以被实例化
5、接口的成员不能有访问修饰符(默认为public);
6、实现接口的子类必须将接口的所有成员函数实现;
7、子类实现接口的方法时,不需要任何关键字,直接实现即可。(抽象类、虚方法需要加override)

抽象类:在面向对象过程中,所有的对象都是类描绘的,反过来,类里面的数据不足以把具体的对象描绘出来,这样的类就是抽象类。

1、抽象方法只能出现在抽象类中,但是抽象类中可以包含普通方法。

2、在父类中定义的抽象方法不能实现。

3、抽象类不能实例化(也就是不能new出来)。

4、抽象类与抽象方法需要添加abstract关键字。

5、子类实现父类的抽象方法时,需要添加override关键字。

6、如果抽象类的子类不是抽象类,那么子类中必须重写父类抽象类的所有抽象方法。

区别:

相同点

  1.不能实例化;

  2.包含未实现的方法声明

  3.派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员)

不同点

  1.接口可以多继承,抽象类不能实现多继承。

  2.接口只能定义抽象规则,抽象类既能定义抽象规则也能提供已实现的成员

  3.接口是一组行为规范,抽象类是一个不完全的类,着重于族的概念

  4.接口支持回调,抽象类不能实现回调,因为继承不支持。

  5.接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法,抽象类可以定义属性、字段、包含有实现的方法

  6.接口可以作用于值类型和引用类型,抽象类只能作用于引用类型(例如:Struct只能继承接口)

  7.抽象类应主要用于关系密切的对象,而接口则是适合为不相关的类提供通用功能

  8.接口着重于Can—Do关系类型,抽象类则偏重于IS—A式关系。

  9.接口多定义对象的行为,抽象类多定义对象的属性。

  10.如果要设计小而简练的功能块,则使用接口,如果要设计大的功能单元,则使用抽象类

  11.接口对应是面向对象编程思想的重要原则:面向接口编程。

9.String和StringBuilder

string创建后分配在字符串常量区,栈中存储的地址指向存储字符串的地址(虽然没存储在堆,但string也是引用类型,这点比较特殊)。大小不可修改,每次使用string类中的方法时,都要再创建一个新的字符串对象,并给其分配内存。
这样就需要再分配新的空间。所以有可能产生很大的开销。

StringBuilder创建后分配在堆区,大小可自由修改。

10.const、readonly、sealed

不同点主要涉及到以下两个常量类型:

静态常量(compile-time constants):指编译器在编译时会对常量进行解析,并将常量的值替换成初始化的值。

动态常量(runtime constants):动态常量的值则是在运行的那一刻才获得,编译器编译期间将其标识为只读常量,而不用常量的值代替,这样动态常量不必在声明的时候就初始化,而可以延迟到构造函数中初始化。

const修饰的常量是静态常量;readonly修饰的常量是动态常量.

const和readonly相同点:

  • const和readonly都是只读的。
  • const默认是static的,而且在编译期间已经解析完成。因此const和static readonly只能有类访问,而readonly通过实例访问。

不同点:

const修饰的常量在声明的时候必须初始化;readonly修饰的常量则可以延迟到构造函数初始化;
const修饰的常量在编译期间就被解析,即常量值被替换成初始化的值;readonly常量只能声明在类中;
const常量既可以声明在类中,也可以在函数体中;但是static readonly常量只能声明在类中。

  • const既可以修饰类中的成员,又可以修饰函数体中的成员;而readonly只能修饰类中的成员。
  • const(静态常量)只能声明为简单的数据类型,如int、浮点型、枚举型、布尔型、字符串型;而readonly(动态常量)则可以修饰对象类型。

sealed

sealed,即密封类和密封方法,在C#中允许把类和方法声明为sealed。

对于类则表示不能继承该类;对于方法则表示不能再次重新该方法,在方法中使用sealed,只能是在已经被重写的方法中定义sealed,表示这个方法不可以再被重写。

11.C#中null,“”,string.Empty

null:表示不引用任何对象的空引用的文字值,null是引用类型变量的默认值。

""和string.Empty:

string str = null 没有创建内存空间,str中存放的是空引用指针;

string str = "" 创建了内存空间,str中存放的是指向堆中的指针。

这两个都是表示空字符串。只不过""理论上重新开辟内存空间,而String.Empty指向一处。不过优化器会优化的!string.Empty不分配存储空间, ""分配一个长度为空的存储空间,所以一般用string.Empty,为了以后跨平台,还是用string.empty

12.c# 三种泛型委托Func、Action、Predicate

Func<T>委托有返回值的泛型委托,封装了最多可以传入16个参数,方法返回void的不能使用Func<T>委托。
Action<T>委托返回值为void,封装了最多可以传入16个参数,用法与Func<T>相同。
Predicate<T>委托返回值为bool类型的委托,可以被Func<T>代替。

C# 委托(delegate)、泛型委托和Lambda表达式 - willingtolove - 博客园

13.NGUI和UGUI区别

  • uGUI的Canvas 有世界坐标和屏幕坐标
  • uGUI的Image可以使用material
  • UGUI通过Mask来裁剪,而NGUI通过Panel的Clip
  • NGUI的渲染前后顺序是通过Widget的Depth,而UGUI渲染顺序根据Hierarchy的顺序,越下面渲染在顶层.
  • UGUI 不需要绑定Colliders,UI可以自动拦截事件
  • UGUI的Anchor是相对父对象,没有提供高级选项,个人感觉uGUI的Anchor操作起来比NGUI更方便
  • UGUI没有Atlas一说,使用Sprite Packer
  • UGUI的Navigation在Scene中能可视化
  • UGUI的事件需要实现事件系统的接口,但写起来也算简单

14.托管资源、非托管资源

理解托管和非托管代码的前提之下,要先了解CLR(公共语言运行库)

  .Net Framework 是由彼此独立又相关的两部分组成:CLR 和 类库, CLR是它为我们提供的服务,类库是它实现的功能.
  .NET的大部分特性----垃圾收集,版本控制,线程管理等,都使用了CLR提供的服务

当你为.NET Framework编译源代码的时候,得到的目标代码不是CPU能识别的机器指令,而是一种叫做"微软中间语言(MSIL,或简称为IL的代码)"的新语言.CLR提供了一个实时编译器,
用来把IL代码编译为本机机器代码.这样一来,CLR能够使代码变得可移植,因为.NET应用程序的源代码必须被编译为IL代码,这些IL代码可以运行在任何提供CLR服务的平台上.从CLR的角度来看,
所有的语言都是平等的,只要有一个能生成IL代码的编译器就行,这就确保了各种语言的互操性.
managed code-托管代码 
由公共语言运行库(CLR)环境(而不是直接由操作系统)执行的代码。托管代码应用程序可以获得公共语言运行库服务,例如自动垃圾回收、运行库类型检查和安全支持等。
这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。
Unmanaged Code - 非托管代码
在公共语言运行库环境的外部,由操作系统直接执行的代码。非托管代码必须提供自己的垃圾回收、类型检查、安全支持等服务;它与托管代码不同,后者从公共语言运行库中获得这些服务。

C#的三大难点之二:托管与非托管_游子的博客-CSDN博客_c# 托管

C# using 三种使用方式 C#中托管与非托管 C#托管资源和非托管资源区别 - 吴玄坤 - 博客园

C#基础知识之托管代码和非托管代码 - 搬砖滴 - 博客园

15.C++里new delete和free

在C++中,申请动态内存与释放动态内存用new/delete 与 malloc/free都可以,而且他们的存储方式相同,new/malloc申请的动态内存位于堆中,无法被操作系统自动回收,需要对应的delete、free释放空间。

malloc/free是C/C++语言的标准库函数,在C语言中需要头文件#include<stdlib.h>的支持。而new/delete是C++的运算符。对于类对象而言,malloc/free无法满足动态对象的要求,对象要求在创建的同时自动执行构造函数,对象消亡时自动执行析构函数,malloc/free不在编译器的控制权限之内,无法执行构造函数和析构函数。

区别:

(1)new 能自动计算需要分配的内存空间,而malloc需要手工计算字节数;

int *a = new int[2];
int *b = (int *)malloc(2*sizeof(int));

(2)new与delete直接带具体类型的指针,malloc和free返回void类型指针;

(3)new类型是安全的,而malloc不是,例如int *p = new  float[2];会报错;而int *p = malloc(2*sizeof(int))编译时编译器就无法指出错误;

(4)new一般分为两步:new操作和构造。new操作对应于malloc,但new操作对应与malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行;

(5)new调用构造函数,malloc不能;delete调用析构函数,free不能;

(6)malloc/free需要库文件stdlib.h支持,new/delete则不需要。

注意:

delete和free被调用后,内存不会立即回收,指针也不会指向空,delete或free仅仅是告诉操作系统,这一块内存被释放了,可以用作其他用途。但是由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,出现野指针的情况。因此,释放完内存后,应该讲该指针指向NULL。

判断点是否在三角形内

16.lua 元表

元表本质上来说是一种用来存放元方法的table。我们可以通过对应的key来得到value值,作用就是修改一个值的行为(更确切的说,这是元方法的能力),需要注意的是,这种修改会覆盖掉原本该值可能存在的相应的预定义行为。

17.lua table实现原理

【Lua 5.3源码】table实现分析_YzlCoder的记事本-CSDN博客1.table的特性在Lua中table是个非常重要的类型,通过使用table的一些特性可以实现许多数据结构,例如map,array queue,stack等。通过使用者角度来讲,table既可以当作array使用也可以当作map使用,那么对于设计者来讲,那么需要保证table的高效率的查找、插入、遍历。当然,table的设计者还提出了metatable(元表)的概念,以供使用者可以用来实...https://blog.csdn.net/y1196645376/article/details/94348873

18.lua 面向对象

Lua实现面向对象两种方法 - 学习使我进步 - 博客园1、用元表和元方法 lua面向对象编程是基于元表metatable,元方法__index来实现的通过元表的__index元方法,将一个table的__index元方法设置为另一个table,那么后者的icon-default.png?t=N7T8https://www.cnblogs.com/wwhhgg/p/12606677.html

19.unity轴点、锚点,锚点属性

轴心点『 Pivot 』:当前UI的轴心点所在位置,蓝色空心圆圈。

锚点 『Anchors』:在UI上由四个三角形组成的像风车一样的图标就是锚点

锚点和轴点确定UI的各个部分在他父对象的相对位置。

20.unity mask实现原理

【详细解析版】Unity UGUI Mask组件实现原理 - 知乎Masking is implemented using the stencil buffer of the GPU. 即Mask是利用了GPU的模板缓冲来实现的,关于模板,打个简单的比方,就像一个面具,可以挡住一部分“脸”的显示一样。Mask的关键代码其实只有一行,…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/339378916

21.class struct

相同点 都可以实现接口

不同点

1.class是引用类型,struct是值类型

2.class允许继承、被继承,struct不允许,只能继承接口

3.class可以初始化变量,struct不可以

4.class可以有无参的构造函数,struct不可以,必须是有参的构造函数,而且在有参的构造函数必须初始化所有成员

使用场景

1.Class比较适合大的和复杂的数据,表现抽象和多级别的对象层次时。

2.Struct适用于作为经常使用的一些数据组合成的新类型,表示诸如点、矩形等主要用来存储数据的轻量级对象时,偏简单值。

3.Struct有性能优势,Class有面向对象的扩展优势。

22.MVC

经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。

23.委托与事件

参考委托与事件 - 知乎

多播委托的原理,它的底层的技术实现

多播委托的原理在于委托本质上是一个对象,它包含对一个或多个方法的引用。通过将多个方法引用添加到同一个委托实例中,就形成了多播委托。当调用多播委托时,它依次调用包含的每个方法。

我们知道多波委托是一个委托可以指向多个方法,那么我们如何取得所有方法的返回值呢?

多播委托的调用返回的是最后一个方法的返回值。如果需要获取所有方法的返回值,可以通过遍历委托的 GetInvocationList 方法,逐个调用每个方法并收集返回值。

委托除了能够用在一些同步调用的环境下,也可以用在一些异步调用的环境下,那么在异步调用的环境下,我们如何使用委托呢?

委托在异步调用中同样适用。可以使用 BeginInvokeEndInvoke 方法进行异步调用,或者利用异步编程的新特性,如 async/await 关键字。

事件是比委托更加安全的,它是对委托的包装,那么在C#底层在C#内部,它是如何做到只让事件在内部被引发的呢?

事件是委托的一种特殊用法,通过事件可以限制对委托的访问,使其只能在类的内部触发。在C#中,事件的声明和使用通常包括 event 关键字,并通过 addremove 关键字来添加或移除事件处理程序。

在C#底层,事件的安全性通过将委托声明为事件时,自动生成的 addremove 存取器来实现。这样,只有在类内部才能触发事件。

  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王大匣

你的鼓励是我创作最大动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值