• Unity 使用技巧与常见问题


    注意:本文章将长期更新,长期修改。

    快速找到对象

    在Hierarchy选中需要查找的对象,在Scene视图中F,即可快速找到需要的对象
    请添加图片描述


    快速对齐对象

    在Game视图中选到对象之后,按住V,此时可以看到移动的中心变到了鼠标所在的位置。现在尝试移动即可将两个Cube无缝连接在一起。
    请添加图片描述


    BMFont 使用

    使用了一个大佬开放插件
    注意: 如遇到字体信息在重启Unity后丢失的情况,可在BMFontEditor.cs脚本中的最后添加 EditorUtility.SetDirty(targetFont);来解决。


    刚体与碰撞体

    刚体使gameObject拥有物理属性,碰撞体使gameObject拥有碰撞属性。


    运行时使程序暂停

    在需要停止的地方添加如下代码即可。编辑器将进入暂停状态:

      Debug.Break();
    
    • 1

    Has Exit Time 的作用

    HasExitTime有两个作用:
    第一个是勾选上了之后,将不用添加条件来转换了。动画播放完,将会自动转换为下一个动作。
    第二个是使用条件来转换的话,不勾选选项动画将会立即切换为另一个。但是勾选了情况下,那么就会在动画播放完之后再切换,也就是说使用条件来切换将不会立即生效。


    命名约定

    公共成员变量/局部变量: myParam
    非公共成员变量: m_MyParam


    Mathf.Approximately

    比较两个浮点值,如果它们相似,则返回 true。
    例如,(1.0 == 10.0 / 10.0) 不会每次都返回 true。 Approximately() 比较两个浮点数,如果它们相互之间的差值处于较小值范围内,则返回 true。


    Input.GetAxis

    Unity 有一个输入管理器(Edit-ProjectSetting-InputManager)用来定义可按名称找到的各种按钮和轴。例如,其中有一个称为 Horizontal 的轴,由 A 和 D 键以及向左和向右键表示。因此,通过该检查,玩家的计算机可以决定角色应该向左还是向右移动。
    Input.GetAxis只能监听键盘和游戏手柄的输入,对于移动平台没有意义。

       float horizontal = Input.GetAxis("Horizontal");
       float vertival = Input.GetAxis("Vertical");
    
    • 1
    • 2

    动画状态添加StateMachineBehaviour

    在Animator界面中,选中需要添加脚本的动画,在Inspector中选中AddBehaviour添加StateMachineBehaviour脚本。
    刚体使gameObject拥有物理属性,碰撞体使gameObject拥有碰撞属性。如果gameObject只有刚体,没有碰撞体,那么gameObject会一直掉落下去。


    Spine插件下载问题

    如果在Spine官网下载的文件不是.unitypackage的话,而是**.gz结尾**的文件的话。尝试换个浏览器下载,笔者换了谷歌浏览器之后就可以下载到.unitypackage。
    在这里插入图片描述


    Spine 动画未播放完,重新播放问题

    两行代码都需要添加

       spine.Skeleton.SetToSetupPose();
       spine.AnimationState.ClearTracks();
    
    • 1
    • 2

    模型动画重复问题

    选中模型中的动画,在Inspector中选中Animation,勾选下方的LoopTime。最后需要点击 Apply才会生效。
    在这里插入图片描述
    在这里插入图片描述


    SpriteAtlas 的使用

    设置Sprite Packer 模式

    Edit---->ProjectSetting------->Editor--------->Mode
    在这里插入图片描述
    Mode参数意义:
    (1)disabled不启用
    (2)enabled for builds(legacy sprite packer):打包时启用(针对sprite packer这种打包方式)
    (3)always enabled(legacy sprite packer):总是启用(针对sprite packer这种打包方式)
    (4)enabled for builds:打包时启用(针对sprite Atlas这种打包方式)
    (5)always enabled(针对sprite Atlas这种打包方式)

    sprite Atlas是2017版本之后的图集打包方式, Sprite Atlas 针对旧版本的图集打包系统Sprite Packer在性能和易用性上的不足,进行了全面改善。
    所以笔者下面只使用spriteAtlas演示。

    创建sprite Atlas

    如下所示创建sprite Atlas:
    在这里插入图片描述

    加入spriteAtlas

    spriteAtlas支持通过文件夹添加,所以建议通过文件夹添加,那样就不用一个一个添加了。添加完之后就可以点击PackPreview查看效果。
    在这里插入图片描述

    查看效果

    这里我们可以回到上面设置Sprite Packer模式中,通过来回修改SpritePacker为enabled for builds或always enabled,然后点击Stats查看效果

    在这里插入图片描述


    模型动画优化

    文件压缩方式:选中含有动作的.fbx文件,再选中Animation,修改Anim.Compression。
    在这里插入图片描述
    选项说明:
    Off 关闭压缩
    Keyframe Reduction 减少没有必要的关键帧
    Optimal 优化压缩,官方会选择最优的压缩方式来进行压缩,建议选择这个


    降低内存垃圾回收(GC)对性能的影响

    String

    1. 在 C# 中,字符串属于引用类型,而非值类型。我们需要减少不必要的字符串创建或更改操作。修改字符串的方法实际上都是返回一个新的String对象原字符串仍然留在内存 中等待回收,那么当字符串较长或是操作频繁时就消耗大量的资源。如果你需要在运行时构建字符串,可使用 StringBuilder 类。
    2. 尽量避免解析 JSON 和 XML 等由字符串组成的数据文件,将数据存储于 ScriptableObjects,或以 MessagePack 或 Protobuf 等格式保存。

    Unity 函数调用

    1. 缓存数组引用,避免在循环进行中进行数组的内存分配。
    2. 尽量使用那些不会产生垃圾回收的函数。比如使用 GameObject.CompareTag,而不是使用 GameObject.tag 手动比对字符串(因为返回一个新字符串会产生垃圾数据)。

    Boxing(打包)

    避免在引用类型变量处传入值类型变量,因为这样做会导致系统创建一个临时对象,在背地里将值类型转换为对象类型(如int i = 123; object o = i ),从而产生垃圾回收的需求。尽量使用正确的类型重写来传入想要的值类型。泛型也可用于类型覆写。

    Coroutines(协同程序)

    虽然 yield 不会产生垃圾回收,但新建 WaitForSeconds 对象会。我们可以缓存并复用 WaitForSeconds 对象,不必在 yield 中再度创建。

        WaitForSeconds waitSec = new WaitForSeconds(0.01f);
        IEnumerator TestWaitSecond()
        {
            yield return waitSec;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    降低每帧的代码量

    有许多代码并非要在每帧上运行,这些不必要的逻辑完全可以在 Update、LateUpdate 和 FixedUpdate 中删去。这些事件函数可以保存那些必须每帧更新的代码,任何无须每帧更新的逻辑都不必放入其中。
    如果必须要使用 Update,可以考虑让代码每隔 n 帧运行一次。

    private int interval = 3;
    void Update()
    {
        if (Time.frameCount % interval == 0)
        {
            ExampleExpensiveFunction();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    避免在 Start/Awake 中加入繁重的逻辑

    当首个场景加载时,每个对象都会调用如下函数:Awake,OnEnable,Start。
    应用完成第一帧的渲染前,我们须避免在这些函数中运行繁重的逻辑。否则,应用的加载时间会出乎意料地长


    避免加入空事件

    即使是空的 MonoBehaviours 也会占用资源,因此我们应该删除空的 Update 及 LateUpdate 方法。
    如果你想用这些方法进行测试,请使用预处理指令:

    #if UNITY_EDITOR
    void Update()
    {
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如此一来,在编辑器中的 Update 测试便不会对构建版本造成不良的性能影响。


    删去 Debug Log 语句

    Log 声明(尤其是在Update、LateUpdate及FixedUpdate中)会拖慢性能,因此我们需要在构建之前禁用 Log 语句。你可以用预处理指令编写一条 Conditional 属性来轻松禁用 Debug Log。比如下方这种的自定义类:

    public static class Logging
    {
        [System.Diagnostics.Conditional("ENABLE_LOG")]
        static public void Log(object message)
        {
            UnityEngine.Debug.Log(message);
        }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用哈希值、避免字符串

    Unity 底层代码不会使用字符串来访问 Animator、Material 和 Shader 属性。出于提高效率的考虑,所有属性名称都会被哈希转换成属性 ID,用作实际的属性名称。
    在 Animator、Material 或 Shader 上使用 Set 或 Get 方法时,我们便可以利用整数值而非字符串。后者还需经过一次哈希处理,并没有整数值那么直接。
    使用 Animator.StringToHash 来转换 Animator 属性名称,用 Shader.PropertyToID 来转换 Material 和 Shader 属性名称

        public static int IS_WALKING;
        private void Awake()
        {
            IS_WALKING = Animator.StringToHash("IsWalking");
        }
        void FixedUpdate()
        {
           //使用ID修改条件
           m_Animator.SetBool(IS_WALKING, isWaking);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    选择正确的数据结构

    由于数据结构每帧可能会迭代上千次,因此其结构对性能有着较大的影响。如果你不清楚数据集合该用 List、Array 还是 Dictionary 表示,可以参考 C# 的 MSDN 数据结构指南来选择正确的结构。

    在这里插入图片描述


    避免在运行时添加组件

    在运行时调用 AddComponent 会占用一定的运行成本,Unity 必须检查组件是否有重复或依赖项


    缓存 GameObjects 和组件

    调用 GameObject.Find、GameObject.GetComponent 和 Camera.main(2020.2以下的版本)会产生较大的运行负担,因此这些方法不适合在 Update 中调用,而应在 Start 中调用并缓存

    private Renderer myRenderer;
    void Start()
    {
        myRenderer = GetComponent<Renderer>();
    }
    
    void Update()
    {
        ExampleFunction(myRenderer);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用 ScriptableObjects(可编程对象)

    固定不变的值或配置信息可以存储在 ScriptableObject 中,不一定得储存于 MonoBehaviour。ScriptableObject 可由整个项目访问,一次设置便可应用于项目全局。

    声明:

    using UnityEngine;
    
    [CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
    public class SpawnManagerScriptableObject : ScriptableObject
    {
        public string prefabName;
    
        public int numberOfPrefabsToCreate;
        public Vector3[] spawnPoints;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用:

    using UnityEngine;
    
    public class Spawner : MonoBehaviour
    {
        // 要实例化的游戏对象。
        public GameObject entityToSpawn;
    
        //上面定义的 ScriptableObject 的一个实例。
        public SpawnManagerScriptableObject spawnManagerValues;
    
        //这将附加到创建的实体的名称,并在创建每个实体时递增。
        int instanceNumber = 1;
    
        void Start()
        {
            SpawnEntities();
        }
    
        void SpawnEntities()
        {
            int currentSpawnPointIndex = 0;
    
            for (int i = 0; i < spawnManagerValues.numberOfPrefabsToCreate; i++)
            {
                //在当前生成点处创建预制件的实例。
                GameObject currentEntity = Instantiate(entityToSpawn, spawnManagerValues.spawnPoints[currentSpawnPointIndex], Quaternion.identity);
    
                //将实例化实体的名称设置为 ScriptableObject 中定义的字符串,然后为其附加一个唯一编号。
                currentEntity.name = spawnManagerValues.prefabName + instanceNumber;
    
                // 移动到下一个生成点索引。如果超出范围,则回到起始点。
                currentSpawnPointIndex = (currentSpawnPointIndex + 1) % spawnManagerValues.spawnPoints.Length;
    
                instanceNumber++;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    WEB前端网页设计 CSS网页代码 基础参数(三)
    DIGIX比赛1
    使用cli批量下载GitHub仓库中所有的release
    ChatGPT自动开发SwiftUI App
    JavaWeb综合案例(黑马程序员2021年JavaWeb课程总结,所有功能均实现,包含数据库sql文件)
    「UG/NX」Block UI 从列表选择部件SelectPartFromList
    2022年java开发面试题整理合集
    hadoop2.2.0伪分布式搭建
    [Python]跟着代码去学习---二维码1:文字生成二维码
    03梯度下降
  • 原文地址:https://blog.csdn.net/qq_28644183/article/details/125487290