面试准备

自我介绍:
你好,我是 孙通,吉林长春人。13年毕业于南京金陵科技学院。应聘U3D游戏开发。
我从事U3D开发5年多,有MMOARPG手游的完整项目经验。
14.10-15.10在上海玩心游戏科技公司参与开发了 迷城物语,于16年初上线。
16.2-17.6月,我在长春新曦雨做游戏开发,主要开发VR游戏的客户端。
17年10-19年6月,在日本一家游戏公司做MMO项目。
从unity4.0开始使用的unity,经理了4.6, 5.3,2017几个大的版本更替,对unity还是比较了解的。

基础部分:

算法部分

二分搜索 Binary Search 
分治 Divide Conquer 
宽度优先搜索 Breadth First Search 
深度优先搜索 Depth First Search
回溯法 Backtracking 
双指针 Two Pointers 
动态规划 Dynamic Programming 
扫描线 Scan-line algorithm
快排 Quick Sort

冒泡排序:
冒泡算法C#

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
namespace 数组排序
{
class Program
{
static void Main(string[] args)
{
int temp = 0;
int[] arr = {23, 44, 66, 76, 98, 11, 3, 9, 7};

#region该段与排序无关
Console.WriteLine("排序前的数组:");
foreach (intiteminarr)
{
Console.Write(item + "");
}
Console.WriteLine();
#endregion

for (int i = 0; i < arr.Length - 1; i++)
{
#region将大的数字移到数组的arr.Length-1-i
for (int j = 0; j < arr.Length - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
#endregion
}
Console.WriteLine("排序后的数组:");
foreach (int item in arr)
{
Console.Write(item+"");
}
Console.WriteLine();
Console.ReadKey();
}
}
}

快速排序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace test
{
class QuickSort
{
static void Main(string[] args)
{
int[] array = { 49, 38, 65, 97, 76, 13, 27 };
sort(array, 0, array.Length - 1);
Console.ReadLine();
}
/**一次排序单元,完成此方法,key左边都比key小,key右边都比key大。


**@param array排序数组


**@param low排序起始位置


**@param high排序结束位置


**@return单元排序后的数组 */
private static int sortUnit(int[] array, int low, int high)
{
int key = array[low];
while (low < high)
{
/*从后向前搜索比key小的值*/
while (array[high] >= key && high > low)
--high;
/*比key小的放左边*/
array[low] = array[high];
/*从前向后搜索比key大的值,比key大的放右边*/
while (array[low] <= key && high > low)
++low;
/*比key大的放右边*/
array[high] = array[low];
}
/*左边都比key小,右边都比key大。//将key放在游标当前位置。//此时low等于high */
array[low] = key;
foreach (int i in array)
{
Console.Write("{0}\t", i);
}
Console.WriteLine();
return high;
}
/**快速排序
*@paramarry
*@return */
public static void sort(int[] array, int low, int high)
{
if (low >= high)
return;
/*完成一次单元排序*/
int index = sortUnit(array, low, high);
/*对左边单元进行排序*/
sort(array, low, index - 1);
/*对右边单元进行排序*/
sort(array, index + 1, high);
}
}
}

数据结构部分

栈 Stack
队列 Queue
链表 Linked List 
数组 Array 
环形数组
哈希表 Hash Table
二叉树 Binary Tree  
堆 Heap
并查集 Union Find
字典树 Trie

Array	需要处理的元素数量确定并且需要使用下标时可以考虑,不过建议使用List<T>
ArrayList	不推荐使用,建议用List<T>
List<T>泛型List	需要处理的元素数量不确定时 通常建议使用
LinkedList<T>	链表适合元素数量不固定,需要经常增减节点的情况,2端都可以增减  查找很慢
Queue<T>	先进先出的情况
Stack<T>	后进先出的情况
Dictionary<K,T>	需要键值对,快速操作 插入慢读取快  实现原理:哈希碰撞加上链表

请简述ArrayList和List的主要区别?
ArrayList存在不安全类型(ArrayList会把所有插入其中的数据都当做Object来处理),装箱拆箱的操作(费时)。
List是泛型类,功能跟ArrayList相似,但不存在ArrayList所说的问题。

常用数据结构的时间复杂度

Data Structure Add Find Delete GetByIndex
Array (T[]) O(n) O(n) O(n) O(1)
Linked list (LinkedList) O(1) O(n) O(n) O(n)
Resizable array list (List) O(1) O(n) O(n) O(1)
Stack (Stack) O(1) - O(1) -
Queue (Queue) O(1) - O(1) -
Hash table (Dictionary) O(1) O(1) O(1) -
Tree-based dictionary (SortedDictionary<K,T>) O(log n) O(log n) O(log n) -
Hash table based set (HashSet) O(1) O(1) O(1) -
Tree based set (SortedSet) O(log n) O(log n) O(log n) -

动态规划(最短路径算法):

C#中传值调用和传引用调用的理解:

如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
如果传递的参数前有ref或者out关键字,那么就是传引用调用。

重写和重载:
override 子类覆盖父类成员 。overload 重载:函数或者方法名字相同,签名不同的情况。

进程,线程,协程 区别简介:
作为面试中必问的一道面试题,通过看博客和书,简单总结如下:

定义:

进程:是程序运行的实例,是系统进行资源分配和调度的一个独立单位,它包括独立的地址空间,资源以及1个或多个线程。

线程:可以看成是轻量级的进程,是CPU调度和分派的基本单位。

区别:

1.调度 :从上面的定义可以看出一个是调度和分派的基本单位,一个是拥有资源的基本单位

2.共享地址空间,资源:进程拥有各自独立的地址空间,资源,所以共享复杂,需要用IPC,同步简单; 线程共享所属进程的资源,共享简单,
但同步复杂,要通过加锁等措施。

3.相互影响: 进程间不会相互影响; 一个线程挂掉可能导致整个进程挂掉。

TCP与UDP的区别:
1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;
5.TCP保证数据正确性,UDP可能丢包。

反射技术概念作用和要点:
可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。
还可以获得每个成员的名称、限定符和参数等。有了反射,即可对每一个类型了如指掌。
如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道。

动态创建一个接口的实现类实例,转型成接口,掉用接口方法。

unity部分:

对unity的理解:

底层使用C++编写,使用Mono环境,C#作为脚本语言。整体是ECS架构,但
是system部分没有明确区分。
基于组件(Component)和Prefab结构让设计更灵活,结构更清晰。
Asset Store插件丰富。

难点:
    内存管理 网络和下载 渲染(drawcall 150)

碰撞器与触发器的区别:
碰撞器会产生物理效果,触发器没有。
物体发生碰撞的必要条件:
两个物体都必须带有碰撞器(Collider),其中一个物体还必须带有Rigidbody刚体。
物体发生触发器的必要条件:
必须有一方是触发器,必须有一方带有刚体。二者缺一不可。

Animator里面的状态机
(animatorstateInfo)

unity里面的函数调用顺序:
Awake——>OnEnable–>Start——>Update——>FixedUpdate——>LateUpdate——>OnGUI——>OnDisable——>OnDestroy
其中FixedUpdate和Update——LateUpdate不分先后

unityGI
(5.0自带的光照系统,一个LIGHTING Probe+refection Probe事实全局光,注意只是支持静态物体)

请描述游戏动画有哪几种,以及其原理:
主要有关节动画、单一网格模型动画(关键帧动画)、骨骼动画。
关节动画把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动
画,角色比较灵活Quake2中使用了这种动画。
单一网络模型动画由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其
改变量,然后插值运算实现动画效果,角色动画较真实。
骨骼动画,广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结
构,由关节相连,可做相对运动,皮肤作为单一网格蒙在骨骼之外,决定角色的外观。皮肤网格每
一个顶点都会受到骨骼的影响,从而实现完美的动画。(骨骼动画是由关节动画发展而来的,如今
基本都使用骨骼动画来实现角色动画)

alpha blend 工作原理
实际显示颜色 = 前景颜色Alpha/255 + 背景颜色(255-Alpha)/255

写光照计算中的diffuse的计算公式:
实际光照强度 I= 环境光(Iambient) + 漫反射光(Idiffuse) + 镜面高光(Ispecular);
环境光:Iambient= Aintensity* Acolor; (Aintensity表示环境光强度,Acolor表示环境光颜色)
漫反射光:Idiffuse = DintensityDcolorN.L;
(Dintensity表示漫反射强度,Dcolor表示漫反射光颜色,N为该点的法向量,L为光源向量)
镜面反射光:Ispecular = SintensityScolor(R.V)^n;
(Sintensity表示镜面光照强度,Scolor表示镜面光颜色,R为光的反射向量,V为观察者向量,n称
为镜面光指数)

lod是什么,优缺点是什么:
LOD技术即Levels of Detail的简称,意为多细节层次。LOD技术指根据物体模型的节点在显示环境
中所处的位置和重要度,决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高
效率的渲染运算。
优点:可根据距离动态地选择渲染不同细节的模型
缺点:加重美工的负担,要准备不同细节的同一模型,同样的会稍微增加游戏的容量。

两种阴影判断的方法工作原理:
阴影由两部分组成:本影与半影
本影:景物表面上那些没有被光源直接照射的区域(全黑的轮廓分明的区域)
半影:景物表面上那些被某些特定光源直接照射但并非被所有特定光源直接照射的区域(半明半暗
区域)
求阴影区域的方法:做两次消隐过程
一次对每个光源进行消隐,求出对于光源而言不可见的区域L;
一次对视点的位置进行消隐,求出对于视点而言可见的面S;
shadow area= L ∩ S
阴影分为两种:自身阴影和投射阴影
自身阴影:因物体自身的遮挡而使光线照射不到它上面的某些可见面
工作原理:利用背面剔除的方法求出,即假设视点在点光源的位置。
投射阴影:因不透明物体遮挡光线使得场景中位于该物体后面的物体或区域受不到光照照射而形成
的阴影
工作原理:从光源处向物体的所有可见面投射光线,将这些面投影到场景中得到投影面,再将这些
投影面与场景中的其他平面求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对
场景进行相应处理得到所要求的视图(利用空间换时间,每次只需依据视点位置进行一次阴影计算
即可,省去了一次消隐过程)
若是动态光源此方法就无效了。

Vertex Shader是什么?怎么计算?
顶点着色器是一段执行在GPU上的程序,用来取代fixed pipeline中的transformation和lighting,
Vertex Shader主要操作顶点。
Vertex Shader对输入顶点完成了从local space到homogeneous space(齐次空间)的变换过
程,homogeneous space即projection space的下一个space。在这其间共有world
transformation, view transformation和projection transformation及lighting几个过程。

MipMap是什么?作用?
在三维计算机图形的贴图渲染中有一个常用的技术被称为Mipmapping。为了加快渲染速度和减少
图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为 MIP
map 或者 mipmap。

用u3d实现2d游戏,有几种方式?
1.利用引擎自带的UI
2.把摄像机设为Orthographic,用面片作为2d元素
3.利用第三方插件:NGUI、2dToolkit

CharacterController和Rigidbody的区别
Rigidbody具有完全真实物理的特性,而CharacterController可以说是受限的Rigidbody,具有一
定的物理效果但不是完全真实的。

u3d中,几种施加力的方式,描述出来
rigidbody.AddForce/AddForceAtPosition,都是rigidbody的成员函数

什么叫做链条关节
Hinge Joint ,他可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距
离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。(简单说就是弹簧)

物体自旋转使用的函数叫什么
transform.Rotate
物体绕某点旋转使用函数叫什么
transform.RotateAround

u3d提供了一个用于保存读取数据的类,(playerPrefs),请列出保存读取整形数据的函数
PlayerPrefs.SetInt 与 PlayerPrefs.GetInt

unity3d提供了几种光源,分别是什么
共4种,Directional Light、Point Light、Spot Light、Area Light(只用于烘培)

物理更新一般在哪个系统函数里?
FixedUpdate,每固定帧绘制时执行一次,和update不同的是FixedUpdate是渲染帧执行,如果你
的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的
计算,因为是跟每帧渲染有关。Update就比较适合做控制。

移动相机动作在哪个函数里,为什么在这个函数里。
LateUpdate,,是在所有update结束后才调,比较适合用于命令脚本的执行。官网上例子是摄像机
的跟随,都是在所有update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角
里还未有角色的空帧出现。

当游戏中需要频繁创建一个物体对象时,我们需要怎么做来节省内存。
做一个pool,游戏开始时预先实例化足够的数量,然后用的时候取不用的时候收回

一个场景放置多个camera并同时处于活动状态,会发生什么
实际看到的画面由多个camera的画面组成,由depth、Clear Flag、Culling Mask都会影响最终合
成效果。

简述prefab的用处和环境
在游戏运行时实例化,prefab相当于一个模版,对你已有的素材、脚本、参数做一个默认配置,以
便于以后修改,同时prefab可以打包成unitypackage,便于团队的交流。

面板里面的内容和edit下面的Projuct Setting的内容

RGB转灰度公式:
Gray = R0.299 + G0.587 + B*0.114

UI自适应原理:
UIRoot root = GameObject.FindObjectOfType();
if (root != null) {
float s = (float)root.activeHeight / Screen.height;
int height = Mathf.CeilToInt(Screen.height * s);
int width = Mathf.CeilToInt(Screen.width * s);
Debug.Log(“height = “ + height);
Debug.Log(“width = “ + width);
}
原理就是根据Screen.width 和Screen.height来动态的计算它的实际高度,动态的修改这个值。

NGUI控件说明(中文) UIAnchor:
通过屏幕实际像素确定每个锚点的坐标。

资源销毁方式:
GameObject.Destroy(gameObject),销毁该物体;
AssetBundle.Unload(false),释放AssetBundle文件内存镜像,不销毁Load创建的Assets对象;
AssetBundle.Unload(true),释放AssetBundle文件内存镜像同时销毁所有已经Load的Assets内存镜像;
Resources.UnloadAsset(Object),释放已加载的Asset对象;
Resources.UnloadUnusedAssets,释放所有没有引用的Asset对象。

说说你对渲染的流程的理解:
渲染的过程就是 将场景绘制在屏幕的过程。
主要分为三个阶段:应用阶段(渲染图元),几何阶段(屏幕顶点信息),光栅化阶段(像素)。
应用阶段 主要是 准备场景 静态模型,地面石头,建筑,动态模型,人物,怪物,树,各种光源
相机等等。主要进行遮挡剔除,忽略掉不在视锥范围的模型,输出要渲染图元。

    几何阶段 主要是顶点变换,顶点→模型空间→世界空间→视锥空间→齐次剪裁空间→屏幕映射。可以使用顶点
    着色器改变顶点位置,实现一些水面布料的效果。

    光栅化阶段: 通过顶点信息来产生像素。中间进行模板测试 深度测试 透明度测试,然后进行混合,
    得到屏幕的像素。

优化部分:

内存优化和GC:
内存占用和GC 不能兼得。通常我们牺牲一部分内存来优化GC。

内存的分配:除了在方法内部 构造的值类型,其他时候构造对象都是分配在堆上的。
Mono向系统申请的内存,只有在程序结束时才会返回给系统。

主要有三个操作会触发垃圾回收:

在堆内存上进行内存分配操作而内存不够的时候都会触发垃圾回收来利用闲置的内存;
GC会自动的触发,不同平台运行频率不一样;
GC可以被申请执行,(执行时间并不确定)。
GC操作可以被频繁触发,特别是在堆内存上进行内存分配时内存单元不足够的时候,
这就意味着频繁在堆内存上进行内存分配和回收会触发频繁的GC操作。

优化:
    利用profiler window 来检测堆内存分配:
    在CPU usage分析窗口中,我们可以检测任何一帧cpu的内存分配情况。
    其中一个选项是GC alloc,通过分析其来定位是什么函数造成大量的堆内存分配操作。
    一旦定位该函数,我们就可以分析解决其造成问题的原因从而减少内存垃圾的产生。

    大体上来说,我们可以通过三种方法来降低GC的影响:

    减少GC的运行次数;
    减少单次GC的运行时间;
    将GC的运行时间延迟,避免在关键时候触发,比如可以在场景加载的时候调用GC

    尽量:
    减少构造对象的次数 能 减少GC的运行次数;比如对象池。对象池引用标记比较集中,并不会明显提高单次GC时间。
    Clear容器不是new新的容器。
    lamuda表达式和匿名函数,注意闭包大小。调用频繁的话还是要写成函数。

DrawCall多少为好,怎么样减少?
(150以下..减小就是答游戏优化的种种)

DrawCall优化,CPU优化:
drawcall影响的是CPU的效率,非常重要的一个优化点。

drawcall就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。

相同的材质,shader和shader的参数完全一致(纹理贴图等等和各种各样的参数)

项目经验部分:

迷城物语
2014.10-2015.10于上海玩心游戏科技有限公司参与开发。 迷城物语为MMOARPG游戏。
实现了技能动画混合子状态机,遮罩。实现技能特效,音效通过外部XML配置功能。
旋风斩动画,分为骑乘和为骑乘是两个不同的动画。overrideAnimationcontroller重写动画控制器。
设计编写了战斗系统:合法性检验,BUFF增益,伤害结算等环节,并且伤害脚本在lua中实现热更新。
实现了宠物功能,包含跟随,战斗,骑乘,合体,徽章镶嵌,升级,宠物AI等功能。

实现商城部分(购买,打折,定时刷新,推送,充值等功能的实现)
实现NPC任务功能,对话,任务交接。
实现怪物对象池,怪物刷新,怪物AI功能。
接入360等渠道SDK。

恐龙星际
恐龙星际是一套综合的VR/AR体验游戏。 我于2016.02-2017.06在长春新曦雨文化产业有限公司主要开发的项目。主要包括unity,UE4开发的2个游戏:
Dinostar -多人在线互动VR项目 (Unity / .Net )
系统由64~100平米的动作捕捉游戏区(OptiTrack),游戏客户端(外星人背包笔记本 + Oculus眼镜)组成。
实现3D UI界面,虚拟现实中UI交互。
搭建游戏场景,并进行优化,遮挡剔除,渲染优化,LOD等。
使用了Behaviordesigner设计了怪物AI。
FinalIK进行动画关键点匹配,实现了虚拟场景中准确拾取现实物品,枪支,火把等物品,
FutureFighter - 单人射击VR项目(UE4 / C++)
HTC+UE4打造的单机项目。我主要负责背包,武器,奖励物品,伤害,关卡,Player,等功能。
一些其他小游戏开发,VR汽车展示,人体器官展示,AR卡牌等。

一个游戏从策划开发到上线,具体的流程是怎样的?

答:这个跟项目有关,有的项目2-3个月就可以完成。但是准备做一个精品项目,所以一开始计划的开发周期就比较长。

至于说每个游戏的具体流程,其实也跟项目有关,不同项目的流程不太一样。

我们公司的项目流程大体是这样的:
首先是策划,策划有一个点子就去找程序,跟程序这边商量一下计划可不可行,然后跟程序商量好之后,策划和程序就会一起去做一个demo,
美术的话就会用一些比较粗糙的,现有的资源。然后开发出来一个demo,内部员工自己去跑一跑,测试一下玩一玩,如果觉得OK,
我们就会去找美术,然后组一个团队。后面如果一个项目真正地立起来了,然后加入额外的程序和策划,
组成大一点的团队,然后进入正常的流程。一般来说一个礼拜会出一个新版本,这样来进行一个迭代。

后端的话,跟前端是同步进行的,客户端这边跟后端约定一个图形机制,先联调一下是否能联通,然后再迭代功能。

战斗系统如何实现:
合法性检验(CD施法距离目标有效性), 目标选的环节,判定环节(暴击miss) ,结算环节,(作弊检验)。
各个环节调用接口函数,具体由lua实现。

rpg类游戏的任务系统怎么设计:
需要有任务id,任务进度,任务是否完成之类的数据,任务上下关联的任务ID。

MoveTo型任务(移动)
Farm型任务(重复)
Collect型任务(收集)
Hunt型任务(狩猎)
Puzzle型任务(解谜)
Challenge型任务(挑战)
附加类:Storytelling型任务(叙事)

游戏同步机制:
帧同步 和状态同步:
都要做 输入指令化(命令化,序列化,没有统一的名字)