💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
Learn IPhoneand iPad Cocos2d Game Delevopment》第9章 。 由于相册空间已满,csdn不允许发布站外图片,所以所有图片以链接方式显示。 为了产生粒子效果,程序员们不得不使用粒子系统。粒子系统喷射大量细小的粒子并对其进行高效的渲染,这要比sprite高效得多,因此你可以模拟出雨、烟、火、雪、爆炸、蒸汽尾迹等效果。 粒子系统由大量属性驱动。所谓大量的意思,指超过30种,它们不仅影响着每个粒子的外观和行为,而且影响着整体的效果。粒子效果是所有粒子共同创造出来的可视化效果。单个粒子无法产生火焰效果,哪怕10个粒子也远远不够。如果不是数百,起码也要成打的粒子才能产生火焰效果。 创建可观的粒子效果是一个需要不断尝试的过程。在代码中尝试改变各种属性,编译,根据运行的结果调整粒子系统,不断改变并重复这些过程——无论如何,这都是十分繁琐的。粒子设计工具在这里派上了用场,我称之为粒子设计器,本章我们将讲述如何使用它。 ## 一、粒子效果示例 Cocos2d提供了大量内置的粒子效果,这给你提供了一个很好的概念。在CCParticleExamples.m中有一些例子,你可以把它们作为模板,然后在你的游戏或者子类中修改它们,你仅仅需要做一些微小的调整。这些例子不需要任何特别的技术,你可以创建这些示例粒子效果,如同普通的CCNode对象。实际上,它们派生自CCNode。 我创建了一个ParticleEffects01项目,演示了cocos2d的所有示例粒子效果。你轻触屏幕就可以快速地翻看这些效果,也可以通过拖曳动作移动它们。有许多粒子效果当它们开始移动后看起来就完全不一样了,看起来效果不错的大粒子束,在开始移动后很可能变得很吃力。 只有一种效果无法移动,这就是CCParticleExplosion效果,它是一次性效果,它一次性喷射所有粒子然后终止喷射,如图所示: [](http://img.ph.126.net/HfbEdaRE9qgx35T5BFYAwA==/567735028042252740.png)[http://img.ph.126.net/HfbEdaRE9qgx35T5BFYAwA==/567735028042252740.png](http://img.ph.126.net/HfbEdaRE9qgx35T5BFYAwA==/567735028042252740.png) 其他效果则可以持续运行,当有粒子生命结束并被删除后又会创建出新的粒子。在这种情况下的挑战是要维持屏幕中粒子数量的平衡。   面则显示了示例程序ParticleEffects01中使用的一些有关粒子效果的方法。particleType作为switch语句的开关变量,表示了要创建的内置粒子系统类型。注意CCParticleSystem指针用于存储粒子系统,因此我在runEffect方法末尾把它传给addChild方法调用。每个示例的粒子效果都继承自CCParticleSystem。 ~~~ -(void) runEffect { // remove any previous particle FX [self removeChildByTag:1 cleanup:YES]; CCParticleSystem* system; switch (particleType) { case ParticleTypeExplosion: system =[CCParticleExplosion node]; break; case ParticleTypeFire: system =[CCParticleFire node]; break; case ParticleTypeFireworks: system =[CCParticleFireworks node]; break; case ParticleTypeFlower: system =[CCParticleFlower node]; break; case ParticleTypeGalaxy: system =[CCParticleGalaxy node]; break; case ParticleTypeMeteor: system =[CCParticleMeteor node]; break; case ParticleTypeRain: system =[CCParticleRain node]; break; case ParticleTypeSmoke: system =[CCParticleSmoke node]; break; case ParticleTypeSnow: system =[CCParticleSnow node]; break; case ParticleTypeSpiral: system =[CCParticleSpiral node]; break; case ParticleTypeSun: system =[CCParticleSun node]; break; default: // do nothing break; } [self addChild:system z:1 tag:1]; [label setString:NSStringFromClass([system class])]; } -(void) setNextParticleType { particleType++; if(particleType == ParticleTypes_MAX) { particleType = 0; } } ~~~ 注意: NSStringFromClass方法在这个例子中很有用,我们用它来显示类名,而不需要每个类都指定一个字符串。这是很酷的Object-c 运行时特性,你可以得到一个NSString表述的类名。如果用C++,你只能去咬自己的脚趾了。这对游戏的逻辑代码没有任何用处,但这是非常有用的调试和日志输出技巧。你可以从苹果的基础框架参考中找到这些方法的完整列表和描述: http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html. 如果你想在自己的工程中使用这些示例效果,你可能会看到一些丑陋的方块,如下图所示。 [http://img.ph.126.net/OVlPXIW-AGFQx-PR7vqXXg==/1042864788729840959.png](http://img.ph.126.net/OVlPXIW-AGFQx-PR7vqXXg==/1042864788729840959.png) 这是因为内建的例子试图加载指定的贴图fire.png,而它已经随同cocos2d-iphone一起发布在Resources/Images目录下。当然,不使用贴图也可以创建出炫目的粒子效果——通过指定粒子的大小。但要想看到内置的示例原本的效果,就需要把fire.png加到你的Xcode工程中。 ## 二、创建粒子效果 很容易派生出一个CCParticleSystem的子类。但是用它去创建出可观且最终效果接近于最初设想的粒子效果就不那么容易了。以下列出了影响粒子系统外观和行为的属性列表: ~~~ ¥  emitterMode = gravity   gravity,centerOfGravity  radialAccel, radialAccelVar speed, speedVar tangentialAccel, tangentialAccelVar ¥  emitterMode = radius   startRadius,startRadiusVar, endRadius, endRadiusVar ␣  rotatePerSecond, rotatePerSecondVar ¥  duration ¥  posVar ¥  positionType   ¥  startSize, startSizeVar, endSize, endSizeVar ␣ ¥  angle, angleVar ¥  life, lifeVar ¥  emissionRate  ¥  startColor, startColorVar, endColor, endColorVar ¥  blendFunc, blendAdditive ¥  texture ~~~ 正如你所想的,这里有许多变量。当然,需要对每个属性的用途有一个彻底的了解。先让我们从创建一个CCParticleSystem派生类开始。 实际上,你可以从CCPointParticleSystem或CCQuadParticleSystem进行派生。PointParticle在1代或2代iOS设备上运行得更快,但在3代和4代设备上(iPhone3GS、iPad、iPhone4)表现不佳。这是由于CPU架构的不同。3代和4代苹果使用ARMv7CPU架构的新技术和特性,比如矢量浮点处理器以及SIMD(单指令多数据技术,用于提高3D图形运算效率)指令集(NEON),因此使用CCQuadParticleSystem能充分利用这些特性。 如果有疑问的话,不管什么设备总是用CCQuadParticelSystem去创建可视效果,或者让cocos2d替你决定buildtarget。你可以查看我在ParticleEffects02项目中加入的ParticleEffectSelfMade文件: ~~~ #import <Foundation/Foundation.h> #import "cocos2d.h" // Depending on the targeted device the ParticleEffectSelfMadeclass will either derive // from CCPointParticleSystem orCCQuadParticleSystem (preferred for iOS 3rd and 4th Generation) @interface ParticleEffectSelfMade : ARCH_OPTIMAL_PARTICLE_SYSTEM { } ~~~ 预处理定义ARCH_OPTIML_PARTICLE_SYSTEM取代了真实的基类名称,它会在编译动态决定从哪一个粒子系统继承。Cocos2d基于处理器架构采用CCQuadParticleSystem或者CCPointParticleSystem给ARCH_OPTIML_PARTICLE_SYSTEM赋值: ~~~ // build each architecture with the optimalparticle system #ifdef __ARM_NEON__ // armv7 #define ARCH_OPTIMAL_PARTICLE_SYSTEM CCQuadParticleSystem #elif __arm__ || TARGET_IPHONE_SIMULATOR // armv6 or simulator #define ARCH_OPTIMAL_PARTICLE_SYSTEM CCPointParticleSystem #else #error(unknown architecture) #endif ~~~ 现在来看ParticleEffectSelMade类的实现,它使用了所有可用的属性。我企图示范每个属性的使用,但你一一对每个属性进行些修改学习效果会更佳,所以我建议你在这个项目中调整这些属性值。同时,你也可以在代码中看到关于这些参数的简短描述: ~~~ @implementation ParticleEffectSelfMade -(id) init { return [self initWithTotalParticles:250]; } -(id) initWithTotalParticles:(int)numParticles { if((self = [super initWithTotalParticles:numParticles])) { //DURATION 时长 //大部分特效使用无限时长 self.duration = kCCParticleDurationInfinity; //对于有时间限制的特效,使用粒子喷射时间的秒数, //self.duration= 2.0f; //如果粒子系统运行达到指定时间,当其所有粒子死亡后将被移出父节点 ﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽adee﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽﷽ ////       // 对于无限时长的粒子系统,该属性无效 self.autoRemoveOnFinish = YES; //MODE 模式 //喷射粒子会受重力的影响 self.emitterMode = kCCParticleModeGravity; //粒子以环行运动 //self.emitterMode= kCCParticleModeRadius; //某些属性只能在特定的emitterMode下使用 if (self.emitterMode == kCCParticleModeGravity) { // 重心会让你以为是粒子显示时的偏移位置 ,实际上是指节点的位置 self.centerOfGravity = CGPointMake(-15, 0); // gravity 会影响粒子x和y方向的速度 self.gravity = CGPointMake(-50, -90); // 径向加速决定了粒子离开发射器后的移动速度,正值表示粒子从发 // 射器出来后呈加速运动,负值表示粒子从发射器出来后作减速运动 self.radialAccel = -90; self.radialAccelVar = 20; // 切向加速使粒子围绕加速器作旋转运动,且旋转速度会越来越快 // (弹弓效应) self.tangentialAccel = 120; self.tangentialAccelVar = 10; // 粒子产生时的运动速度 self.speed = 15; self.speedVar = 4; } else if (self.emitterMode == kCCParticleModeRadius) { // 开始半径:从发射的位置到发射器的距离 self.startRadius = 100; self.startRadiusVar = 0; // 粒子移动的结束半径。如果比开始半径小,则粒子向内旋转,如果 // 比开始半径大,作外向旋转。可以用 // kCCParticleStartRadiusEqualToEndRadius 常量做出完 //  整的环形运动效果 self.endRadius = 10; self.endRadiusVar = 0; // 旋转速度 self.rotatePerSecond = 180; self.rotatePerSecondVar = 0; } //EMITTER POSITION 喷射器位置 //喷射器位置默认在节点中心 //这是粒子产生后的显示位置 self.position = CGPointZero; self.posVar = CGPointZero; //positionType 决定了当节点移动时已喷射出的粒子是否要重新定位 //(kCCPositionTypeGrouped) ,或者保持在原地 //   (kCCPositionTypeFree). self.positionType = kCCPositionTypeFree; //PARTICLE SIZE 粒子大小 //每个粒子的大小(以像素为单位) self.startSize = 40.0f; self.startSizeVar = 0.0f; self.endSize = kCCParticleStartSizeEqualToEndSize; self.endSizeVar = 0; //ANGLE (DIRECTION) 角度或方向 //粒子喷射角度,0表示正上方 self.angle = 0; self.angleVar = 0; //PARTICLE LIFETIME 粒子寿命 //粒子在屏幕上显示的时间 self.life = 5.0f; self.lifeVar = 0.0f; //PARTICLE EMISSION RATE 粒子喷射速度 //每秒中喷射的粒子数目,当self.particleCount>= // self.totalParticles时,会停止创建新粒子--如果你想制造间歇式爆 //   发的效果,在两次爆发间会产生一个暂停 self.emissionRate = 30; //通常设为该值,你可以改变它 self.totalParticles = 250; //PARTICLE COLOR 粒子颜色 //开始颜色必须设置一个有效的颜色值,否则粒子会不可见。其他颜色 //是可选的。这些颜色决定了粒子在生命周期开始和结束时的颜色。 startColor.r = 1.0f; startColor.g = 0.25f; startColor.b = 0.12f; startColor.a = 1.0f; startColorVar.r = 0.0f; startColorVar.g = 0.0f; startColorVar.b = 0.0f; startColorVar.a = 0.0f; endColor.r = 0.0f; endColor.g = 0.0f; endColor.b = 0.0f; endColor.a = 1.0f; endColorVar.r = 0.0f; endColorVar.g = 0.0f; endColorVar.b = 1.0f; endColorVar.a = 0.0f; //BLEND FUNC 混合函数 //混合函数用于计算透明色,参数1是源,参数2是目 //有效的参数包括:GL_ZERO  GL_ONE  GL_SRC_COLOR  //GL_ONE_MINUS_SRC_COLOR  GL_SRC_ALPHA // GL_ONE_MINUS_SRC_ALPHA   GL_DST_ALPHA  // GL_ONE_MINUS_DST_ALPHA self.blendFunc = (ccBlendFunc){GL_SRC_ALPHA, GL_DST_ALPHA}; //等同于把blendFunc设置为: {GL_SRC_ALPHA, GL_ONE} //self.blendAdditive= YES; //PARTICLE TEXTURE 粒子贴图 self.texture = [[CCTextureCache sharedTextureCache] addImage: @"fire.png"]; } return self; } @end ~~~ 1、可变属性 有许多属性以Var后缀命名,这些是可变属性,它们为属性的取值定义了一个模糊的范围。例如life=5 和 lifeVar=1,意味着每个粒子的寿命为5秒,并在5±1的范围内波动。 如果你不想使用可变属性,将其设置为0。可变属性使粒子呈现更有机和模糊的行为及外观。但当你在设计一种新效果时,除非你相当的有经验,可变属性会造成一定的干扰。我建议初学者不使用可变属性或者将可变属性设置为比较小的值。 2、粒子数 totalParticles属性控制了粒子效果的粒子数目,它通常用initWithTotalParticles方法初始化,但你可以在此后修改。粒子数直接影响粒子效果的外观和性能。 ~~~ -(id) init { return [selfinitWithTotalParticles:250]; } ~~~ 粒子数太小无法得到很炫的效果,太多则导致除了一个白块外你什么也看不到。而且,太多的粒子会浪费帧率。因此,粒子设计工具不会让你创建超过2000个粒子的效果。 3、发射器周期 duration属性定义粒子发射的时间。如果设置为2,则只发射2秒钟的新粒子然后终止: ~~~ self.duration= 2.0f; ~~~ 如果你想让粒子系统在停止发射而且最后一个粒子已经消失时,将粒子效果节点从父节点删除,可将autoRemoveOnFinish属性设置为 YES: ~~~ self.autoRemoveOnFinish= YES; ~~~ autoRemoveOnFinish属性只能和有限运行的粒子系统一起使用。Cocos2d定义了一个kCCParticleDurationInfinity常量(等于-1),用于无限运行的粒子效果。大部分粒子效果属于此类。 ~~~ self.duration= kCCParticleDurationInfinity; ~~~ 4、发射器模式 有两种发射器模式:重力模式和径向模式,由emitterMode属性指定。这两种模式使用类似的参数实现了截然不同两种效果,如后面两张图所示。它们有一两个独特 ~~~ ParticleEffects[6332:207]*** Terminating app due to uncaught exception'NSInternalInconsistencyException', reason: 'Particle Mode should be Radius' ~~~ 技巧:一般,针对你的目标效果,你应当使用最少的粒子数。粒子尺寸也很重要——每个粒子的尺寸越小,其性能就越好。 (1)重力模式 重力模式使粒子趋向或背离某个圆心运动。使用下面的代码设置重力模式: ~~~ self.emitterMode= kCCParticleModeGravity; ~~~ 重力模式使用以下独有属性(只能在 self.emitterMode= kCCParticleModeGravity 时使用): ~~~ self.centerOfGravity= CGPointMake(-15, 0);  self.gravity = CGPointMake(-50, -90); self.radialAccel= -90; self.radialAccelVar= 20; self.tangentialAccel= 120; self.tangentialAccelVar= 10; self.speed =15; self.speedVar= 4; ~~~ centerOfGravity定义了一个CGPoint,距离新粒子出现位置的偏移量。centerOfGravity这个名称很容易让人误解。真正的重心其实是节点的位置,centerOfGravity实际上是重心的某个偏移位置。而gravity属性定义了粒子在x和y轴上的加速度。由于重心的作用,粒子的重力加速度不能太大,而centerOfGravity也不应该偏离的太远。上面这些数值能给你一个比较好的参考。 radialAccel 属性定义了粒子从发射器出来的加速度。该参数为负时,粒子的移动速度会逐渐减慢。tangentialAccel属性与此类似,但它指的是粒子围绕发射器旋转的加速度——负值呈正时针旋转,正值呈反时针旋转。 很显然,speed属性就是粒子的速度。下图显示了重力模式下的粒子效果。 [http://img.ph.126.net/ukhAO3gFmhjlork4tOcvfA==/2560014913200276340.png](http://img.ph.126.net/ukhAO3gFmhjlork4tOcvfA==/2560014913200276340.png) 2)径向模式 径向模式使粒子呈圆环运动。它还可以做出螺旋效果(内旋或者外旋)。使用下面代码设置径向模式: self.emitterMode= kCCParticleModeRadius; 如同重力模式,径向模式也有几个独有的属性,只能用于 self.emitterMode = kCCParticleModeRadius的情况: ~~~ self.startRadius= 100; self.startRadiusVar= 0; self.endRadius= 10; self.endRadiusVar= 0; self.rotatePerSecond= -180; self.rotatePerSecondVar= 0; ~~~ startRadius属性定义粒子将被喷射的位置距离重心(粒子效果节点的位置)的距离。类似,endRadius定义粒子旋转结束的位置到重心的距离。如果你想到达闭环运动的效果,可以将endRadius和startRadius设置为相同的常量值: ~~~ self.endRadius= kCCParticleStartRadiusEqualToEndRadius; ~~~ rotatePerSecond属性影响粒子移动方向和速度,如果startReadius和endRadius不同,则是指旋转的圈数。 [http://img.ph.126.net/DUj0vgcCdols7DaMfyB3uw==/2847400864421855304.png](http://img.ph.126.net/DUj0vgcCdols7DaMfyB3uw==/2847400864421855304.png) 重力模式和径向模式的效果如图所示,你会发现尽管除了各自独有的属性外其他属性完全相同,但两者的显示结果却完全不同。要测试这点,把ParticleEffectSelMade类中以下语句取消注释: ~~~ //self.emitterMode= kCCParticleModeRadius; ~~~ ## 粒子位置 通过移动节点,你可以移动效果。但效果又一个posVar属性决定了新粒子产生时的位置的可变范围。默认情况下,position和posVar都位于节点中心: ~~~ self.position= CGPointZero; self.posVar =CGPointZero; ~~~ 粒子位置的一个非常重要的作用是粒子是否会随着节点的移动而动。例如,如果你想创建一个星点围绕玩家角色运动的效果,你可能想让星点随着玩家一起运动,这就要设置如下属性: ~~~ self.positionType= kCCPositionTypeGrouped; ~~~ 另外,如果想创建玩家着火并且让粒子呈现尾迹效果(粒子会留在原地,不跟随角色移动),则需要这样设置positionType属性: ~~~ self.positionType= kCCPositionTypeFree; ~~~ 自由移动常用于蒸汽、火焰、发动机尾气等效果, 粒子在喷射后就不再和发射器联系在一起,但会在跟随在物体后面。 ## 粒子大小 粒子大小用startSize和endSize属性描述(以像素为单位),这两个属性分别指定了粒子被喷射出来和粒子被移除时的粒子大小。粒子大小会从startSize逐渐向endSize变化。 ~~~ self.startSize= 40.0f;  self.startSizeVar = 0.0f; self.endSize= kCCParticleStartSizeEqualToEndSize; self.endSizeVar= 0; ~~~ 常量kCCParticleStartSizeEqualToEndSize用于确保粒子在整个生命周期中不会变化。 ## 粒子方向 angle属性设定粒子最初的发射方向,为0表示粒子向正上方发射(发射模式为重力模式时)。对于径向模式,它决定了喷射点在开始半径上的位置,随着该值的增加,喷射点会沿着开始半径(startRadius)作反时针移动。 ~~~ self.angle =0; self.angleVar= 0; ~~~ ## 粒子寿命 粒子寿命即粒子从开始到结束所经过的秒数,当粒子生命终止时,粒子会简单的淡出并消失。粒子寿命使用life属性设置。注意,粒子寿命越长,同一时间内在屏幕上显示的粒子越多。当达到粒子数上限时,粒子喷射停止直到有粒子死亡。 ~~~ self.life =5.0f; self.lifeVar= 1.0f; ~~~ 喷射速度emissionRate指每秒钟能生成的粒子数目。同粒子存活数totalParticles属性一起,二者对粒子效果的外观有重要影响。 ~~~ self.emissionRate= 30; self.totalParticles= 250; ~~~ 一般,你需要平衡emissionRate和totalParticles两个属性。你可以用totalParticles除以life作为emissionRate: ~~~ self.emissionRate= self.totalParticles / self.life; ~~~ 小技巧:通过调整粒子寿命,粒子存活数,以及喷射速度,你可以创建间歇喷发效果,由于屏幕中的粒子数已达到最大,新粒子不再产生,导致粒子束呈现周期性的中断。如果你不希望见到粒子流中出现间断,则需要增加粒子存活数,或者减少粒子寿命/或喷发速度。当然,你也可以使用emissionRate = totalParticles/ life。 ## 粒子颜色 每个粒子都会从起始色向终止色进行颜色渐变,创建一种生动的彩色效果。你至少要设置一个startColor,否则粒子不可见-因为默认被设置为黑色。颜色值是典型的ccColor4F结构体,即用4个浮点数分别表示r,g,b颜色和alpha值。每个浮点数取值范围在0-1之间,代表该颜色数从0到100%之间,或者alpha值在完全透明到完全不透明之间。例如,一种白色塑料的颜色可以用如下方法表示: ~~~ startColor.r= 1.0f; startColor.g= 0.25f;  startColor.b = 0.12f; startColor.a= 1.0f; startColorVar.r= 0.0f; startColorVar.g= 0.0f; startColorVar.b= 0.0f; startColorVar.a= 0.0f; endColor.r =0.0f; endColor.g =0.0f; endColor.b =0.0f; endColor.a =1.0f; endColorVar.r= 0.0f; endColorVar.g= 0.0f; endColorVar.b= 1.0f; endColorVar.a= 0.0f; ~~~ ## 粒子混合模式 混合(blend)是指粒子在显示前需要计算其所有像素。blendFunc的取值是一个ccBlendFunc结构体,用以指定源混合模式和目标混合模式: self.blendFunc= (ccBlendFunc){GL_SRC_ALPHA, GL_DST_ALPHA}; 混合时采用源图像(粒子)的r,g,b以及alpha值与屏幕图片原有的颜色值进行混合(粒子被渲染时)。实际上,粒子是以某种方式和其背景混合在一起的,blendFunc属性决定了源图像的哪些及多少颜色需要和背景的哪些及多少颜色进行混合。 blendFunc属性对于粒子的显示有深刻影响。通过在源图像和目标图像上配合使用下列混合模式,你可以创建出乎意料的效果,或者仅仅是一个个黑色颗粒。这需要大量的经验和练习。 ~~~ GL_ZERO GL_ONE  GL_SRC_COLOR   GL_ONE_MINUS_SRC_COLOR  GL_SRC_ALPHA  GL_ONE_MINUS_SRC_ALPHA  GL_DST_ALPHA ␣ GL_ONE_MINUS_DST_ALPHA ~~~ 在[www.khronos.org/opengles/documentation/opengles1_0/html/glBlendFunc.html](http://www.khronos.org/opengles/documentation/opengles1_0/html/glBlendFunc.html),你能找到许多关于OpenGL混合模式的信息及细节。 源图和目标图配合使用GL_SRC_ALPHA和GL_ONE模式常用于创建叠加式混合,在绘制大量粒子摞在一起时会导致一种非常亮甚至白色的效果。 ~~~ self.blendFunc= (ccBlendFunc){GL_SRC_ALPHA, GL_ONE}; ~~~ 当然,你也可以简单地将blendAdditive 属性设为 YES, 等同于将 blendFunc 设为 GL_SRC_ALPHA 和 GL_ONE: ~~~ self.blendAdditive= YES; ~~~ 通常,创建透明的粒子需要使用GL_SRC_ALPHA 和 GL_ONE_MINUS_SRC_ALPHA 模式: ~~~ self.blendFunc= (ccBlendFunc){GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}; ~~~ ## 粒子贴图 如果不使用贴图,所有的粒子都会是平淡的有色颗粒。要在粒子效果中使用贴图,用CCTextTureCache的addImage方法增加一个图片,该方法会将指定的图片文件加载并返回一个CCTexture2D对象: ~~~ self.texture= [[CCTextureCache sharedTextureCache] addImage:@"fire.png"]; ~~~ 粒子图片最好看起来像云块,或者接近于球体。如果粒子图片存在有强烈对比区域、呈不规则形状,例如图片redcorss.png(一个红叉形状), 通常不利于粒子效果的显示。这会很容易导致颗粒感的出现,因为粒子间很难混合。某些效果可以这样干,比如前面提到的在角色头顶旋转的小星点。 粒子贴图的最重要的一点,是图片大小不得超过64x64像素。贴图尺寸越小,粒子效果的性能越好。   ## 粒子设计器 粒子设计器是一种在cocos2d和iOSOpenGL下创建粒子效果的图形工具。可以在[http://particledesigner.71squared.com](http://particledesigner.71squared.com/)下载其试用版。 这是一个难得的工具,为你在创建粒子效果时省去大量的时间。它强大的地方是当你一改变粒子效果的属性,就可以从屏幕上看到其效果。粒子设计器的用户界面默认显示了一个可视化的粒子效果列表。通过选择,可以编辑粒子效果,通过点击或者点击右上角的Emitter Config按钮可以切换到Emitter Config视图: [http://img.ph.126.net/TZd-2tQGaIzQXW9Ka8JjBQ==/3102417193321709960.png](http://img.ph.126.net/TZd-2tQGaIzQXW9Ka8JjBQ==/3102417193321709960.png) 你应该通过前面的介绍认出这些参数了。粒子设计器中只有很少的几个属性是无效的,不能编辑。一个是positionType。另一个是endRadiusVar,这是在径向模式中使用的。后者意味着你不能创建外向旋转的粒子效果(径向模式)。但你还是可以从粒子设计器加载效果,然后通过代码修改某些属性。这只是一个可以忽略不计的小缺陷。 仅有的一个特别地方是粒子贴图Particle Texture。这里既没有可以加载图片的按钮,双击这个地方也没有任何反应。窍门是,Particle Texture栏只接受拖拽。吧图片从Finder拖拽到ParticleTexture栏,如果它变成了绿色,就可以了。当你拖拽图片后,图片就会应用于该粒子效果。如果你使用了超过64*64的图片,粒子设计器会警告你,但仍然会使用该图片,不过会将图片缩放为64*64,而不管源图的比例。 粒子设计器的预览窗口如下,就像一个模拟器窗口: [http://img.ph.126.net/4OpprKS8nVOqKT8RaUjS0g==/1561060220854158265.png](http://img.ph.126.net/4OpprKS8nVOqKT8RaUjS0g==/1561060220854158265.png) 也可以将它设置为iPad的屏幕,可以通过点击这个iPad/iPhone来改变屏幕朝向,也可以通过设计器菜单栏上的Orientation按钮操作。 点击并在预览窗口中进行拖动,粒子效果也会随之而动,这将使你更容易看出移动的粒子效果。 注意,BackgroundColor设置对真实的粒子效果没有影响,它只能改变预览窗口的背景色。这对于设计一个在明亮背景下显示的昏暗的粒子效果是有帮助的,因为你可以在预览窗口中看到效果。 如果你缺少创意,你可以使用Ramdomize按钮。你可能会奇怪ramdomize 在粒子设计器中是什么意思。Urban词典告诉我, ramdomize是random的俚语。我猜开发人员是不是因为使用ramdomize会显得更酷一些。 这绝对是一个创意,但它并不能做到任何属性都随机变化。例如,Ramdomize的Emitter Type、Emitter Location和一些与特定Emitter Type相关的参数始终不会改变。  一旦你寻找到自己的创意,你可以拖动滑动条,然后在预览窗口查看效果。不停地调整其效果直到你满意。尽管这非常有趣,但你很快就发现你制作出来的新效果只能让你高兴一下子。 提示:设计粒子效果的时候一定要小心!要牢记,游戏还需要计算和渲染大量其他对象。如果你在预览窗口查看效果时有60fps的帧率,这并不意味着在游戏中这个粒子效果也能达到60fps。别忘记在你的游戏中测试你的粒子效果并始终关注它的帧率。还要保证在设备上也要进行同样的测试。在模拟器上体现出来的游戏性能经常是不真实的,别把它当真。粒子设计器预览窗口也同样如此。     ## 使用粒子设计器效果 假设几个小时后,你做出了一个完美的粒子效果。现在,你想把它用在cocos2d中。首先需要保存粒子效果。当你点击粒子设计器保存按钮时,弹出如下对话框: [http://img.ph.126.net/VWiYCf3Nqh7IcyKUTeqGqg==/612208074362537220.png](http://img.ph.126.net/VWiYCf3Nqh7IcyKUTeqGqg==/612208074362537220.png) 要保存为cocos2d能够使用的粒子效果,你必须使用cocos2d格式(plist)进行保存。你也可以钩上Embedtexture选项,这会将纹理贴图保存在plist文件中。这个好处是你只需要把plist文件加到Xcode项目就可以了,坏处是如果不在粒子设计器中加载粒子效果,你将无法改变粒子贴图。 保存之后,你可以把效果的plist以及png文件(如果未使用Embedtexture)加到Xcode项目的Resources组。在ParticleEffects03项目中,我加入了两个效果,一个效果带有一个单独的png贴图,而另一个效果的贴图则嵌在了plist文件中。 下面显示了对runEffect方法的修改,以加载用粒子设计器设计出来的效果。 ~~~ -(void) runEffect{ // 删除原先的粒子效果 [selfremoveChildByTag:1 cleanup:YES]; CCParticleSystem*system; switch (particleType){ caseParticleTypeDesignedFX: system = [CCQuadParticleSystemparticleWithFile:@"fx1.plist"]; break; caseParticleTypeDesignedFX2: system = [CCQuadParticleSystemparticleWithFile:@"fx2.plist"]; system.positionType = kCCPositionTypeFree; break; caseParticleTypeSelfMade: system = [ParticleEffectSelfMade node]; break; default: break; } CGSizewinSize = [[CCDirector sharedDirector] winSize]; system.position= CGPointMake(winSize.width / 2, winSize.height / 2); [self addChild:system z:1tag:1]; [label setString:NSStringFromClass([systemclass])]; } ~~~ 初始化CCParticleSystem时,用particleWithFile方法加载粒子设计器效果的plist文件。在case语句中,我用CCQuadParitcleSystem的原因是在所有的iOS文件中它都能运行。你也可以使用ARCh_OPTIMAL_PARTICLE_SYSTEM来代替实际的类名: ~~~ system =[ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:@"fx1.plist"]; ~~~ 提示:粒子设计器的效果必须用CCQuadParticleSystem或者CCPointParticleSystem。 尽管 CCParticleSystem是前两者的父类, 也实现了 particleWithFile 方法, 但除非你使用前面的两个子类,加载设计器的效果后它不会显示任何东西。 补充一下,我把两行代码移到了switchcase之外以避免代码重复,它们是用于把粒子系统节点的位置放到屏幕中心的。   ## 共享粒子效果 对于粒子设计器这是一个很酷的功能,你可以把你的创意和其他用户共享。在设计器菜单里,只需选择Share->Share Emitter,就弹出一个对话框,让你输入要共享的效果的标题、描述: [http://img.ph.126.net/maz-k4aWBqMLRRJ-Gb1mkg==/25332747920817771.png](http://img.ph.126.net/maz-k4aWBqMLRRJ-Gb1mkg==/25332747920817771.png) 共享出来的粒子效果不一定完全满足你的要求,但也能提供一个好的开始,以此开始你自己的创意。它们能帮助你较快地实现需要的效果,至少能激发创意。 我建议你浏览一下这些效果列表,试着找一些感觉。 ## 射击游戏和粒子效果 我现在想看看这些效果在游戏中的样子。让我们继续推进这个射击游戏的开发进程。在这章的ShootEmUp04项目,你会看到如图所示的效果: [http://img.ph.126.net/InHErGyRu8mBOq3T6jU40w==/2485986994325373221.png](http://img.ph.126.net/InHErGyRu8mBOq3T6jU40w==/2485986994325373221.png) 在EnemyEntity类中,gotHit方法是加入粒子爆炸效果的最佳地方,如下所示。我决定为Boss使用一种专门的效果,因为它是如此的大。 ~~~ -(void)gotHit { hitPoints--; if (hitPoints <= 0) { self.visible= NO; // 当敌人被摧毁时,播放粒子效果 CCParticleSystem* system; if (type == EnemyTypeBoss) { system = [ARCH_OPTIMAL_PARTICLE_SYSTEMparticleWithFile:@"fx-explosion2.plist"]; } else {  system= [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:@"fx-explosion.plist"]; } // 设置不能在 Particle Designer 中设置的参数 system.positionType = kCCPositionTypeFree; system.autoRemoveOnFinish = YES; system.position = self.position;  [[GameScenesharedGameScene] addChild:system]; } } ~~~ 粒子效果文件 fx-explosion.plist和 fx-explosion2.plist 必须加到项目的Resources组中。粒子系统的初始化在前面已经提过。因为粒子效果应该也必须独立于敌人,不需要为创建它做过多的工作。首先,aurtoRemoveOnFinish设为YES,以便效果能自动移除。效果也应该定位到敌人的当前位置,以便爆炸时显示在合理位置。 我把粒子效果加到GameScene,因为敌人是无法主动显示粒子效果的。当敌人刚刚出现时是不可见的,它的出现会被粒子效果打断。最主要的是,所有的EnemyEntity对象都被加到了CCSpriteBatchNode中,后者不允许你加入任何非CCSprite对象。如果粒子效果被加到EnemyEntity对象,会导致一个运行时异常。 在玩这个带有粒子效果的游戏时,你会注意到当第1次显示粒子效果时会有短暂的停顿。这是因为cocos2d需要加载粒子贴图——无论贴图是嵌入在plist中的,还是单独的贴图文件,这都是个缓慢的过程。为了避免这种情况,我在GameScene中加入了一种预加载的机制:在init方法中针对每一个粒子效果都调用preloadParticleEffect方法: ~~~ // 预加载粒子贴图,每个粒子效果调用一次 [self preloadParticleEffects:@"fx-explosion.plist"]; [self preloadParticleEffects:@"fx-explosion2.plist"];  preloadParticleEffects 方法只是创建粒子效果。因为返回的是autorelease对象,它的内存将自动释放。但它所加载的贴图仍然存在于CCTextureCache中。 -(void)preloadParticleEffects:(NSString*)particleFile { [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:particleFile]; } ~~~ 如果你没有使用在plist中嵌入贴图,你可以通过调用CCTextureCache的addImage方法预加载粒子贴图: ~~~  [[CCTextureCache sharedTextureCache]addImage:particleFile]; ~~~ ## 结论 本章真是一个视觉盛宴。Cocos2d提供了大量的粒子效果,极好地说明了所能达到的效果,它们是高效而且易于使用的。 但使用源代码创建粒子效果是十分痛苦的事情。有那么多的属性需要设置;有一些又是某种emitter mode专有的;有一些从名称上容易混淆无法理解其真实含义。但是在对每个属性进行过一些讲解后,你应该对这些属性放到一个效果中有什么作用有一个基本的了解,同时知道其中最为关键的参数是哪些。 然后我们介绍了粒子设计器。这个工具非常有用——也很有趣。当你移动滑块,然后观察屏幕上的结果时,你突然就对粒子效果有了一个完整的理解,甚至你还可以吧你的作品与其他人共享,并体验他人的成果。 最终,我们的射击游戏也得到了改进,现在敌人被摧毁时能够播放粒子效果了。这为游戏增色不少。 下一章,我们将介绍tilemaps。