前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是 Unity 常问问题总结,有帮助的可以收藏。
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 当对象被销毁时调用。
DontDestroyOnLoad(transform.gameObject);
两个物体都必须带有碰撞器Collider,其中一个物体还必须带有Rigidbody刚体。
1.碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。
2. 当ls Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;
3.当ls Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/lExit函数。
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 ,不然无法通过编译。
详细请看:
https://blog.csdn.net/xiaoyaoACi/article/details/119957547?csdn_share_tail=%7B"type"%3A"blog"%2C"rType"%3A"article"%2C"rId"%3A"119957547"%2C"source"%3A"qq_35787977"%7Dhttps://blog.csdn.net/xiaoyaoACi/article/details/119957547?csdn_share_tail=%7B:,:,:,:%7D
测试:
解释:a:Start函数正常运行
b:Update函数正常运行
c: 进入协程
d:协程挂起,正常运行Update函数
b:Update函数正常运行
c: 进入协程
d:协程挂起,正常运行Update函数
e: 因为协程是yield return null等待一帧后,第一帧的协程由挂起,变成正常运行,输出e。
Invoke() 方法是 Unity3D 的一种委托机制
如: Invoke("Test", 5); 它的意思是:5 秒之后调用 Test() 方法;
使用 Invoke() 方法需要注意 3点:
1 :它应该在 脚本的生命周期里的(Start、Update、OnGUI、FixedUpdate、LateUpdate)中被调用;
2:Invoke(); 不能接受含有参数的方法;
3:在 Time.ScaleTime = 0; 时, Invoke() 无效,因为它不会被调。
InvokeRepeating("Test", 2 , 3);
这个方法的意思是指:2 秒后调用 Test() 方法,并且之后每隔 3 秒调用一次 Test() 方法。
被激活时设置了,但是此时将引擎对象设置为false,还会被执行。
还有三个重要的方法:
- IsInvoking:用来判断某方法是否被延时,即将执行。
- CancelInvoke() : 停止当前脚本中所有的Invoke和InvokeRepeating方法。
- CancelInvoke("MethodName") : 停止当前脚本某个Invoke和InvokeRepeating方法。
Invoke方法:执行没有被挂起,相当于设置完被调用函数的执行时间后即时向下执行。应用到每隔一段时间执行某个函数很方便。
Coroutine方法:新开一条执行序列(跟新建线程差不多)并挂起,等待中断指令结束。开销不大。当需要挂起当前执行时使用。
协程的效率比Invoke高。
代码:
脚本禁止:都会正常运行。
如果把物体直接隐藏:Invoke正常运行,coroutine不会正常运行。
原因:
因为游戏物体隐藏了,一切与游戏物体相关的脚本生命周期都会停止,协程自然也会停止 ;
如果游戏对象没有隐藏,只是将脚本隐藏,游戏对象照样可以通过反射获取协程迭代器对象继续协程的执行。
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;
}
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;
角色控制器顾名思义,是 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);
详细请看:
详解Unity的几种移动方式实现_梦小天幼的博客-CSDN博客_unity移动方式最近在学习如何制作 FPS 游戏,学习了如何使用角色控制器来控制角色的移动跳跃等等,结合之前学到的使用 transform,刚体等使物体移动,不同的移动方式适用于不同的场景,今天就来简要盘点一下各种移动方式以及其优劣之处,若有不对之处,请多多指教。...https://blog.csdn.net/weixin_43147385/article/details/123892842?spm=1001.2014.3001.5506
Unity3D中玩家的移动方式,三大类型,八种方式_正在奋斗中的小志的博客-CSDN博客_unity玩家移动Unity3D中玩家的移动方式,三大类型,八种方式https://blog.csdn.net/Sea3752/article/details/124108814?spm=1001.2014.3001.5502
GameObject.Find()通过对象名称(Find方法)
Transform.Find()通过对象名称(Find方法)
例子:
GameObejct go = GameObject.Find("对象名").GetComponent<获取对象上面的组件>();
GameObject.FindWithTag 通过标签获取单个游戏对象(FindWithTag方法)。
GameObject.FindGameObjectWithTag()通过标签获取单个游戏对象(FindGameObjectWithTag方法)。
例子:
GameObejct go = GameObject.FindGameObjectWithTag("对象设置的tag值").GetComponent<获取对象上面的组件或者脚本>();
GameObject.FindObjectOfType()通过类型获取单个游戏对象(FindObjectOfType方法)
GameObject.FindObjectsOfType()通过类型获取多个游戏对象(FindObjectsOfType方法)7. transform.GetChild()通过索引获取单个游戏对象
例子:
m_Palyer = GameObject.FindObjectOfType<直接获取类:class>();
详细请看:
【Unity优化篇】 | Unity脚本代码优化策略,快速获取 游戏对象 和 组件 的方法【文末送书】_呆呆敲代码的小Y的博客-CSDN博客本文是 Unity优化篇 系列的一篇文章,同时也包是含在 『Unity系统学习专栏⭐️』里的文章。本专栏是我总结的Unity学习类的文章,适合Unity入门和进阶的小伙伴。订阅该专栏之后 Unity基础知识学习 、Unity 进阶技巧、Unity 优化 篇 几个专栏的文章都可以查看。对Unity感兴趣的小伙伴千万不要错过哦,目前专栏正在优惠中,具体内容可以看该专栏的导航帖。本篇文章就来讲一下 Unity中的脚本代码优化策略,一起来学习一下吧!https://blog.csdn.net/zhangay1998/article/details/122242319#t4
不同的 Camera 的 Depth,值越大越后渲染
相同 Camera 下的不同 SortingLayer
相同 SortingLayer 下的不同 Z 轴/Order in Layer
Camera 模式下渲染顺序:基于同 Layer 同 OrderInLayer,因为渲染顺序优先 级是:camera 的 depth>Layer>OrderInLayer>Z 轴,注意 UI 的渲染顺序最后是OrderInLayer>transform 的层级
NavMesh是一种基于多边形网络的寻路导航系统,整个寻路分为导航网格的构建和寻路算法两个部分。
(1)烘焙导航网格
(2)需要导航的物体添加 NavMeshAgent 组件
(3)运行时候使用 NavMeshAgent.SetDestination 函数进行导航
Navigation导航系统基础
Unity零基础到入门 ☀️| 万字教程 对 Unity 中的 Navigation导航系统基础 全面解析+实战演练【收藏不迷路】_呆呆敲代码的小Y的博客-CSDN博客导航系统。导航系统,顾名思义,就是游戏中的一个寻路功能。本文对Unity中的导航Navigation 系统做了一个详细的说明,包括案例和效果展示!请品尝!https://xiaoy.blog.csdn.net/article/details/119785178
深入理解游戏中寻路算法(转)_今天的技术超过许嵩了吗?的博客-CSDN博客_寻路算法如果你玩过MMOARPG游戏,比如魔兽,你会发现人物行走会很有趣,为了模仿人物行走的真实体验,他们会选择最近路线达到目的地,期间会避开高山或者湖水,绕过箱子或者树林,直到走到你所选定的目的地。这种看似寻常的寻路在程序实现起来就需要一定的寻路算法来解决,如何在最短时间内找到一条路径最短的路线,这是寻路算法首先要考虑的问题。在这篇文章中我们会循序渐进来讲解寻路算法是如何演进的,你会看到一种算法...https://blog.csdn.net/Marmara01/article/details/88758279?spm=1001.2014.3001.5506
Canvas创建出来后,默认就是该模式,该模式和摄像机无关,即使场景内没有摄像机,UI游戏物体照样渲染。
屏幕空间:电脑或者手机显示屏的2D空间,只有x轴和y轴。
覆盖模式:UI元素永远在3D元素的前面。
设置成该模式后需要指定一个摄像机游戏物体,指定后UGUIl就会自动出现在该摄像机的"投射范围"内,和NGUI的默认Ul Root效果一致,如果隐藏掉摄像机,UGUI当然就无法渲染。
这种模式可以用来实现在UI上显示3D模型的需求,比如很多MMO游戏中的查看人物装备的界面,可能屏幕的左侧有一个运动的3D人物,左侧是一些UI元素。通过设置Screen Space-Camera模式就可以实现上述的需求。
设置成该模式后UGUIl就相当于是场景内的一个普通的“Cube游戏模型”,可以在场景内任意的移动UGUI元素的位置,通常用于怪物血条显示和VR开发。
详细请看:Canvas的三种画布渲染模式 - 走看看第一个参数RenderMode的渲染模式有三种:Screen Space-Overlay、Screen Space-Camera以及World Space。 1.Screen Space-Overlahttp://t.zoukankan.com/Dearmyh-p-9657224.html
属性 |
作用 |
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 通过物理大小调节来维持缩放。
把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活,Quake2中使用这种动画;
广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结构,有关节相连,可做相对运动,皮肤作为单—网格蒙在骨骼之外,决定角色的外观;
由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其改变量,然后插值运算实现动画效果,角色动画较真实。
多分率下的适配问题主要从两个方向解决
1.解决多分辨率下UI的相对位置保持不变,就是改变锚点位置就可以解决。
2.解决多分辨率下UI的大小尺寸保持不变,也就是适配尺寸。Unity中Canvas的大小不能直接修改,需要用Canvas下面的一个组件CanvasScaler来进行修改。
CanvasScaler中UI Scale Mode有三种模式,Constant Pixel Size、Scale With Screen Size、Constant Physical Size,其中第二个就是根据屏幕分辨率来进行缩放适配。在这个模式下,有两个参数,一个是我们在开发过程中的标准分辨率,一个是屏幕的匹配模式,通过这里面的设置,就可以完成多分辨率下的适配问题。
Unity3D手游保持不同分辨率的UI自适应:
Unity 3D手游对不同分辨率屏幕的UI自适应 - _nostalgia - 博客园目前安卓手机的屏幕大小各异,没有统一的标准,因此用Unity 3D制作的手游需要做好对不同分辨率屏幕的UI自适应,否则就会出现UI大小不一和位置错位等问题。 我们的项目在开发时的参照分辨率(Referhttps://www.cnblogs.com/notorious/p/12932610.html
平行光: Directional Light
点光源: Point Light
聚光灯: Spot Light
区域光源: Area Light
射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射。
从一个起点向一个方向发射—条物理射线,返回碰撞到的物体的碰撞信息。
Net是一个语言平台,Mono为.Net提供集成开发环境,集成并实现了.NET的编译器、CLR和基础类库,使得.Net暖既可以运行在windows也可以运行于linux,Unix,Mac OS等。
Unity 因为方便和跨平台选择了 C#作为主要的开发语言。而且 C#的跨平台是基于.Net Framework 框架下的(CIL,通用描述语言)和 CLR(通用运行环境的)。 在经过各种考量后,Unity 选择了开源,并且平台支持性很好的 Mono 这一开源 的.Net Framework 跨平台实现方案。
1.lmgae比Rawlmage更消耗性能。
2.lmage只能使用Sprite属性的图片,但是Rawlmage什么样的都可以使用。
3.mage适合放一些有操作的图片,裁剪平铺旋转什么的,针对Image Type属性。RawImage就放单独展示的图片就可以,性能会比Image好很多。
1. 函数:
OnMouseEnter: 鼠标进入时调用一次
OnMouseOver: 鼠标停留(经过)时一直调用
OnMouseExit: 鼠标退出时调用一次
OnMouseDown: 鼠标按下时调用一次
OnMouseDrag: 鼠标拖拽(按住)时一直调用
OnMouseUp: 鼠标抬起时调用一次
2. 实际使用:使用时一般都是成对使用
OnMouseEnter,OnMouseOver,OnMouseExit 一组。比如模拟选中状态:鼠标进入时物体变色,鼠标退出时再变回来。
OnMouseDown,OnMouseDrag,OnMouseUp 一组。比如射击游戏:鼠标按下拖拽时调整方向,抬起时发射子弹。
当鼠标按下并停留在当前游戏对象上时,OnMouseOver,OnMouseDrag会同时触发。
检测原理:
- 只能检测当前脚本挂载的游戏对象。
- 当前游戏对象需要有碰撞体。
- 不能有其他物体(UI)遮挡到此游戏对象。
总结为一局话就是:OnMouseXXX的原理是通过鼠标的射线检测来判断鼠标当前位置是否碰到了挂载脚本游戏对象的碰撞体。
碰撞函数:
OnCollisionEnter: 进入碰撞时触发一次。
OnCollisionStay: 在碰撞体中停留时每帧触发一次。
OnCollisionExit: 离开碰撞体时触发一次。
触发函数:
OnTriggerEnter: 进入碰撞体时触发一次。
OnTriggerStay: 在碰撞体中停留时每帧触发一次。
OnTriggerExit: 离开碰撞体是触发一次。
PS:上面这六个方法,还有对应2D碰撞体的六个方法(如:OnCollisionEnter2D) 函数后面添加2D接口,触发条件和使用方式和3D一致。 使用时注意碰撞体和检测函数同步接口,即用2D碰撞体必须用2D函数。
函数执行条件:
- 两个物体需要都有碰撞体(Collider)组件。
- 检测方(挂载脚本物体)需要有刚体(Rigidbody)组件。
- Collider上都不勾选IsTrigger(有一方勾选则执行触发函数)。
三个函数:
OnApplicationPause: 检测到暂停的帧结束 --> 切换到后台和回来时调用。
OnApplicationFocus: 当屏幕 获得/失去 焦点时调用
OnApplicationQuit: 当程序退出时调用。
实际应用:
OnApplicationPause: 游戏停止保存数据/游戏继续数据初始化。
OnApplicationFocus: 失去焦点关闭背景音乐/获得焦点继续播放音乐。
OnApplicationQuit: 在移动端大退时也会对调用,但不会触发上面两个方法。
对应函数
- OnDisable: 当对象被禁用时调用此函数(其父物体被禁用也会触发)。
- OnDestroy: 在对象存在的最后一帧的所有帧更新之后调用此函数。
实际应用:
- OnDisable: 通常和OnEnable配合使用。比如:在OnEnable添加监听,在OnDisable移除监听
- OnDestroy: 当物体销毁或者场景关闭时触发。比如:子弹打到墙壁时,需要销毁子弹并触发一个打击音效。
详细请看:
一文读懂Unity常用生命周期函数 -- 超级详细、不服来辩~_陈言必行的博客-CSDN博客_unity 周期函数基础!Unity生命周期函数你还没搞明白是怎么回事吗?本文通过实例讲解带你读懂Unity的生命周期。https://czhenya.blog.csdn.net/article/details/117963888?spm=1001.2014.3001.5506
当我们需要点击一个button,而button与摄像机之间还有一个UI层,例如Plane,这时候点击就会失效,解决方法:
- 创建一个GameObject,挂上Canvas Group组件
- 在该组件中取消勾选Blcok Raycasts
- 将需要穿透的物体(Plane)放到这个GameObject下面。
Mask的实现原理:
详细请看:【Unity源码学习】遮罩:Mask与Mask2D - 知乎前言UGUI的裁切分为Mask和Mask2D两种目录Mask原理分析RectMask2D原理分析RectMask2D和Mask的性能区分 一、Mask原理分析 Mask:IMaskable,IMaterialModifier我们先来看Mask。它可以给Mask指定一张裁切图裁切子元素…https://zhuanlan.zhihu.com/p/136505882
1. 贝姆垃圾收集器
Untiy使用的GC机制是通过贝姆垃圾收集器(Boehm GC)来实现的,是应用在C/C++语言上的一个保守的垃圾回收器,同时也适用于其它执行环境的各类编程语言,比如我们使用的基于mono实现的C#。
2. 回收机制
Unity的GC的是采用 贝姆垃圾收集器 ,本质上采用的是非分代非压缩的标记清除算法。
它会在需要进行GC时占用主线程,进行遍历-标记-垃圾回收的过程,然后在归还主线程控制权。这会导致帧数的突然下降,产生卡顿(不过因为该实现是非压缩式的,所以卡顿现象相对较轻,但是对内存利用率进一步下降了,会有内存碎片的问题)。
所以我们需要慎重地处理对象的创建(内存请求),还有释放(使用GC管理内存是没有主动释放内存的接口的,但是我们可以通过消除对某个对象的引用来做到这一点)。此外,Unity的代码分为两部分:托管与非托管,GC影响的只有托管部分的代码使用的堆内存。而且这个托管堆占用的地址空间不会返还给操作系统。
标记清楚算法可以解决两个变量相互引用,产生标记的现象。
Unity使用的垃圾机制是Boehm GC算法,他是非分代,非压缩的。非分代意味着每次回收都需要扫描整个堆,而非压缩则意味着内存分配后整个托管堆会存在间隙或者说内存碎片。这两个特点意味着Unity中托管堆的垃圾回收机制会耗时较长而且内存利用率存在着问题。
1. 停止所有进行托管堆内存分配的线程。
2. 找到所有不再被需要的内存,将其标记为垃圾。
3. 将所有被标记的内存释放到空闲内存。
4. 恢复之前停止的托管堆内存分配的线程。
GC对性能影响的原因(占用主线程进行大量工作),而优化GC即是减小占用GC占用主线程时花费的CPU时间,所以优化GC优化的是CPU时间,而非内存。
Animation和Animator 虽然都是控制动画的播放,但是它们的用法和相关语法都是大有不同的。Animation控制一个动画的播放,而Animator是多个动画之间相互切换,并且Animator有一个动画控制器,俗称动画状态机。
Animator利用它做动画的切换是很方便的,但是它有一个缺点就是占用内存比Animation大。
详细请看:
Unity常用API方法与类_林枫依依的博客-CSDN博客_unity api一、事件函数(生命周期函数)1. Reset()调用情况:此函数只能在编辑器模式下(不运行时)调用;调用时间:当脚本第一次挂载到对象身上或者使用了Reset命令之后调用;调用次数与作用:会调用一次,来初始化脚本的各个属性,Reset最常用于在检视面板中提供良好的默认值。2. Awake()调用情况:(1)在加载场景时,初始化包含脚本的激活状态的 GameObject时;(2)GameObject从非激活状态转变为激活状态时;(3)在初始化使用Instantiate创建的Ghttps://blog.csdn.net/qq_41603955/article/details/123439572
表示从上一帧到当前帧时间,以秒为单位。(两帧之间调用的间隔。)
作用
解决在不同cpu频率下的设备,玩家移动,旋转等相关操作(和渲染帧有关的操作)可以保证在单位时间内移动的量是相等的。
详细请看:
Unity Time类详解_极客猫的博客-CSDN博客_time.unscaleddeltatime以下是从极客学院视频教程里的截图,详细的说明了Time下各个变量的表示和含义。https://blog.csdn.net/liuyang_sy/article/details/46811125
Assetsbundle
即将资源打成asset bundle放在服务器或本地磁盘,然后使用WWW模块get 下来,然后从这个bundle中load某个object,unity官方推荐也是绝大多数商业化项目使用的一种方式。
Resource.Load
可以直接load并返回某个类型的Object,前提是要把这个资源放在Resource命名的文件夹下,Unity不管有没有场景引用,都会将其全部打入到安装包中
AssetDatabase.loadasset
这种方式只在editor范围内有效,游戏运行时没有这个函数,它通常是在开发中调试用的。
动态分离出来也是为了减少绘制次数,动态的话 Canves会重新绘制,不分开就会重新绘制一遍静态和动态,增加DC。
Text是像素渲染放大之后就会模糊,使用Text父物体的放大缩小会影响子物体Text的清晰度, TMPtext不会,它是网格渲染TMPtext会把字体生成一个类似于贴图的东西然后读取贴图的坐标来获取对应的文字,更换文字的消耗会比Text大,TMPtext适用于那种不会变动的文字,特别是在量大的情况下,性能比Text高一些,需要经常变动的问题用Text好点,TMPtext在字体库很大的情况下查找更换会比较慢。
思路:
红点系统基于MVC的思想,将分为三层:数据层,驱动层,显示层。
1.数据层中的数据结构,考虑到需要层级的联系,所以以结点为核心,每个结点会持有其父结点和子结点,有点像双向链表的前驱后继,但是它构成的不是链表而是树。
2.当一个结点状态发生变化,它会通知到其父结点,父节点会自行处理变化去通知它自己的父结点,有点递归的意思,如果是数量通知,子节点的消息会以此累计到自己的父节点中,以此类推,具体看需求。
3.整个系统数据层驱动层与展示层是剥离的,展示层需要显示什么结点的内容,以该结点的key去注册,数据层与显示层实现了观察者模式,即可收到每次该结点状态变化的通知,并实时更新界面。
详细请看:
Unity之红点树系统多层级高效能_彩色墨水的博客-CSDN博客_unity红点系统Unity之红点树系统多层级高效能效果图设计思路可多层级嵌套树可显示数量可动态变更节点状态可动态注册节点或者撤销节点事件场景搭建代码参考工程项目https://blog.csdn.net/weixin_44186849/article/details/122471049
Unity手游实战:从0开始SLG——独立功能扩展(三)用树实现客户端红点系统 - 知乎红点大家一定不陌生,不管是游戏还是软件,甚至是手机系统都通过红点的直观方式来告知用户或者玩家,你有新的消息,请注意查收。 红点系统是一个比较让人讨厌的系统,至少对于游戏开发人员来说,是的。因为涉及的…https://zhuanlan.zhihu.com/p/85978429
有限状态机系统:是指在不同阶段会呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。这样的系统在某一时刻一定会处于其所有状态中的一个状态,此时它接收一部分允许的输入,产生一部分可能的响应,并且迁移到一部分可能的状态。
1). 基本节点是状态:他包含了一系列运行在该状态的行为以及离开这个状态的条件。
2). 状态可以任意跳转,实现简单,但是对于大的状态机很难维护,状态逻辑的重用性低。
3). 每一个状态的逻辑会随着一些新状态的增加而越来越复杂。维持状态的数量和状态逻辑复杂性是一个很大的难点。需要合理的分割以及重用状态。
4). 状态机状态的复用性很差,一旦一些因素变化导致这个环境发生变化。你只能新增一个状态,并且给这个新状态添加连接他以及其他状态的跳转逻辑。
5). 状态机的跳转条件一旦不满足,就会一直卡在某一个状态。
行为树:一个流行的AI技术,涵盖了层次状态机,事件调度,事件计划,行为等一系列技术。实现AI的过程更加得有技巧,框架设计者较为全面考虑了我们可能会遇到的种种情况,把每种情况都抽象成了一个类型的节点,而我们要做的就是按照规范去写节点,然后把节点连接成一颗行为树。更加得具有面向对象的味道,行为模块间的藕合度相对较低。
1). 高度模块化状态,去掉状态中的跳转逻辑,使得状态变成一个“行为”。
2). "行为"和"行为"之间的跳转是通过父节点的类型来决定的。比如并行处理两个行为,在状态机里面无法同时处理两个状态。
3). 通过增加控制节点的类型,可以达到复用行为的目的。
4). 可视化编辑。
罗列一下行为树的概念:
优点:
1. 行为逻辑和状态数据分离,任何节点写好以后可以反复利用。
2. 重用性高,可用通过重组不同的节点来实现不同的行为树。
3. 呈线性的方式扩展,易于扩展。
4. 可配置,把工作交给designer。
5. 能够胜任"AI" “掉宝”等等场景。
缺点:
1. 每一帧都从root开始,有可能会访问到所以的节点,相对State Machine消耗更多的cpu。
2. 任何一个简单的操作都必须要使用节点。
扩展:各种节点的详细描述:
Selector Node 选择节点
描述:从头到尾,按顺序选择第一个执行条件为真的子节点,遇到True停止。
处理流程:当执行本类型Node时,它将从begin到end迭代执行自己的Child Node:如遇到一个Child Node执行后返回True,那停止迭代,本Node向自己的Parent Node也返回True;否则所有Child Node都返回False,那本Node向自己的Parent Node返回False。
Sequence Node 序列节点
描述:从头到尾,按顺序执行每一个子节点,遇到False停止。
处理流程:当执行本类型Node时,它将从begin到end迭代执行自己的Child Node:如遇到一个Child Node执行后返回False,那停止迭代,本Node向自己的Parent Node也返回False;否则所有Child Node都返回True,那本Node向自己的Parent Node返回True。
Parallel Node 并行节点
描述:从头到尾,平行执行它的所有子节点。
详细请看:
AI逻辑实现-用行为树取代状态机_听着rap写代码的博客-CSDN博客关注AI的朋友可能都看过赖勇浩翻译的《有限状态机时代终结的10大理由》 ,里面谈到了状态机的诸多弊端。同时在ppt(附上下载地址)中述说了行为树的诸多优点,这里就不在赘述了。更多得是想总结一下自己玩了一阵子行为树后的一些实践体会。个人体会:状态机来实现AI更符合我们思维的朴素表达,我想任何一个有经验的coder都能直观得去写一个自己的AI状态机。它用于一些简单的ai其实是没有大问题https://blog.csdn.net/goodeveningbaby/article/details/42213149
状态机和行为树_PresleyGo的博客-CSDN博客_行为树和状态机区别对游戏NPC的行为控制一般有2种,一种是状态机,一种是行为树。状态机:Unity对人物动画的控制是基于状态机的,如下图:可以看到,每个状态除了包含自身的状态行为外,还需要和其他的状态打交道,需要输入状态机之间切换的条件。因为这一点,导致增加状态会使得状态机越来越复杂。状态机是一种网状结构,耦合性很大。行为树:图片源自:https://blog.csdn.net/goo...https://blog.csdn.net/PresleyGo/article/details/104042749
利用动画层级Animatoion中的Layer,设置替身模板Avatar Mask 中可以播放或者禁止的人物节点,这样该层动画只会影响人物节点的一部分。然后设置权重和设置对应的Mask来对不同的动画层覆盖操作。达到混合的效果。
以枪口方向为轴,上下或者左右为展开方向。获取夹角。利用点乘的方法。
1. 通过Vector3.Dot(向量A,向量B)获取两个向量的点乘C。
2. 然后通过Acos C 然后由弧度转换为角度即可。
3. 弧度乘以Mathf.Rad2Deg得到角度;
结果不一样。因为transform本质是一个矩阵。矩阵乘法不支持交换律。
UnitySendMessage(“Cube111”,"methodName111","传给unity。。。");
参数1:场景中的模型名称,Cube111就是我们定义的一个箱子。
参数2:脚本方法名称methodName111就是上面脚本中的方法,
参数3:为一个char *类型的可以向Unity中传递数据。
值(value)类型 :Unity中的vector3、quaternion是structs,它们是值。
引用(feference)类型 :Unity中的transform、gameobject是类类型,所以它们是引用。引用类型是对数据存储位置的引用,引用类型指向的内存区域称为堆。
在决定定义引用类型还是值类型时,关键因素是:如果逻辑上是固定大小不可变的值,就考虑定义成值类型,如果逻辑上是可引用的可变的对象,就定义成引用类型。例如字符串类型,它的大小是根据值的大小而改变的,所以它是引用类型。
引用类型在堆上,值类型在栈上?
1. 引用类型的实例总是在堆上的没有错,但是方法内部声明的变量和方法参数在栈上。 而且实例变量的值总是存储在实例本身存储的地方。例如如一个类中有个int变量,虽然它是一个值类型,但它在堆上。
2. 引用类型的变量包含了两个存储位置:直接和变量关联的存储位置,以及由变量中存储的值引用的存储位置。
3. 对于直接与变量关联的存储位置和值类型变量关联的存储位置,它们的存储位置没有区别,也就是引用本身会和值类型一样,如果一直变量短时间存在,就在栈的临时存储池中分配。
注意:不要创建消耗内存大于16字节的值类型,因为一个引用的大小就是32字节或64字节,如果复制值类型的代价比作为引用复制时高出四倍,就应该把它设计为引用类型了。
但是值类型也有值类型的好处,值类型的效率更高,而当我们需要从其它作用域修改一个值类型的数据时,我们需要显式传引用;也正因如此,很多数学类都使用结构体类型而非类类型,例如unity中的vector,quaternion等;
在C#中,结构体也有自己的构造函数、常量和方法等,尽管有些许语法区别,但是一个结构体也可以被视为一个轻量级的类;
详细请看:
unity中的值类型和引用类型_莫之的博客-CSDN博客_unity值类型和引用类型的区别一、值类型和引用类型概念值(value)类型 :Unity中的vector3、quaternion是structs,它们是值。引用(feference)类型 :Unity中的transform、gameobject是类类型,所以它们是引用。引用类型是对数据存储位置的引用,引用类型指向的内存区域称为堆。在决定定义引用类型还是值类型时,关键因素是:如果逻辑上是固定大小不可变的值,就考虑定义...https://blog.csdn.net/qq_36383623/article/details/96213731
ScriptableObject是一个数据容器,它可以用来保存大量数据。
主要的用处就是在项目中通过将数据存储在ScriptableObject对象,避免值拷贝来减少游戏运行中的内存占用。
当你有一个预制体,上面挂了一个存有不变数据的MonoBehaviour 脚本时,每次我们实例化预制体时都将产生一次数据拷贝,这时我们可以使用ScriptableObject对象来存储数据,然后通过引用来访问预制体中的数据。这样可以避免在内存中产生一份拷贝数据。与MonoBehaviour 一样,ScriptableObject也继承自Unity基类object,但是与MonoBehaviour不同的是,ScriptableObject不能和GameObject对相关联,相反,通常我们会将它保存为Assets资源。
在编辑器模式下,我们可以在编辑和运行时将数据保存到ScriptableObject,因为保存ScriptableObject需要用到编辑器空间个脚本,但是在开发模式下不能使用ScriptableObject来保存数据,但是你可以使用ScriptableObject资源中已保存的数据。
详细请看:
scriptableobject类理解_zhaixh_89的博客-CSDN博客_scriptableobjectScriptableObject是一个数据容器,它可以用来保存大量数据,主要的用处就是在项目中通过将数据存储在ScriptableObject对象,避免值拷贝来减少游戏运行中的内存占用。当你有一个预制体,上面挂了一个存有不变数据的MonoBehaviour 脚本时,每次我们实例化预制体时都将产生一次数据拷贝,这时我们可以使用ScriptableObject对象来存储数据,然后通过引用来访问预制...https://blog.csdn.net/zhaixh_89/article/details/105117059
ScriptableObject - 镜子-眼泪 - 博客园ScriptableObject简单实现详解https://www.cnblogs.com/jzyl/p/14934661.html