选自过去1~2周 自己所看到外文内容:https://twitter.com/unity3d 和各种其他博客来源吧
1、 在Unity中 使用 IronPython
https://qiita.com/syutotyou/items/bd9f9a74a054934277c1
环境:
windows10
unity 2018.2.14f1
IronPython 2.7.9
https://ironpython.net/
在Unity项目中导入 在/ IronPython 2.7 / Platforms / Net35 下的
IronPython.dll
IronPython.Modules.dll
Microsoft.Dinamic.dll
Microsoft.Scripting.dll
Microsoft.Scripting.Core.dll
Microsoft.Scripting.Metadata.dll
到Unity 项目的 : Assets / Plugins 下, 然后就可以使用测试了。
2、 获取当前属于LayerMask (layer的集合)中的GameObject
public static GameObject[] FindGameObjectsWithLayerMask(LayerMask layerMask)
{
GameObject[] goArray = GameObject.FindObjectsOfType();
List goList = new List();
foreach (GameObject go in goArray) {
// LayerMask bit check
if (((1 << go.layer) & layerMask.value) != 0) {
goList.Add(go);
}
}
if (goList.Count == 0) {
return null;
}
return goList.ToArray();
}
3、 使用隔行扫描加速绘图
曾经有一种称为交错的技术。 一种提高FPS的技术。
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%AC%E3%83%BC%E3 %82%B9
https://qiita.com/drfters/items/3c4a06619950fa1218ae
它真的加速了吗?
以2688x1242分辨率 且应用后处理效果。
4、 [Unity]在实例化类和结构时GC Alloc的区别
using UnityEngine;
using UnityEngine.Profiling;
public class Example : MonoBehaviour
{
public class Hoge { }
public struct Fuga { }
private void Start()
{
var count = 1000000;
var samplerA = CustomSampler.Create( "AAAAAAAAAAAAAAAAAAAA" );
samplerA.Begin();
for ( int i = 0; i < count; i++ )
{
new Hoge();
}
samplerA.End();
var samplerB = CustomSampler.Create( "BBBBBBBBBBBBBBBBBBBB" );
samplerB.Begin();
for ( int i = 0; i < count; i++ )
{
new Fuga();
}
samplerB.End();
}
}
如果我们创建1,000,000 个Hoge类和Fuga结构的实例
5、 [Unity]获取游戏对象根路径的扩展方法
using UnityEngine;
public static class GameObjectExt
{
private static string GetRootPath( this GameObject gameObject )
{
var path = gameObject.name;
var parent = gameObject.transform.parent;
while ( parent != null )
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
}
using UnityEngine;
public class Test : MonoBehaviour
{
private void Awake()
{
Debug.Log( gameObject.GetRootPath() );
}
}
我要说的是其实Unity存在这个API, (我没有测试他们之间的性能, 感兴趣的可以试一试)
AnimationUtility.CalculateTransformPath
6、 [Unity]编辑器扩展,使Unity编辑器无法播放
using UnityEditor;
[InitializeOnLoad]
public static class Example
{
static Example()
{
EditorApplication.playModeStateChanged += OnChange;
}
private static void OnChange( PlayModeStateChange state )
{
if ( state == PlayModeStateChange.ExitingEditMode )
{
EditorApplication.isPlaying = false;
}
}
}
有什么用? 可以在Play之前做一些检查, 就行如果C#有编译报错Unity就无法Play一样。 如下检查示例,
[Unity]一个编辑器扩展,如果在Inspector中没有设置引用的项目,则阻止编辑器播放。
您可以通过将以上脚本添加到Unity项目的“Editor”文件夹中来使用它。
using JetBrains.Annotations;
using UnityEngine;
public class Test : MonoBehaviour
{
[NotNull] public Sprite m_sprite; // 例如,应用NotNull属性的变量
}
using Sirenix.OdinInspector;
using UnityEngine;
public class Test : MonoBehaviour
{
[Required] public Sprite m_sprite; // 对于应用了Odin的Required属性的变量
}
7、 Unity单元测试的变更 :
《Unity 2018单元测试 》 https://www.nowsprinting.com/entry/2019/04/01/000000
, 菜单变化, Assembly Definition变化, 是否Play模式的变化。 程序集依赖变化等。
《如果您不能从Unity中的测试代码引用您自己的类,该怎么办?》: https://qiita.com/kazukomati/items/d5f5bec204ce99ee7ad5
8、 如何获取一个static event 的注册数。
https://stackoverflow.com/questions/24003458/how-to-easilly-see-number-of-event-subscriptions-while-debugging
using System;
using System.Reflection;
public static class DelegateExt
{
public static int GetLength( string name )
{
return GetLength( typeof( T ), name );
}
public static int GetLength( this Type self, string name )
{
var attrs =
BindingFlags.GetField |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public
;
var field = self.GetField( name, attrs );
if ( field == null )
{
throw new ArgumentException( $"name is invalid parameter: {name}" );
}
var d = field.GetValue( null ) as Delegate;
if ( d == null ) return 0;
var list = d.GetInvocationList();
if ( list == null ) return 0;
var length = list.Length;
return length;
}
}
测试
using UnityEngine;
public class Example : MonoBehaviour
{
private void Awake()
{
var t = typeof( Application );
Application.focusChanged += Application_focusChanged ;
Application.lowMemory += Application_lowMemory ;
Application.lowMemory += Application_lowMemory ;
Application.logMessageReceived += Application_logMessageReceived ;
Application.logMessageReceived += Application_logMessageReceived ;
Application.logMessageReceived += Application_logMessageReceived ;
Application.logMessageReceivedThreaded += Application_logMessageReceived ;
Application.logMessageReceivedThreaded += Application_logMessageReceived ;
Application.logMessageReceivedThreaded += Application_logMessageReceived ;
Application.logMessageReceivedThreaded += Application_logMessageReceived ;
Application.quitting += Application_quitting ;
Application.quitting += Application_quitting ;
Application.quitting += Application_quitting ;
Application.quitting += Application_quitting ;
Application.quitting += Application_quitting ;
Debug.Log( t.GetLength( "focusChanged" ) );
Debug.Log( t.GetLength( "lowMemory" ) );
Debug.Log( t.GetLength( "s_LogCallbackHandler" ) );
Debug.Log( t.GetLength( "s_LogCallbackHandlerThreaded" ) );
Debug.Log( t.GetLength( "quitting" ) );
}
private void Application_focusChanged( bool obj ) { }
private void Application_lowMemory() { }
private void Application_logMessageReceived( string condition, string stackTrace, LogType type ) { }
private void Application_quitting() { }
private void OnDestroy()
{
Application.focusChanged -= Application_focusChanged ;
Application.lowMemory -= Application_lowMemory ;
Application.lowMemory -= Application_lowMemory ;
Application.logMessageReceived -= Application_logMessageReceived ;
Application.logMessageReceived -= Application_logMessageReceived ;
Application.logMessageReceived -= Application_logMessageReceived ;
Application.logMessageReceivedThreaded -= Application_logMessageReceived ;
Application.logMessageReceivedThreaded -= Application_logMessageReceived ;
Application.logMessageReceivedThreaded -= Application_logMessageReceived ;
Application.logMessageReceivedThreaded -= Application_logMessageReceived ;
Application.quitting -= Application_quitting ;
Application.quitting -= Application_quitting ;
Application.quitting -= Application_quitting ;
Application.quitting -= Application_quitting ;
Application.quitting -= Application_quitting ;
}
}
注意上方代码中: 例如,Application.logMessageReceived和Application.logMessageReceivedThreaded具有
s_LogCallbackHandler和s_LogCallbackHandlerThreaded的内部名称,因此
必须确保DelegateExt.GetLength中指定的名称正确。
using System.Reflection;
using System.Text;
using UnityEngine;
public class Example : MonoBehaviour
{
private void Awake()
{
var attrs =
BindingFlags.GetField |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public
;
var t = typeof( Application );
var fields = t.GetFields( attrs );
var sb = new StringBuilder();
foreach ( var n in fields )
{
sb.AppendLine( n.Name );
}
Debug.Log( sb.ToString() );
}
}
内部名称使用如上所述的反射
lowMemory
s_LogCallbackHandler
s_LogCallbackHandlerThreaded
OnAdvertisingIdentifierCallback
focusChanged
wantsToQuit
quitting
s_RegisterLogCallbackDeprecated
9、 垃圾回收问题:
网上有说 Unity GC.Collect() 不会立即执行垃圾回收? 有谁知道出处来源哪。Unity官方有说过么?
https://onevcat.com/2012/11/memory-in-unity3d/
http://ask.manew.com/question/36611
这里提到立即收集:
https://books.google.com.hk/books?id=80NADwAAQBAJ&pg=PA411&lpg=PA411&dq=Unity++GC.Collect()+immediately++%EF%BC%9F&source=bl&ots=6cNkzg5aCY&sig=ACfU3U0_zPtrjXSlO_uPdlZia_Ts7tjREw&hl=zh-CN&sa=X&ved=2ahUKEwizlv612b_hAhWbKqYKHT3nDjE4ChDoATABegQICBAB#v=onepage&q=Unity%20%20GC.Collect()%20immediately%20%20%EF%BC%9F&f=false
官方:
https://unity3d.com/de/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection-unity-games?playlist=44069
https://docs.unity3d.com/Manual/UnderstandingAutomaticMemoryManagement.html?_ga=2.245838532.133659043.1554634170-1366382285.1535636176
或者这种代码. 具体什么结论 不知道~。
10、 [Unity] 仅描绘模型轮廓的着色器
https://qiita.com/HhotateA/items/6b08dff9babdb08e9f77
11、比较DI框架Zenject和Spring
https://qiita.com/segur/items/d0b636529483af6cb1c4
可能是因为C#和Java相似,但我认为Zenject和Spring非常相似。
Spring是一个Web后端开发,Zenject是一个游戏系统开发,使用场景完全不同,我再次认为在其中任何一个中使用的DI架构真的很棒。
12、 项目在优化, 发现跨语言的交互 还是特别消耗的, C# <=> Lua, C# <=> C/C++
Unity项目优化中肯定会提到缓存Transform 组件等等。
下面的博文, 作者用了很多中方式来实现缓存组件的功能。 每一种都会有性能比较。
https://habr.com/ru/post/303562/
https://github.com/KonH/UnityCache
13、 看到一篇博文 其中提到一下工具 : C#是一种低级语言?https://habr.com/ru/post/443804/
InliningAnalyzer 内联分析仪
https://marketplace.visualstudio.com/items?itemName=StephanZehetner.InliningAnalyzer
内联分析器显示方法调用是否将由 JIT 编译器内联。方法调用会在源代码中突出显示, 并显示 JIT 编译器给出的内联失败的原因。
内联是一种编译器优化, 它将函数调用站点替换为被调用方的主体。在公共语言运行时, 这是由 JIT 编译器完成的。对于性能关键代码, 了解方法是否内联可能很重要。更多的内联通常更好, 但并不总是这样。如果调整代码, 请使用要内联的方法, 始终使用探查器或基准库来测试它是否确实对您的方案产生了影响。
其他工具: VS2019 Add-in -- 单击任何方法或类以查看.NET Core的JIT为它们生成的内容(ASM)。 https://github.com/EgorBo/Disasmo
或者是这个网站, 之前介绍过 SharpLab.io
类似的 C++ 的汇编代码查看工具: https://godbolt.org/z/l2QZLY
14、 一个比较新的 开源游戏引擎 , 也是基于C# 的 。
https://habr.com/ru/post/441608/
https://www.duality2d.net/
https://github.com/AdamsLair/duality/
像评论中提到的, 同样基于C#开源的Defold或Godot Engine。 旧XNA / Monogame 。 都是很好的学习资料。
15、 20个游戏教孩子编程
https://habr.com/ru/post/440376/
16、 今天看Unity的文档发现一个工具:
https://plugins.jetbrains.com/plugin/11701-heap-allocations-viewer/update/56415
代码中会有特殊颜色的下划线标记问题:
类似的工具还搜索到 :
https://www.youtube.com/watch?v=Tw-wgT-cXYU
https://github.com/Microsoft/RoslynClrHeapAllocationAnalyzer
https://marketplace.visualstudio.com/items?itemName=MukulSabharwal.ClrHeapAllocationAnalyzer
17、 用一只小型汽车为您的鼠标围绕桌面进行竞赛。
说明:
免责声明:由于应用程序窃取焦点,上下文菜单无法运行。 在Explorer中使用时,可能还会发生一些 奇怪的事情。
免费下载 : https://papercookies.itch.io/cursor-car
18、Debug.Log 原来支持两个参数, 第二个参数就是当点击log的时候可以 让对象高亮
19、 相机一旦固定视角(能看到的角度范围有限),会带来很多优化方案, 比如场景模型制作的时候直接背面剔除。 天空盒的处理等。
下面是
“这是我在短途徒步旅行中用来渲染海洋的一个小技巧! 水只是跟随玩家的飞机。 纹理和顶点动画都在世界空间中完成!”
https://twitter.com/adamgryu/status/1112783007566450691
20、 绝对建议在Unity的实体组件系统上查看@Icetigris的面向数据设计 ppt !
主要解释 什么是 ECS , 通过比较 面向对象和 面向数据的设计差异。 特别是对高速缓存的利用上的优缺点。
《GDC 2019 Understanding Data-Oriented Design for Entity Component Systems》
很好地可视化为什么DOD不仅有利于缓存性能,还可以大大简化您的应用程序代码!
https://docs.google.com/presentation/d/1s8XV63Dy092i1FSfUFUvfXrne2OmisrlxM5zbr4F8gQ/edit#slide=id.g52aa31112b_0_139
可以在CSDN资源 中搜索 下载: GDC 2019 Understanding Data-Oriented Design for Entity Component Systems
============================================= 性能分析 =======================
21、 让结构体更快。
结构体跟类比 速度上要快的。 结构非常适合控制内存布局和避免使用GC
。 下面两个结构体, 他们的内存占用不同, 这里测试 更新位置:player.Position += player.Velocity * time.
struct PlayerExtras
{
public Vector3 Position; // 3 * 4 = 12
public Vector3 Velocity; // 3 * 4 = 12
public int Health; // 4
public int MaxHealth; // 4
public int NumLives; // 4
public int Score; // 4
public int TeamId; // 4
public int LeftHandWeaponId; // 4
public int RightHandWeaponId; // 4
public int NumWins; // 4
public int NumLosses; // 4
public int MatchmakingRank; // 4
// Total: 64
}
struct PlayerBasics
{
public Vector3 Position; // 3 * 4 = 12
public Vector3 Velocity; // 3 * 4 = 12
public int Health; // 4
public int MaxHealth; // 4
// Total: 32
}
结果 Basis这种结构 速度快。
原因其实和 Unity推出的 ECS 差不多, 更好的利用CPU的高速缓存, 增加命中率。
https://jacksondunstan.com/articles/5131
正如这个 https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html 页面整齐地显示的那样,对于数组的一半元素使用CPU缓存意味着我们只需要等待1-4纳秒的内存而不是100纳秒来从RAM中获取它。25-100倍的加速为我们提供了巨大的速度提升,使得使用结构的“Basis”版本的速度更快。
通常,如果需要高效迭代结构,请尽量让结构很小。这样可以提高代码的性能,即使不通过更有效地使用CPU缓存来改变代码也是如此。
注意: 顺便在说一些, 《 使用对象句柄使结构更有用》
结构可以是保持垃圾收集器不受影响并更有效地使用CPU数据缓存的好方法。如果您的结构中包含任何引用类型【最常见的就是string】字段,则无法再使用sizeof(MyStruct)。这确实限制了它的实用性。 string我们存储的东西不是直接存储在结构中,而是存储可用于获取string的。可以用一个简单的int。因此,我们将在结构体外部存储托管对象,并使用它int来识别个体object。
https://jacksondunstan.com/articles/3860
L1缓存访问:1纳秒
L2缓存访问:4纳秒
RAM访问:100纳秒
这意味着使用CPU缓存中的内存与RAM中的内存相比,有25-100倍的优势。
粗略地说,通过CPU缓存和RAM访问时间,我们通过使用缓存友好的结构体而不是类来获得13.46倍的加速。
解决方式是避免 虚函数virtual function调用 和 Delegates
22、 C# 语言中集合使用的常见错误。
https://jacksondunstan.com/articles/5145
①、 字典查找调用两边
if (myDictionary.Contains(myKey)) { int value = myDictionary[myKey];
最好是 :
int value; if (myDictionary.TryGetValue(myKey, out value))
②、没有设置初始容量
没有初始容量, 我们并不总是事先知道正确的尺寸,但是一个好的猜测至少可以节省大部分的重新分配和复制。
集合类型List
如何没有指定, 反编译发现。
private static readonly T[] EmptyArray = new T[0]; public List() { _items = EmptyArray; }
构造的数组大小就是0 , 之后会有比较多的重新分配 1,2,4,8,16,32,64 等等。
至于Dictionary
public Dictionary ()
{ Init (10,null ); }
③、结构体装箱。
这种情况发生在类似的泛型类中List
已知的是 枚举作为字典的Key 会产生装箱。 自行百度 怎么避免。
其它情况就是比较 Object.Equals 时需要装箱。
struct IntStruct { public int Value; }
return list.Contains(new IntStruct { Value = 1 }); // 此时Contains会装箱
return dict[new IntStruct { Value = 1 }];
一个因为要得到 Object.GetHashCode 哈希值。 一个是 Object.Equals ,都是object操作。
如何避免 请参考文章: https://jacksondunstan.com/articles/5148
④、 使用LINQ而不是内置方法
LINQ提供的扩展System.Linq方法通常具有与集合类型(如List和Dictionary)提供的内置名称相似的名称。
在使用的时候注意。
⑤ 、参数传递接口
集合类型实现了许多接口。List
使用的时候就可能这样
void TakeList(List
void TakeInterface(IList
List
TakeList(list); // 传递类 list.Add(1); //非虚方法调用 快点。
TakeInterface(list); // 传递接口(多态) list.Add(1); //虚方法调用。慢点。
而且 接口类型IList
List
评论中: 关于最后两点,发现LinqFaster(https://github.com/jackmott/LinqFaster)在使用Lists时是LINQ的绝佳替代品。
23、 现在Unity的代码都是选择 il2cpp , mono 方式很快要废弃了。
看到一个系列, 可以更好的了解细节。
C#7.3的IL2CPP输出:元组
C#7.3的IL2CPP输出:模式匹配
C#7.3的IL2CPP输出:ref返回值和局部变量
C#7.3的IL2CPP输出:本地功能fixed,和stackalloc
C#7.3的IL2CPP输出:其他一切
IL2CPP Output for Iterators, Switch, and Using
IL2CPP Output for 无用代码的影响
IL2CPP中的C#6
IL2CPP输出:readonly,sizeof,IntPtr,typeof,GetType
IL2CPP减速的常见功能
不安全代码的IL2CPP输出
C#如何查看生成Il2CPP/C++代码什么样
IL2CPP中的循环
IL2CPP Output: Abstract Classes, Sealed Classes, and Delegates
IL2CPP Function and Boxing Costs
另外三个IL2CPP令人惊讶: 字符串文字,泛型,异常
三个IL2CPP优化 :转换,数组边界检查和空检查。
在编译时获取结构的大小:我们知道,我们的游戏是要在任的x86或ARM处理器和运行x,y以及z字段将是连续的。每个float字段是四个字节,因此向量的大小为12个字节。不幸的是,1)struct包含非值类型,例如a string。应该将结构转换为使用对象句柄,但直到那时我们根本无法知道它的大小。 2)第二个原因是struct包含一个指针。指针具有不同的大小。 文章中提供一个Editor脚本,生成一个名为的文件TypeSizes.cs。它包含一个静态类,其中包含静态字段,指示游戏中没有任何object字段的所有结构的大小。当没有指针字段时,它使用const。当有指针字段,它使用static readonly和sizeof。
阅读IL2CPP输出时遇到的三个惊喜:建议:考虑使用常量和参数而不是静态变量。建议:考虑使用自定义构造函数而不是默认构造函数和对象初始值设定项。建议:当需要保存每个实例的内存时,请考虑使用结构而不是类。
Unity 2018.3中的作业安全(Job-Safe)API.
下过下面编辑器脚本打印出所有 安全API . 目前仅在macOS上运行,但如果需要,可以通过更改managedDirPath以下内容轻松修改以支持Windows或Linux :
现在高达301作业安全的API,但很多都被Unity 给private或者internal
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEditor;
public static class JobSafeMenuItem
{
[MenuItem("Help/List Job Safe APIs")]
private static void JobSafe()
{
#if !UNITY_EDITOR_OSX
throw new Exception("Only macOS is supported at this time");
#endif
string managedDirPath = Path.Combine(
Path.Combine(
EditorApplication.applicationPath,
"Contents"),
"Managed");
Type nativeMethodAttribute = Assembly.LoadFile(
Path.Combine(
Path.Combine(
managedDirPath,
"UnityEngine"),
"UnityEngine.SharedInternalsModule.dll"))
.GetType("UnityEngine.Bindings.NativeMethodAttribute");
string[] paths = Directory.GetFiles(
managedDirPath,
"*.dll",
SearchOption.AllDirectories);
var methodsByType = new Dictionary>(512);
object[] emptyParameters = { };
void AddIfThreadSafe(Type type, Attribute attribute, string method)
{
if (nativeMethodAttribute.IsInstanceOfType(attribute)
&& (bool)nativeMethodAttribute
.GetProperty("IsThreadSafe")
.GetGetMethod()
.Invoke(attribute, emptyParameters))
{
List list;
if (!methodsByType.TryGetValue(type, out list))
{
list = new List(32);
methodsByType[type] = list;
}
list.Add(method);
}
}
BindingFlags allFlags =
BindingFlags.NonPublic
| BindingFlags.Static
| BindingFlags.Instance;
foreach (var path in paths)
{
Assembly assembly = Assembly.LoadFile(path);
foreach (Type type in assembly.GetTypes())
{
foreach (MethodInfo meth in type.GetMethods(allFlags))
{
foreach (Attribute attribute in meth.GetCustomAttributes())
{
AddIfThreadSafe(type, attribute, meth.ToString());
}
}
foreach (PropertyInfo prop in type.GetProperties(allFlags))
{
foreach (Attribute attribute in prop.GetCustomAttributes())
{
AddIfThreadSafe(type, attribute, prop.ToString());
}
}
}
}
StringBuilder builder = new StringBuilder(1024 * 10);
var sortedMethodsByType = new List>>(
methodsByType);
sortedMethodsByType.Sort((a, b) => a.Key.Name.CompareTo(b.Key.Name));
foreach (var pair in sortedMethodsByType)
{
pair.Value.Sort();
foreach (string method in pair.Value)
{
builder.Append(pair.Key.Name);
builder.Append(": ");
builder.AppendLine(method);
}
}
Debug.Log(builder.ToString());
EditorGUIUtility.systemCopyBuffer = builder.ToString(); // 复制到系统剪贴板。将其粘贴到文本编辑器中以查看完整结果。
}
}
24、 FixedUpdate如何工作 ?
早些年做个测试一个测试 , 但是还是为了测试 使用协程倒计时和 正常倒计时是否有偏差。 测试结果肯定是有偏差的。 当时顺便测试 TimeScale的 Update, FixedUpdate 的影响。
log结果上看 FixedUpdate 的执行频率确认相当有规律。 那是因为在正常情况下。
阅读 文章 https://jacksondunstan.com/articles/4824 请注意,有时FixedUpdate在一个帧中有两个调用,有时只有一个调用。
当 FixedUpdate 比FPS 更频繁地调用,这意味着不是每个帧都会调用FixedUpdate。
假设我们的游戏由于某种原因表现非常糟糕,我们的每帧运行速度为5 FPS,总共200毫秒。
然后我们将所需的FixedUpdate速率设置为100 Hz,这应该意味着每10毫秒调用一次。最后,我们将最大更新间隔配置为FixedUpdate50毫秒Edit > Project Settings > Time。以下是此方案的表现:
在这种情况下, FixedUpdate当应该被调用20次时,但是实际上只调用10次。
首先,Unity确实会尝试以FixedUpdate所需的速率调用。但是,这些调用需要适合离散帧。这意味着当Unity动态调整到实际帧速率时,任何给定帧上可能会有零个,一个或多个调用。这也意味着调用FixedUpdate不一定在指定的时间间隔内。
当FixedUpdate函数本身是帧速率减慢的原因时,会发生一个特别令人讨厌的情况。这很可能导致Unity试图“赶上”额外的调用,FixedUpdate以便平均所需的速率。这些额外的调用将进一步降低帧速率,因为FixedUpdate在这种情况下很昂贵的。情况可能会复合,直到达到最大间隔。此时帧速率可能已经完全消失。