问题
- 近来在调试unity程序时发现了几个关于初始化问题的bug,在刚进入play 模式时会报错,提示…对象为null,但是前进到下一帧时又一切正常,这个奇怪的错误让我很疑惑。
解决过程
看到是第一帧出错,第一反应就是想到是有对象没有被初始化,于是开始找出错的脚本中初始化的部分的问题,结果发现已经在构造函数和start函数中正确初始化了,按理不该出现这个问题。
然后经过几次反复的实验和Debug.Log(),终于反应过来,unity会在start函数中进行对象的初始化,也就意味着,构造函数会在start函数中进行调用,以初始化必要的成员,也就是start函数中的语句是在构造函数执行后执行的,但是代码中我们的构造函数中的相关对象的初始化是依赖于start函数中相关的初始化的,着就导致了在构造函数中进行初始化操作时,由于需要调用start函数中初始化的对象,而start中的初始化语句是在后面执行的,所有出现了值为null的情况。
知道了问题的来源自然就容易解决了,把所有的初始化代码全部放到构造函数中或者把所有的初始化代码全部放到start函数中即可
深入探究
- 当初学C++时就被一直强调构造函数的重要性,这里当然不能停留在解决眼前的问题上,所以,我稍微深入的了解下unity中构造函数和Start,Awake函数等的关系,最简单的研究方式当然是。。。百度了,先贴几个别人或者时官方文档上的简单介绍:
— Unity官方对构造函数的说明
— Unity官方对Start函数的说明
— Unity3D开发中 使用C# 很多文档都有提到这个“避免使用构造函数 不要在构造函数中初始化任何变量”为什么?【知乎】 可以先阅读链接里面的文章有一个大概的了解,接下来就来研究下构造函数和Unity自带的函数的调用关系。
先把实验中使用的代码贴上来
public class MessageTest : MonoBehaviour
{
static int i;
bool start;
bool FixedStart;
public void Awake()
{
Debug.Log("Awake函数" + i);
}
public void Start()
{
Debug.Log("start函数" + i);
}
private void Update()
{
if (start)
{
Debug.Log("update" + i);
start = false;
}
}
private void FixedUpdate()
{
if (FixedStart)
{
Debug.Log("fixedupdate" + i);
FixedStart = false;
}
}
public MessageTest()
{
i += 1;
start = true;
FixedStart = true;
Debug.Log("构造函数" + i);
}
}
这个代码很简单,就是一个简单的脚本,使用静态变量用于对构造函数的调用次数计数,其中静态变量也会自动初始化为0,另外使用两个bool值对两个update函数进行控制,防止一直输出。
进入play模式,查看控制台输出:
其中可以看到构造函数是被调用了三次的,我尝试通过断点的方式查看调用堆栈。。但是VS没有显示,所以没办法,不清楚完整的调用过程,所以这里我 大胆的猜测一下调用过程:
考虑到unity是组件式的架构,并且每个脚本都是一个组件,所以,首先我猜测,第一次的调用 是在初始化这个脚本组件的时候调用的,相当于初始化脚本
其次,第二次是初始化GameObject的时候调用的,因为unity中所有的脚本都是挂在Gameobject上面的,即Awake函数中,而Start函数是不负责初始化脚本内容,只是相当于在脚本被激活时进行调用的一个函数
最后,在物体被销毁后时会调用一次构造函数,为什么是物体销毁后呢?因为我在运行模式中通过添加和移除脚本发现,其中destory函数会被调用,但是并没有调用构造函数。至于物体被销毁时为什么会调用构造函数,这里暂时没想到是什么原因,如果有知道的朋友欢迎留言探讨。