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);
    }
}
相关推荐
ellis19702 小时前
Unity插件SafeArea Helper适配异形屏详解
unity
nnsix3 小时前
Unity Physics.Raycast的 QueryTriggerInteraction枚举作用
unity·游戏引擎
地狱为王3 小时前
Cesium for Unity叠加行政区划线
unity·gis·cesium
小贺儿开发12 小时前
Unity3D 八大菜系连连看
游戏·unity·互动·传统文化
在路上看风景12 小时前
25. 屏幕像素和纹理像素不匹配
unity
ۓ明哲ڪ13 小时前
Unity功能——创建新脚本时自动添加自定义头注释
unity·游戏引擎
熬夜敲代码的小N13 小时前
Unity大场景卡顿“急救包”:从诊断到落地的全栈优化方案
java·unity·游戏引擎
派葛穆15 小时前
Unity-realvirtual-S7通讯快速配置(未完结)
unity·游戏引擎
w-白兰地1 天前
【Addressable远端加载资源】
unity·addressable·资源加载
小张不爱写代码1 天前
[Unity 技巧] 如何自定义 Inspector 变量显示名称 (CustomLabel)
unity·游戏引擎