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 ...
随机推荐
- C罗转会尤文图斯
皇家马德里头号球星C罗转会意甲尤文图斯,结束了9年的皇马生涯,已获得5座金球奖.
- Jmeter学习记录
JSON正则表达式提取规则 https://www.cnblogs.com/hc1020/p/7723720.html Jmeter非GUI下执行日志 执行命令 ./jmeter -n -t $ ...
- 2018-2019-2 网络对抗技术 20165304 Exp5 MSF基础应用
2018-2019-2 网络对抗技术 20165304Exp5 MSF基础应用 原理与实践说明 1.实践原理 1).MSF攻击方法 主动攻击:扫描主机漏洞,进行攻击 攻击浏览器 攻击其他客户端 2). ...
- JavaScript值全等判断
作为开发员,很多时候拿到数据之后都是要做数据判断,比较特别的情况就是我们需要做数组判断和对象判断,经常的我们就array === array ,object === object;但是可惜是我们得到的 ...
- P1100 高低位交换
题目描述 给出一个小于2^{32}232的正整数.这个数可以用一个3232位的二进制数表示(不足3232位用00补足).我们称这个二进制数的前1616位为“高位”,后1616位为“低位”.将它的高低位 ...
- 记一次easywechat企业付款问题
由easywechat的cli "./vendor/bin/easywechat payment:rsa_public_key" 获取RSA公钥时 生成的.pem文件内的公钥默认是 ...
- tomcat 配置细节
1.对于url中文乱码问题: (有时进行url编码也不能能够解决,找到server.xml添加URIEncoding="UTF-8",就可以了) tomcat配置编码: ...
- Vue相关文章
1.新手向:Vue 2.0 的建议学习顺序 2.用webstorm搭建vue项目 3.vue-cli3.0项目结构
- [Docker] 容器持久化数据的首选机制 Volume
Volume 是 docker 容器生成持久化数据的首选机制.bind mounts 依赖主机机器的目录机构,volume 完全由 docker 管理.volume 较 bind mounts 有几个 ...
- loadrunner 关联函数web_reg_save_param
当我们每次访问网站都需要提交从服务器获取的动态文本时就会需要用到关联函数,就好像每次乘坐火车票我们都需要用最新的火车票,如果用旧车票就不能做火车,如果我们采用了录制时的旧动态码如usersession ...