Unity3D 中 脚本(MonoBehaviour) 生命周期WaitForEndOfFrame需要注意的地方
首先看看MonoBehaviour的生命周期
先上个图(来源 http://blog.csdn.net/qitian67/article/details/18516503):

1.Awake 和 Start的区别
相信很多人都有个类似的疑惑: 在MonoBehaviour中,为什么会有Awake 和 Start 函数? 他们又有何区别?
这个在我初学U3D时也有过的疑惑,但是通过实践得出结论,Awake 会在MonoBehaviour 创建时候即被调用,相当于构造函数。
而Start 函数 会在下一帧更新时 才会被调用到,并仅只调用一次,而Update 函数会在 Start 函数的下一帧开始被调用。
所以有时稍微不注意, 就会出现如下的Bug:
public class MonoTest{
public int testNum = ;
public void Awake(){
testNum = ;
print("AWAKE :"+testNumber);
}
public void Start(){
testNum = ;
print("START :"+testNumber);
}
}
//-----------------------
// Create and Invoke
public class InvokeTest{
MonoTest mt;
bool inited = false;
void Update(){
if(!mt){
mt = gameObject.AddCompoment<MonoTest>();
//AWAKE: 2
//如果在此处修改 mt.testNum 的值, 则将在下一帧会调用MonoTest.Start 被覆盖为3
}
//mt 刚创建时:
// false 2
// Update 在以后调用的输出结果
// true 3
print(inited+" "+mt.testNum);
inited = true;
}
}
2. Destroy 与 DestroyImmediate
Destroy 与 DestroyImmediate 和 Start 与 Awake 其实也一样,不过我是到今天才知道,也是我写下此文的主因。
我第一次用 DestroyImmediate 的时候 是在做编辑器插件(学习 iTweenPath的源码 来做编辑器),当时需要在编辑器下点击一下button 立即删除,代码大概如下:
using UnityEngine;
using System.Collections; [ExecuteInEditMode]
public class DestroyInEditor : MonoBehaviour { public GameObject destroyTarget;
public bool destroy = false; void Update () {
if(destroy && destroyTarget)
{
Destroy(destroyTarget);
destroy = false;
}
}
}
当在Inspector 上将 destroy 设置为true,便会将destroyTarget 销毁。
但是在编辑器模式下会抛出一下异常:
Destroy may not be called from edit mode! Use DestroyImmediate instead.
Also think twice if you really want to destroy something in edit mode. Since this will destroy objects permanently.
UnityEngine.Object:Destroy(Object)
编辑模式下 不能调用Destroy,请使用 DestroyImmediate 。 当时因为英语不好,不明白Immediate 是什么意思,虽然知道是立即,但是并不了解 Destroy 和 DestroyImmediate的区别。
知道今天 使用NGUI.Table ,NGUI.Table 可以将所有子对象进行排版,但是我有一个要求是当点击next 的时候,将Table所有子对象清空,并添加新的子对象s再进行排版。
问题来了,要清空所有子对象,调用Table.RemoveChild()并没有用,原因可以自己查看 NGUI 的源码。
所以我自己写了个函数 将所有子对象清空:
//伪代码
public void Clear(){
foreach(var c in children){
Destroy(c.gameObject);
}
}
并新增 新的子对象
//伪代码
public void Reset(List<Transform> newList){
Clear();
foreach(var c in newList){
// TODO: reset localScale
c.parent = transform;
}
table.Reposition();
}
新问题出现了,排版功能有BUG了。 但是经过我手动 Reposition(Inspector上右击Table 点击Execute) 排版正常了。
而问题就是处在 Destroy 函数.
下面我们进行模拟测试一下,简化这个问题
测试环境
测试代码
using UnityEngine;
using System.Collections; public class DestroyTest : MonoBehaviour { public bool destroy = false; void Update () {
if (!destroy)
return; var childCount = transform.childCount;
print("Destroy Before:" + childCount); for(var i = ; i < childCount; ++i)
{
Destroy(transform.GetChild(i).gameObject);
} print("Destroy After:" + transform.childCount); destroy = false; }
}
测试结果:
Destroy Before:
Destroy After:
也就是说,当我们调用Destroy 时,Unity3D并没有真的将gameObject销毁,而是将gameObject 设置为Destroy标记。待到更新下一帧的间隙时,才真的将gameObject销毁。至于这个间隙,一般是在Render(GPU工作中)时 利用相对空闲CPU 将这些gameObject处理,当然除了处理Destroy Unity3D 还很多其他事。
答案呼之欲出了,将Destroy改为 DestroyImmediate 即可,现在终于明白立即原来是这个意思!
3.碰撞检测
说到碰撞,先了解下U3D的碰撞组件,看这里:http://www.cnblogs.com/neverdie/p/Unity3D_RigidBody2D_Collider2D.html
我这里先copy两张比较重要的图作备用
如果对一个碰撞器勾选了Is Trigger选项,它就不会与其他没有勾选Is Trigger的碰撞器发生刚体碰撞,而会发生“Trigger 碰撞”,也就是说,这时碰撞时发送的消息是Trigger消息,而不是Collision消息,相应地在脚本中我们要对OnTriggerEnter进行重载,而不是对OnCollisionEnter进行重载。
下图对Collision和Trigger进行了总结,在分别勾选某些属性时,都会发送哪些消息:

这里并不是要说碰撞,而是说 FixedUpdate 和 Update, 根据上图我们都知道FixedUpdate 有可能因为更新物理的原因而在一帧内被调用多次,而Update 一帧最多只调用一次。
最初我以为 FixedUpdate 和Update 是多线程同步进行,但其实不是。凡是实际到脚本的代码都只能单线程处理!注:除了WaitForEndOfFrame.
所以, 有时候移植其他游戏的时候发现一些代码会进行比较底层的碰撞检测,例:
public class Player
{
// player 的当前位置
int x;
int y; //将player 移动到 y+offsetY 的位置 (
// 返回值: player 实际移动的位置
// 如果没有碰撞 则 返回值=offsetY
// 如果中途产生碰撞 则返回player 下落的位置
public int MoveToY(int offsetY)
{
if(HitTest("block"))
throw new Exception("已经产生碰撞,不能移动");
for(var i=;i<=offsetY;i+=offsetY/)//有什么余数的暂时不考虑
{
y += i;
if(HitTest("block"))
{
y -= offsetY/;
return i - offsetY/; //返回上一次的移动结果
}
y -= i;
}
y += offsetY;
return offsetY;
} }
但是如果移植到U3D 并使用 U3D自带的物理引擎碰撞系统,就不一定work了。因为你移动的过程中其实并没有将实际的移动位置更新到物理引擎,只是做了个缓存而已,只有在调用FixedUpdate的内部函数(物理引擎处理)时,才会将最新的位置设置到物理引擎上,甚至是渲染引擎也使用最新的位置。
测试代码:
using UnityEngine;
using System.Collections; public class CollidingTest : MonoBehaviour { void Update () {
var p = transform.localPosition;
//p.x = 1;
//transform.localPosition = p;
//p.x = 0;
//transform.localPosition = p;
for(var i=0.0f;i<;i+=0.02f)
{
p.x = i;
transform.localPosition = p;
}
p.x = ;
transform.localPosition = p;
} private void OnTriggerEnter2D(Collider2D other)
{
print("ENTER:"+other);
} //private void OnTriggerStay2D(Collider2D other)
//{
// print("STAY:"+other);
//} private void OnTriggerExit2D(Collider2D other)
{
print("EXIT:"+other);
} }
整个过程中并没有发生碰撞callback,这个是我们需要注意的,至于怎么解决,我还在思考当中。以后会给个答案!
4.WaitForEndOfFrame
刚才第3点已经提到了WaitForEndOfFrame了,一般是这样使用
public class WaitForEndFrameTest{
void Awake(){
StartCoroutine(CallPluginAtEndOfFrames());
}
IEnumerator CallPluginAtEndOfFrames(){
while (true){
// Wait until all frame rendering is done
//TODO: Render Plugin but not component
yield return new WaitForEndOfFrame();
GL.IssuePluginEvent();
}
}
}
这是我在写渲染插件的带马上摘抄下来的,。。。
这里我也懵圈了,我印象中 在TODO上是不能修改有关U3D的任何东西的,因为此时是GPU渲染时候,如果中途修改了transform的信息会出BUG。
但是经过测试发现,在 CallPluginAtEndOfFrames中修改 transform的信息并没有真的改变了gameObject 的位置。
using UnityEngine;
using System.Collections;
using System.Threading;
using System.Collections.Generic; public class WaitForEndFrameTest : MonoBehaviour
{
//List<int> list = new List<int>();
void Awake()
{
StartCoroutine(CallPluginAtEndOfFrames());
} private void Update()
{
var pos = transform.localPosition;
print("UPDATE " + pos +" "+Thread.CurrentThread.ManagedThreadId);
} IEnumerator CallPluginAtEndOfFrames()
{
while (true)
{
// Wait until all frame rendering is done
//TODO: Render but not component
var pos = transform.localPosition;
pos.x = ;
transform.localPosition = pos;
yield return new WaitForEndOfFrame();
pos = transform.localPosition;
pos.x = ;
transform.localPosition = pos;
GL.IssuePluginEvent(); print("HELLO "+pos+" "+Thread.CurrentThread.ManagedThreadId);
//list.Add(3);
}
}
}
GameObject 加上测试脚本后运行,刚开始 HELLO 和 UPDATE 都有输出,但是一旦在编辑器中 修改了 GameObject的位置, CallPluginAtEndOfFrames 就再也没有迭代下去了?
有待更详细的测试和验证。
Unity3D 中 脚本(MonoBehaviour) 生命周期WaitForEndOfFrame需要注意的地方的更多相关文章
- Unity3D脚本(MonoBehaviour)生命周期
场景中有2个物体:A,B 每一个物体上绑定2个脚本:A,B 初始化log: Object : A , Script : B , Message : Awake Object : A , Script ...
- unity3d中脚本生命周期(MonoBehaviour lifecycle)
最近在做一个小示例,发现类继承于MonoBehaviour的类,有很多个方法,于是乎必然要问出一个问题:这么多个方法,执行先后顺序是如何的呢?内部是如何进行管理的呢?于是在网上找了许多资料,发现了Ri ...
- (转)unity3d中脚本生命周期(MonoBehaviour lifecycle)
自:http://blog.csdn.net/qitian67/article/details/18516503 最近在做一个小示例,发现类继承于MonoBehaviour的类,有很多个方法,于是乎必 ...
- Unity3d脚本的生命周期
接下来,做出一下讲解:最先执行的方法是Awake,这是生命周期的开始,用于进行激活时的初始化代码,一般可以在这个地方将当前脚本禁用:this.enable=false,如果这样做了,则会直接跳转到On ...
- Unity脚本的生命周期中几个重要的方法
1.function Update () {} 正常更新,用于更新逻辑.此方法每帧都会由系统自动调用一次.2.function LateUpdate () {} 推迟更新,此方法在Update() 方 ...
- 【转】Unity3D中脚本的执行顺序和编译顺序
支持原文,原文请戳: Unity3D中脚本的执行顺序和编译顺序 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦 ...
- Unity3D笔记八 Unity生命周期及动画学习
Unity脚本从唤醒到销毁有着一套比较完善的生命周期,添加任何脚本都必须遵守自身生命周期法则.下面介绍一下生命周期中由系统自身调用的几个比较重要的方法. Update(){}.正常更新,用于更新逻 ...
- MonoBehaviour生命周期
MonoBehaviour生命周期 上图中重要的信息点很多,需要特别注意的是所有脚本的Awake方法都执行完才会执行Start,但是如果在Awake 中开启了一个协程这个协程中每一帧执行一些操作然后等 ...
- spring IOC 容器中 Bean 的生命周期
IOC 容器中 Bean 的生命周期: 1.通过构造器或工厂方法创建 Bean 实例 2.为 Bean 的属性设置值和对其他 Bean 的引用 3.调用 Bean 后置处理器接口(BeanPostPr ...
随机推荐
- 在Kali Linux中下载工具Stegsolve
关键字:Java,Stegsolve,Write Up 一.首先需要配置Java环境. 1.下载最新的Java JDK. 注意选择Accept License Agreement,并下载.tar.gz ...
- pyinstaller 打包生成exe之后运行提示‘no module name 'xxx'’错误
python 3.7 pyinstaller 3.4 具体情况: pycharm中点击运行可成功执行,生成正确结果,没有报错. 双击run.py(程序运行的主文件),运行,可生成正确结果,没有报错. ...
- java中Method.invoke方法参数解析
通过发射的机制,可以通过invoke方法来调用类的函数.invoke函数的第一个参数是调用该方法的实例,如果该方法是静态方法,那么可以用null或者用类来代替,第二个参数是变长的,是调用该方法的参数. ...
- redis bind的坑
启动redis时,发现外网访问不了 检查以下方面 1. ping redis 的ip 2. 检查防火墙端口是否开放3. bind bind bind指的是绑定哪个ip可以访问 bind 要填写你自己r ...
- 深度原理与框架-图像超分辨重构-tensorlayer
图像超分辨重构的原理,输入一张像素点少,像素较低的图像, 输出一张像素点多,像素较高的图像 而在作者的文章中,作者使用downsample_up, 使用imresize(img, []) 将图像的像素 ...
- Spring事务实现分析
一.Spring声明式事务用法 1.在spring配置文件中配置事务管理器 <bean id="baseDataSource" class="com.alibaba ...
- Vue note
1.npm run build 时,font:xx/xx "xxxx" 这种样式打包后会无效,只能写成font-size:xxx; line-height:xxx; font-fa ...
- open函数新建文件报错
报错原因很多,我这里只写我遇到的: 给的路径或者文件名中包含了这些字符的:/\:*?"><| 都不行,我说的是Windows平台下的.
- ssh服务简介及应用与服务的进程的类型
SSH ,由 IETF 的网络小组(Network Working Group)所制定:SSH 为建立在应用层基础上的安全协议.SSH 是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议.利 ...
- linux dd 本地挂载
losetup /dev/loop0 /root/test.img mkfs.ext4 /dev/loop0 mount /dev/loop0 /data