收录提交入口,利于优化的网站模板,广东省政务服务网,广州开发小程序1.Avoid Find() and SendMessage() at runtime SendMessage() 方法和 GameObject.Find() 相关的一系列方法都是开销非常大的。SendMessage()函数调用的耗时大约是一个普通函数调用的2000倍#xff0c;GameObject.Find() 则和场景的复杂度相关#xff0c;场景越复杂#xff0…1.Avoid Find() and SendMessage() at runtime SendMessage() 方法和 GameObject.Find() 相关的一系列方法都是开销非常大的。SendMessage()函数调用的耗时大约是一个普通函数调用的2000倍GameObject.Find() 则和场景的复杂度相关场景越复杂调用自然越慢。有时候在场景的初始化时比如Awake和Start函数中调用Find函数是可以理解的但是即使是这样也应该是用来获取一定已经存在于这个场景的objects并且场景内的物体最好越少越好。在runtime运行时调用这些函数都将会带来很大的开销从而有可能会引起帧率下降。 依赖Find()和SendMessage()函数是架构设计代码设计中一个典型不给力的表现 这通常是初学者经常犯的错尽管Unity已经在文档中一遍又一遍的建议大家避免使用这些函数。
尽可能的不使用这些函数甚至打破了我们之前的原则只有需要优化的时候再优化不要过度的提前优化。为了避免使用这些函数我们应该在代码的原型阶段就设计好。
让我们举个栗子下边的是一个简单的EnemyManagerComponent类它的作用是存储一个GameObject的List用来表示游戏中的敌人并且提供一个KillAll()方法在需要的时候去销毁他们 之后我们会在Scene中放置一个GameObject并且加入这个脚本把这个物体叫做EnemyManager。
下边的代码会通过Prefab生成一些敌人并且通知EnemyManager它们的存在 在循环体内初始化数据和调用函数是一个很危险的行为这会有可能带来很差的性能表现但我们调用的是开销很大的函数例如Find时我们应该尽可能的寻找方法减少调用次数。因此一个优化点就是用一个本地变量用于保存将函数调用提到循环外边。另一个很重要的优化是用GetComponet代替SendMessage这开销会小很多。优化后的代码如下 有很多种方法都能给这个小节提出的问题带来优化每一种有各自的优缺点 1.用已经存在的Object保存引用
2.Static 类
3.单例
4.全局的消息系统 2.Assigning references to preexisting objects
一个简单的解决 interobject communication 问题的方法是使用Unity内置的序列化系统可以解决。也就是俗称的在Hierarchy里直接拖拽GameObject或者Prefab到面板对应变量上。但是public属性违反了类的封装原则这是很危险的因此可以使用[SerializeField]这个attribute特性这样可以使得private 和 protect的属性也能在Inspector中序列化。比如下边的例子 但是要注意的是拖拽的操作有可能拖拽不合适类型的物体或者忘记处理而变成null。还有就是Unity不能序列化static 和 readonly修饰的变量和属性等。 3.Static Classes
虽然Static Classes有不方便调试不便于修改和扩展功能在系统中到处直接引用等等缺点但是却是目前非常简便的一种解决方案。单例模式是一种非常普遍常用的设计模式它保证内存中同一类型只有一个实例。单例模式在处理重度的数据传输比如读取文件下载解析等时非常适合。单例未必需要是全局的它们最重要的特性是只有一个实例。单例最简单的一种实现方法就是通过C#的Static Class静态类。
把上一小节中的例子改成用Static Class实现代码如下 Static Class中所有的方法属性等都必须是static类型Static Class中的字段可以直接初始化也可以通过构造函数初始化。 Static Class的缺点是没办法和Unity中的Inspector window结合也就是没法像Monobehaviours一样使用有时候就得写一个匹配的辅助类来帮忙 尽管有这些缺点但是使用Static Classes这个方法也要比使用Find和SendMessage强的多。
4.Singleton Components
前一节提到过Static Class无法和Unity一起顺利工作不能使用MonoBehaviour的各种特性也不能在运行时在Inspector window中看到从而难于调试。因此实现一个基于MonoBehaviour的单例是一个不错的解决方案 最简单的使用方式如下边代码所示 这个方案的缺点是要注意有可能DontDestroyOnLoad(永远不会被调用到最好是在子类的Awake防范重调用下。当然在有些时候在切换场景时销毁再重新创建也是不错的选择一切都根据使用场景决定。
另外要注意的是OnDestroy的危险比如观察者模式很多物体的取消注册时机都写在了OnDestroy函数里但是Unity并不保证OnDestroy的时序因此有可能当某个物体调用自己的OnDestroy时调用到了已经销毁的单例这有可能带来致命的错误。
最好的解决方案就是永远不要在OnDestroy函数里调用单例但是如果非要使用解决方案如下
第一步我们需要加个标志用来跟踪单例是否存活 第二步要提供一个途径来获取单例是否存活的状态 最后任何在Destroy函数里调用单例的地方都要先去验证 这个单例方案也使用到了Find方法但是只是在初始化时调用一次因此还可以接受。但是它的初始化时机可能并不是在场景的初始化时而是在第一次使用时因此有可能在那个时机会给性能带来影响。因此也可以在场景的初始化时调用单例来保证其在场景初始化时就初始化好。
另外一个缺点就是如果以后我们想改掉这个单例模式变成可能有多个实例或者想把它变得更模块化代码的改动量将会非常大。