Unity 开发注意事项

1. 空Unity消息

Unity消息被运行时事件调用,即使消息体为空也会被调用。因此,删除空消息避免不必要的处理。

例如:

cs 复制代码
using UnityEngine;

class Camera : MonoBehaviour
{
    private void FixedUpdate()
    {
    }

    private void Foo()
    {
    }
}

应该删除未使用的 FixedUpdate 方法。

2. 标签比较效率低下

使用"=="进行标签比较效率要比使用内置的"CompareTag "方法比较的效率低,所以尽量使用"CompareTag "进行标签比较。

例如:

cs 复制代码
using UnityEngine;

public class Camera : MonoBehaviour
{
    private void Update()
    {
        Debug.Log(tag == ""tag1"");
    }
}

改为:

using UnityEngine;

public class Camera : MonoBehaviour
{
    private void Update()
    {
        Debug.Log(CompareTag(""tag1""));
    }
}

3. 非通用GetComponent的用法

为了类型安全,首选使用GetComponent、TryGetComponent、GetComponents、GetComponentInChildren、GetComponentsInChildren、GetComponentInParent和GetComponentsInParent的泛型形式。

例如:

cs 复制代码
using UnityEngine;

class Camera : MonoBehaviour
{
	private Rigidbody rb;

    private void Start()
    {
        rb = GetComponent(typeof(Rigidbody)) as Rigidbody;
    }
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
	private Rigidbody rb;

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
}

4. Time.fixedDeltaTime 和 Update 一起使用时

Update 是依赖于帧率的,在Update中 应该用 Time.deltaTime 而不是用 Time.fixedDeltaTime。

同理,FixedUpdate 不依赖帧率,在 FixedUpdate 中 应该用Time.fixedDeltaTime而不是 Time.deltaTime。

5. Unity对象上的空合并

Unity 重写了Unity对象的null比较运算符,因此,不要对Unity对象使用null合并运算符(??),同理,也不要对Unity 对象使用null传递运算符(?.),is not null 也是不允许的,

他们是不兼容的。

例如:

cs 复制代码
例1:
using UnityEngine;

class Camera : MonoBehaviour
{
	public Transform a;
	public Transform b;

	public Transform NC()
	{
		return a ?? b;
	}
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
	public Transform a;
	public Transform b;

	public Transform NC()
	{
		return a != null ? a : b;
	}
}

例2:

using UnityEngine;

class Camera : MonoBehaviour
{
	public Transform NP()
	{
		return transform?.transform;
	}
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
	public Transform NP()
	{
		return transform != null ? transform : null;
	}
}

例3:

using UnityEngine;

class Camera : MonoBehaviour
{
    public Transform a = null;

    public void Update()
    {
        if (a is not null) { }
    }
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
    public Transform a = null;

    public void Update()
    {
        if (a != null) { }
    }
}

6. 缺少InitializeOnLoad的静态构造函数

在类上使用 InitializeOnLoad 属性标记的时候,应该提供静态的构造函数,它将在编辑器启动的时候调用。

cs 复制代码
using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
class Camera : MonoBehaviour
{
}

改为:

using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
class Camera : MonoBehaviour
{
    static Camera()
    {
    }
}

7. 组件实例创建

组件实例创建时应该使用 AddComponent() 方法将组件添加到一个物体上,而不是使用 new 来创建实例。

cs 复制代码
using UnityEngine;

class Foo : MonoBehaviour { }

class Camera : MonoBehaviour
{
    public void Update() {
        Foo foo = new Foo();
    }
}

改为:

using UnityEngine;

class Foo : MonoBehaviour { }

class Camera : MonoBehaviour
{
    public void Update() {
        Foo foo = gameObject.AddComponent<Foo>();
    }
}

8. ScriptableObject 实例的创建

使用 CreateInstance() 方法创建 ScriptableObject 的实例,而不是使用new。

cs 复制代码
using UnityEngine;

class Foo : ScriptableObject { }

class Camera : MonoBehaviour
{
    public void Update() {
        Foo foo = new Foo();
    }
}

改为:

using UnityEngine;

class Foo : ScriptableObject { }

class Camera : MonoBehaviour
{
    public void Update() {
        Foo foo = ScriptableObject.CreateInstance<Foo>();
    }
}

9. SerializeField 属性无效或冗余

SerializeField 属性对于公共字段是多余的,对于属性或静态/只读字段无效。与 SerializeReference 不同,编译器允许您在属性上使用 SerializeField 属性,即使该属性无效且无法在 Unity 编辑器中运行。

cs 复制代码
using System.Collections;
using UnityEngine;

public class SerializedAttributes : MonoBehaviour
{
    [SerializeField] // correct usage
    private string privateField;
    
    [SerializeField] // redundant usage
    public string publicField;

    [SerializeField] // invalid usage
    private string PrivateProperty { get; set; }

    [SerializeField] // invalid usage
    static string staticField;

    [SerializeField] // invalid usage
    readonly field readonlyField;
}

改为:

using System.Collections;
using UnityEngine;

public class SerializedAttributes : MonoBehaviour
{
    [SerializeField] // correct usage
    private string privateField;
    
    public string publicField;

    private string PrivateProperty { get; set; }

    static string staticField;

    readonly field readonlyField;
}

10. InitializeOnLoadMethod、RuntimeInitializeOnLoadMethod 或 DidReloadScripts 属性的方法签名不正确

InitializeOnLoadMethod、RuntimeInitializeOnLoadMethod 或 DidReloadScripts 修饰的方法或者属性必须是无参的,否则,Unity 不会调用它或抛出 NullReferenceException。

cs 复制代码
using UnityEditor;

class Loader
{
    [InitializeOnLoadMethod]
    private void OnLoad(int foo, string bar) {
    }
}

改为:

using UnityEditor;

class Loader
{
    [InitializeOnLoadMethod]
    private static void OnLoad() {
    }
}

11. 获取方法名称的不安全方式

使用InvokeInvokeRepeatingCancelInvokeStartCoroutineStopCoroutine第一个参数是字符串文字不是类型安全的。相反,建议使用nameof运算符或直接调用协程。这样做的另一个好处是该方法能够使用重命名重构,而无需记住更新字符串文字。

cs 复制代码
using UnityEngine;
using System.Collections;

class Camera : MonoBehaviour
{
    void Start()
    {
        Invoke("InvokeMe", 10.0f)
        StartCoroutine("MyCoroutine");
    }

    private void InvokeMe()
    {
        // ...
    }

    private IEnumerator MyCoroutine()
    {
        // ...
    }
}

改为:

using UnityEngine;
using System.Collections;

class Camera : MonoBehaviour
{
    void Start()
    {
        Invoke(nameof(InvokeMe), 10.0f)
        StartCoroutine(MyCoroutine());
    }

    private void InvokeMe()
    {
        // ...
    }

    private IEnumerator MyCoroutine()
    {
        // ...
    }
}

12. SetPixels 调用很慢

Unity 对 RGBA 颜色使用两种不同的表示形式:

  • Color:每个颜色分量都是一个浮点值,范围从 0 到 1。(这种格式在所有显卡和着色器内部使用)。
  • Color32:每个颜色分量都是一个字节值,范围从 0 到 255。(32 位 RGBA)。

Color32速度更快,内存使用量减少 4 倍。ColorColor32可以隐式地相互转换。

SetPixels相比,SetPixels32速度更快并且使用更少的内存。

cs 复制代码
using UnityEngine;

public class ExampleClass : MonoBehaviour
{
    void Start()
    {
        Renderer rend = GetComponent<Renderer>();
        Texture2D texture = Instantiate(rend.material.mainTexture) as Texture2D;
        rend.material.mainTexture = texture;

        // ...

        Color[] colors = new Color[3];
        colors[0] = Color.red;
        colors[1] = Color.green;
        colors[2] = Color.blue;
        texture.SetPixels(colors);

        // ...

    }
}

上例中,如果 32 位 RGBA 与您的场景兼容,请改用SetPixels32

13. 在关键信息中,System.Reflection 特性的性能

不要在关键消息如 Update, FixedUpdate, LateUpdate, or OnGUI中使用System.Reflection,System.Reflection会很慢可能导致滞后。

如果一定要使用 System.Reflection,可以在Start()或者 Awake()中缓存一个变量,然后在关键信息中使用缓存的变量。

14. 对 GameObject.gameObject 进行不必要的间接调用

Unity GameObject有一个gameObject的属性,它会返回 ​​​​​​​thisGameObject.gameObject.gameObject这样类似的调用是多余的,并且会影响性​​能。

15. 设置位置和旋转效率低下

出于性能原因考虑,Transform/TransformAccess应该尽可能少的访问,如果需要依次设置Postion和Rotation,可以使用 SetPositionAndRotation() 方法代替。同样依次设置localPosition和localRotation,也可以用SetLocalPositionAndRotation()方法代替。

相反,如果依次获取position和Rotation,也可以用GetPositionAndRotation()方法代替。Local 同理。

cs 复制代码
using UnityEngine;

class Camera : MonoBehaviour
{
    void Update()
    {
        transform.position = new Vector3(0.0f, 1.0f, 0.0f);
        transform.rotation = transform.rotation;
    }
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
    void Update()
    {
        transform.SetPositionAndRotation(new Vector3(0.0f, 1.0f, 0.0f), transform.rotation);
    }
}

16. 标量计算优先于矢量计算

在紧密循环或性能关键部分中工作时,请记住标量数学比向量数学更快。因此,只要交换或关联算术允许,就尝试最小化各个数学运算的成本。您可以在这里查看Unity 网站上的相关文档。

cs 复制代码
using UnityEngine;

class Camera : MonoBehaviour
{
    public void Compute()
    {
		Vector3 x;
		float a, b;
		
		Vector3 slow = a * x * b;
    }
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
    public void Compute()
    {
		Vector3 x;
		float a, b;
		
		Vector3 fast = a * b * x;
    }
}

17. GetComponent 总是分配

Component.TryGetComponent或者GameObject.TryGetComponent将尝试检索给定类型的组件。与 GetComponent相比,最大的区别是,当请求的组件不存在时,此方法不会分配。也就是说,如果确定类型存在可以使用GetComponent 获取组件,如果可能获取不到类型,尽量使用TryGetComponent来获取组件。

cs 复制代码
using UnityEngine;

class Camera : MonoBehaviour
{
    public void Update() 
    {
        var rb = gameObject.GetComponent<Rigidbody>();
        if (rb != null) {
            Debug.Log(rb.name);
        }
    }
}

改为:

using UnityEngine;

class Camera : MonoBehaviour
{
    public void Update() 
    {
        if (gameObject.TryGetComponent<Rigidbody>(out var rb)) {
            Debug.Log(rb.name);
        }
    }
}

18. 使用非分配物理 API

引入了物理查询 API 的非分配版本。您可以将RaycastAll调用替换为RaycastNonAlloc,将SphereCastAll调用替换为SphereCastNonAlloc,等等。您可以重复使用预先分配的数组来存储结果,而不是为每个调用分配一个新数组。这将提高性能,特别是对于频繁的调用。

cs 复制代码
using UnityEngine;

class Camera : MonoBehaviour
{
    void Update() {
        var result = Physics.RaycastAll(Vector3.zero, Vector3.zero);
        // ...
        result = Physics.RaycastAll(Vector3.zero, Vector3.zero);
    }
}
相关推荐
皮皮陶1 小时前
Unity WebGL交互通信
unity·交互·webgl
程序员正茂2 小时前
PICO+Unity MR空间网格
unity·mr·pico
程序员正茂2 小时前
PICO+Unity MR空间锚点
unity·pico·空间锚点
龙中舞王4 小时前
Unity学习笔记(2):场景绘制
笔记·学习·unity
虾球xz7 小时前
游戏引擎学习第五天
学习·算法·游戏引擎
逐·風10 小时前
Unity编辑器的高级扩展技术
unity·编辑器·游戏引擎
虾球xz11 小时前
游戏引擎学习第六天
学习·游戏引擎
霸王•吕布13 小时前
游戏引擎中LOD渲染技术
游戏引擎·lod·高低模渲染·blender制作lod模型·lod层级·lod原理
Go_Accepted1 天前
代码修改材质参数
unity
冰凌糕1 天前
Unity3D UI 双击和长按
unity