使用unity3d需要注意到細節


操作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);
    }
}

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的時候耦合性更低。

Invoke, yield 等函數會受 Time.timeScale 影響

Unity3D提供了一個十分方便的調節時間的函數Time.timeScale。對於初次使用Unity3D的使用者,會誤導性的認為Time.timeScale同樣可以適用於游戲中的暫停(Pause)和開始(Resume)。所以很多人有習慣寫:

Time.timeScale = 0.0f

對於游戲的暫停/開始,是游戲系統設計的一部分,而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

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()的調用將不會起任何作用。

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: compB.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 01
A hello foobar 01
Bye bye A, we'll miss you
B 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 {
}






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM