Unity游戏开发客户端面经——Unity(初级)
Posted 正在奋斗中的小志
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity游戏开发客户端面经——Unity(初级)相关的知识,希望对你有一定的参考价值。
前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是 Unity 常问问题总结,有帮助的可以收藏。
1. 生命周期
Awake —> OnEnable —> Start —> FixedUpdate —>Update —> LateUpdate—> OnGUl —> OnDisable —> OnDestroy
详细介绍
1. Awake
Awake用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次,Awake在所有对象被初始化之后调用,
当脚本设置为不可用时,运行时Awake方法仍然会执行一次。
所以你可以安全的与其他对象对话或用诸如GameObject.FindWithTag 这样的函数搜索它们。每个游戏物体上的Awake以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息,Awake总是在Start之前被调用。它不能用来执行协同程序。
2. OnEnable
当对象变为可用或激活状态时被调用事件监听。
3. Start
Start在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Start总是在Awake之后执行。这允许你协调初始化顺序。
4. Update
Update每帧调用一次用于更新游戏场景和状态,比较适合做控制。
5. FixedUpdate
每隔固定物理时间间隔调用一次用于物理状态的更新,和Update不同的是FixedUpdate逻辑帧,Update是渲染帧。如果帧率很低,可以每帧调用该函数多次;如果帧率很高,可能在帧之间完全不调用该函数。
FixedUpdate 内应用运动计算时,无需将值乘以 Time.deltaTime。这是因为 FixedUpdate 的调用基于可靠的计时器(独立于帧率)。
详细请看:FixedUpdate真的是固定的时间间隔执行吗?聊聊游戏定时器 - 慕容小匹夫 - 博客园0x00 前言 有时候即便是官方的文档手册也会让人产生误解,比如本文将要讨论的Unity引擎中的FixedUpdate方法。 This function is called every fixed fhttps://www.cnblogs.com/murongxiaopifu/p/7683140.html
6. LateUpdate
每帧调用一次(在 update 之后调用) 用于更新游戏场景和状态,和摄像机相关的更新。
官网上例子是摄像机的跟随,都是所有的Update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
7. OnGUI
OnGUI 渲染和处理 OnGUI 事件。
8. OnDisable
OnDisable 当对象变为不可用或非激活状态时被调用事件移除。
9. OnDestroy
OnDestroy 当对象被销毁时调用。
2. 如何让已经存在的GameObject在LoadLevel后不被卸载掉
DontDestroyOnLoad(transform.gameObject);
3. 碰撞
物体发生碰撞的必要条件
两个物体都必须带有碰撞器Collider,其中一个物体还必须带有Rigidbody刚体。
碰撞器与触发器
1.碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。
2. 当ls Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;
3.当ls Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/lExit函数。
4. 如果既要检测到物体的接触,又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域,这时就可以用到触发器。
4. 协程(进程/线程)
1. 概念
进程
保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己独立的地址空间,有自己的堆,不同进程间可以进行进程间通信,上级挂靠单位是操作系统。一个应用程序相当于一个进程,操作系统会以进程为单位,分配系统资源(CPU 时间片、内存等资源),进程是资源分配的最小单位。
线程
线程从属于进程,也被称为轻量级进程,是程序的实际执行者。线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程只有一个进程。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
协程
协程是伴随着主线程一起运行的一段程序。
注意点:
协程与协程之间是并行执行,与主线程也是并行执行,同一时间只能执行一个协程提起协程,自然是要想到线程,因为协程的定义就是伴随主线程来运行的!
一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。
协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
协成是单线程下由应用程序级别实现的并发。
2.线程与协程的区别
协同程序与多线程情况下的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针,但与其它协同程序共享全局变量等很多信息。
协程(协同程序): 同一时间只能执行某个协程。开辟多个协程开销不大。协程适合对某任务进行分时处理。
线程: 同一时间可以同时执行多个线程。开辟多条线程开销很大。线程适合多任务同时处理。
1.协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程实际上是在一个线程中,只不过每个协程对CPU进行分时,协程可以访问和使用unity的所有方法和component。
2.线程,多线程是阻塞式的,每个IO都必须开启一个新的线程,但是对于多CPU的系统应该使用thread,尤其是有大量数据运算的时刻,但是IO密集型就不适合;而且thread中不能操作unity的很多方法和component。
线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起。
2. 协程作用
在Unity中只有主线程才能访问Unity3D的对象、方法、组件。当主线程在执行一个对资源消耗很大的操作时,在这一帧我们的程序就会出现帧率下降,画面卡顿的现象!
那这个时候我们就可以利用协程来做这件事,因为协程是伴随着主线程运行的,主线程依旧可以丝滑轻松的工作,把脏活累活交给协程处理就好了!简单来说:协程是辅助主线程的操作,避免游戏卡顿。
3. 操作
3.1定义协程
IEnumerator Test(string str)
//代码块
Debug.Log("协程被启动了!"+ str);
yield return null;
//代码块
3.2 启动协程
1. Startcoroutine (string methodName):通过协程的方法名(字符串形式)启动。
2. StartCoroutine (string methodName,object values):带参数的通过方法名(字符串形式)进行调用。
3. Startcoroutine (IEnumerator routine):通过调用方法的形式启动。
3.3 停止携程
1.stopcoroutine (string methodName):通过方法名(字符串)来关闭协程。
2.stopCoroutine(IEnumerator routine):通过调用方法的形式来关闭协程。
3.stopCoroutine(Coroutine routine):通过指定的协程来关闭。
4.stopAllCoroutine() 的作用是停止所有该脚本中启动的协程。
void StopTest()
//第一种方式:通过调用方法的形式来关闭协程
StopCoroutine(Test1());
//第二种方式:通过方法名(字符串)来关闭协程
StopCoroutine("Test1");
//第三种方式:通过指定的协程来关闭
Coroutine a = StartCoroutine(Test1());
StopCoroutine(a);
//关闭该脚本中启动的所有协程!
StopAllCoroutines();
4. 底层原理
协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义一个迭代方法。
注意:提起IEnumerator就会想到IEnumerable,可千万不能搞混了!
StartCoroutine 接受到的是一个 IEnumerator ,这是个接口,并且是枚举器或迭代器的意思。
yield 是 C#的一个关键字,也是一个语法糖,背后的原理会生成一个类,并且也是一个枚举器,而且不同于 return,yield 可以出现多次。
yield 实际上就是返回一次结果,因为我们要一次一次枚举一个值出来,所以多个 yield 其实是个状态模式,第一个 yield 是状态 1,第二个 yield 是状态 2,每次访问时会基于状态知道当前应该执行哪一个 yield,取得哪一个值。
从程序的角度讲,协程的核心就是迭代器。想要定义一个协程方法有两个因素,第一:方法的返回值为 IEnumerator 。第二,方法中有 yield关键字。当代码满足以上两个条件时,此方法的执行就具有了迭代器的特质,其核心就是 MoveNext方法。方法内的内容将会被分成两部分:yield 之前的代码和 yield 之后的代码。yield之前的代码会在第一次执行MoveNext时执行, yield之后的代码会在第二次执行MoveNext方法时执行。而在Unity中,MoveNext的执行时机是以帧为单位的,无论你是设置了延迟时间,还是通过按钮调用MoveNext,亦或是根本没有设置执行条件,Unity都会在每一帧的生命周期中判断当前帧是否满足当前协程所定义的条件,一旦满足,当前帧就会抽出CPU时间执行你所定义的协程迭代器的MoveNext。注意,只要方法中有yield语句,那么方法的返回值就必须是 IEnumerator ,不然无法通过编译。
详细请看:
测试:
解释:a:Start函数正常运行
b:Update函数正常运行
c: 进入协程
d:协程挂起,正常运行Update函数
b:Update函数正常运行
c: 进入协程
d:协程挂起,正常运行Update函数
e: 因为协程是yield return null等待一帧后,第一帧的协程由挂起,变成正常运行,输出e。
5. Invoke与InvokeRepeating
1. Invoke
Invoke() 方法是 Unity3D 的一种委托机制
如: Invoke("Test", 5); 它的意思是:5 秒之后调用 Test() 方法;
使用 Invoke() 方法需要注意 3点:
1 :它应该在 脚本的生命周期里的(Start、Update、OnGUI、FixedUpdate、LateUpdate)中被调用;
2:Invoke(); 不能接受含有参数的方法;
3:在 Time.ScaleTime = 0; 时, Invoke() 无效,因为它不会被调。
2. InvokeRepeating
InvokeRepeating("Test", 2 , 3);
这个方法的意思是指:2 秒后调用 Test() 方法,并且之后每隔 3 秒调用一次 Test() 方法。
被激活时设置了,但是此时将引擎对象设置为false,还会被执行。
还有三个重要的方法:
- IsInvoking:用来判断某方法是否被延时,即将执行。
- CancelInvoke() : 停止当前脚本中所有的Invoke和InvokeRepeating方法。
- CancelInvoke("MethodName") : 停止当前脚本某个Invoke和InvokeRepeating方法。
3. Invoke与协程的区别
Invoke方法:执行没有被挂起,相当于设置完被调用函数的执行时间后即时向下执行。应用到每隔一段时间执行某个函数很方便。
Coroutine方法:新开一条执行序列(跟新建线程差不多)并挂起,等待中断指令结束。开销不大。当需要挂起当前执行时使用。
协程的效率比Invoke高。
4. 正在运行的脚本,隐藏物体与禁止脚本导致OnDisable,Invoke与coroutine是否正常运行?
代码:
脚本禁止:都会正常运行。
如果把物体直接隐藏:Invoke正常运行,coroutine不会正常运行。
原因:
因为游戏物体隐藏了,一切与游戏物体相关的脚本生命周期都会停止,协程自然也会停止 ;
如果游戏对象没有隐藏,只是将脚本隐藏,游戏对象照样可以通过反射获取协程迭代器对象继续协程的执行。
6. 对象移动方式
1.Transform
通过 Update 函数每帧更新其位置来达到移动目的。
1.1 Transform.position
向量相加
最基础的移动方式,每帧+=计算好的新位置,更加直观。
public float speed = 3.0f;
void Update()
transform.position += transform.forward * Time.deltaTime * speed;
1.2 Transform.Translate
在平移的方向和距离上移动变换。
每秒向某方向移动多少距离,此种方法和上一种没有太大区别,但当需要坐标转换时,使用此方法可省略转换步骤。
translate(V3 向量,坐标系(留空默认为 Space.Self));
public float speed = 3.0f;
void Update()
transform.Translate(Vector3.forward * Time.deltaTime * speed);
//transform.Translate(transform.forward*Time.deltaTime*mMoveSpeed, Space.World);
2. Vector3
Vector3 类型可以存储物体的位置、方向。 V3 自带的类方法通过对位置的一些运算得到相对平滑的参数,其移动本质还是修改物体的 position。
2.1 Vector3.Lerp
两个向量之间的线性差值,适用于从某点移动到某点(或跟随某物体),缓动效果。
Lerp(当前位置(V3),目标位置(V3),时间(float)) 时间越小,缓动效果越慢。
public Transform target; //被跟随的物体
public float speed = 3.0f;
void Update()
Vector3 lerp = Vector3.Lerp(transform.position, target.position, Time.deltaTime * speed);
transform.position = lerp;
2.2 Vector3.Slerp
两个向量之间的球形(弧线)差值,适用于从某点移动到某点(或跟随某物体),缓动效果,当前位置与目标位置距离越远,效果越明显。非匀速。
Slerp(当前位置(V3),目标位置(V3),时间(float)) 时间越小,缓动效果越慢
public Transform target; //被跟随的物体
public float speed = 3.0f;
void Update()
Vector3 slerp = Vector3.Slerp(transform.position, target.position, Time.deltaTime * speed);
transform.position = slerp;
2.3 Vector3.MoveTowards
和 Lerp 函数基本相同,但此函数多了一个最大速度限制,且是匀速朝目标运动,而 Lerp 和 Slerp 则是将抵达位置时放缓(减速)
MoveTowards(当前位置(V3),目标位置(V3),最大速度(float))
速度参数:取正向目标靠近,取负则远离目标。
public Transform target; //被跟随的物体
public float speed = 1.0f;
void Update()
Vector3 movetowards = Vector3.MoveTowards(transform.position, target.position, Time.deltaTime * speed);
transform.position = movetowards;
移动: transform.position = Vector3.MoveTowards(transform.position,
transform.position + transform.forward * Time.deltaTime * mMoveSpeed, Time.deltaTime * mMoveSpeed);
2.4 Vector3.SmoothDamp
官方翻译为:“平滑阻尼”,无比丝滑的从 A 移动到 B 点,速度可控,比较适用于摄像机跟随,Lerp 也比较适用于摄像机跟随,这俩的区别在于
SmoothDamp(当前位置(V3),目标位置(V3),当前速度(ref:V3),所需时间(float),最大速度(float,可选),Time.deltaTime(默认)(可选))
当前速度:一开始赋值为 0,每次调用该方法自动修改此参数,注意设为全局变量,且为 ref
所需时间:该值越小,越快抵达目标。
public Transform target; //被跟随的物体
public Vector3 currentVelocity = Vector3.zero; //当前速度
public float smoothTime = 0.3f; //所需时间
void Update()
Vector3 smoothdamp = Vector3.SmoothDamp(transform.position, target.position, ref currentVelocity, smoothTime);
transform.position = smoothdamp;
3. Rigidbody
Rigidbody 组件通过物理模拟来控制一个物体的位置,当使用此组件控制物体移动时,应在 FixedUpdate 函数中更新数据,该方法会在每一次执行物理模拟前被调用,这样要比 Update 函数更加精确。
3.1 AddForce
添加一个方向的力到刚体,刚体将开始移动,这种方式适合模拟外力作用下的刚体运动,如子弹。但注意,此力是累加的,不适合重复施加力来模拟物体!
AddForce(有方向的力(V3),力的模式(ForceMode,默认:ForceMode.Force))
ForceMode(力的模式):
Force(可持续的力,受质量影响)
Acceleration(可持续的加速度,不受质量影响)
Impulse(一个瞬间冲击力,受质量影响)
VelocityChange(一个瞬间速度变化,不受质量影响)
public float forceNumber = 20f;
public Rigidbody rig; //获取当前物体的刚体组件
void FixedUpdate()
Vector3 force = new Vector3(0, 0, forceNumber);
rig.AddForce(force, ForceMode.Force);
按秒移动,向刚体添加一个力,这里效果表示加一个力来对抗阻力 :mRigidbody.AddForce(transform.forward * mMoveSpeed);
让addforce后的物体立即停下:velocity.zero = 0;
3.2 MovePosition
移动刚体到一个新的位置,移动的同时受到物理模拟的影响。
MovePosition(新的位置(V3)),有重力影响。
public Vector3 speed = new Vector3(0, 0, 1);
public Rigidbody rig; //获取当前物体的刚体组件
void FixedUpdate()
rig.MovePosition(transform.position + speed * Time.deltaTime);
将运动学刚体移动到某个位置
mRigidbody.MovePosition(transform.position + transform.forward * mMoveSpeed * Time.deltaTime);
3.3 Velocity
瞬间给一个物体恒定的速度,将该物体提升到这个速度,保持。相比较 AddForce 更加适合跳跃功能。每次跳跃都是恒定高度。做跳跃的话:
public Vector3 high = new Vector3(0, 0, 10);
public Rigidbody rig; //获取当前物体的刚体组件
private void FixedUpdate()
rig.velocity += high * Time.deltaTime;
按秒移动,刚体的速度矢量。它表示刚体位置的变化率。
mRigidbody.velocity = transform.forward * mMoveSpeed;
4. Character Controller
角色控制器顾名思义,是 Unity 推出的特别用于角色移动的组件,使用角色控制器的物体有刚体的效果,但不会翻滚(意思是运动仅受限于碰撞体,不受其他因素影响),很适合角色移动。还可以设置斜坡参数,一定坡度自动抬升,本身也是个碰撞体。
4.1 SimpleMove
以一定速度移动角色,以秒为单位,无需乘以时间,具备重力。
SimpleMove(有方向的力(V3))
public float speed = 5;
public CharacterController cc; //获取当前物体的刚体组件
void Update()
cc.SimpleMove(transform.forward * speed);
4.2 Move
以一定速度移动角色,不具备重力,需要自行计算下落
Move(有方向的力(V3))
public float speed = 5;
public CharacterController cc; //获取当前物体的刚体组件
void Update()
cc.Move(transform.forward * speed * Time.deltaTime);
按帧移动,用附加的CharacterController组件来提供游戏对象的移动。因为这里移动不受重力影响,所以加一个向下的移动来模拟重力。
mCharacterController.Move(-transform.up * Time.deltaTime *mMoveSpeed);
详细请看:
7. 获取对象方式
7.1用Find查询
GameObject.Find()通过对象名称(Find方法)
Transform.Find()通过对象名称(Find方法)
例子:
GameObejct go = GameObject.Find("对象名").GetComponent<获取对象上面的组件>();
7.2用标签 Tag
GameObject.FindWithTag 通过标签获取单个游戏对象(FindWithTag方法)。
GameObject.FindGameObjectWithTag()通过标签获取单个游戏对象(FindGameObjectWithTag方法)。
例子:
GameObejct go = GameObject.FindGameObjectWithTag("对象设置的tag值").GetComponent<获取对象上面的组件或者脚本>();
7.3用Type
GameObject.FindObjectOfType()通过类型获取单个游戏对象(FindObjectOfType方法)
GameObject.FindObjectsOfType()通过类型获取多个游戏对象(FindObjectsOfType方法)7. transform.GetChild()通过索引获取单个游戏对象
例子:
m_Palyer = GameObject.FindObjectOfType<直接获取类:class>();
详细请看:
8. Ugui/Ngui渲染顺序
不同的 Camera 的 Depth,值越大越后渲染
相同 Camera 下的不同 SortingLayer
相同 SortingLayer 下的不同 Z 轴/Order in Layer
Camera 模式下渲染顺序:基于同 Layer 同 OrderInLayer,因为渲染顺序优先 级是:camera 的 depth>Layer>OrderInLayer>Z 轴,注意 UI 的渲染顺序最后是OrderInLayer>transform 的层级
9. 寻路
NavMesh是一种基于多边形网络的寻路导航系统,整个寻路分为导航网格的构建和寻路算法两个部分。
9.1如何用 NavMesh 动态寻路
(1)烘焙导航网格
(2)需要导航的物体添加 NavMeshAgent 组件
(3)运行时候使用 NavMeshAgent.SetDestination 函数进行导航
Navigation导航系统基础
9.2 游戏中的寻路算法
10. 画布的三种模式/缩放模式
10.1屏幕空间-覆盖模式(Screen Space-Overlay)
Canvas创建出来后,默认就是该模式,该模式和摄像机无关,即使场景内没有摄像机,UI游戏物体照样渲染。
屏幕空间:电脑或者手机显示屏的2D空间,只有x轴和y轴。
覆盖模式:UI元素永远在3D元素的前面。
10.2屏幕空间-摄像机模式(Screen Space-Camera)
设置成该模式后需要指定一个摄像机游戏物体,指定后UGUIl就会自动出现在该摄像机的"投射范围"内,和NGUI的默认Ul Root效果一致,如果隐藏掉摄像机,UGUI当然就无法渲染。
这种模式可以用来实现在UI上显示3D模型的需求,比如很多MMO游戏中的查看人物装备的界面,可能屏幕的左侧有一个运动的3D人物,左侧是一些UI元素。通过设置Screen Space-Camera模式就可以实现上述的需求。
10.3 世界空间模式(WorldSpace)
设置成该模式后UGUIl就相当于是场景内的一个普通的“Cube游戏模型”,可以在场景内任意的移动UGUI元素的位置,通常用于怪物血条显示和VR开发。
10.4缩放模式
属性 | 作用 |
Ul Scale Mode | Canvas中U元素的缩放模式 |
Constant Pixel Size | 使UI保持自己的尺寸,与屏幕尺寸无关。 |
Scale With Screen Size | 屏幕尺寸越大,UI越大 |
Constant Physical Size | 使U元素保持相同的物理大小,与屏幕尺寸无关。 |
Constant Pixel Size、Constant Physical Size实际上他们本质是一样的,只不过Constant Pixel Size通过逻辑像素大小调节来维持缩放,而Constant Physical Size 通过物理大小调节来维持缩放。
11. 游戏动画
11.1 关节动画
把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活,Quake2中使用这种动画;
11.2骨骼动画
广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结构,有关节相连,可做相对运动,皮肤作为单—网格蒙在骨骼之外,决定角色的外观;
11.3 单—网格模型动画(关键帧动画)
由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其改变量,然后插值运算实现动画效果,角色动画较真实。
12. 不同分辨率保持UI一致性
多分率下的适配问题主要从两个方向解决
1.解决多分辨率下UI的相对位置保持不变,就是改变锚点位置就可以解决。
2.解决多分辨率下UI的大小尺寸保持不变,也就是适配尺寸。Unity中Canvas的大小不能直接修改,需要用Canvas下面的一个组件CanvasScaler来进行修改。
CanvasScaler中UI Scale Mode有三种模式,Constant Pixel Size、Scale With Screen Size、Constant Physical Size,其中第二个就是根据屏幕分辨率来进行缩放适配。在这个模式下,有两个参数,一个是我们在开发过程中的标准分辨率,一个是屏幕的匹配模式,通过这里面的设置,就可以完成多分辨率下的适配问题。
Unity3D手游保持不同分辨率的UI自适应:
13. Unity光源
平行光: Directional Light
点光源: Point Light
聚光灯: Spot Light
区域光源: Area Light
14.射线
14.1射线检测的原理
射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射。
14.2射线Raycast原理
从一个起点向一个方向发射—条物理射线,返回碰撞到的物体的碰撞信息。
15. Mono
15.1 Net与Mono的关系
Net是一个语言平台,Mono为.Net提供集成开发环境,集成并实现了.NET的编译器、CLR和基础类库,使得.Net暖既可以运行在windows也可以运行于linux,Unix,Mac OS等。
15.2 Mono 和 Unity 的区别
Unity 因为方便和跨平台选择了 C#作为主要的开发语言。而且 C#的跨平台是基于.Net Framework 框架下的(CIL,通用描述语言)和 CLR(通用运行环境的)。 在经过各种考量后,Unity 选择了开源,并且平台支持性很好的 Mono 这一开源 的.Net Framework 跨平台实现方案。
16. Image和Rawlmage的区别
1.lmgae比Rawlmage更消耗性能。
2.lmage只能使用Sprite属性的图片,但是Rawlmage什么样的都可以使用。
3.mage适合放一些有操作的图片,裁剪平铺旋转什么的,针对Image Type属性。RawImage就放单独展示的图片就可以,性能会比Image好很多。
17. Unity中常用的函数
17.1鼠标
1. 函数:
OnMouseEnter: 鼠标进入时调用一次
OnMouseOver: 鼠标停留(经过)时一直调用
OnMouseExit: 鼠标退出时调用一次
OnMouseDown: 鼠标按下时调用一次
OnMouseDrag: 鼠标拖拽(按住)时一直调用
OnMouseUp: 鼠标抬起时调用一次
2. 实际使用:使用时一般都是成对使用
OnMouseEnter,OnMouseOver,OnMouseExit 一组。比如模拟选中状态:鼠标进入时物体变色,鼠标退出时再变回来。
OnMouseDown,OnMouseDrag,OnMouseUp 一组。比如射击游戏:鼠标按下拖拽时调整方向,抬起时发射子弹。
当鼠标按下并停留在当前游戏对象上时,OnMouseOver,OnMouseDrag会同时触发。
检测原理:
- 只能检测当前脚本挂载的游戏对象。
- 当前游戏对象需要有碰撞体。
- 不能有其他物体(UI)遮挡到此游戏对象。
总结为一局话就是:OnMouseXXX的原理是通过鼠标的射线检测来判断鼠标当前位置是否碰到了挂载脚本游戏对象的碰撞体。
17.2 碰撞与触发
碰撞函数:
OnCollisionEnter: 进入碰撞时触发一次。
OnCollisionStay: 在碰撞体中停留时每帧触发一次。
OnCollisionExit: 离开碰撞体时触发一次。
触发函数:
OnTriggerEnter: 进入碰撞体时触发一次。
OnTriggerStay: 在碰撞体中停留时每帧触发一次。
OnTriggerExit: 离开碰撞体是触发一次。
PS:上面这六个方法,还有对应2D碰撞体的六个方法(如:OnCollisionEnter2D) 函数后面添加2D接口,触发条件和使用方式和3D一致。 使用时注意碰撞体和检测函数同步接口,即用2D碰撞体必须用2D函数。
函数执行条件:
- 两个物体需要都有碰撞体(Collider)组件。
- 检测方(挂载脚本物体)需要有刚体(Rigidbody)组件。
- Collider上都不勾选IsTrigger(有一方勾选则执行触发函数)。
17.3 应用程序
三个函数:
OnApplicationPause: 检测到暂停的帧结束 --> 切换到后台和回来时调用。
OnApplicationFocus: 当屏幕 获得/失去 焦点时调用
OnApplicationQuit: 当程序退出时调用。
实际应用
Unity游戏开发客户端面经——数学(初级)
前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是 数学 常问问题总结,有帮助的可以收藏。
1. 四元数与欧拉角
1.1 四元数概念
四元数(以后不特指四元数=单位四元数)是四维空间中一个超球上面的点,满足w²+x²+y²+z²=1;而纯四元数是四维空间在w=0时的一个子空间的点,形式为0, q,特别注意的是纯四元数与四元数是不同的概念。
四元数是复数虚部扩展的结果,复数的虚部为1个,而四元数虚部有3个,且两两互相正交,其中实部是cosθ/2,而虚部为一个単位轴乘以sinθ/2。
四元数自由度并没有四个维度,由于存在w²+x²+y²+z²=1这个约束,它的自由度其实只有3,且每个四元数可以对应一个特征向量,即n。
四元数Quaternion的作用
表示旋转,因此旋转角度计算时用到四元数。
详细请看:
1.2 欧拉角概念
欧拉角是由三个角组成,这三个角分别是Yaw,Pitch,Roll。很难翻译这三个单词,Yaw 表示绕y轴旋转的角度,Pitch表示绕x轴旋转的角度,Roll表示绕z轴旋转的角度。也就是说,任意的旋转角度都可以通过这三次按照先后顺序旋转得到。矩阵很难让人具体形象表示,欧拉角就容易多了。注意可能很多地方三个角的先后次序不一样。
1.3 四元数对于欧拉角的优点
- 避免万向节死锁
- 两个四元数之间更容易插值
- 能进行增量旋转
- 给定方位的表达式有两种,互为负。
1.4 欧拉角的万向节死锁
我们依次绕物体坐标系的X轴、Y轴、Z轴旋转,当Y轴旋转了90度之后,Z就会指向原来的X轴。这样一来,我们事实上只绕了X轴和Y轴两个轴旋转,第三根轴的自由度就丢失了。
2. 点乘
概念
点乘,也叫向量的内积、数量积。
描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影。
计算方式
向量a·向量b=|a||b|cos<a,b>
求两个向量的夹角(用单位向量)
3. 叉乘
概念
叉乘,也叫向量的外积、向量积,得到的向量垂直于原来的两个向量。
计算方式
|向量c|=|向量a×向量b|=|a||b|sin<a,b>
A×B = -B×A
这里点乘叉乘只记录了几何角度,详细请看:
向量的点乘和叉乘区别及几何意义https://baijiahao.baidu.com/s?id=1736495807922098016&wfr=spider&for=pc
4. 向量归一化
向量归一化即将向量的方向保持不变,大小归一化到1。
5. 判断一个点在矩形内外
详细请看:
6. 判断一个点在三角形内外
1.面积法
求三角形面积的方法就可以用上面提到的利用叉积就行了,注意记得加 上绝对值,因为叉积可能为负。还有种简单的方法是利用内角和为 180 °
2.同侧法
若点P在点A、B、C内,则ABXAP,BCxBP,CAxCP结果都为正(负),则可以认为P在A、B、C内,若任意一个结果不同则P在ABC外。(因为正负看的sin夹角的大小,在两个夹角小于90°的情况下,“右手定则”来判定,即右手向第二个因数弯曲,看大拇指是否改变,同向为正,异向为负。)
7. 判断一个对象B在对象A的前后左右(点乘叉乘应用)
点乘前后,叉乘左右。
7.1前后判断
生成两个向量:
- 对象A自身为原点,指向面向前方的方向。
- 对象A自身为原点,指向对象B的方向。
两个向量点乘,正为B在A的前方,负为B在A的后方,0为左右两侧。
(点乘正负看向量夹角,小于90°为正,大于90°则为负,等于90°正为水平两侧)
7.2左右判断
向量A 叉乘 向量B,结果为正,B在A的左侧,结果为负,B在A的右侧。
(注意:Unity当中使用左手,因为Unity使用的是左手坐标系,叉乘方向相反)
假设向量A和B 都在xz平面上
向量A叉乘向量B
y大于0 证明B在A右侧
y小于0 证明B在A左侧
详细请看:
希望此篇文章可以帮助到更多的同学
以上是关于Unity游戏开发客户端面经——Unity(初级)的主要内容,如果未能解决你的问题,请参考以下文章