To create special visual effects, game programmers often make use of particlesystems. Particle systems work by emitting vast numbers of tiny particles and rendering them efficiently, much more efficiently than if they were sprites. This allows you to simulate effects such as rain, fire, snow, explosions, vapor trails, and many more.
Particle systems are driven by a great number of properties. By great I mean about 30 properties, which all influence not only the appearance and behavior of individual particles but the whole particle effect. The particle effect is the totality of all particles working together to create a particular visual outcome. One particle alone does not make a fire effect; ten still don’t get close enough. You would want several dozens, if not hundreds, of particles to work together in just the right way to create the fire effect.
Creating convincing particle effects is a trial-and-error process. Trying all the various properties in source code and tweaking a particle system by compiling the game, seeing what it looks like, and then making changes and repeating this process is cumbersome to say the least. That's where a particle design tool comes in handy, and I know just the right one: it's called Particle Designer, and I'll explain how it works in this chapter.
Cocos2d comes with a number of built-in particle effects that give you a good idea of the kinds of effects they'll produce. You can use them as placeholders in your game or subclass and modify the examples defined in the CCParticleExamples.m file if you just want to apply some minor tweaks. The good thing about them is that you don't need any outside help; you create the example particle effects as if they were simple CCNode
objects. As a matter of fact, they are actually derived from CCNode
.
I created a project called ParticleEffects01 that shows all the cocos2d example particle effects. You can cycle through the examples by quickly tapping the screen, and you can also drag and move them with your finger. Many particle effects look totally different as soon as they start moving, as you can see in Figure 9–1, so what seems like just a huge blob of particles may well work as an engine exhaust effect if the particle effect is moving.
There's only one type of effect that can't be moved once started, and these are one-time effects like the CCParticleExplosion
shown in Figure 9–2. What's special about this effect is that it emits all its particles at once and stops emitting new particles instantly. All other particle effects run continuously, always creating new particles while those that have exceeded their lifetime are removed. The challenge in that situation is to balance the total number of particles that are on the screen.
Listing 9–1 shows the relevant methods used in the ParticleEffects01 example project. By using the current particleType
variable in the switch
statement, the corresponding built-in particle effect is created. Note that a CCParticleSystem
pointer is used to store the particles, so I need to use the addChild
code only once at the end of the runEffect
method. Every example particle effect is derived from CCParticleSystem
.
Listing 9–1. Using the Built-in Effects
-(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;
}
}
NOTE: The NSStringFromClass
method is very helpful in this example for printing out the name of the class without having to enter dozens of matching strings. It's one of the cool runtime features of the Objective-C language that you're able to get a class's name as a string. Try to do that in C++, and you'll be biting your toenails. The Objective-C Runtime Programming Guide is a good starting point if you like to dive into this advanced topic or if you just want to learn how Objective-C works on a lower level: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
.
For game-play code, the NSStringFromClass
and related methods hardly play any role, but they're very helpful debugging and logging tools. You can find a complete list and description of these methods in Apple's Foundation Function Reference: http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html
.
If you use one of these example effects in your own project, you might be shocked to see ugly, square pixels. Figure 9–3 shows this effect very clearly. This occurs because all of thebuilt-in particle effects try to load a specific texture named fire.png, which is distributed with cocos2d-iphone in the Resources/Images folder. You can still create very good particle effects even without a texture, provided that the particle sizes remain fairly small. But to see the built-in particle effects as they were intended, you need to add the fire.png image to your Xcode project.
You can easily create your own subclass of the CCParticleSystem
class. What's not so easy is creating a convincing particle effect with it, let alone one that comes close to what you originally envisioned. The following is the list of properties grouped by function that determine the particle system's look and behavior:
emitterMode = gravity
sourcePosition
gravity
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
As you can imagine, there's a lot to tweak here, and that is the main problem of achieving what you want: you don't see the effect of your changes until you rebuild and run your project. You'll learn that when we get to Particle Designer later in this chapter how much it streamlines the creation of new particle effects.
For now let's do it the hard way first and start at the beginning to gain an understanding about how the cocos2d particle system works. To program a particle effect from scratch, you'll first learn how to subclass the CCParticleSystem
class and how to initialize it. A detailed description of the particle system's properties follows.
To create your own particle effect without Particle Designer, you should subclass either from CCParticleSystemPoint
or from CCParticleSystemQuad
. Point particles are a bit faster on first- and second-generation iOS devices but don't perform well on third- and fourth-generation devices like the iPhone 3GS, iPad, and iPhone 4. This is because of the change in CPU architecture. The ARMv7 CPU architecture used in third- and fourth-generation devices introduced optimizations and new features, such as a vector floating-point processor and the SIMD instruction set (NEON).The CCParticleSystemQuad
class benefits fromboth.
If in doubt, Iprefer touse the CCParticleSystemQuad
since it performs reasonably well across all devices and creates exactly the same visual effects. Or let cocos2d make that decision for you based on the build target. You'll see how you can do so in the ParticleEffectSelfMade
class I've added to the ParticleEffects02 project, which you can see in Listing 9–2.
Listing 9–2. Subclassing from the Optimal Particle System Class
#import <Foundation/Foundation.h>
#import "cocos2d.h"
// Depending on the targeted device the ParticleEffectSelfMade class will either derive
// from CCParticleSystemPoint or CCParticleSystemQuad
@interface ParticleEffectSelfMade : ARCH_OPTIMAL_PARTICLE_SYSTEM
{
}
@end
The preprocessor definition ARCH_OPTIMAL_PARTICLE_SYSTEM
, instead of an actual class name, is used to determine during compilation which of the two particle systems this class should subclass from. The definition in cocos2d is based on the processor architecture and results either in a CCParticleSystemQuad
or in a CCParticleSystemPoint
:
// build each architecture with the optimal particle system
#ifdef __ARM_NEON__
// armv7
#define ARCH_OPTIMAL_PARTICLE_SYSTEM CCParticleSystemQuad
#elif __arm__ || TARGET_IPHONE_SIMULATOR
// armv6 or simulator
#define ARCH_OPTIMAL_PARTICLE_SYSTEM CCParticleSystemPoint
#else
#error(unknown architecture)
#endif
Now let's look at the implementation of the self-made particle effect, which uses all available properties. I'll attempt to explain each of them, but it's much better to see it for yourself and experiment with the parameters, so I encourage you to tweak the properties in this project. In the ParticleEffects02 project (Listing 9–3), you'll also find comments describing each parameter in brief.
Listing 9–3. Manually Setting a Particle System's Properties
#import "ParticleEffectSelfMade.h"
@implementation ParticleEffectSelfMade
-(id) init
{
return [self initWithTotalParticles:250];
}
-(id) initWithTotalParticles:(int)numParticles
{
if ((self = [super initWithTotalParticles:numParticles]))
{
self.duration = kCCParticleDurationInfinity;
self.emitterMode = kCCParticleModeGravity;
// some properties must only be used with a specific emitterMode!
if (self.emitterMode == kCCParticleModeGravity)
{
self.sourcePosition = 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;
}
else if (self.emitterMode == kCCParticleModeRadius)
{
self.startRadius = 100;
self.startRadiusVar = 0;
self.endRadius = 10;
self.endRadiusVar = 0;
self.rotatePerSecond = -180;
self.rotatePerSecondVar = 0;
}
self.position = CGPointZero;
self.posVar = CGPointZero;
self.positionType = kCCPositionTypeFree;
self.startSize = 40.0f;
self.startSizeVar = 0.0f;
self.endSize = kCCParticleStartSizeEqualToEndSize;
self.endSizeVar = 0;
self.angle = 0;
self.angleVar = 0;
self.life = 5.0f;
self.lifeVar = 1.0f;
self.emissionRate = 30;
self.totalParticles = 250;
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;
self.blendFunc = (ccBlendFunc){GL_SRC_ALPHA, GL_DST_ALPHA};
// or use this shortcut to set the blend func to: GL_SRC_ALPHA, GL_ONE
//self.blendAdditive = YES;
self.texture = [[CCTextureCache sharedTextureCache] addImage:@"fire.png"];
}
return self;
}
@end
In Listing 9–3 you will have noticed how verbose the code is simply because so many particle system properties can be initialized. And most of them need to be set to acceptable values in order to get a reasonably meaningful particle effect displayed on the screen. Some properties are even mutually exclusive and can't be used together. It's time to take a close look t what these particle system properties actually do.
You'll notice that many properties have companion properties suffixed with Var
. These are variance properties, and they determine the range of fuzzyness that is allowed for the corresponding property. Take, for example, the properties life = 5
and lifeVar = 1
. These values mean that on average each particle will live for five seconds. The variance allows a range of 5-1 to 5+1. So, each particle will get a random lifetime between four to six seconds.
If you don't want any variation, set the Var
variable to 0. Variation is what gives particle effects their organic, fuzzy behavior and appearance. But variation can also be confusing when you design a new effect, so unless you have some experience, I recommend starting with a particle effect that has little or no variance.
Let's get acquainted with particles by starting with the total number of particles in the particle effect, controlled by the totalParticles
property. The totalParticles
variable is usually set by the initWithTotalParticles
method but can be changed later. The number of particles has a direct impact both on the look of the effect and on performance.
-(id) init
{
return [self initWithTotalParticles:250];
}
Use too few particles and you won't get a nice glow, but it may be sufficient to sprinkle a few stars around the player's head when he runs into a wall. Use too many particles and it might not be what you want either, because many particles are rendered on top of one another and possibly blended so you basically end up with a white blob. Furthermore, using too many particles easily kills your framerate. There's a reason why the Particle Designer tool won't let you create effects with more than 2,000 particles.
TIP: In general, you should aim to achieve the desired effect with the smallest number of particles. Particle size also plays an important role—the smaller the size of individual particles, the better the performance will be. Especially with Particle Effects, it is important to test them on first- and second-generation devices because they can have a severe and negative impact on performance on the older devices.
The duration
property determines how long particles will be emitted. If set to 2, it will create new particles for two seconds and then stop. It's that simple:
self.duration = 2.0f;
If you'd like the particle effect node to be automatically removed from its parent node once the particle system has stopped emitting particles and the last particles have vanished, set the autoRemoveOnFinish
property to YES
:
self.autoRemoveOnFinish = YES;
The autoRemoveOnFinish
property is a convenience feature that is meaningful only if used in conjunction with particle systems that don't run infinitely, like one-time explosion effects. Cocos2d defines a constant kCCParticleDurationInfinity (equals: -1)
for infinitely running particle effects.
self.duration = kCCParticleDurationInfinity;
The majority of particle effects are infinitely running and can be stopped only by removing them from the node hierarchy. Infinitely running particle effects ignore the autoRemoveOnFinish
property.
There are two emitter modes: gravity
and radius
, controlled by the emitterMode
property. These two modes create fundamentally different effects even if most of the parameters are the same, as you can see when you compare Figure 9–4 with Figure 9–5. Both modes use several exclusive properties (see Listing 9–3)that must not be set if they are not supported by the current mode; otherwise, you'll receive a runtime exception from cocos2d like this:
ParticleEffects[6332:207] *** Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Particle Mode should be Radius'
Gravity mode lets particles fly toward or away from a center point. Its strength is that it allows very dynamic, organic effects. You can set gravity mode with this line:
self.emitterMode = kCCParticleModeGravity;
Gravity mode uses the following exclusive properties, which can be used only when emitterMode
is set to kCCParticleModeGravity
:
self.sourcePosition = 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;
The sourcePosition
determines the offset as a CGPoint
from the node's position where new particles appear. The name is a bit misleading in that the actual center of gravity is the node's position, and sourcePosition
is an offset to that center of gravity. The gravity
property then determines the speed with which particles accelerate in the x
and y
directions. In this case, the negative values indicate that the gravitational force will accelerate particles toward the left (-50) and downward (-90). But this acceleration is relative to the particle node's position. Since particles start out at a sourcePosition
offset of -15 (slightly offset to the left), they will perform a counterclockwise movement around the particle node's position. You can see this effect in Figure 9–4, and tweaking the values in the ParticleEffects02 project helps to understand how sourcePosition
and gravity
affect the movement of particles.
For the center of gravity to have any impact, the gravity of the particles shouldn't be too high, and the sourcePosition
should not be offset too far. The previous values give you a good working example that you can tweak.
The radialAccel
property defines how fast particles accelerate the further they move away from the emitter. This parameter can also be negative, which makes particles slow down as they move away. The tangentialAccel
property is similar in that it lets particles rotate around the emitter and speed up as they move away. Negative values let the particles spin clockwise, and positive values spin them counterclockwise.
The speed
property should be fairly obvious—it's simply the speed of the particles. It has no particular unit of measurement. Figure 9–4 shows an example particle effect using gravity mode. Particles are attracted by the particle node's position and start out slightly to the left of the particle node's position, so they perform a counterclockwise radial movement.
Radius mode causes particles to rotate in a circle. It also allows you to create spiral effects with particles either rushing inward or rotating outward. You set radius mode with this line:
self.emitterMode = kCCParticleModeRadius;
Like gravity mode, radius mode hasexclusive properties. The following propertiescan be used only when emitterMode
is set to kCCParticleModeRadius
:
self.startRadius = 100;
self.startRadiusVar = 0;
self.endRadius = 10;
self.endRadiusVar = 0;
self.rotatePerSecond = -180;
self.rotatePerSecondVar = 0;
The startRadius
property determines how far away from the particle effect node's position the particles will be emitted. Likewise, the endRadius
determines the distance from the node's position the particles will rotate toward. If you want to achieve a perfect circle effect, you can set endRadius
to the same as the startRadius
using this constant:
self.endRadius = kCCParticleStartRadiusEqualToEndRadius;
Using the rotatePerSecond
property, you can influence the direction the particles move and the speed with which they move, and thus the number of times they rotate around if startRadius
and endRadius
are different.
The same particle effect that was shown in Figure 9–4 using gravity mode is shown in Figure 9–5 using radius mode, and you'll notice how different it looks, despite all other properties—except for the exclusive ones—being the same. To test this, uncomment the following line in the ParticleEffects02 project:
//self.emitterMode = kCCParticleModeRadius;
By moving the node, you also move the effect. But the effect also has a posVar
property that determines the variance in the position where new particles will be created. By default, both are at the center of the node:
self.position = CGPointZero;
self.posVar = CGPointZero;
A very important aspect of particle positions is whether existing particles should move relative to the node's movement or whether they should not be influenced at all by the node's position. For example, if you have a particle effect that creates stars around your player-character's head, you would want the stars to follow the player as he moves around. You can achieve this effect by setting this property:
self.positionType = kCCPositionTypeGrouped;
On the other hand, if you want to set your player on fire and you want the particles to create a trail-like effect as the player moves around, you should set the positionType
property like this:
self.positionType = kCCPositionTypeFree;
The free movement is best used with effects like steam, fire, engine exhaust smoke, and similar effects that move around with the object they are attached to and should give the impression of not being connected to the object that emits these particles.
The size of particles is given in pixels using the startSize
and endSize
properties, which determine the size of the particles when they are emitted and how big they are when they are removed. The size of the particle gradually scales from startSize
to endSize
.
self.startSize = 40.0f;
self.startSizeVar = 0.0f;
self.endSize = kCCParticleStartSizeEqualToEndSize;
self.endSizeVar = 0;
The constant kCCParticleStartSizeEqualToEndSize
can be used to ensure that the particle size does not change during a particle's lifetime.
The direction in which particles are initially emitted is set with the angle
property and measured in degrees from 0 to 360. A value of 0
means that particles will be emitted upward, but this is trueonly for thegravity emitterMode
. In radius emitterMode
the angle property determines where on the startRadius
the particles will be emitted; higher values will move the emission point counterclockwise along the radius.
self.angle = 0;
self.angleVar = 0;
A particle's lifetime determines how many seconds it will take to transition from start to end, where the particle will simply fade out and disappear. The life
property sets the lifetime of individual particles. Keep in mind that the longer particles live, the more particles will be on-screen at any given time. If the total number of particles is reached, no new particles will be spawned until some existing particles have died.
self.life = 5.0f;
self.lifeVar = 1.0f;
The emissionRate
property directly influences how many particles are created per second. Together with the totalParticles
property, it has a big impact on what the particle effect looks like.
self.emissionRate = 30;
self.totalParticles = 250;
In general, you will want to balance the emissionRate
so that it matches the particle lifetime with the totalParticles
allowed in the particle effect. You can do so by dividing totalParticles
by life
and set the result as the emissionRate
:
self.emissionRate = self.totalParticles / self.life;
TIP: By tweaking particle lifetime, the total number of particles allowed in the system, and the emissionRate
, you can create burst effects by allowing the stream of particles to be frequently interrupted just because the number of particles on-screen is limited and new particles are emitted relatively quickly. On the other hand, if you notice undesirable gaps in your particle stream, you need to either increase the number of allowed particles or preferably reduce the lifetime and/or emission rate. In that case, you should use emissionRate = totalParticles / life
.
Each particle can transition from a starting color to an end color, creating the vibrant colors particle effects are known for. You should at least set the startColor
in a particle effect; otherwise, the particles may not be visible at all since the default color is black. The colors are of type ccColor4F
, a struct with four floating-point members: r, g, b,
and a
, corresponding to the colors red, green, and blue, as well as the alpha channel, which determines the color's opacity. The value range for each of these members goes from 0 to 1, with 1 being the full color.
If you want a completely white particle color, you'd set all four r
, g
, b
, and a
members to 1
.If you want a red color, you only need to set the r
and a
values to1.0f. If you want blue and then set b
and a
to 1.0f.Note that the a
value is the alpha transparency of the color. If you leave it at the default value of 0.0f, the color will be completely translucent and thus not visible.
// startColor is mostly red and fully opaque
startColor.r = 1.0f;
startColor.g = 0.25f;
startColor.b = 0.12f;
startColor.a = 1.0f;
// startColor has no variance (plus/minus 0.0f)
startColorVar.r = 0.0f;
startColorVar.g = 0.0f;
startColorVar.b = 0.0f;
startColorVar.a = 0.0f;
// endColor is a fully opaque black color
endColor.r = 0.0f;
endColor.g = 0.0f;
endColor.b = 0.0f;
endColor.a = 1.0f;
// endColorVar specifies a full variance range for color blue
// the end of lifetime color of a particle will be randomly between black and blue
endColorVar.r = 0.0f;
endColorVar.g = 0.0f;
endColorVar.b = 1.0f;
endColorVar.a = 0.0f;
Blending refers to the computation a particle's pixels go through before being displayed on-screen. The property blendFunc
takes a ccBlendFuncstruct
as input, which provides the source and destination blend modes:
self.blendFunc = (ccBlendFunc){GL_SRC_ALPHA, GL_DST_ALPHA};
Blending works by taking the red, green, blue, and alpha of the source image (the particle) and mixing it with the colors of any images that are already on-screen when the particle is rendered. In effect, the particle blends in a certain way with its background, and blendFunc
determines how much and what colors of the source image are blended how much and with which colors of the background.
The formula to determine the final pixel color on-screen is as follows:
(source color * source blend function) + (destination color * destination blend function)
Let's assume our example source pixel has the RGB values (0.1, 0.2, 0.3) and the destination pixel has the RGB values (0.4, 0.5, 0.6). Both color values are multiplied by the blend function, the simplest being GL_ONE
, which equals 1.0. That means the resulting pixel's color will be as follows:
(0.1 * 1 + 0.4 * 1, 0.2 * 1 + 0.5 * 1, 0.3 * 1 + 0.6 * 1) = (0.5, 0.7, 0.9)
The blendFunc
property has a very profound effect on how particles are displayed. By using a combination of the following blend modes for both source and target, you can create rather bizarre effects or simply cause the effect to render as black squares. There's lots of room for experimentation.
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
You'll find more information on the OpenGL blend modes and details about the blend calculations in the OpenGL ES documentation at www.khronos.org/opengles/documentation/opengles1_0/html/glBlendFunc.html
.
TIP: Since it's hard to imagine which blend functions will create which results with varying images, I'd like to point you to an article that describes the most common blend operations with example images: www.machwerx.com/2009/02/11/glblendfunc/
.
Even more interesting is the Visual glBlendFunc tool developed by Anders Riggelsen: www.andersriggelsen.dk/OpenGL/
. With any HTML5-compatible browser you can play with various images and blend functions to see the results instantly.
Note that you can also modify the blendFunc
property of other cocos2d nodes, namely, all nodes that conform to the CCBlendProtocol
such as the classes CCSprite
, CCSpriteBatchNode
, CCLabelTTF
, CCLayerColor
, and CCLayerGradient
.
The source and target blend modes GL_SRC_ALPHA
and GL_ONE
are frequently combined to create additive blending, resulting in very bright or even white colors where many particles are drawn on top of each other:
self.blendFunc = (ccBlendFunc){GL_SRC_ALPHA, GL_ONE};
Alternatively, you can simply set the blendAdditive
property to YES
, which is the same as setting blendFunc
to GL_SRC_ALPHA
and GL_ONE
:
self.blendAdditive = YES;
The normal blend mode is set using GL_SRC_ALPHA
and GL_ONE_MINUS_SRC_ALPHA
, which creates transparent particles:
self.blendFunc = (ccBlendFunc){GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA};
Without a texture, all particles would be flat, colored squares, as in Figure 9–3. To use a texture for a particle effect, provide one using the CCTextureCache
method addImage
, which returns the CCTexture2D
for the given image file:
self.texture = [[CCTextureCache sharedTextureCache] addImage:@"fire.png"];
Particle textures look best if they are cloudlike and roughly spherical. It's often detrimental to the particle effect if the texture has high-contrast areas, resembling a particular shape or form, like the redcross.png from the shoot-'em-up game. This makes it easier to see individual particles because they don't blend too well with each other. Some effects can use this to their advantage, like the aforementioned stars circling the player's head. In addition, cartoonlike images or gradients work best, whereas photorealistic images tend to create an undefinable pixel mess. To demonstrate this, you'll see three different textures applied to the same particle effect in Figure 9–6.
The most important aspect of particle textures is that the image must be at most 64x64 pixels in size. The smaller the texture size, the better it is for the performance of the particle effect.
The Particle Designer is an interactive tool for creating particle effects for cocos2d and iOS OpenGL applications. You can download the trial version at http://particledesigner.71squared.com
.
This is an invaluable tool that will save you a lot of time creating particle effects. Its power is that you can immediately see what happens on-screen when you change a particle effect's properties, and you can share your creations with others and get inspiration from other developers' particle effects.
By default, the Particle Designer's user interface shows a visual list of particle effects. To edit a particular effect, select it and switch to the Emitter Config
view (Figure 9–7) by either double-clicking it or clicking the Emitter Config
button in the upper-right corner.
You should recognize these parameters from the description of the self-made particle effect properties. There are only a few properties that are not available and can't be edited in Particle Designer. One is the positionType
, and another is the endRadiusVar
property for the radius emitter mode. The latter means you can't create particle effects that rotate outward in radius mode. But you can always load a Particle Designer effect and then tweak it in code by overriding certain properties, like setting the positionType
to kCCPositionTypeFree
, as you'll do later in Listing 9–4. This is just a minor nuisance compared to actually seeing on-screen how your effect changes as you move the sliders.
NOTE: The Particle Designer developers are currently working on Particle Designer 2.0, which will feature a redesigned and less crowded user interface. They also plan to create specialized controls to allow you to change both value and variance using a single gauge-like control. So if your Particle Designer looks quite different from the one in Figure 9–7, you're using version 2.0. If version 2.0 is not available as you read this, you should know that the upgrade to version 2.0 will be free for existing customers.
The only unusual control is the Particle Texture. There is no button to load an image, and double-clicking the field doesn't do anything either. The trick here is that the Particle Texture box only accepts images that are dragged and dropped onto it. Just drag any image from Finder over to this box, and the box will turn green, signaling that it will accept that image. Once you drop the image, it will be used by the particle effect.
CAUTION: Particle Designer will warn you if you drop an image that is larger than 512x512 pixels. It will use the image anyway but will scale the image down to 512x512 pixels, regardless of its original aspect ratio. Also, note that the previous limitation of 64x64 pixel textures has been removed with Particle Designer 1.3.
The Particle Designer preview window in Figure 9–8 looks just like the iPhone Simulator. It can also be set to the iPad screen size, and the orientation can be changed by clicking the iPad
/iPhone
and Orientation
buttons in Particle Designer's menu bar, to the right of the Load
, Save,
and Save As
buttons. By clicking and dragging inside the preview window, you can move the particle effect around, which helps you see how the effect might look on moving objects.
Notice that the Background Color settings are not part of the actual effect. They will change the background color of the preview window. This is useful if your game has bright colors and you want to design a dark or dim effect and still be able to see what you're doing.
If you lack inspiration, you can always make use of the Ramdomize
button. You can also ponder about the meaning of the word ramdomize, which is how it's spelled in the Particle Designer. The Urban Dictionary tells me that ramdom is a cooler form of random. So, I'm guessing the developers just thought their randomizer to be extra cool. Well, it's definitely inspiring even though it doesn't randomize all of the available properties. For example, Ramdomize
will never change the emitter type, the emitter location, and many emitter type–specific parameters.
Once you found your inspiration, you'll want to slide the sliders and watch what happens in the preview window. Take your time and tweak an effect until you like it. Careful, though, because it's a very captivating, even mesmerizing, activity, and you'll easily find yourself making new particle effects just for the fun of it.
CAUTION: Be careful when designing particle effects! First, keep in mind that your game has to calculate and render a lot of other things, too. If the effect you're currently designing runs at 60 FPS in Particle Designer's preview window, that doesn't mean it won't kill your framerate when you use it in your game. Always test new particle effects in your game and keep an eye on the framerate. Also, make sure to run these tests on a device, in particular on older devices! Your game's performance in the iPhone/iPad Simulator is often misleading and thus must be regarded as completely irrelevant. The same goes for the Particle Designer preview window.
I'm assuming that, hours later, you've made the perfect particle effect and now you'd like to use that in cocos2d. I made mine, and the first step is to save the particle effect. When you click the Save
or Save As
button in Particle Designer, you'll be presented with a dialog as shown in Figure 9–9.
For the saved particle effect to be usable by cocos2d, you must set the File Format
setting to cocos2d (plist)
. You can also check the Embed texture
box, which will save the texture into the plist file. The benefit is that you only have to add the plist file to your Xcode project; the downside is that you then can't change the effect's texture without loading the particle effect back into Particle Designer.
After saving the effect, you have to add the effect plist and, if you didn't embed the texture, the effect's PNG file to your Xcode project's Resources group. In the ParticleEffects03 project, I've added both variants, one effect with a separate PNG texture and another effect that has the texture embedded in the plist file.
Listing 9–4 shows how I modified the runEffect
method to load the Particle Designer effects.
Listing 9–4. Using a Particle Effect Created with Particle Designer
-(void) runEffect
{
// remove any previous particle FX
[self removeChildByTag:1 cleanup:YES];
CCParticleSystem* system;
switch (particleType)
{
case ParticleTypeDesignedFX:
system = [CCParticleSystemQuad particleWithFile:@"fx1.plist"];
break;
case ParticleTypeDesignedFX2:
system = [CCParticleSystemQuad particleWithFile:@"fx2.plist"];
system.positionType = kCCPositionTypeFree;
break;
case ParticleTypeSelfMade:
system = [ParticleEffectSelfMade node];
break;
default:
// do nothing
break;
}
CGSize winSize = [[CCDirector sharedDirector] winSize];
system.position = CGPointMake(winSize.width / 2, winSize.height / 2);
[self addChild:system z:1 tag:1];
[label setString:NSStringFromClass([system class])];
}
You initialize a CCParticleSystem
with a Particle Designer effect by using the particleWithFile
method and providing the particle effect's plist file as parameter. In this case, I chose a CCParticleSystemQuad
since it performs well on all iOS devices. You can also leave this decision to cocos2d by using the ARCH_OPTIMAL_PARTICLE_SYSTEM
keyword in place of an actual class name:
system = [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:@"fx1.plist"];
CAUTION: Particle Designer effects must be initialized with either CCParticleSystemQuad
or CCParticleSystemPoint
. Even though CCParticleSystem
, the parent class for the aforementioned subclasses, implements the particleWithFile
method, it won't display anything unless you use one of the subclasses, either Quad or Point particle systems, when loading Particle Designer effects.
As a side note, I moved the two lines that position the particle system node at the center of the screen outside of the switch
case to avoid duplicating that code. As I mentioned before, it's always good practice to minimize the amount of code duplication, and this is one area where I've seen developers simply copying and pasting what's already there.
What's very cool about Particle Designer is that you can share your creations with other Particle Designer users. From the Particle Designer menu, simply choose Share
and then Share Emitter
to open a dialog that lets you enter a title and description for your particle effect, as shown in Figure 9–10.
CAUTION: As the message in Figure 9–8 indicates, you should be careful to only upload artwork for which you have the rights to share and distribute or for which you are the copyright holder. Otherwise, you risk violating someone's copyright or may be violating a nondisclosure or other agreement if you work under contract.
In Figure 9–11, you can see the effect I just submitted in the lower-right corner.
Shared particle effects may not always be perfect for your requirements, but they often provide good starting points for your own effects. They help you achieve the desired effect much faster, and, at the very least, they can be an inspiration. I encourage you to scroll through the list of effects and try as many as you can to gain a good sense of what's possible, what looks good, and what just doesn't work.
I'd love to see some of these effects in the game! Let's take the shoot-'em-up game to a new level. You'll find the results in this chapter's ShootEmUp04 project along with added sound effects, as well as in Figure 9–12.
In the EnemyEntity
class, the gotHit
method is the perfect place to add destructive particle explosions, as Listing 9–5 shows. I decided the boss monster should have its own particle effect, mostly because it's so big. And purple.
Listing 9–5. Adding an Explosion Effect to the Shoot 'em Up Game
-(void) gotHit
{
hitPoints--;
if (hitPoints <= 0)
{
self.visible = NO;
// Play a particle effect when the enemy was destroyed
CCParticleSystem* system;
if (type == EnemyTypeBoss)
{
system = [ARCH_OPTIMAL_PARTICLE_SYSTEM
particleWithFile:@"fx-explosion2.plist"];
}
else
{
system = [ARCH_OPTIMAL_PARTICLE_SYSTEM
particleWithFile:@"fx-explosion.plist"];
}
// Set some parameters that can't be set in Particle Designer
system.positionType = kCCPositionTypeFree;
system.autoRemoveOnFinish = YES;
system.position = self.position;
[[GameScene sharedGameScene] addChild:system];
}
}
The particle effects fx-explosion.plist andfx-explosion2.plistmust be added as resources to the Xcode project. The particle system is initialized as before. Since the particle effect should and must be independent from the enemy that creates it, a few preparations are necessary. First, the autoRemoveOnFinish
flag is set to YES
so that the effect automatically removes itself. This works because both explosions run only for a short time. The effect also needs the current position of the enemy so that it's displayed at the correct position.
I add the particle effect to the GameScene
because the enemy itself can't display the particle effect. To start, it's invisible. It also might be respawned very soon, which would interfere with the particle effect. But most importantly, all EnemyEntity
objects are added to a CCSpriteBatchNode
, which does not allow you to add anything but CCSprite
objects. If the particle effect were added to the EnemyEntity
object, a runtime exception would be inevitable.
As you play the game with the new particle effects, you may notice that the first time one of these effects displays, the game play stops for a short moment. That's because cocos2d is loading the particle effect's texture—a rather slow process, whether the texture is embedded into the plist, as in this case, or provided as a separate texture. To avoid that, I've added a preloading mechanism to the GameScene:
the init
method now calls the preloadParticleEffect
method for each particle effect used during gameplay:
// To preload the textures, play each effect once off-screen
[self preloadParticleEffects:@"fx-explosion.plist"];
[self preloadParticleEffects:@"fx-explosion2.plist"];
The preloadParticleEffects
method simply creates the particle effect. Because the returned object is an autorelease object, its memory will be freed automatically. But the texture it loads will still be in the CCTextureCache
.
-(void) preloadParticleEffects:(NSString*)particleFile
{
[ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:particleFile];
}
If you chose not to embed the texture inside the particle effect plist file, you can preload the particle effect textures simply by calling the CCTextureCacheaddImage
method:
[[CCTextureCache sharedTextureCache] addImage:particleFile];
This chapter was truly a visual joyride! The stock effects provided by cocos2d give a good indication of what the particle effect is able to deliver. They're quick and easy to use.
But it was also excruciating to create a particle effect in source code. There are so many properties to tweak; some are exclusive to specific emitter modes; some have misleading names, and they aren't exactly straightforward to figure out. With the explanation for each property, however, you should now have a good understanding how a particle effect is put together and what the most important parameters are.
We then saw how particle effects can shine with Particle Designer. This tool is extremely helpful—and lots of fun—to work with it. Suddenly, when you can move sliders and see the results immediately on-screen, it changes your whole view on particle effects, and even more so since you can share your creations with others and experiment with other designers' effects.
Finally the Shoot ‘em Up game got a makeover and now plays particle explosions when enemies are destroyed. This makes for a much more lively game.
In the next chapter I'll set the Shoot 'em Up game aside for a bit to tell you all you need to know about tilemaps.