QuHai互联科技 Unity小题部分

1、OnEnable、Awake、FixedUpdate、Start、Update运行时的发生顺序?哪些可能在同一个对象周期中反复的发生?

脚本生命周期概述 Unity官方文档链接_Unity版本21.3

  1. Awake():在游戏对象被实例化时调用,只会在对象的生命周期中调用一次。
  2. OnEnable():当游戏对象被启用时调用,可以被多次调用。例如当使用SetActive()函数启用物体时,就会调用OnEnable()函数。
  3. Start():在Awake()方法执行完毕后调用,只会在对象的生命周期中调用一次。
  4. FixedUpdate():是固定的物理帧更新,一般每秒执行50次左右。在物理计算之前被调用,可以被多次调用。
  5. 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()函数中执行的操作:

  1. 获取对其他游戏对象的引用:在Start()函数中获取对其他游戏对象的引用,如物体的Transform,Rigidbody等组件。这些引用可以在后续的代码中使用。
  2. 设置游戏对象的初始状态:可以在Start()函数中设置游戏对象的初始状态,如位置、旋转、缩放等。这样在游戏开始时,游戏对象就可以按照预期的状态来显示。
  3. 初始化变量:在Start()函数中,也可以初始化变量,例如设置初始值或获取初始参数,以便在后续的代码中使用。
  4. 加载资源:在Start()函数中可以加载音频、图像、场景等资源,以便在游戏中使用。
  5. 注册事件:游戏中的各种事件通常需要通过事件管理器进行注册和处理。在Start()函数中可以注册这些事件,以便在游戏中触发。
    总而言之,Start()函数通常被用于执行初始化操作,为游戏对象设置初始状态,以及初始化变量等操作。

🙆‍♀️FixedUpdate运行顺序是不是在Update后面?

👩‍🍳 在 Unity 引擎中,FixedUpdate() 是 FixedUpdate 帧调用的函数,而 Update() 是普通的 Update 帧调用的函数。它们的区别在于 FixedUpdate 的执行频率是固定的,而 Update 的执行频率可变。
在 FixedUpdate 机制下,物理引擎会紧密地协同更新所有的碰撞箱,所以它的调用频率通常是恒定不变的,而且实践经验中 FixedUpdate 一般比 Update 调用频率低。
因此,可以得出以下结论:

  1. FixedUpdate() 的执行顺序不一定在 Update() 之后,它们是由同一帧调用的,具体的执行顺序取决于引擎内部的实现。
  2. 由于 FixedUpdate() 的调用频率较低,所以如果需要更新游戏对象的物理状态,应该在 FixedUpdate() 中执行。而如果只需要更新游戏对象的外观或其他属性,可以在 Update() 中执行。
  3. 为了避免多次修改同一变量造成的不可预期问题,应该避免在 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#中的集合类,它们的主要区别在于实现方式和效率。

  1. 实现方式

ArrayList 是 .NET Framework 1.0 的时候就存在的,它使用Object类型数组来存储元素。因为Object类型可以表示任何类的对象,所以ArrayList 中可以存储各种类型的数据。

List 是 .NET Framework 2.0 时新增的一个泛型集合,使用起来更加便捷,且强类型安全(即在编译期就能检测出错误)。

  1. 效率

由于 ArrayList 存储的是Object类型,所以每次操作元素的时候都需要进行一次装箱拆箱操作,这个过程对性能影响较大,尤其是当集合元素量很大时,此种影响非常显著。

而 List 存储的是泛型类型,操作元素时不需要进行装箱拆箱操作,因此速度优于 ArrayList。

综上,如果我们需要存放同一种类型的数据,建议使用 List 类型,可以提高程序的效率。如果需要存放不同类型的数据,才用 ArrayList。

ArrayList实现了IList而List<T>既实现了IList又实现了IList<T>

  1. 类型安全:List是泛型集合,可以指定集合中元素的类型,而ArrayList是非泛型集合,可以存储任何类型的对象。

  2. 性能:List比ArrayList性能更好,因为List是泛型集合,可以避免装箱和拆箱操作,而ArrayList需要进行装箱和拆箱操作。

  3. 扩展性:List比ArrayList更具扩展性,因为List支持LINQ查询和Lambda表达式,可以更方便地进行数据操作。

  4. 可读性:List比ArrayList更易读,因为List的代码更简洁明了,不需要进行类型转换等操作。

  5. 安全性: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;
        }
    }
}

单例类的作用是确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这种设计模式通常用于需要全局访问的资源,例如日志记录器、数据库连接等。

单例类的优点包括:

  1. 全局访问:单例类可以提供一个全局访问点,使得其他类可以方便地访问该类的实例。

  2. 节省资源:由于单例类只有一个实例,因此可以节省系统资源,避免创建多个相同的对象。

  3. 简化代码:单例类可以简化代码,避免在多个地方创建相同的对象。

单例类的缺点包括:

  1. 可能会导致性能问题:由于单例类只有一个实例,因此可能会导致性能问题,特别是在多线程环境下。

  2. 可能会导致代码耦合:由于单例类是全局访问的,因此可能会导致代码耦合,使得代码难以维护和测试。

  3. 可能会导致单例类的生命周期过长:由于单例类只有一个实例,因此可能会导致单例类的生命周期过长,使得对象无法被垃圾回收。



4、ref和out的区别

  • 两者都是按地址传递的,使⽤后都将改变原来参数的数值
  • ref会把参数的数值传递进函数,但是out会把参数的数值清空,也就是说out类型的参数传递进去后,参数的数值为空,⽆法输出,因此必须初始化⼀次

⼀⾔以概之:ref是有进有出,out是只出不进

  • ref参数的传递必须先初始化,⽽out不需要显式初始化


5、Unity优化内存的几种方式,列举三种以上



6、Unity中如何实现一个2D项目



7、简述unity项目中的特殊文件夹以及其作用



8、Unity实现UI的淡入淡出

9、简述渲染管线

10、Unity中实现截图功能的思路

实现Unity中的截图功能,以下是步骤:

  1. 创建一个Texture2D对象用于储存截图数据。
  2. 使用RenderTexture来渲染场景,这样能保留所有渲染效果,包括后期处理。
  3. 使用Camera.Render()方法将场景渲染到RenderTexture中。
  4. 使用Texture2D.ReadPixels()方法将RenderTexture中的像素读入Texture2D中。
  5. 使用Texture2D.Apply()方法将像素上传到GPU。
  6. 通过文件流将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等低端平台进行一些适配,以保证该功能在不同平台上的兼容性。

posted @ 2023-04-21 16:11  专心Coding的程侠  阅读(57)  评论(0编辑  收藏  举报