unity

GameObject.Instantate前后对字段赋值在生命周

2018-11-02  本文已影响4人  雨落随风

有些问题不去试试,不清不楚不方便搞事情,就像笔者今天做的这个备忘一样。

写在前面

实验流程

1 .测试用c#类:

using UnityEngine;

public class TestInstantatLifeCycle : MonoBehaviour {
    GameObject go;
    void Update () {
        if (Input.GetMouseButtonDown(2))
        {
            TestLifecycle();
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            if (null != go)
            {
                Debug.Log("重新加载Prefab");
                TestLifecycle(true);
            }
        }
    }
    #region TestCode
    private void TestLifecycle(bool ifReload = false)
    {
        if (null == go || ifReload)
        {
            go = Resources.Load<GameObject>("一个预制体");
        }

        Debug.Log("加载资源!");
        TestChangeFieldValueAfterResourceLoad before = go.GetComponent<TestChangeFieldValueAfterResourceLoad>();
        Debug.Log((null == before) + ":" + (null == go));
        Debug.Log("1.赋值前 " + before.AA+":"+before.BB);
        before.AA = "After Resources.Load";
        before.BB = new TestRefrenceField();
        Debug.Log("2.赋值后 " + go.GetInstanceID() + " : " + before.AA + " : " + before.GetInstanceID()+":"+before.BB.BB); //编辑模式Resource加载后实例化前赋值字段会导致Resource目录下的预制物字段的值被修改。 运行模式下只修改内存值,app重开恢复默认。
        Debug.Log("实例化前");
        GameObject go_after = GameObject.Instantiate(go);
        Debug.Log("实例化了");
        TestChangeFieldValueAfterResourceLoad after = go_after.GetComponent<TestChangeFieldValueAfterResourceLoad>();      //引用的参数,如果没有标记可序列化,Resource.Load之后修改了数据在instantate时依旧加载默认值,不能序列化的类默认值null。
        Debug.Log("3.实例化后 " + go_after.GetInstanceID() + " : " + after.AA + " : " + after.GetInstanceID()+" : "+after.BB.BB); 
        after.AA = "xxx实例化后 ";
        after.BB = new TestRefrenceField() {BB="实例化后赋值" };
        Debug.Log("4.实例化后赋值 " + go_after.GetInstanceID() + " : " + after.AA + " : " + after.GetInstanceID()+" : "+after.BB.BB);  //实例化后赋值,Awake 里面用的还是初始值,Start里面会受到影响。 也就是Awake里面加载UI组件,start订阅事件,这样保证就算是事件被触发了,事件里面用到的数据也是最新的
    }
    #endregion
}

  1. 游戏对象上将挂载的组件,含 2 个字段,一个值类型,一个引用类型。
using UnityEngine;
public class TestChangeFieldValueAfterResourceLoad : MonoBehaviour
{
    public string AA = "初始值";
    public TestRefrenceField BB;
    private void Awake()
    {
        Debug.Log("Awake" + AA);
        BB = new TestRefrenceField()
        {
            BB="Inside and Awake"
        };
        Debug.Log(BB.BB);
    }
    void Start()
    {
        Debug.Log("Start" + AA);
        Debug.Log(BB.BB);
    }
}
public class TestRefrenceField
{
    public string BB = "AOE";
}
  1. 触发测试的方法,输出如下:


    Debug输出

经验总结

  1. 常识性的结论:

    • Resource.Load 是将 Prefab 二进制文件 通过实例化方式加载到内存,尽管他们是对象了 ,但不会渲染出也不会执行生命周期函数。仅当使用 GameObject.Instantate 实例化后生命周期函数才开始运转。
    • 值类型和可序列化的引用类型,将会预先写入 Prefab 二进制内。所以 Resource.Load 出来是存在默认值的。
    • 常规的引用类型的字段,没有数据写入 Prefab 中,所以加载到内存时 该字段指向 null 。
  2. 本实验的结论:

    • 尝试在 Resource.Load 后对 string 类型的字段赋值,发现:
      a. 此修改对 Instantate 实例化的游戏对象有效。
      b. 此修改对原 Prefab 文件数据产生修改 ,Editor 模式下直接就保存在预制体中了,Runtime 则仅在内存中且对本次启动全局有效 ,软件重启数据恢复。
    • 尝试在 Resource.Load 后对 引用类型 的字段赋值,发现:
      a. 此修改对 Load 在内存的数据有效
      b. 此修改对 Instantate 动作无效,即:实例化的时候 该字段依旧为 null 。
    • 在 GameObject.Instantate 后对任意类型字段赋值,此修改对 Awake 函数中的引用(应用)无效,即保持默认值。
    • 在 GameObject.Instantate 后对任意类型字段赋值,此修改对 Start 函数中的引用(应用)开始产生影响。
    • 综上:对 Instantate 得到的游戏对象字段进行修改,务必在 Start 函数再对传入的数据进行处理,否则多半出现明明赋值了还报 null的情况
  3. 一直半解最可怕了,毕竟生命周期和脚本执行顺序混乱就会异常不断。要不Unity 整个 Execution order 干嘛?

写到最后

发现 Execution Order 设置的便捷方式,用的人少知道快捷方式的就更少了吧,没必要单独成文,强插一波吧:


上一篇下一篇

猜你喜欢

热点阅读