赞
踩
目录
0、FSM(状态机)、HFSM(分层状态机)、BT(行为树)的区别
4、七:简述四元数Quaternion的作用,四元数对欧拉角的优点?
5、如何安全的在不同工程间安全地迁移asset数据?三种方法
6、OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生?
7、MeshRender中material和sharedmaterial的区别?
10、简述一下对象池,你觉得在FPS里哪些东西适合使用对象池?
11、CharacterController和Rigidbody的区别?
20、在物体发生碰撞的整个过程中,有几个阶段,分别列出对应的函数 三个阶段
21、Unity3d的物理引擎中,有几种施加力的方式,分别描述出来
24、Unity3d提供了一个用于保存和读取数据的类(PlayerPrefs),请列出保存和读取整形数据的函数
25、Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,请列出系统自带的几个重要的方法。
27、在场景中放置多个Camera并同时处于活动状态会发生什么?
28、如何销毁一个UnityEngine.Object及其子类?
30、请描述为什么Unity3d中会发生在组件上出现数据丢失的情况
39、Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?
41、U3D中用于记录节点空间几何信息的组件名称,及其父类名称
46、当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?
47、请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义?
56、八十三:Unity中,照相机的Clipping Planes的作用是什么?调整Near、Fare两个值时,应该注意什么?
57、如何在Unity3D中查看场景的面试,顶点数和Draw Call数?如何降低Draw Call数?
61、将Camera组件的ClearFlags选项选成Depth only是什么意思?有何用处?
62、如何让已经存在的GameObject在LoadLevel后不被卸载掉?
63、在编辑场景时将GameObject设置为Static有何作用?
64、有A和B两组物体,有什么办法能够保证A组物体永远比B组物体先渲染?
65、将图片的TextureType选项分别选为Texture和Sprite有什么区别
66、问一个Terrain,分别贴3张,4张,5张地表贴图,渲染速度有什么区别?为什么?
67、什么是DrawCall?DrawCall高了又什么影响?如何降低DrawCall?
69、Unity的Shader中,Blend SrcAlpha OneMinusSrcAlpha这句话是什么意思?
游戏人工智能AI中最常听见的就是这三个词拉:
1)FSM
这个不用说拉,百度一大堆解释,
简单将就是将游戏AI行为分为一个一个的状态,状态与状态之间的过渡通过事件的触发来形成。
比如士兵的行为有“巡逻”,“追击敌人”,“攻击敌人”,“逃跑”等行为,
响应的事件就有“发现敌人”,“追到敌人”,“敌人逃跑”,“敌人死亡”,“自己血量不足”等。
那么可以写成这样一个状态机:
1.士兵 “巡逻”,如果 “发现敌人”,那么,“追击敌人”
2.士兵 “追击敌人”, 如果 “追到敌人”, 那么,“攻击敌人”
3.士兵 “追击敌人”, 如果 “敌人死亡”, 那么,继续 “巡逻”
4.士兵 “攻击敌人”, 如果 “敌人死亡”, 那么,继续 “巡逻”
5.士兵 “攻击敌人”, 如果 “血量不足”, 那么,“逃跑”
其中,士兵就是这个FSM的执行者,红色的就是状态,蓝色的就是事件,
整个状态机的行为可以总结为:
当前状态=>是否满足条件1,如果是,则跳转到对应状态
否则=>是否满足条件2,如果是,则跳转到对应状态
由此可看出,状态机是一种“事件触发型”AI,就是只有事件的触发才会发生引起状态的变化。
2)HFSM
简单来说,就是FSM当状态太多的时候,不好维护,于是将状态分类,抽离出来,将同类型的
状态做为一个状态机,然后再做一个大的状态机,来维护这些子状态机。
举个决策小狗行为的例子:
我们对小狗定义了有很多行为,比如跑,吃饭,睡觉,咆哮,撒娇,摇尾巴等等,如果每个行为都是一个状态,
用常规状态机的话,我们就需要在这些状态间定义跳转,比如在“跑”的状态下,如果累了,那就跳转到“睡觉”状态,
再如,在“撒娇”的状态下,如果感到有威胁,那就跳转到“咆哮”的状态等等,我们会考量每一个状态间的关系,定
义所有的跳转链接,建立这样一个状态机。如果用层次化的状态机的话,我们就先会把这些行为“分类”,把几个小状
态归并到一个状态里,然后再定义高层状态和高层状态中内部小状态的跳转链接。
其实层次化状态机从某种程度上,就是限制了状态机的跳转,而且状态内的状态是不需要关心外部状态的跳转的,
这样也做到了无关状态间的隔离,比如对于小狗来说,我们可以把小狗的状态先定义为疲劳,开心,愤怒,然后这些
状态里再定义小状态,比如在开心的状态中,有撒桥,摇尾巴等小状态,这样我们在外部只需要关心三个状态的跳
转(疲劳,开心,愤怒),在每个状态的内部只需要关心自己的小状态的跳转就可以了。这样就大大的降低了状态机的复杂度,
另外,如果觉得两层的状态机还是状态太多的话,可以定义更多的状态层次以降低跳转链接数。
(摘自此文章)
3)Behavir Tree
谈到游戏AI,很明显智能体拥有的知识条目越多,便显得更智能,但维护
庞大数量的知识条目是个噩梦:使用有限状态机(FSM),分层有限状态机(HFSM),
决策树(Decision Tree)来实现游戏AI总有那么些不顺意。
试试Next-Gen AI的行为树(Behavior Tree)吧。
虽说Next-Gen AI,但距其原型提出已有约10年时间,而微软Halo系列估计
已用了超过8年了,Spore和一些著名游戏也早已使用行为树作为它们的AI结构。
如从未接触,那wikipedia(http://en.wikipedia.org/wiki/Behavior_Trees)
绝对是入门好资料。
先借用网上的一张图来诠释下行为树到底是怎么样的
———————————————————————
行为树(Behavior Tree)具有如下的特性:
它只有4大类型的Node:
* Composite Node 组合节点
* Decorator Node 修饰节点
* Condition Node 条件节点(叶节点)
* Action Node 动作节点(叶节点)
任何Node被执行后,必须向其Parent Node报告执行结果:成功 / 失败。
这简单的成功 / 失败汇报原则被很巧妙地用于控制整棵树的决策方向。
———————————————————————
先看Composite Node,其实它按复合性质还可以细分为3种:
* Selector Node
当执行本类型Node时,它将从begin到end迭代执行自己的Child Node:
如遇到一个Child Node执行后返回True,那停止迭代,
本Node向自己的Parent Node也返回True;否则所有Child Node都返回False,
那本Node向自己的Parent Node返回False。
* Sequence Node
当执行本类型Node时,它将从begin到end迭代执行自己的Child Node:
如遇到一个Child Node执行后返回False,那停止迭代,
本Node向自己的Parent Node也返回False;否则所有Child Node都返回True,
那本Node向自己的Parent Node返回True。
* Parallel Node
并发执行它的所有Child Node。
而向Parent Node返回的值和Parallel Node所采取的具体策略相关:
Parallel Selector Node: 一False则返回False,全True才返回True。
Parallel Sequence Node: 一True则返回True,全False才返回False。
Parallel Hybird Node: 指定数量的Child Node返回True或False后才决定结果。
Parallel Node提供了并发,提高性能。
不需要像Selector/Sequence那样预判哪个Child Node应摆前,哪个应摆后,
常见情况是:
(1)用于并行多棵Action子树。
(2)在Parallel Node下挂一棵子树,并挂上多个Condition Node,
以提供实时性和性能。
Parallel Node增加性能和方便性的同时,也增加实现和维护复杂度。
PS:上面的Selector/Sequence准确来说是Liner Selector/Liner Sequence。
AI术语中称为strictly-order:按既定先后顺序迭代。
Selector和Sequence可以进一步提供非线性迭代的加权随机变种。
Weight Random Selector提供每次执行不同的First True Child Node的可能。
Weight Random Sequence则提供每次不同的迭代顺序。
AI术语中称为partial-order,能使AI避免总出现可预期的结果。
———————————————————————
再看Decorator Node,它的功能正如它的字面意思:它将它的Child Node执行
后返回的结果值做额外处理后,再返回给它的Parent Node。很有些AOP的味道。
比如Decorator Not/Decorator FailUtil/Decorator Counter/Decorator Time…
更geek的有Decorator Log/Decorator Ani/Decorator Nothing…
———————————————————————
然后是很直白的Condition Node,它仅当满足Condition时返回True。
———————————————————————
最后看Action Node,它完成具体的一次(或一个step)的行为,视需求返回值。
而当行为需要分step/Node间进行时,可引入Blackboard进行简单数据交互。
———————————————————————
整棵行为树中,只有Condition Node和Action Node才能成为Leaf Node,而也
只有Leaf Node才是需要特别定制的Node;Composite Node和Decorator Node均
用于控制行为树中的决策走向。(所以有些资料中也统称Condition Node和Action
Node为Behavior Node,而Composite Node和Decorator Node为Decider Node。)
更强大的是可以加入Stimulus和Impulse,通过Precondition来判断masks开关。
通过上述的各种Nodes几乎可以实现所有的决策控制:if, while, and, or,
not, counter, time, random, weight random, util…
———————————————————————
总的来说,行为树具有如下几种优点,确实是实现AI框架的利器,甚至是一种
通用的可维护的复杂流程管理利器:
> 静态性
越复杂的功能越需要简单的基础,否则最后连自己都玩不过来。
静态是使用行为树需要非常着重的一个要点:即使系统需要某些”动态”性。
其实诸如Stimulus这类动态安插的Node看似强大,
但却破坏了本来易于理解的静态性,弊大于利。
Halo3相对于Halo2对BT AI的一个改进就是去除Stimulus的动态性。
取而代之的做法是使用Behavior Masks,Encounter Attitude,Inhibitions。
原则就是保持全部Node静态,只是根据事件和环境来检查是否启用Node。
静态性直接带来的好处就是整棵树的规划无需再运行时动态调整,为很多优化
和预编辑都带来方便。
> 直观性
行为树可以方便地把复杂的AI知识条目组织得非常直观。
默认的Composite Node的从begin往end的Child Node迭代方式就像是处理一个
预设优先策略队列,也非常符合人类的正常思考模式:先最优再次优。
行为树编辑器对优秀的程序员来说也是唾手可得。
> 复用性
各种Node,包括Leaf Node,可复用性都极高。
实现NPC AI的个性区别甚至可以通过在一棵共用的行为树上不同的位置来
安插Impulse来达到目的。
当然,当NPC需要一个完全不同的大脑,比如70级大BOSS,
与其绞尽脑汁在一棵公用BT安插Impulse,不如重头设计一棵专属BT。
> 扩展性
虽然上述Node之间的组合和搭配使用几乎覆盖所有AI需求。
但也可以容易地为项目量身定做新的Composite Node或Decorator Node。
还可以积累一个项目相关的Node Lib,长远来说非常有价值。
--- 补充
每个节点都应该有以下三种状态:
Running,
Success,
Failed
Running状态用于表明该节点的结果不能立刻获知,比如游戏中的角色进行“向目标移动”
这个动作,很显然这个动作不能在这一帧中立刻完成,当行为树运行到此节点时,并不能
获知是success或者failed,于是返回running,表示该节点正在运行中,并记录此节点
的位置,下一帧运行到此节点的父节点时,则从此节点继续运行,跳过之前的节点。
有限状态机:可用于做任务动画的切换,也可用于做简单的AI。将行为分为一个个的状态,状态与状态之间的过渡通过事件的触发来形成。
分层有限状态机:简单说,就是状态太多时,不好维护,于是将状态分类,将同类的状态作为一个状态机,然后再做一个大的状态机,来维护这些子状态机。
行为树:行为树与FSM不同,它是一种“轮询式机制”,即每次更新都会遍历树,判定逻辑是否成立,是否该继续往下执行。常用于英雄或敌方等有多种复杂行为的单位。如下图:
答:在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。换句话说,开启协程就是开启一个可以与程序并行的逻辑。可以用来控制运动、序列以及对象的行为。
协程的三种启动/停止方式:
1)StartCoroutine(string method);
2)StartCoroutine(Coroutine routine);
3)StartCoroutine(IEnumrator routine);
启动依次对应停止方式,不然,可能不生效
1)StopCoroutine(string method);
2)StopCoroutine(Coroutine routine);
3)StopCoroutine(IEnumrator routine);
例如:IEnumerator Test(){....}
1)StartCoroutine(“Test”); => StopCoroutine(“Test”);
2)Coroutine routine= Test();
StartCoroutine(routine); => StopCoroutine(routine)
3)IEnumrator routine = StartCoroutine(Test());
=> StopCoroutine(routine );
注意:其他的情况
1)禁用启动协程的脚本,是无法停止协程的;
2)禁用启动协程的脚本挂载的GameObject ,是可以停止协程的
3)销毁启动协程的脚本,是可以停止协程的
4)销毁启动协程的脚本挂载的GameObject ,是可以停止协程的
5)协程依赖 MonoBehaviour
6)协程返回值一般只能是 IEnumrator
7)可能存在回调地狱 (注意 async 、await 异步)
答:碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。
当Is Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;
当Is Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/Exit函数。
如果既要检测到物体的接触又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域这时就可以用到触发器。
答:两个物体都必须带有碰撞器Collider,其中一个物体还必须带有Rigidbody刚体。
答:四元数用于表示旋转
相对欧拉角的优点:能进行增量旋转、避免万向锁、给定方位的表达方式有两种,互为负(欧拉角有无数种表达方式)
答: 1.将Assets和Library一起迁移 2.导出包package 3.用unity自带的assets Server功能
答:Awake–>OnEnable->Start,OnEnable在同一周期中可以反复地发生!
答:修改sharedMaterial将改变所有物体使用这个材质的外观,并且也改变储存在工程里的材质设置。 不推荐修改由sharedMaterial返回的材质。如果你想修改渲染器的材质,使用material替代。
答:网络接口层:这是协议栈的最低层,对应OSI的物理层和数据链路层,主要完成数据帧的实际发送和接收。
网络层:处理分组在网络中的活动,例如路由选择和转发等,这一层主要包括IP协议、ARP、ICMP协议等。
传输层:主要功能是提供应用程序之间的通信,这一层主要是TCP/UDP协议。
应用层:用来处理特定的应用,针对不同的应用提供了不同的协议,例如进行文件传输时用到的FTP协议,发送email用到的SMTP等。
答: 四种。 平行光:Directional Light 点光源:Point Light 聚光灯:Spot Light 区域光源:Area Light
对象池就存放需要被反复调用资源的一个空间,比如游戏中要常被大量复制的对象,子弹,敌人,以及任何重复出现的对象。
Rigidbody具有完全真实物理的特性,而CharacterController可以说是受限的的Rigidbody,具有一定的物理效果但不是完全真实的。
LateUpdate,是在所有的Update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是所有的Update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
在游戏运行时实例化,prefab相当于一个模板,对你已经有的素材、脚本、参数做一个默认的配置,以便于以后的修改,同事prefab打包的内容简化了导出的操作,便于团队的交流。
简而言之,GPU的图形(处理)流水线完成如下的工作:(并不一定是按照如下顺序)。
顶点处理:这阶段GPU读取描述3D图形外观的顶点数据并根据顶点数据确定3D图形的形状及位置关系,建立起3D图形的骨架。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Vertex Shader(定点着色器)完成。
光栅化计算:显示器实际显示的图像是由像素组成的,我们需要将上面生成的图形上的点和线通过一定的算法转换到相应的像素点。把一个矢量图形转换为一系列像素点的过程就称为光栅化。例如,一条数学表示的斜线段,最终被转化成阶梯状的连续像素点。
纹理帖图:顶点单元生成的多边形只构成了3D物体的轮廓,而纹理映射(texture mapping)工作完成对多变形表面的帖图,通俗的说,就是将多边形的表面贴上相应的图片,从而生成“真实”的图形。TMU(Texture mapping unit)即是用来完成此项工作。
像素处理:这阶段(在对每个像素进行光栅化处理期间)GPU完成对像素的计算和处理,从而确定每个像素的最终属性。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Pixel Shader(像素着色器)完成。
最终输出:由ROP(光栅化引擎)最终完成像素的输出,1帧渲染完毕后,被送到显存帧缓冲区。
总结:GPU的工作通俗的来说就是完成3D图形的生成,将图形映射到相应的像素点上,对每个像素进行计算确定最终颜色并完成输出。
答:是指在显示器上为了显示出图像而经过的一系列必要操作。 渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。
主要步骤有: 本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。
答:有很多种方式,例如
1.压缩自带类库;
2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;
3.释放AssetBundle占用的资源;
4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;
5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)
6.代码中少产生临时变量
1.Resources.Load(); 2.AssetBundle
Unity5.1版本后可以选择使用Git: https://github.com/applexiaohao/LOAssetFramework.git
答: 1.使用本身的GUI、UGUI
2.把摄像机的Projection(投影)值调为Orthographic(正交投影),不考虑z轴;
3.使用2d插件,如:2DToolKit、NGUI
答:OnCollisionEnter、 OnCollisionStay、 OnCollisionExit
答:rigidbody.AddForce、 rigidbody.AddForceAtPosition
答:Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。
答: Transform.Rotate()
答:PlayerPrefs.SetInt()、 PlayerPrefs.GetInt()
答:Awake——>Start——>Update——>FixedUpdate——>LateUpdate——>OnGUI——>OnDisable——>OnDestroy
主要执行顺序
编辑器->初始化->物理系统->输入事件->游戏逻辑->场景渲染->GUI渲染->物体激活或禁用->销毁物体->应用结束
主要函数介绍Reset 是在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个最常用的默认值。
Awake 用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如 GameObject.FindWithTag 这样的函数搜索它们。每个游戏物体上的Awke以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息 ,Awake总是在Start之前被调用。它不能用来执行协同程序。
OnDisable 不能用于协同程序。当对象变为不可用或非激活状态时此函数被调用。
Start 在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。
FixedUpdate 当MonoBehaviour启用时,其在每一帧被调用。处理Rigidbody时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)。
OnTriggerEnter 可以被用作协同程序,在函数中调用yield语句。当Collider(碰撞体)进入trigger(触发器)时调用OnTriggerEnter。
OnCollisionEnter 相对于OnTriggerEnter,传递的是Collision类而不是Collider。Collision包含接触点,碰撞速度等细节。如果在函数中不使用碰撞信息,省略collisionInfo参数以避免不必要的运算。注意如果碰撞体附加了一个非动力学刚体,只发送碰撞事件。可以被用作协同程序。
当鼠标在GUIElement(GUI元素)或Collider(碰撞体)上点击时调用OnMouseDown。
Update 是实现各种游戏行为最常用的函数。
yield 一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。StartCoroutine函数是立刻返回的,但是yield可以延迟结果。直到协同程序执行完毕。
LateUpdate 是在所有Update函数调用后被调用。这可用于调整脚本执行顺序。例如:当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。
渲染和处理GUI事件时调用。这意味着你的OnGUI程序将会在每一帧被调用。要得到更多的GUI事件的信息查阅Event手册。如果Monobehaviour的enabled属性设为false,OnGUI()将不会被调用。
OnApplicationQuit,当用户停止运行模式时在编辑器中调用。当web被关闭时在网络播放器中被调用。
答: FixedUpdate,每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。
FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。 Update就比较适合做控制。
答:游戏界面可以看到很多摄像机的混合。????
答: 使用Destroy()方法;
答:主要有关节动画、骨骼动画、单一网格模型动画(关键帧动画)。 关节动画:把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活,Quake2中使用这种动画;
骨骼动画,广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结构,有关节相连,可做相对运动,皮肤作为单一网格蒙在骨骼之外,决定角色的外观;
单一网格模型动画由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其改变量,然后插值运算实现动画效果,角色动画较真实。
答: 一般是组件上绑定的物体对象被删除了
答:Alpha Blend 实现透明效果,不过只能针对某块区域进行alpha操作,透明度可设。
答:diffuse = Kd x colorLight x max(N*L,0);Kd 漫反射系数、colorLight 光的颜色、N 单位法线向量、L 由点指向光源的单位向量、其中N与L点乘,如果结果小于等于0,则漫反射为0。
答:LOD(Level of detail)多层次细节,是最常用的游戏优化技术。它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。
缺点:增加了内存
本影和半影:
本影:景物表面上那些没有被光源直接照射的区域(全黑的轮廓分明的区域)。
半影:景物表面上那些被某些特定光源直接照射但并非被所有特定光源直接照射的区域(半明半暗区域) 工作原理:从光源处向物体的所有可见面投射光线,将这些面投影到场景中得到投影面,再将这些投影面与场景中的其他平面求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对场景进行相应处理得到所要求的视图(利用空间换时间,每次只需依据视点位置进行一次阴影计算即可,省去了一次消隐过程)
答:顶点着色器是一段执行在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几个过程。
答:MipMapping:在三维计算机图形的贴图渲染中有常用的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为MipMap。
答:
语法不同处:1.抽象类中可以有字段,接口没有。
2.抽象类中可以有实现成员,接口只能包含抽象成员。
3.抽象类中所有成员修饰符都可以使用,接口中所有的成员都是对外的,所以不需要修饰符修饰。
用法不同处:1.抽象类是概念的抽象,接口关注于行为。
2.抽象类的子类与父类的关系是泛化关系,耦合度较高,而实现类和接口之间是实现的关系,耦合度比泛化低。
3.一个类只能继承一个类,但是可以实现多个接口。
答:mono是.net的一个开源跨平台工具,就类似java虚拟机,java本身不是跨平台语言,但运行在虚拟机上就能够实现了跨平台。.net只能在windows下运行,mono可以实现跨平台编译运行,可以运行于Linux,Unix,Mac OS等。
答:Unity的脚本语言基于Mono的.Net平台上运行,可以使用.NET库,这也为XML、数据库、正则表达式等问题提供了很好的解决方案。
Unity里的脚本都会经过编译,他们的运行速度也很快。这三种语言实际上的功能和运行速度是一样的,区别主要体现在语言特性上。
JavaScript、 C#、Boo
答:仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用
支持:如果同时你要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。
注意:C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象
答:多线程程序同时运行多个线程 ,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只在必要时才被挂起。
除主线程之外的线程无法访问Unity3D的对象、组件、方法。
Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。 StartCoroutine为什么叫协同程序呢,所谓协同,就是当你在StartCoroutine的函数体里处理一段代码时,利用yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。
Unity多线程(Thread)和主线程(MainThread)交互使用类——Loom工具
熟悉Unity的developer都知道在Unity中的线程不能使用Unity的对象,但可以使用Unity的值类型变量,如Vector3等。这样就使得线程在Unity中显的很鸡肋和蹩脚,因为很多函数很都是UnityEngine类或函数的调用的,对于哪些是可以在多线程使用,风雨冲进行了如下总结:
0. 变量(都能指向相同的内存地址)都是共享的
1. 不是UnityEngine的API能在分线程运行
2. UnityEngine定义的基本结构(int,float,Struct定义的数据类型)可以在分线程计算,如 Vector3(Struct)可以 , 但Texture2d(class,根父类为Object)不可以。
3. UnityEngine定义的基本类型的函数可以在分线程运行,如
int i = 99;
print (i.ToString());
Vector3 x = new Vector3(0,0,9);
x.Normalize();
类的函数不能在分线程运行
obj.name
实际是get_name函数,分线程报错误:get_name can only be called from the main thread.
Texture2D tt = new Texture2D(10,10);
实际会调用UnityEngine里的Internal_Create,分线程报错误:Internal_Create can only be called from the main thread.
其他transform.position,Texture.Apply()等等都不能在分线程里运行。
结论: 分线程可以做 基本类型的计算, 以及非Unity(包括.Net及SDK)的API。
D.S.Qiu觉得Unity做了这个限制,主要是Unity的函数执行机制是帧序列调用,甚至连Unity的协程Coroutine的执行机制都是确定的,如果可以使用多线程访问UnityEngine的对象和api就得考虑同步问题了,也就是说Unity其实根本没有多线程的机制,协程只是达到一个延时或者是当指定条件满足是才继续执行的机制。
我们的项目目前还有没有比较耗时的计算,所以还没有看到Thread的使用。本来一直没有太考虑着方面的事情,直到在UnityGems.com看到Loom这个类,叹为观止呀
Our class is called Loom. Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to.
There are only two functions to worry about:
You access Loom using Loom.Current - it deals with creating an invisible game object to interact with the games main thread.
我们只需要关系两个函数:RunAsync(Action)和QueueOnMainThread(Action, [optional] float time) 就可以轻松实现一个函数的两段代码在C#线程和Unity的主线程中交叉运行。原理也很简单:用线程池去运行RunAsync(Action)的函数,在Update中运行QueueOnMainThread(Acition, [optional] float time)传入的函数。
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- using System;
- using System.Threading;
- using System.Linq;
-
- public class Loom : MonoBehaviour
- {
- public static int maxThreads = 8;
- static int numThreads;
-
- private static Loom _current;
- private int _count;
- public static Loom Current
- {
- get
- {
- Initialize();
- return _current;
- }
- }
-
- void Awake()
- {
- _current = this;
- initialized = true;
- }
-
- static bool initialized;
-
- static void Initialize()
- {
- if (!initialized)
- {
-
- if(!Application.isPlaying)
- return;
- initialized = true;
- var g = new GameObject("Loom");
- _current = g.AddComponent<Loom>();
- }
-
- }
-
- private List<Action> _actions = new List<Action>();
- public struct DelayedQueueItem
- {
- public float time;
- public Action action;
- }
- private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
-
- List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();
-
- public static void QueueOnMainThread(Action action)
- {
- QueueOnMainThread( action, 0f);
- }
- public static void QueueOnMainThread(Action action, float time)
- {
- if(time != 0)
- {
- lock(Current._delayed)
- {
- Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action});
- }
- }
- else
- {
- lock (Current._actions)
- {
- Current._actions.Add(action);
- }
- }
- }
-
- public static Thread RunAsync(Action a)
- {
- Initialize();
- while(numThreads >= maxThreads)
- {
- Thread.Sleep(1);
- }
- Interlocked.Increment(ref numThreads);
- ThreadPool.QueueUserWorkItem(RunAction, a);
- return null;
- }
-
- private static void RunAction(object action)
- {
- try
- {
- ((Action)action)();
- }
- catch
- {
- }
- finally
- {
- Interlocked.Decrement(ref numThreads);
- }
-
- }
-
-
- void OnDisable()
- {
- if (_current == this)
- {
-
- _current = null;
- }
- }
-
-
-
- // Use this for initialization
- void Start()
- {
-
- }
-
- List<Action> _currentActions = new List<Action>();
-
- // Update is called once per frame
- void Update()
- {
- lock (_actions)
- {
- _currentActions.Clear();
- _currentActions.AddRange(_actions);
- _actions.Clear();
- }
- foreach(var a in _currentActions)
- {
- a();
- }
- lock(_delayed)
- {
- _currentDelayed.Clear();
- _currentDelayed.AddRange(_delayed.Where(d=>d.time <= Time.time));
- foreach(var item in _currentDelayed)
- _delayed.Remove(item);
- }
- foreach(var delayed in _currentDelayed)
- {
- delayed.action();
- }
-
-
-
- }
- }
-
- 怎么实现一个函数内使用多线程计算又保持函数体内代码的顺序执行,印象中使用多线程就是要摆脱代码块的顺序执行,但这里是把原本一个函数分拆成为两部分:一部分在C#线程中使用,另一部还是得在Unity的MainThread中使用,怎么解决呢,还得看例子:
-
- //Scale a mesh on a second thread
- void ScaleMesh(Mesh mesh, float scale)
- {
- //Get the vertices of a mesh
- var vertices = mesh.vertices;
- //Run the action on a new thread
- Loom.RunAsync(()=>{
- //Loop through the vertices
- for(var i = 0; i < vertices.Length; i++)
- {
- //Scale the vertex
- vertices[i] = vertices[i] * scale;
- }
- //Run some code on the main thread
- //to update the mesh
- Loom.QueueOnMainThread(()=>{
- //Set the vertices
- mesh.vertices = vertices;
- //Recalculate the bounds
- mesh.RecalculateBounds();
- });
-
- });
- }
这个例子是对Mesh的顶点进行放缩,同时也是一个使用闭包(closure)和lambda表达式的一个很好例子。看完例子,是不是很有把项目中一些耗时的函数给拆分出来,D.S.Qiu就想用这个方法来改进下NGUI的底层机制(看下性能不能改进)。
小结:
D.S.Qiu在编程技术掌握还是一个菜鸟,Thread还是停留在实现Runable接口或继承Thread的一个水平上,对多线程编程的认识还只是九牛一毛。本来我以为Loom的实现会比较复杂,当我发现只有100多行的代码是大为惊叹,这也得益于现在语言的改进,至少从语言使用的便利性上还是有很大的进步的。
有了Loom这个工具类,在很多涉及UnityEngine对象的耗时计算还是可以得到一个解决方法的:
如在场景中用A*算法进行大量的数据计算
变形网格中操作大量的顶点
持续的要运行上传数据到服务器
二维码识别等图像处理
Loom简单而又巧妙,佩服Loom的作者。
答:Transform 父类是 Component
答: 1)点乘描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影
2)叉乘得到的向量垂直于原来的两个向量
3)标准化向量:用在只关系方向,不关心大小的时候
答:用于表示线性变换:旋转、缩放、投影、平移、仿射
注意矩阵的蠕变:误差的积累
答:不美观,OnGUI很耗费时间,使用不方便
答:
多屏幕分辨率下的UI布局一般考虑两个问题:
1、布局元素的位置,即屏幕分辨率变化的情况下,布局元素的位置可能固定不动,导致布局元素可能超出边界;
2、布局元素的尺寸,即在屏幕分辨率变化的情况下,布局元素的大小尺寸可能会固定不变,导致布局元素之间出现重叠等功能。
为了解决这两个问题,在Unity GUI体系中有两个组件可以来解决问题,分别是布局元素的Rect Transform和Canvas的Canvas Scaler组件。
1、Rect Transform,主要原理是Pivot(锚点)对布局元素的位置控制作用,Privot对UI对象的位置的控制是通过UI对象与Pivot相对的Pos X、Pox Y或者Top、Left、Botton和Right取值进行控制的,在不同屏幕分辨率下,UI对象的这些取值相当于Privot是固定不变的,这也是导致屏幕分辨率变化的情况下,布局元素的位置固定不动的原因。但是我们可以通过控制Privot的位置来确保UI对象一直在画布之内而不会超出界限。比如
(1)刚开始三个Button的Pivot都在中心点,那么他们的pos X和pos Y都是相对于中心点Pivot的坐标点,而且在改变屏幕分辨率时这些posX和posY不会变动。如下面两幅图对比所示:
原始分辨率
拉动窗口后,分辨率发生改变
(2)那么如何来实现呢?我们可以把每个Button的Pivot的放到他们各自的对应的角上,则可以确保他们一致在屏幕范围内。
这里写图片描述
当我们改变窗口分辨率时,我们发现这时Button均在屏幕范围内了。
这里写图片描述
这时在一定程度上满足了我们的要求,当时我们发现Button的尺寸并未因为屏幕尺寸变小相应的变小,这时我们需要借助Canvas 的Canvas Scaler组件来实现。
2、Canvas Scaler组件。
这里写图片描述
(1)UI Scale Mode,设置成Scale with Screen Size,即随着屏幕尺寸变化;
(2)Reference Resolution:参考分辨率,即默认的分辨率。
(3)Screen Match Mode:屏幕匹配模式。
Screen Match Mode—Match Width Or Height
Match是一个滑条,拉在最左时是Width ,最右时是Height,中间则是按比例混合。
当处于最左边时,屏幕高度对于UI大小完全没有任何影响,只有宽度会对UI大小产生影响。假设宽度为Reference Resolution宽度的x倍,则UI整体缩放为Reference Resolution状态下的x倍。也就是说只有宽度等于Reference Resolution宽度时,才能做到pixel perfect,否则像素就会有拉伸
当处于最右边时,与上述情况正好相反,决定整体缩放值的是高度,而宽度则没有任何影响
处于中间某处时,对上述两者的影响进行权重加成
Screen Match Mode—Expand
当屏幕分辨率大于参考分辨率时,选择变化较小的一个方向(横向还是纵向),作为放大Canvas Scale的标准,另一方向上的变化则是在整体缩放以后再进行补偿性的变化。此举旨在减少扩大分辨率时由于非等比扩大而对UI整体布局造成影响。适合制作较小标准尺寸,扩充到较大屏幕。
例如:Reference Resolution为800*600,(假设此时Canvas Scale为(1,1,1))。如果实际分辨率为800*800,那么Canvas Scale还保持为(1,1,1)因为宽度没有发生变化,只是单纯的高度增加了200。所以势必对布局造成一定得影响。1000*600的情况也是一样,Canvas Scale没有变化,只是单纯宽度增加了200。但如果实际分辨率变为1000*800,那么Canvas Scale就变成(1.25,1.25,1.25)。因为宽度是参考分辨率宽度的1.25倍,高度是1.33倍,那么取较小的1.25。 这个1.25倍的意义是:整体Canvas渲染放大1.25倍,横向或纵向的变化超过了1.25倍,则靠拉伸Canvas来变化,此时因为这部分变化,可能会对布局产生一些相对较小的影响,例如相对位置、某些元素的长宽比。
Screen Match Mode—Shrink
和Expand类似,但是更适合于缩小的情形。它会在屏幕尺寸缩小时,通过缩小CanvasScale尽量减少由于非等比缩小对布局产生的影响。按照影响较小的一个方向缩小的比例去缩小CanvasScale,然后再通过变形调整另外一个方向。
答:穿透(碰撞检测失败)(例如CS射击游戏,可以使用开枪时发射射线,射线碰撞到则掉血击中)
答:当物体是否可见切换之时。可以用于只需要在物体可见时才进行的计算。
答:如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。
区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。
答: String是字符串常量。
String是字符串变量 ,线程安全。
StringBuilder是字符串变量,线程不安全。
String类型是个不可变的对象,当每次对String进行改变时都需要生成一个新的String对象,然后将指针指向一个新的对象,如果在一个循环里面,不断的改变一个对象,就要不断的生成新的对象,所以效率很低,建议在不断更改String对象的地方不要使用String类型。
StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。这一点我们平时使用中也许都知道,连接操作频繁的时候,使用StringBuilder对象。
string创建后分配在字符串常量区,栈中存储的地址指向存储字符串的地址(虽然没存储在堆,但string也是引用类型,这点比较特殊)。大小不可修改,每次使用string类中的方法时,都要再创建一个新的字符串对象,并给其分配内存。
这样就需要再分配新的空间。所以有可能产生很大的开销。StringBuilder创建后分配在堆区,大小可自由修改。
String a1 = "abc"; //系统分配内存给a1 a1+="def"; //系统分配新的内存给a1,并让a1重新指向新的内存空间,会产生较多内存开销 StringBuilder sb = new StringBuilder(20); //指定分配大小。超出容量的话,会自动增倍容量。 sb.Append("abc"); //分配到堆区 sb.Append("def"); //不会被销毁,而是直接追加到后面。 StringBuilder中常用方法 StringBuilder sb = new StringBuilder(20); sb.Append("abc"); //添加字符串到结尾 sb.AppendLine("123"); //添加字符串到结尾,并多个回车 int age = 19; sb.AppendFormat("年龄:{0}",age); //添加格式化字符串到结尾 sb.Insert(2, "xxx"); //插入字符串到指定字符位置 sb.Remove(2, 1); //从某个下标开始移除指定长度字符 sb.Replace("x", "111"); //把sb中所有指定字符替换为其他指定字符。 sb.Capacity = 25; //设置sb对象的最大长度。 sb.Clear("abc"); //删除所有字符值类型是存储在内存中的栈,而引用类型的在栈中存储引用类型变量的地址,其本身存储在堆中。
1、它是比较的栈里面的值是否相等(值比较)
对于值类型,如果对象的值相等,则相等运算符 () 返回 true,否则返回 false。
对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。2、Equals它比较的是堆里面的值是否相等(引用地址值比较)
3、Object.ReferenceEquals(obj1,obj2)它是比较的是内存地址是否相等
答:LightMap:就是指在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。
答:
Unity3D支持C#、javascript等,cocos2d-x 支持c++、Html5、Lua等。
cocos2d 开源 并且免费
Unity3D支持iOS、Android、Flash、Windows、Mac、Wii等平台的游戏开发,cocos2d-x支持iOS、Android、WP等。
答:
1) 表面着色器的抽象层次比较高,它可以轻松地以简洁方式实现复杂着色。表面着色器可同时在前向渲染及延迟渲染模式下正常工作。
2) 顶点片段着色器可以非常灵活地实现需要的效果,但是需要编写更多的代码,并且很难与Unity的渲染管线完美集成。
3) 固定功能管线着色器可以作为前两种着色器的备用选择,当硬件无法运行那些酷炫Shader的时,还可以通过固定功能管线着色器来绘制出一些基本的内容。
- function Start() {
- // - After 0 seconds, prints "Starting 0.0"
- // - After 0 seconds, prints "Before WaitAndPrint Finishes 0.0"
- // - After 2 seconds, prints "WaitAndPrint 2.0"
- // 先打印"Starting 0.0"和"Before WaitAndPrint Finishes 0.0"两句,2秒后打印"WaitAndPrint 2.0"
- print ("Starting " + Time.time );
- // Start function WaitAndPrint as a coroutine. And continue execution while it is running
- // this is the same as WaintAndPrint(2.0) as the compiler does it for you automatically
- // 协同程序WaitAndPrint在Start函数内执行,可以视同于它与Start函数同步执行.
- StartCoroutine(WaitAndPrint(2.0));
- print ("Before WaitAndPrint Finishes " + Time.time );
- }
-
- function WaitAndPrint (waitTime : float) {
- // suspend execution for waitTime seconds
- // 暂停执行waitTime秒
- yield WaitForSeconds (waitTime);
- print ("WaitAndPrint "+ Time.time );
- }
-
作用:一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。 缺点:协同程序并非真线程,可能会发生堵塞。
答:射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射 。
答:
1)socket通常也称作"套接字",实现服务器和客户端之间的物理连接,并进行数据传输,主要有UDP和TCP两个协议。Socket处于网络协议的传输层。
2)http协议传输的主要有http协议 和基于http协议的Soap协议(web service),常见的方式是 http 的post 和get 请求,web 服务。
答:剪裁平面 。从相机到开始渲染和停止渲染之间的距离。
答:在Game视图右上角点击Stats。降低Draw Call 的技术是Draw Call Batching
Alpha Test,中文就是透明度测试。简而言之就是V&F shader中最后fragment函数输出的该点颜色值(即上一讲frag的输出half4)的alpha值与固定值进行比较。Alpha Test语句通常于Pass{}中的起始位置。Alpha Test产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。
答: 1.使用assetbundle,实现资源分离和共享,将内存控制到200m之内,同时也可以实现资源的在线更新
2.顶点数对渲染无论是cpu还是gpu都是压力最大的贡献者,降低顶点数到8万以下,fps稳定到了30帧左右
3.只使用一盏动态光,不是用阴影,不使用光照探头
4.粒子系统是cpu上的大头,剪裁粒子系统
5.合并同时出现的粒子系统
6.自己实现轻量级的粒子系统,Animator也是一个效率奇差的地方
7.把不需要跟骨骼动画和动作过渡的地方全部使用animation,控制骨骼数量在30根以下
8.animator出视野不更新
9.删除无意义的animator
10.animator的初始化很耗时(粒子上能不能尽量不用animator)
11.除主角外都不要跟骨骼运动apply root motion
12.绝对禁止掉那些不带刚体带包围盒的物体(static collider )运动 NUGI的代码效率很差,基本上runtime的时候对cpu的贡献和render不相上下
13每帧递归的计算finalalpha改为只有初始化和变动时计算
14去掉法线计算
15不要每帧计算viewsize 和windowsize
16filldrawcall时构建顶点缓存使用array.copy
17.代码剪裁:使用strip level ,使用.net2.0 subset
18.尽量减少smooth group
19.给美术定一个严格的经过科学验证的美术标准,并在U3D里面配以相应的检查工具
答:对旋转角度进行计算时用到四元数
答:仅深度,该模式用于对象不被裁剪。
- void Awake()
- {
- DontDestroyOnLoad(transform.gameObject);
- }
-
答:设置游戏对象为Static将会剔除(或禁用)网格对象当这些部分被静态物体挡住而不可见时。因此,在你的场景中的所有不会动的物体都应该标记为Static。
答:把A组物体的渲染对列大于B物体的渲染队列
答:Sprite作为UI精灵使用,Texture作用模型贴图使用。
答:没有区别,因为不管几张贴图只渲染一次。
答:Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:
Dynamic Batching
Static Batching
高级特性Shader降级为统一的低级特性的Shader。
答:可以有cookies – 带有 alpha通道的立方图(Cubemap )纹理。点光源是最耗费资源的。
答:作用就是Alpha混合。公式:最终颜色 = 源颜色*源透明值 + 目标颜色*(1 - 源透明值)
答: 原理就是对水面的贴图纹理进行扰动,以产生波光玲玲的效果。用shader可以通过GPU在像素级别作扰动,效果细腻,需要的顶点少,速度快
简单来说在一个Canvas下,需要相同的material,相同的纹理以及相同的Z值。例如UI上的字体Texture使用的是字体的图集,往往和我们自己的UI图集不一样,因此无法合批。还有UI的动态更新会影响网格的重绘,因此需要动静分离。
协程Coroutine在Unity中一直扮演者重要的角色。可以实现简单的计时器、将耗时的操作拆分成几个步骤分散在每一帧去运行等等,而尽量不阻塞主线程运行。
1)什么是热更新?
在安卓、iOS平台,热更新表示在更新游戏资源或逻辑的时候不需要开发者将游戏再打包、上传、审核、发布、玩家重新下载安装包更新游戏,仅需要开发者打出新的ab(AssetBundle)资源文件放到网上,然后游戏程序下载新的ab资源文件替换本地的资源文件来实现游戏更新的流程。
热更代码是特殊的资源。
2)主流的代码热更方案有哪些?
lua(xLua、tolua、ulua)、C#(DLL、ILRuntime、LSharp)
iOS:IL2CPP,AOT(Ahead of Time,运行前编译)
安卓:Mono,JIT(Just in Time,动态(即时)编译)
DLL基于动态即时编译,只能在JIT模式下使用,即无法在iOS平台使用
lua有自己的虚拟机和运行时解释器,不受限于编译方式(IL2CPP、Mono)
ILRuntime和LSharp也有自己的虚拟机和运行时解译引擎,也不受限于编译方式
3)ILRuntime有什么优势?
https://ourpalm.github.io/ILRuntime/public/v1/guide/index.html
无缝访问C#工程的现成代码,无需额外抽象脚本API
直接使用VS2015进行开发,ILRuntime的解译引擎支持.Net 4.6编译的DLL
执行效率是L#的10-20倍
选择性的CLR绑定使跨域调用更快速,绑定后跨域调用的性能能达到slua的2倍左右(从脚本调用GameObject之类的接口)
支持跨域继承
完整的泛型支持
拥有Visual Studio的调试插件,可以实现真机源码级调试。支持Visual Studio 2015 Update3 以及Visual Studio 2017
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。