QuHai互联科技 Unity小题部分
1、OnEnable、Awake、FixedUpdate、Start、Update运行时的发生顺序?哪些可能在同一个对象周期中反复的发生?
脚本生命周期概述 Unity官方文档链接_Unity版本21.3
- Awake():在游戏对象被实例化时调用,只会在对象的生命周期中调用一次。
- OnEnable():当游戏对象被启用时调用,可以被多次调用。例如当使用SetActive()函数启用物体时,就会调用OnEnable()函数。
- Start():在Awake()方法执行完毕后调用,只会在对象的生命周期中调用一次。
- FixedUpdate():是固定的物理帧更新,一般每秒执行50次左右。在物理计算之前被调用,可以被多次调用。
- Update():在每一帧渲染之前被调用,可以被多次调用。
总结:在同一个对象的生命周期中,可能有多个函数被反复调用。例如,在每一帧渲染之前都会调用Update()函数,而物理计算之前会调用FixedUpdate()函数。另外,当游戏对象在活动和隐藏之间切换时,会调用OnEnable()和OnDisable()函数。而在游戏对象实例化时,只会调用一次Awake()和Start()函数。因此,在编写Unity脚本时,需要根据具体需求选择正确的函数进行编写。
🙆♀️Awake start是不是在物体存在的时候只调用 一次, OnEnable的话,通过设置物体的active,然后可以多次执行
👩🍳 是的,Awake和Start在物体被实例化时只会被调用一次。OnEnable在物体被启用时会被调用,这意味着如果你通过代码设置了物体的active属性为false,那么OnEnable方法将不会被调用。当你再次将物体的active属性设置为true时,OnEnable方法将再次执行,所以OnEnable方法可以在物体有需要的时候被多次执行。
需要特别注意的是,OnEnable在Awake和Start方法之后被调用,如果在这两个方法中对某些成员进行了初始化或修改,则在OnEnable中可能必须对这些成员进行进一步处理。
👩🍳 在Unity中,可以将各种初始化操作写在Start()函数中。以下是一些通常在Start()函数中执行的操作:
- 获取对其他游戏对象的引用:在Start()函数中获取对其他游戏对象的引用,如物体的Transform,Rigidbody等组件。这些引用可以在后续的代码中使用。
- 设置游戏对象的初始状态:可以在Start()函数中设置游戏对象的初始状态,如位置、旋转、缩放等。这样在游戏开始时,游戏对象就可以按照预期的状态来显示。
- 初始化变量:在Start()函数中,也可以初始化变量,例如设置初始值或获取初始参数,以便在后续的代码中使用。
- 加载资源:在Start()函数中可以加载音频、图像、场景等资源,以便在游戏中使用。
- 注册事件:游戏中的各种事件通常需要通过事件管理器进行注册和处理。在Start()函数中可以注册这些事件,以便在游戏中触发。
总而言之,Start()函数通常被用于执行初始化操作,为游戏对象设置初始状态,以及初始化变量等操作。
🙆♀️FixedUpdate运行顺序是不是在Update后面?
👩🍳 在 Unity 引擎中,FixedUpdate() 是 FixedUpdate 帧调用的函数,而 Update() 是普通的 Update 帧调用的函数。它们的区别在于 FixedUpdate 的执行频率是固定的,而 Update 的执行频率可变。
在 FixedUpdate 机制下,物理引擎会紧密地协同更新所有的碰撞箱,所以它的调用频率通常是恒定不变的,而且实践经验中 FixedUpdate 一般比 Update 调用频率低。
因此,可以得出以下结论:
- FixedUpdate() 的执行顺序不一定在 Update() 之后,它们是由同一帧调用的,具体的执行顺序取决于引擎内部的实现。
- 由于 FixedUpdate() 的调用频率较低,所以如果需要更新游戏对象的物理状态,应该在 FixedUpdate() 中执行。而如果只需要更新游戏对象的外观或其他属性,可以在 Update() 中执行。
- 为了避免多次修改同一变量造成的不可预期问题,应该避免在 FixedUpdate() 和 Update() 中同时修改同一个变量。如果必须这样做,应该使用线程安全的方式进行同步。
Unity中的Update与FixedUpdate CSDN:Hello Bug.博主的文章
——执行顺序
Update在FixedUpdate之后执行。例如有两个脚本A和B,A中有Update和FixedUpdate,B中有FixedUpdate,A中有Update会在A和B的FixedUpdate都执行后再执行
测试: 先修改FixedUpdate的时间步长
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestFixedUpate : MonoBehaviour
{
int FixedUpdateCount = 0;
string objectName;
// Start is called before the first frame update
void Start()
{
objectName = gameObject.name;
}
private void FixedUpdate()
{
FixedUpdateCount++;
Debug.Log(objectName+"FixedUpdate " + FixedUpdateCount);
}
// Update is called once per frame
float timer = 0;
void Update()
{
timer += Time.deltaTime;
Debug.Log(objectName+"Update:"+Time.deltaTime);
}
}
2、List和ArrayList的区别
C# 中ArrayList使用Object类型数据来存储元素,可以存储各种类型的数据,操作元素时需要进行装箱拆箱操作 当集合元素很大的时候,性能有影响
List<T>
是泛型集合, 可以指定集合中元素的类型,并且支持LINQ查询和Lambda表达式
List和ArrayList都是C#中的集合类,它们的主要区别在于实现方式和效率。
- 实现方式
ArrayList 是 .NET Framework 1.0 的时候就存在的,它使用Object类型数组来存储元素。因为Object类型可以表示任何类的对象,所以ArrayList 中可以存储各种类型的数据。
List 是 .NET Framework 2.0 时新增的一个泛型集合,使用起来更加便捷,且强类型安全(即在编译期就能检测出错误)。
- 效率
由于 ArrayList 存储的是Object类型,所以每次操作元素的时候都需要进行一次装箱拆箱操作,这个过程对性能影响较大,尤其是当集合元素量很大时,此种影响非常显著。
而 List 存储的是泛型类型,操作元素时不需要进行装箱拆箱操作,因此速度优于 ArrayList。
综上,如果我们需要存放同一种类型的数据,建议使用 List 类型,可以提高程序的效率。如果需要存放不同类型的数据,才用 ArrayList。
ArrayList实现了IList而List<T>
既实现了IList又实现了IList<T>
-
类型安全:List是泛型集合,可以指定集合中元素的类型,而ArrayList是非泛型集合,可以存储任何类型的对象。
-
性能:List比ArrayList性能更好,因为List是泛型集合,可以避免装箱和拆箱操作,而ArrayList需要进行装箱和拆箱操作。
-
扩展性:List比ArrayList更具扩展性,因为List支持LINQ查询和Lambda表达式,可以更方便地进行数据操作。
-
可读性:List比ArrayList更易读,因为List的代码更简洁明了,不需要进行类型转换等操作。
-
安全性:List比ArrayList更安全,因为List在编译时就可以检查类型错误,而ArrayList只能在运行时才能检查类型错误。
3、使用c#写一个单例类,并简述一下单例类的作用和优缺点
以下是一个使用C#实现的单例类示例:
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
单例类的作用是确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这种设计模式通常用于需要全局访问的资源,例如日志记录器、数据库连接等。
单例类的优点包括:
-
全局访问:单例类可以提供一个全局访问点,使得其他类可以方便地访问该类的实例。
-
节省资源:由于单例类只有一个实例,因此可以节省系统资源,避免创建多个相同的对象。
-
简化代码:单例类可以简化代码,避免在多个地方创建相同的对象。
单例类的缺点包括:
-
可能会导致性能问题:由于单例类只有一个实例,因此可能会导致性能问题,特别是在多线程环境下。
-
可能会导致代码耦合:由于单例类是全局访问的,因此可能会导致代码耦合,使得代码难以维护和测试。
-
可能会导致单例类的生命周期过长:由于单例类只有一个实例,因此可能会导致单例类的生命周期过长,使得对象无法被垃圾回收。
4、ref和out的区别
- 两者都是按地址传递的,使⽤后都将改变原来参数的数值
- ref会把参数的数值传递进函数,但是out会把参数的数值清空,也就是说out类型的参数传递进去后,参数的数值为空,⽆法输出,因此必须初始化⼀次
⼀⾔以概之:ref是有进有出,out是只出不进
- ref参数的传递必须先初始化,⽽out不需要显式初始化
5、Unity优化内存的几种方式,列举三种以上
6、Unity中如何实现一个2D项目
7、简述unity项目中的特殊文件夹以及其作用
8、Unity实现UI的淡入淡出
9、简述渲染管线
10、Unity中实现截图功能的思路
实现Unity中的截图功能,以下是步骤:
- 创建一个Texture2D对象用于储存截图数据。
- 使用RenderTexture来渲染场景,这样能保留所有渲染效果,包括后期处理。
- 使用Camera.Render()方法将场景渲染到RenderTexture中。
- 使用Texture2D.ReadPixels()方法将RenderTexture中的像素读入Texture2D中。
- 使用Texture2D.Apply()方法将像素上传到GPU。
- 通过文件流将Texture2D转成PNG或JPG格式的图片,存储在磁盘上。
public class Screenshot : MonoBehaviour
{
public KeyCode screenshotKey = KeyCode.Space; //设置截图快捷键
public int width = 1920; //定义截图的大小
public int height = 1080;
private RenderTexture renderTexture;
private Texture2D screenshotTexture;
void Start ()
{
//初始化渲染纹理和截图纹理
renderTexture = new RenderTexture(width, height, 24);
screenshotTexture = new Texture2D(width, height, TextureFormat.RGB24, false);
}
void Update ()
{
//检测按键
if (Input.GetKeyDown(screenshotKey))
{
CaptureScreenshot();
}
}
void CaptureScreenshot()
{
//渲染场景到纹理中
Camera cam = Camera.main;
cam.targetTexture = renderTexture;
cam.Render();
//读取像素到截图纹理中
RenderTexture.active = renderTexture;
screenshotTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
screenshotTexture.Apply();
RenderTexture.active = null;
//保存到文件中
byte[] bytes = screenshotTexture.EncodeToPNG();
string filename = "screenshot" + Time.time.ToString() + ".png";
System.IO.File.WriteAllBytes(filename, bytes);
}
}
Unity3d中渲染到RenderTexture的原理,几种方式以及一些问题 -CSDN博主「leonwei」文章
🙆♀️ "大部分情况我们渲染到rt就是为了将其作为tex继续给其他mat使用。" rt应该是说rendertexture,mat指什么呢?
👩🍳是的,"rt"是指"render texture"(渲染纹理),它通常用于一些高级渲染技术中,例如通过多次渲染生成反射、折射效果等。而使用渲染纹理生成Texture2D对象则是一种常见的截图实现方式。
在Unity中,"mat"通常是指"material"(材质)。Material是游戏对象表面上渲染的外观,可以包含多个纹理和其他渲染属性。通过将RenderTexture作为材质的纹理之一,可以将先前渲染到RenderTexture上的图像应用于其他游戏对象或场景元素的渲染中。以以下代码片段为例:
Texture2D tex = new Texture2D(128, 128, TextureFormat.RGB24, false);
Camera cam = GetComponent<Camera>();
RenderTexture rt = new RenderTexture(tex.width, tex.height, 24);
void Start() {
// 设置相机的TargetTexture为rt
cam.targetTexture = rt;
// 渲染相机
cam.Render();
// 恢复相机的TargetTexture为空值
cam.targetTexture = null;
// 将渲染结果转换成Texture2D
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();
// 将Texture2D应用到材质上
Material mat = GetComponent<Renderer>().material;
mat.mainTexture = tex;
}
在这个示例中,我们首先将一个相机的TargetTexture设置为RenderTexture对象,然后渲染该相机。接着,我们将渲染结果保存到一个Texture2D对象中,最后通过设置一个Renderer组件的Material属性,将该Texture2D应用于游戏对象表面上的渲染上。
需要注意的是,由于使用了RenderTexture和Texture2D等高级的渲染技术,建议在开发过程中注意优化性能,避免出现卡顿等问题。
//读取像素到截图纹理中
RenderTexture.active = renderTexture;
这个怎么理解
👩🍳 在Unity中,渲染到屏幕或纹理需要一个激活的RenderTexture对象。这个对象定义了渲染输出的目标。使用"RenderTexture.active"语句可以将任何RenderTexture对象设置为活动渲染目标。
在截图功能的实现中,我们将场景渲染到一个RenderTexture中,因此需要将它设置为活动渲染目标,以便在接下来的读像素操作中,可以从该RenderTexture对象中读取像素数据,并将它们存储到我们定义的Texture2D对象中。
所以这里代码的意思是:将RenderTexture对象(renderTexture)设置为当前的活动渲染目标。
🙆♀️RenderTexture.active = renderTexture;
screenshotTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
screenshotTexture.Apply();
这个ReadPixels,Apply()函数的用法
👩🍳 RenderTexture.active = renderTexture; 设置renderTexture为活动渲染目标。
screenshotTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0); 读取从(0, 0)开始,宽度为width,高度为height的像素矩形,并将像素信息存储在screenshotTexture中。 ReadPixels是耗时操作,因为它从GPU复制像素数据到系统内存中。
screenshotTexture.Apply(); 将screenshotTexture中的修改应用到纹理上,确保该纹理的可见像素与screenshotTexture一致。Apply不会返回一个新的纹理,而是直接将修改后的纹理应用到GPU中。
总之,上述代码的目的是将renderTexture中指定区域的像素读入screenshotTexture,并确保修改应用到纹理上。
🙆♀️ 老呱头–夜勤安保队长 : @菜鸟章章 设置rt后会作为渲染目标画上去的 你会在commandbuffer中看到setRT相关的指令
这个commandbuffer在哪? (PS:此问题目前已超纲)
👩🍳 Command Buffer是Unity中一种用于定制和优化渲染管线的系统。它可以在任何与相机相关的阶段中注入自己的命令,并且可以在GPU执行前进行优化。具有这种特性的命令缓存区可用于在Unity引擎中实现各种高级渲染效果,例如后处理、倒影以及像素光线追踪等。
在使用Command Buffer之前,我们需要先创建一个CommandBuffer对象,并为其指定要注入的渲染阶段,例如相机在渲染前或者渲染后,或者在某个渲染队列中。然后,我们可以通过CommandBuffer调用API向其中添加Rendering commands,从而实现高级渲染效果。
回到你的问题,如果你正在使用CommandBuffer来实现RenderTexture的功能,那么你需要将设置RenderTexture为当前渲染目标的命令添加到CommandBuffer中。下面是一个简单的示例:
using UnityEngine;
using UnityEngine.Rendering;
public class MyCommandBuffer : MonoBehaviour {
CommandBuffer commandBuffer;
RenderTexture renderTexture;
void Start() {
// 创建一个CommandBuffer对象
commandBuffer = new CommandBuffer();
// 创建一个RenderTexture对象
renderTexture = new RenderTexture(256, 256, 0);
// 将设置RenderTexture为当前渲染目标的命令添加到CommandBuffer中
commandBuffer.SetRenderTarget(renderTexture);
commandBuffer.ClearRenderTarget(true, true, Color.clear);
// 在相机渲染前注入CommandBuffer
Camera mainCamera = Camera.main;
mainCamera.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
}
这段代码中,我们在Start方法中创建了一个RenderTexture和一个CommandBuffer对象,并将设置RenderTexture为当前渲染目标的命令添加到CommandBuffer中。然后,在相机渲染前使用Camera.AddCommandBuffer API将CommandBuffer对象注入相机的渲染管线中,从而实现了RenderTexture的功能。
需要注意的是,使用Command Buffer实现高级渲染效果时需要对OpenGL ES和Vulkan等低端平台进行一些适配,以保证该功能在不同平台上的兼容性。