尊敬的高级会员,您现在拥有了强大的天眼免回复查看能力!
——————————————————————————————————————————————————————————————————————————————————
——————————————————————————————————————————————————————————————————————————————————
1.操作transform.localPosition的时候请小心
移动GameObject是非常平常的一件事情,以下代码看起来很简单:
-
transform.localPosition += new Vector3 ( 10.0f * Time.deltaTime, 0.0f, 0.0f );
复制代码
但是小心了,假设上面这个GameObject有一个parent, 并且这个parent GameObject的localScale是(2.0f,2.0f,2.0f)。你的GameObject将会移动20.0个单位/秒。
因为该 GameObject的world position等于:
-
Vector3 offset = new Vector3( my.localPosition.x * parent.lossyScale.x, my.localPosition.y * parent.lossyScale.y, my.localPosition.z * parent.lossyScale.z );Vector3 worldPosition = parent.position + parent.rotation * offset;
复制代码
换句话说,上面这种直接操作localPosition的方式是在没有考虑scale计算的时候进行的,为了解决这个问题,Unity3d提供了Translate函数,
所以正确的做法应该是:
-
transform.Translate ( 10.0f * Time.deltaTime, 0.0f, 0.0f );
复制代码
曝出在Inspector的变量同样的也能被Animation View Editor所使用
有时候我们会想用Unity3D自带的Animation View Editor来做一些简单的动画操作。而Animation Editor不仅可以操作Unity3D自身的component,
还可以操作我们自定义的MonoBehavior中的各个Property。所以加入 你有个float值需要用曲线操作,你可以简单的将它曝出到成可以serialize的类型,如:
-
public float foobar = 1.0f;
复制代码
这样,这个变量不仅会在Inspector中出现,还可以在animation view中进行操作,生成AnimationClip供我们通过AnimationComponent调用。
范例:
-
public class TestCurve : MonoBehaviour
-
{
-
public float foobar = 0.0f;
-
IEnumerator Start ()
-
{
-
yield return new WaitForSeconds (2.0f);
-
animation.Play(“foobar_op”);
-
InvokeRepeating ( “LogFoobar”, 0.0f, 0.2f );
-
yield return new WaitForSeconds (animation[“foobar_op”].length);
-
CancelInvoke (“LogFoobar”);
-
}
-
void LogFoobar ()
-
{
-
Debug.Log(“foobar = ” + foobar); }}
复制代码
2.GetComopnent<T> 可以取父类类型
Unity3D 允许我们对MonoBehavior做派生,所以你可能有以下代码:
-
public class foo : MonoBehaviour { …} public class bar : foo { …}
复制代码
假设我们现在有A,B两个GameObject, A包含foo, B包含bar, 当我们写
-
foo comp1 = A.GetComponent<foo>();bar comp2 = B.GetComponent<bar>();
复制代码
可以看到comp1, comp2都得到了应得的Component。那如果我们对B的操作改成:
-
foo comp2 = B.GetComponent<foo>();
复制代码
答案是comp2还是会返回bar Component并且转换为foo类型。你同样可以用向下转换得到有效变量:
bar comp2_bar = comp2 as bar;
合理利用GetComponent<base_type>()可以让我们设计Component的时候耦合性更低。
3.Invoke, yield 等函数会受 Time.timeScale 影响
Unity3D提供了一个十分方便的调节时间的函数Time.timeScale。对于初次使用Unity3D的使用者,
会误导性的认为Time.timeScale同样可以适用于游戏中的暂停(Pause)和开始(Resume)。
所以很多人有习惯写:
对于游戏的暂停/开始,是游戏系统设计的一部分,而Time.timeScale不不是用于这个部分的操作。
正确的做法应该是搜集需要暂停的脚本或 GameObject,
通过设置他们的enabled = false 来停止他们的脚本活动或者通过特定函数来设置这些物件暂停时需要关闭那些操作。
Time.timeScale 更多的是用于游戏中慢镜头的播放等操作,在服务器端主导的游戏中更应该避免此类操作。
值得一提的是,Unity3D的许多时间相关的函数都和 timeScale挂钩,而timeScale = 0.0f将使这些函数或动画处于完全停止的状态,这也是为什么它不适合做暂停操作的主要原因。
这里列出受timeScale影响的一些主要函数和Component:
MonoBehaviour.Invoke(…)
MonoBehaviour.InvokeRepeating(…)
yield WaitForSeconds(…)
GameObject.Destroy(…)
Animation Component
Time.time, Time.deltaTime
…
4.Coroutine 和 IEnumerator的关系
初写Unity3D C#脚本的时候,我们经常会犯的错误是调用Coroutine函数忘记使用StartCoroutine的方式。如:
TestCoroutine.cs
-
IEnumerator CoLog () { yield return new WaitForSeconds (2.0f); Debug.Log(“hello foobar”);}
复制代码
当我们用以下代码去调用上述函数:
-
TestCoroutine testCo = GetComponent<TestCoroutine>();testCo.CoLog ();testCo.StartCoroutine ( “CoLog” );
复制代码
那么testCo.CoLog()的调用将不会起任何作用。
5.StartCoroutine, InvokeRepeating 和其调用者关联
通常我们只在一份GameObject中去调用StartCoroutine或者InvokeRepeating,
我们写:
-
StartCoroutine ( Foobar() );InvokeRepeating ( “Foobar”, 0.0f, 0.1f );
复制代码
所以如果这个GameObject被disable或者destroy了,这些coroutine和invokes将会被取消。就好比我们手动调用:
-
StopAllCoroutines ();CancelInvoke ();
复制代码
这看上去很美妙,对于AI来说,这就像告诉一个NPC你已经死了,你自己的那些小动作就都听下来吧。
但是注意了,假如这样的代码用在了一个Manager类型的控制AI上,他有可能去控制其他的AI, 也有可能通过Invoke, Coroutine去做一些微线程的操作,这个时候就要明确StartCoroutine或者InvokeRepeating的调用者的设计。讨论之前我 们先要理解,StartCoroutine或InvokeRepeating的调用会在该MonoBehavior中开启一份Thread State, 并将需要操作的函数,变量以及计时器放入这份Stack中通过并在引擎每帧Update的最后,Renderer渲染之前统一做处理。所以如果这个 MonoBehavior被Destroy了,那么这份Thread State也就随之消失,那么所有他存储的调用也就失效了。
如果有两份GameObject A和B, 他们互相知道对方,假如A中通过StartCoroutine或InvokeRepeating去调用B的函数从而控制B,这个时候Thread State是存放在A里,当A被disable或者destroy了,这些可能需要一段时间的控制函数也就失效了,这个时候B明明还没死,也不会动了。更 好的做法是让在A的函数中通过B.StartCoroutine ( … ) 让这份Thread State存放于B中。
-
// class TestCortouine
-
public class TestCoroutine : MonoBehaviour
-
{
-
public IEnumerator CoLog ( string _name )
-
{
-
Debug.Log(_name + ” hello foobar 01″);
-
yield return new WaitForSeconds (2.0f);
-
Debug.Log(_name + ” hello foobar 02″); }}
-
// component attached on GameObject A
-
public class A: MonoBehaviour
-
{
-
public GameObject B; void Start ()
-
{ TestCoroutine compB = B.GetComponent<TestCoroutine>();
-
// GOOD, thread state in B
-
// same as: comp
-
B.StartCoroutine ( “CoLog”, “B” );
-
compB.StartCoroutine ( compB.CoLog(“B”) );
-
// BAD, thread state in A
-
StartCoroutine ( compB.CoLog(“A”) );
-
Debug.Log(“Bye bye A, we'll miss you”);
-
Destroy(gameObject);
-
// T_T I don't want to die… }}
复制代码
以上代码,得到的结果将会是:
B hello foobar 01A hello foobar 01Bye bye A, we'll miss youB hello foobar 02
如不需要Start, Update, LateUpdate函数,请去掉他们
当你的脚本里没有任何Start, Update, LateUpdate函数的时候,Unity3D将不会将它们加入到他的Update List中,有利于脚本整体效率的提升。
我们可以从这两个脚本中看到区别:
Update_01.cs
-
public class Update_01 : MonoBehaviour { void Start () {} void Update () {}}
复制代码
Update_02.cs
-
public class Update_02 : MonoBehaviour {
-
}
复制代码
===========================================分割线==============
1.减少固定增量时间
将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。
2.减少GetComponent的调用使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为”缓存”的引用)。
例如,如果您使用如下的代码:
-
function Update ()
-
{
-
transform.Translate(0, 1, 0);
-
}
复制代码
通过下面的更改您将获得更好的性能:
-
var myTransform : Transform;
-
-
function Awake () {
-
-
myTransform = transform;
-
-
}
-
-
function Update () {
-
-
myTransform.Translate(0, 1, 0);
-
-
}
复制代码
3.避免分配内存
您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。
您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。
同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。
这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。
但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于”传值”操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。
4.最小化GUI
使用GUILayout 函数可以很方便地将GUI元素进行自动布局。然而,这种自动化自然也附带着一定的处理开销。
您可以通过手动的GUI功能布局来避免这种开销。
此外,您也可以设置一个脚本的useGUILayout变量为 false来完全禁用GUI布局:
-
function Awake () {
-
useGUILayout = false;
-
}
复制代码
5.使用iOS脚本调用优化功能
UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。
从Mono的脚本调用 C/C++函数也存在着一定的性能开销。
您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。
此设置的选项有:
Slow and Safe – Mono内部默认的处理异常的调用
Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。
不过,它并不支持异常,因此应谨慎使用。
它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。
6.优化垃圾回收
如上文所述,您应该尽量避免分配操作。
但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用:
如果堆比较小,则进行快速而频繁的垃圾回收
这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。
像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。
如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,
垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。
因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。
通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),
但它们可以快速处理并且对游戏的影响很小:
-
if (Time.frameCount % 30 == 0)
-
{
-
System.GC.Collect();
-
}
复制代码
但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间
如果堆比较大,则进行缓慢且不频繁的垃圾回收
这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。
如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。
但是,Mono运行时会尽可能地避免堆的自动扩大。
因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的”无用”对象):
-
function Start()
-
{
-
var tmp = new System.Object[1024];
-
// make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks
-
for (var i : int = 0; i < 1024; i++)
-
tmp = new byte[1024];
-
// release reference
-
tmp = null;
-
}
复制代码
游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:
System.GC.Collect();
另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。
===分割线==========================
1.粒子系统运行在iPhone上时很慢,怎么办?
答:iPhone拥有相对较低的fillrate 。
如果您的粒子效果覆盖大部分的屏幕,而且是multiple layers的,这样即使最简单的shader,也能让iPhone傻眼。
我们建议把您的粒子效果baking成纹理序列图。
然后在运行时可以使用1-2个粒子,通过动画纹理来显示它们。这种方式可以取得很好的效果,以最小的代价。