查看: 913|回复: 0
打印 上一主题 下一主题

[其他] (转)使用unity3d需要注意到细节 (含timescale)

[复制链接]

9903

主题

126

听众

7万

积分

首席设计师

Rank: 8Rank: 8

纳金币
53480
精华
316

最佳新人 热心会员 灌水之王 活跃会员 突出贡献 荣誉管理 论坛元老

跳转到指定楼层
楼主
发表于 2016-5-29 02:28:13 |只看该作者 |倒序浏览
操作transform.localPosition的时候请小心
移动GameObject是非常平常的一件事情,一下代码看起来很简单:
transform.localPosition += [url=]new[/url] 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等于:
  1. ector3 offset = new Vector3( my.localPosition.x * parent.lossyScale.x,
  2.                               my.localPosition.y * parent.lossyScale.y,
  3.                               my.localPosition.z * parent.lossyScale.z );
  4. Vector3 worldPosition = parent.position + parent.rotation * offset;
复制代码
换句话说,上面这种直接操作localPosition的方式是在没有考虑scale计算的时候进行的,为了解决这个[color=rgb(85, 85, 85) !important]问题
,[color=rgb(85, 85, 85) !important]unity3d提供了Translate函数,所以正确的做法应该是:
  1. 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的类型,如:
  1. public float foobar = 1.0f;
复制代码
这样,这个变量不仅会在Inspector中出现,还可以在animation view中进行操作,生成AnimationClip供我们通过AnimationComponent调用。
范例:
  1. public class TestCurve : MonoBehaviour {
  2.     public float foobar = 0.0f;

  3.     IEnumerator Start () {
  4.         yield return new WaitForSeconds (2.0f);
  5.         animation.Play("foobar_op");
  6.         InvokeRepeating ( "LogFoobar", 0.0f, 0.2f );
  7.         yield return new WaitForSeconds (animation["foobar_op"].length);
  8.         CancelInvoke ("LogFoobar");
  9.     }

  10.     void LogFoobar () {
  11.         Debug.Log("foobar = " + foobar);
  12.     }
  13. }
复制代码

GetComopnent<T> 可以取父类类型
Unity3D 允许我们对MonoBehavior做派生,所以你可能有以下代码:

  1. <pre class="code csharp" name="code" style="color: rgb(0, 0, 0); white-space: pre-wrap;"><span class="kw1" style="line-height: 18px;">public</span> <span class="kw4" style="line-height: 18px;">class</span> foo <span class="sy0" style="line-height: 18px;">:</span> MonoBehaviour <span class="br0" style="line-height: 18px;">{</span>
  2.     <span class="sy0" style="line-height: 18px;">...</span>
  3. <span class="br0" style="line-height: 18px;">}</span>

  4. <span class="kw1" style="line-height: 18px;">public</span> <span class="kw4" style="line-height: 18px;">class</span> bar <span class="sy0" style="line-height: 18px;">:</span> foo <span class="br0" style="line-height: 18px;">{</span>
  5.     <span class="sy0" style="line-height: 18px;">...</span>
  6. <span class="br0" style="line-height: 18px;">}</span></pre>
复制代码

假设我们现在有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 [url=]new[/url] 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中。
  1. // class TestCortouine
  2. public class TestCoroutine : MonoBehaviour {
  3.     public IEnumerator CoLog ( string _name ) {
  4.         Debug.Log(_name + " hello foobar 01");
  5.         yield return new WaitForSeconds (2.0f);
  6.         Debug.Log(_name + " hello foobar 02");
  7.     }
  8. }

  9. // component attached on GameObject A
  10. public class A: MonoBehaviour {
  11.     public GameObject B;

  12.     void Start () {
  13.         TestCoroutine  compB = B.GetComponent<TestCoroutine>();

  14.         // GOOD, thread state in B
  15.         // same as: compB.StartCoroutine ( "CoLog", "B" );
  16.         compB.StartCoroutine ( compB.CoLog("B") );

  17.         // BAD, thread state in A
  18.         StartCoroutine ( compB.CoLog("A") );

  19.         Debug.Log("Bye bye A, we'll miss you");      
  20.         Destroy(gameObject); // T_T I don't want to die...
  21.     }       
  22. }
复制代码

以上代码,得到的结果将会是:
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 {}



分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

手机版|纳金网 ( 闽ICP备2021016425号-2/3

GMT+8, 2024-9-23 05:16 , Processed in 0.227624 second(s), 33 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部