Unity优化——脚本优化策略2

简介: Unity优化——脚本优化策略2

一、更快的GameObject空引用检查


事实证明,对GameObject执行空引用检查会导致一些不必要的开销。与典型的C#对象相比,GameObject和MonoBehaviour是特殊对象,因为它们在内存中有两个表示:一个表示存在于管理C#代码的相同系统管理的内存中,C#代码是用户编写的(代码托管),而另一个表示存在于另一个单独处理的内存空间中(本机代码)。数据可以在这两个内存空间之间移动,但是每次这种移动都会导致额外的CPU开销和可能的额外内存分配。


这种效果通常称为跨越本机-托管的桥接。如果发生这种情况,就可能会为对象的数据生成额外的内存分配,以便垮桥复制,这需要垃圾收集器最终执行一些内存自动清理操作。本文不细讲。目前只要知道,有许多微妙的方式会意外地触发这种额外的开销,对GameObject的简单空引用检查就是其中之一:

if(gameObject != null){
//对gameObject做一些事情
}

另一种方法是System.Object.ReferenceEquals(),它生成功能相当的输出,起运行速度大约是原来的两倍(尽管它确实稍微混淆了代码的用途)。

  if (!System.Object.ReferenceEquals(gameObject, null))
        {
            //对gameObject做一些事情
        }


这既适用于GameObject,也适用于MonoBehaviour,还适用于其他Unity对象,这些对象既有原生的也有托管的表现形式,比如WWW类。然而,一些基本测试显示任何一个空引用检查方法仍只消耗纳秒。因此,除非执行大量的空引用检查,否则最多只能获得很少的好处。然而,这是一个值得在未来记住的警告,因为它会经常出现。


二、避免从GameObject取出字符串属性


通常,从对象中检索字符串属性与检索C#中的任何其他引用类型属性是相同的;这种检索应该不增加内存成本。然而,从GameObject中检索字符串属性是另一种意外跨越本机-托管桥接的微妙方式。

GameObject中受此行为影响的两个属性是tag和name。因此,在游戏过程中使用者两种属性是不明智的,应该只在性能无关紧要的地方使用它们,比如编辑器脚本。然而,Tag系统通常用于对象的运行时标识,这对于某些团队来说是一个重要问题。


例如,下面的代码会在循环的每次迭代中导致额外的内存分配:

for(int i= 0; i < listOfObjects.Count; i++)
        {
            if (listOfObjects[i].tag == "player")
            {
                //对这个对象做一些事
            }
        }

根据对象的组件和类类型来标识对象,以及标识不涉及字符串对象的值,这通常是一种更好的实践,但有时会陷入困境。也许刚开始时并不知道,我们继承了别人的代码库,或者把它当作一种变通方法。假设出于某种原因,标记系统出了问题,我们希望避免本地-托管桥接的开销成本。


幸运的是,tag属性最常用于比较,而GameObject提供了CompareTag()方法,这是比较tag属性的另一种方法,它完全避免了本机-托管的桥接。

用CompareTag()方法来代替上面的直接比较方法,通过profiler分析得到结论:处理时间减少了一半,且由于不会导致内存分配,因此也不会导致垃圾回收。


这说明,必须尽可能避免访问name和tag属性。如果需要对标记进行比较,应该使用CompareTag()。但是,name属性没有对应的方法,因此尽可能使用tag属性。


提示:向CompareTag()传递字符串不会导致运行时内存分配,因此应用程序在初始化期间分配这样的硬编码字符串,在运行时只是引用它们。


三、使用合适的数据结构


C#System.Collections名称空间中提供了许多不同的数据结构,我们不应该反复使用相同的名称空间。软件开发中一个常见的性能问题是简单地为了便利而使用不适当的数据结构来解决问题。最常见的两种数据结构是List<T>和Dictionary<K,V>。


如果想遍历一边对象,最好用列表,因为它实际上是一个动态数组,对象、引用在内存中彼此相邻,因此迭代导致的缓存丢失最小。如果两个对象相互关联,且希望快速获取、插入或删除这些关联,最好使用字典。例如,可以将一个关卡编号与特定的场景文件相关联,或者将一个代表角色不同身体部分的enum与这些身体部分的Collider组件相关联。


然而,数据结构通常需要同时处理两种情况:快速找出哪个对象映射到另一个对象,同时还能遍历组。通常,该系统的开发人员使用字典,然后对其进行迭代。然而,与遍历列表相比,这个过程非常慢,因为它必须检查字典中每个可能的散列,才能对其进行完全遍历。


在这些情况下,最好在列表和字典中存储数据,以便更好支持这种行为。这需要额外的内存开销来维护多个数据结构,插入和删除操作需要每次从数据结构中添加和删除对象,但迭代列表的好处和迭代字典形成鲜明的对比。


四、避免运行时修改Transform的父节点


在Unity的早期版本中,Transform组件的引用通常是在内存中随机排序的。这意味着在多个Transform上的迭代是相当慢的,因为存在缓存丢失的可能性。这样做的好处是,修改GameObject的父节点为另一个对象并不会造成显著的性能下降,因为Transform操作起来很想堆数据结构,插入和删除的速度相对较快。这种行为是我们无法控制的,所以只能接受。


但是,在Unity5.4以后,Transform组件的内存分布发生了很大变化。从那时起,Transform组件的父子关系操作起来更像动态数组,因此Unity尝试将所有共享相同元素的Transform按顺序存储在预先分配的内存缓冲区的内存中,并在Hierarchy窗口中根据父元素下面的深度进行排序。这种数据结构允许在整个组中进行更快的迭代,这对物理和动画等多个子系统特别有利。这种变化的缺点是,如果将一个GameObject的父物体重新指定为一个对象,父对象必须将新的子对象放入预先分配的内存缓冲区中,并根据新的深度对这些Transform排序,另外,如果父对象没有预先分配足够的空间来容纳新的子对象,就必须扩展缓冲区,以便以深度优先的顺序容纳新的子对象及其所有的子对象。对于较深、复杂的GameObject结构,这可能需要一些时间来完成。


通过GameObject.Instantiate()实例化新的GameObject时,它的一个参数是希望将GameObject设置为其父节点的Transform,默认值是null,把Transform放在Hierarchy窗口的根元素下。在Hierarchy窗口根元素下的所有Transform都需要分配一个缓冲区来存储它当前的子元素以及以后可能添加的子元素(子Transform元素不需要这样做)。但是,如果在实例化之后立即将Transform的父元素重新修改为另一个元素,它将丢弃刚才分配的缓冲区!为了避免这种情况,应该将父Transform参数提供给GameObject.Instantiate()调用,它跳过了这个缓冲区分配步骤。


另一种降低这个过程成本的方法是让根Transform在需要之前就预先分配一个更大的缓冲区,这样就不需要在同一帧中扩展缓冲区,给它重新制定另一个GameObject导缓冲区中。这可以通过修改Transform的HierarchyCapacity属性来实现。如果能够估计父元素包含的子Transform的数量,就可以节省大量不必要的内存分配。


相关文章
|
7月前
|
大数据 API 图形学
Unity优化——批处理的优势
Unity优化——批处理的优势
215 0
|
7月前
|
存储 人工智能 Java
Unity优化——脚本优化策略4
Unity优化——脚本优化策略4
120 0
|
5月前
|
存储 设计模式 监控
运用Unity Profiler定位内存泄漏并实施对象池管理优化内存使用
【7月更文第10天】在Unity游戏开发中,内存管理是至关重要的一个环节。内存泄漏不仅会导致游戏运行缓慢、卡顿,严重时甚至会引发崩溃。Unity Profiler作为一个强大的性能分析工具,能够帮助开发者深入理解应用程序的内存使用情况,从而定位并解决内存泄漏问题。同时,通过实施对象池管理策略,可以显著优化内存使用,提高游戏性能。本文将结合代码示例,详细介绍如何利用Unity Profiler定位内存泄漏,并实施对象池来优化内存使用。
346 0
|
3月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
213 3
|
4月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
125 0
|
4月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
100 0
|
4月前
|
开发者 图形学 UED
深度解析Unity游戏开发中的性能瓶颈与优化方案:从资源管理到代码执行,全方位提升你的游戏流畅度,让玩家体验飞跃性的顺滑——不止是技巧,更是艺术的追求
【8月更文挑战第31天】《Unity性能优化实战:让你的游戏流畅如飞》详细介绍了Unity游戏性能优化的关键技巧,涵盖资源管理、代码优化、场景管理和内存管理等方面。通过具体示例,如纹理打包、异步加载、协程使用及LOD技术,帮助开发者打造高效流畅的游戏体验。文中提供了实用代码片段,助力减少内存消耗、提升渲染效率,确保游戏运行丝滑顺畅。性能优化是一个持续过程,需不断测试调整以达最佳效果。
104 0
|
6月前
|
人工智能 图形学
【unity小技巧】使用动画状态机脚本实现一个简单3d敌人AI功能
【unity小技巧】使用动画状态机脚本实现一个简单3d敌人AI功能
65 0
|
6月前
|
人工智能 定位技术 图形学
【Unity小技巧】一个脚本实现控制3D远程/近战敌人AI
【Unity小技巧】一个脚本实现控制3D远程/近战敌人AI
59 0
|
6月前
|
自然语言处理 图形学
【unity实战】一个通用的FPS枪支不同武器射击控制脚本
【unity实战】一个通用的FPS枪支不同武器射击控制脚本
106 0