有些问题不去试试,不清不楚不方便搞事情,就像笔者今天做的这个备忘一样。
写在前面
- 这个笔记就是记录一个测试结果。
- 测试的目的是确认一个字段 在 Resource.Load 后获得,并在Instantate前后 赋值,观察它在生命周期中的表现:
实验流程
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
}
- 游戏对象上将挂载的组件,含 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";
}
-
触发测试的方法,输出如下:
Debug输出
经验总结
-
常识性的结论:
- Resource.Load 是将 Prefab 二进制文件 通过实例化方式加载到内存,尽管他们是对象了 ,但不会渲染出也不会执行生命周期函数。仅当使用 GameObject.Instantate 实例化后生命周期函数才开始运转。
- 值类型和可序列化的引用类型,将会预先写入 Prefab 二进制内。所以 Resource.Load 出来是存在默认值的。
- 常规的引用类型的字段,没有数据写入 Prefab 中,所以加载到内存时 该字段指向 null 。
-
本实验的结论:
- 尝试在 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的情况 。
- 尝试在 Resource.Load 后对 string 类型的字段赋值,发现:
-
一直半解最可怕了,毕竟生命周期和脚本执行顺序混乱就会异常不断。要不Unity 整个 Execution order 干嘛?
写到最后
发现 Execution Order 设置的便捷方式,用的人少知道快捷方式的就更少了吧,没必要单独成文,强插一波吧:
- 添加脚本?拖~
- 移除脚本?拖~
- 调整执行顺序?还是拖。
-
还有这样的属性也不知好不好用?
网友评论