unity中父类选择器下有多个特效,怎么选择播放其中一个

unity(47)
这里用到了在编辑器下控制粒子播放的代码,我加入了多组件编辑、暂停、记录Time的功能。
代码下载地址:
使用方法:
导入资源,将EditParticleSystem.cs挂载到带有ParticleSystem组件的GameObject身上,同时选中这几个GameObject,Inspector中便可操作,具体过程请看上面的gif图。
另外也有类似功能的讲解。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:64759次
积分:1338
积分:1338
排名:千里之外
原创:60篇
转载:15篇
评论:10条
(2)(3)(2)(3)(4)(6)(10)(6)(19)(1)(4)(1)(1)(1)(2)(5)(4)(7)
邮箱:zhiheng.
豆瓣:/people//Unity3d中对象池(ObjectPool)的实现
什么是对象池?
池(Pool),与集合在某种意义上有些相似。 水池,是一定数量的水的集合;内存池,是一定数量的已经分配好的内存的集合;线程池,是一定数量的已经创建好的线程的集合。那么,对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合[1]。
在C/C++的程序中,如果一种对象,你要经常用malloc/free(或new/delete)来创建、销毁,这样子一方面开销会比较大,另一方面会产生很多内存碎片,程序跑的时间一长,性能就会下降。这个时候,就产生了对象池。可以事先创建好一批对象,放在一个集合中,以后每当程序需要新的对象时候,都从对象池里获取,程序用完该对象后,再把该对象归还给对象池。这样,就会少了很多的malloc/free(new/delete)的调用,在一定程度上提高了系统的性能,尤其在动态内存分配比较频繁的程序中效果较为明显。
Unity3d中的对象池
在使用unity3d做游戏时,经常有同一个Prefab用到多次,需要反复实例化(Instantiate)。但实例化是很消耗资源的,所以在游戏加载时把一批Prefab实例化好放在对象池中,游戏中用的时候拿出来,不用的时候放回去,避免反复申请和销毁。
存入对象池的元素应具有如下特征:1&场景中大量使用 2&存在一定的生命周期,会较为频繁的申请和释放。
关于多线程的考虑
因为Unity的API只能被主线程调用,我理解Unity提供的用户空间是单线程的(脚本中写While(true)挂在GameObject上,点运行整个Unity会卡死)。所以我们不需要将池实现支持多线程。在支持多线程的应用中,单例的初始化通常要加一个锁,在这里也没有必要。
希望实现具有以下特征的对象池
新增对象种类时操作简单,能够灵活控制每个对象池中生成的对象数量。
接口简单,易于申请和回收。
模块结构清晰,耦合度低。
申请和回收时,可以根据具体的对象类型做个性化操作。
1. 模块设计
*UML规则参考自UML 基础: 类图
模块中主要使用以下类
ObjectPoolMgr是对象池管理类。它对外提供接口,对内管理对象池实例。外部进行申请和释放操作都是通过调用ObjectPoolMgr中的接口进行的。
ObjectPool是对象池基类,它是抽象类,包含了通用的成员和方法。作为一个基类,它提可被子类继承的方法是virtual的,便于实现多态。
CubePool, SpharePool是具体的对象池实现类,继承自ObjectPool,可以override基类的alloc和recycle等方法实现个性化的操作。
PrefabInfo挂在对象池中的每一个对象上,用来记录对象类型等信息。
2. 数据管理方式
数据管理方式如图,单例ObjectPoolMgr中使用字典管理着多种对象池实例,每种对象池实例中使用队列管理一定数量的同类型对象。
3.主要接口和调用流程
对象池管理类ObjectPoolMgr类中提供对外接口,主要是alloc和recycle,声明如下
//申请接口。type是申请的对象类型,lifeTime是生存时间public static GameObject alloc( string type, float lifeTime = 0);
游戏调用alloc时,ObjectPoolMgr会根据传入的对象类型type,从自身成员poolDic中取出相应的对象池实例subPool(如果该对象池还没有实例就创建一个),再调用subPool的alloc方法,取出一个GameObject返回给游戏。
//回收接口。参数是待回收GameObject public static void recycle(GameObject recycleObj);
游戏在使用申请到的GameObject时可能会在其中添加子物体,回收前会判断一下recycleObj中是否嵌套有其它属于对象池的Prefab,如果存在就分别进行回收。
另外如果对象池中待分配对象数量超过了用户设置的个数,直接销毁recycleObj而不再放回对象池。
4.对象池的创建时机
过早创建对象池,池中的对象会占用大量内存。若等到游戏使用对象时再创建对象池,可能因为大量实例化造成掉帧。所以,我认为在Loading界面创建下一个场景需使用的对象池是较为合理的。比如天天飞车中的NPC车,金币,赛道,在进入单局比赛后才用到。可以在进入比赛的Loading界面预先创建金币,NPC车,赛道的对象池,比赛中直接申请使用。
5.具体实现
1&ObjectPoolMgr
ObjectPoolMgr是对象池的管理类,提供接口,
public static GameObject alloc( string type, float lifetime = 0);public static void recycle(GameObject recycleObj);
参数lifeTime是存活时间,以秒为单位,定义如下
lifeTime & 0 lifeTime秒后自动回收对象。
lifeTime = 0 不自动回收对象,需游戏主动调用recycle回收。
lifeTime & 0 创建Pool实例并实例化Pool中的对象,但不返回对象,返回值null。
当lifeTime&0时,分配出去的GameObject上挂的PrefabInfo脚本会执行倒计时协程,计时器为0时调用recycle方法回收自己。它的适用对象如射击游戏中的子弹,申请时设定了lifeTime后不必关心回收的问题,当然游戏可以计时器在到时前主动发起回收。
lifeTime & 0的目的预创建对象池,在游戏场景Loading时可以用这个方法先把对象池创建起来,避免游戏中创建对象池造成掉帧。
ObjectPoolMgr用成员poolDic维护已分配的对象池实例
使用objectPoolList记录面板上的用户设置
ObjectPoolMgr初始化时会在Unity的层次(Hierarchy)面板中创建GameObject并添加自身脚本。开发者可以在Inspector面板中直接创建新的对象池,如下图。Pre Alloc Size是对象池创建时预申请的对象数量。Auto Increase Size是池中的对象被申请完后进行一定数量的自增。prefab对象池关联的预制类型
比如,当游戏需要申请Cube对象时,会调用GameObject cube = ObjectPoolMGR.alloc("Cube");方法。ObjectPoolMGR.alloc代码如下
public static GameObject alloc( string type, float lifetime = 0){//根据传入type取出或创建对应类型对象池ObjectPool subPool = Instance._getpool(type);//从对象池中取一个对象返回GameObject returnObj = subPool.alloc(lifetime);return returnO}
ObjectPoolMgr会根据传入的类型type,调用_getpool(type)找到对应的Pool,再从其中取一个对象返回。
代码中_getpool(string type)是ObjectPoolMgr中的私有方法。前面说过,ObjectPoolMgr有一个成员poolDic用来记录已创建的对象池实例,_getpool方法先去poolDic中查找,找到直接返回。如果找不到说明还未创建,使用反射创建对象池,记录入poolDic,代码如下
使用_getpool获得了对象池实例后,会调用对象池的alloc方法分配一个对象。
2&对象池基类ObjectPool
其中记录了对象池的通用方法和成员,如下
12345678910
protected Queue queue = new Queue (); //用来保存池中对象[SerializeField]protected int _freeObjCount = 0; //池中待分配对象数量public int preAllocC //初始化时预分配对象数量public int autoIncreaseC //池中可增加对象数量protected bool _binit = //是否初始化[HideInInspector]public GameO //prefab引用[HideInInspector]public string objTypeS //池中对象描述字符串
ObjectPool中的方法
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
public virtual GameObject alloc( float lifetime){//如果没有进行过初始化,先初始化创建池中的对象if (!_binit){_init();_binit =}if (lifetime&0){Debug.LogWarning( "lifetime &= 0, return null" ); //lifetime&0时,创建对象池并返回null}GameObject returnOif (_freeObjCount & 0){ //池中有待分配对象returnObj = queue.Dequeue(); //分配_freeObjCount--;} else { //池中没有对象了,实例化一个returnObj = Instantiate(prefab , new Vector3(0,0,0), Quaternion.identity) as GameOreturnObj.SetActive( false ); //防止挂在returnObj上的脚本自动开始执行returnObj.transform.parent = this .}//使用PrefabInfo脚本保存returnObj的一些信息PrefabInfo info = returnObj.GetComponent《 PrefabInfo 》();if (info == null ){info = returnObj.AddComponent 《 PrefabInfo 》();}if (lifetime & 0){info.lifetime =}info.types = objTypeSreturnObj.SetActive( true );return returnO}public virtual void recycle(GameObject obj){//待分配对象已经在对象池中if (queue.Contains(obj)){Debug.LogWarning( "the obj " + obj.name + " be recycle twice!" );}if ( _freeObjCount & preAllocCount + autoIncreaseCount ){Destroy(obj); //当前池中object数量已满,直接销毁} else {queue.Enqueue(obj); //入队,并进行resetobj.transform.parent = this .obj.SetActive( false );_freeObjCount++;}}
这里要注意的是,基类alloc和recycle方法要使用虚函数,子类override实现多态。
3&对象池子类CubePool
子类override父类的alloc和recycle,进行个性化的申请和回收工作。
public class CubePool : ObjectPool {public override GameObject alloc( float lifetime){GameObject cubeObject= base .alloc(lifetime);//在这里进行CubePool个性化的的初始化工作return cubeO}}
当然也可以直接复用基类的alloc方法,甚至不写CubePool类。当ObjectPoolMgr申请一个Cube但找不到CubePool类时,会使用通用方法进行分配和回收。
4&PrefabInfo
PrefabInfo是挂在prefab实例上,用来记录prefab类型和lifetime等数据。
1234567891011121314
public class PrefabInfo : MonoBehaviour {[HideInInspector]public float lifetime = 0;void OnEnable(){if (lifetime & 0){StartCoroutine(countTime(lifetime));}}IEnumerator countTime( float lifetime){yield return new WaitForSeconds(lifetime);ObjectPoolMGR.recycle(gameObject);}}
新增Pool方法
为一个新Perfab创建对象池需要以下两步,
1.在unity面板中把prefab挂上,并设置prefab的实例化数量和可增加数量
2.(可选)实现一个对应的Pool脚本。如果不实现,这个对象池会使用通用的申请和回收方法。
调用申请和释放方法
//申请GameObject obj1 = ObjectPoolMGR.alloc( "Cube" ,5);GameObject obj2 = ObjectPoolMGR.alloc( "Sphare" );//回收ObjectPoolMGR.recycle(obj2);
存在的问题
对于从对象池中申请的GameObject,目前在游戏使用中不能改变其层次结构,不能添加新的Component,也不能在其中新增非对象池分配的GameObject。因为这些改变在回收时无法被发现,再次复用时可能出现意想不到的结果。
介于这种情况,我的使用方法是如果使用过程中改变了对象结构,用完后就Destroy,不再recycle。如果改变是可预期的,也可以重写子类recycle进行处理。
当游戏收到内存告警时,应该可以释放对象池,增加可用内存。释放的策略可以有多种:
释放ObjectPoolMgr中所有的对象池(能释放大量内存,但Destroy会造成CPU消耗,下次申请时还要重新创建对象池)
压缩对象池中的对象数量(用户在使用对象池时设置了池中GameObject的基本数量和可增加数量,可以把增加的释放掉)
释放一些不常用的对象池和其中的对象(不常用的定义可以有很多,比如被申请的次数最少,最久未被使用等)
释放指定一种或几种对象池等等
同样的,游戏申请GameObject而对象池中可申请数量为空,就需要扩展对象池。扩展对象池的策略可以有:
1&实例化两个GameObject,一个返回给游戏,一个放入池中以备下次申请
2&按照用户设置的AutoIncreaseCount,每次池为空时实例化相应数量的对象
空间申请和释放策略可以有多种,可以组合使用,但没有万全之策,可以根据游戏的特点去实现。
实现时踩的一个小坑
对象池基类ObjectPool的Start()和Update()方法最好不要使用,因为创建的子类会自动生成这两个方法,一不小心就覆盖了。
我把对象池用于自己制作的小游戏SpaceBattle,做了一个简单测试。场景中有很多敌人,陨石,子弹(如图),我把这三种prefab放入对象池。
不使用对象池:
场景中有1843个GameObject,cpu使用呈震荡上涨趋势。因为不断有销毁和实例化,GameObject数量抖动上涨,Total GCAlloc抖动剧烈,有较频繁的内存回收。帧数降到了30左右。
使用对象池:
场景中有2291个GameObject,较上一个场景稍复杂一些。cpu抖动较上面平缓,基本保持在60帧到30帧之间,播放特效时帧数降低(特效未使用对象池)。最后能保持在60帧左右。随着Total GameObject数量缓步上涨,Total GCAlloc曲线平滑,说明内存操作不频繁,可以达到节省系统资源的效果。
可以看出,对象池对降低系统资源消耗是有作用的。在不使用对象池的测试中也遇到了一些极端情况:游戏频繁实例化和销毁对象时cpu剧烈抖动,这种情况应该尽力避免。
另外也要合理设置对象池中的预分配对象数量。过多会占用大量内存,过少效果不好。
注:代码和文章都是年初写的,最近才整理发出来。时间久了可能会有记忆模糊或的地方,如果有疏漏或是错误还望大家不吝指出,谢谢
[1] 深入浅出对象池
[2] Unity3D - 物件池 ( Object Pool ) 子彈發射範例
[3] Object Pooling – For Unity3D
[4] 《Unity3D》通过对象池模式,管理场景中的元素
立即阅读相关好文章
《贪婪洞窟》谈贪婪设计丨腾讯GAD游戏创新大赛丨
游戏美术3D设计干货回顾丨为VR优化UE4渲染器丨
这么做设计才好玩丨Unity教程
MOBA类游戏核心设计分析
手游常断线怎么办?试试断线重连处理
游戏网络开发(一):UDP vs. TCP
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
专业游戏开发社区
服务广大游戏开发者,为开发者们解决游戏开发中的难题
今日搜狐热点当前位置: >
在unity3D操作中的一些细节问题
时间: 08:59 来源:互联网 作者:网络 浏览:
操作transform.localPosition的时候请小心移动GameObject是非常平常的一件事情,一下代码看起来很简单:transform.localPosition += new Vector3 ( 10.0f * Time.deltaTime, 0.0f, 0.0f );但是小心了,假设上面这个GameObject有一个parent, 并且这个parent GameObject的localScale是(2.0f,2.0f,2.0f)。你的GameObject将会移动20.0个单位/秒。因为该 GameObject的world position等于:Vector3 offset = new Vector3( my.localPosition.x * parent.lossyScale.x,my.localPosition.y * parent.lossyScale.y,my.localPosition.z * parent.lossyScale.z );Vector3 worldPosition = parent.position + parent.rotation *换句话说,上面这种直接操作localPosition的方式是在没有考虑scale计算的时候进行的,为了解决这个问题,Unity3D提供了Translate函数,所以正确的做法应该是:transform.Translate ( 10.0f * Time.deltaTime, 0.0f, 0.0f );曝出在Inspector的变量同样的也能被Animation View Editor所使用有时候我们会想用Unity3D自带的Animation View Editor来做一些简单的动画操作。而Animation Editor不仅可以操作Unity3D自身的component,还可以操作我们自定义的MonoBehavior中的各个Property。所以加入 你有个float值需要用曲线操作,你可以简单的将它曝出到成可以serialize的类型,如:public float foobar = 1.0f;这样,这个变量不仅会在Inspector中出现,还可以在animation view中进行操作,生成AnimationClip供我们通过AnimationComponent调用。范例:public class TestCurve : MonoBehaviour {public float foobar = 0.0f;IEnumerator Start () {yield return new WaitForSeconds (2.0f);animation.Play(“foobar_op”);InvokeRepeating ( “LogFoobar”, 0.0f, 0.2f );yield return new WaitForSeconds (animation[&foobar_op&].length);CancelInvoke (“LogFoobar”);}void LogFoobar () {Debug.Log(“foobar = ” + foobar);}}&GetComopnent&T& 可以取父类类型Unity3D 允许我们对MonoBehavior做派生,所以你可能有以下代码:public class foo : MonoBehaviour {…}public class bar : foo {…}假设我们现在有A,B两个GameObject, A包含foo, B包含bar, 当我们写foo comp1 = A.GetComponent&foo&();bar comp2 = B.GetComponent&bar&();可以看到comp1, comp2都得到了应得的Component。那如果我们对B的操作改成:foo comp2 = B.GetComponent&foo&();答案是comp2还是会返回bar Component并且转换为foo类型。你同样可以用向下转换得到有效变量:bar comp2_bar = comp2合理利用GetComponent&base_type&()可以让我们设计Component的时候耦合性更低。Invoke, yield 等函数会受 Time.timeScale 影响Unity3D提供了一个十分方便的调节时间的函数Time.timeScale。对于初次使用Unity3D的使用者,会误导性的认为Time.timeScale同样可以适用于游戏中的暂停(Pause)和开始(Resume)。所以很多人有习惯写:Time.timeScale = 0.0f对于游戏的暂停/开始,是游戏系统设计的一部分,而Time.timeScale不不是用于这个部分的操作。正确的做法应该是搜集需要暂停的脚本或 GameObject,通过设置他们的enabled = false 来停止他们的脚本活动或者通过特定函数来设置这些物件暂停时需要关闭那些操作。Time.timeScale 更多的是用于游戏中慢镜头的播放等操作,在服务器端主导的游戏中更应该避免此类操作。值得一提的是,Unity3D的许多时间相关的函数都和 timeScale挂钩,而timeScale = 0.0f将使这些函数或动画处于完全停止的状态,这也是为什么它不适合做暂停操作的主要原因。这里列出受timeScale影响的一些主要函数和Component:MonoBehaviour.Invoke(…)MonoBehaviour.InvokeRepeating(…)yield WaitForSeconds(…)GameObject.Destroy(…)Animation ComponentTime.time, Time.deltaTime…Coroutine 和 IEnumerator的关系初写Unity3D C#脚本的时候,我们经常会犯的错误是调用Coroutine函数忘记使用StartCoroutine的方式。如:TestCoroutine.csIEnumerator CoLog () {yield return new WaitForSeconds (2.0f);Debug.Log(“hello foobar”);}当我们用以下代码去调用上述函数:TestCoroutine testCo = GetComponent&TestCoroutine&();testCo.CoLog ();testCo.StartCoroutine ( “CoLog” );那么testCo.CoLog()的调用将不会起任何作用。StartCoroutine, InvokeRepeating 和其调用者关联通常我们只在一份GameObject中去调用StartCoroutine或者InvokeRepeating, 我们写:StartCoroutine ( Foobar() );InvokeRepeating ( “Foobar”, 0.0f, 0.1f );所以如果这个GameObject被disable或者destroy了,这些coroutine和invokes将会被取消。就好比我们手动调用:StopAllCoroutines ();CancelInvoke ();这看上去很美妙,对于AI来说,这就像告诉一个NPC你已经死了,你自己的那些小动作就都听下来吧。但是注意了,假如这样的代码用在了一个Manager类型的控制AI上,他有可能去控制其他的AI, 也有可能通过Invoke, Coroutine去做一些微线程的操作,这个时候就要明确StartCoroutine或者InvokeRepeating的调用者的设计。讨论之前我 们先要理解,StartCoroutine或InvokeRepeating的调用会在该MonoBehavior中开启一份Thread State, 并将需要操作的函数,变量以及计时器放入这份Stack中通过并在引擎每帧Update的最后,Renderer渲染之前统一做处理。所以如果这个 MonoBehavior被Destroy了,那么这份Thread State也就随之消失,那么所有他存储的调用也就失效了。如果有两份GameObject A和B, 他们互相知道对方,假如A中通过StartCoroutine或InvokeRepeating去调用B的函数从而控制B,这个时候Thread State是存放在A里,当A被disable或者destroy了,这些可能需要一段时间的控制函数也就失效了,这个时候B明明还没死,也不会动了。更 好的做法是让在A的函数中通过B.StartCoroutine ( … ) 让这份Thread State存放于B中。// class TestCortouinepublic class TestCoroutine : MonoBehaviour {public IEnumerator CoLog ( string _name ) {Debug.Log(_name + ” hello foobar 01″);yield return new WaitForSeconds (2.0f);Debug.Log(_name + ” hello foobar 02″);}}// component attached on GameObject Apublic class A: MonoBehaviour {public GameObject B;void Start () {TestCoroutine compB = B.GetComponent&TestCoroutine&();// GOOD, thread state in B// same as: compB.StartCoroutine ( “CoLog”, “B” );compB.StartCoroutine ( compB.CoLog(“B”) );// BAD, thread state in AStartCoroutine ( compB.CoLog(“A”) );Debug.Log(“Bye bye A, we’ll miss you”);Destroy(gameObject); // T_T I don’t want to die…}}以上代码,得到的结果将会是:B hello foobar 01A hello foobar 01Bye bye A, we’ll miss youB hello foobar 02如不需要Start, Update, LateUpdate函数,请去掉他们当你的脚本里没有任何Start, Update, LateUpdate函数的时候,Unity3D将不会将它们加入到他的Update List中,有利于脚本整体效率的提升。我们可以从这两个脚本中看到区别:Update_01.cspublic class Update_01 : MonoBehaviour {void Start () {}void Update () {}}Update_02.cspublic class Update_02 : MonoBehaviour {}
(责任编辑:脚印)
免责声明:Unity之家部分内容来源于互联网,如有侵权,请联系我们,本站将立即进行处理。
猜你也喜欢看这些 ??????
其他类型的Unity入门 ??????

我要回帖

更多关于 css 选择父类 的文章

 

随机推荐