Hello,
I released my second game (first one using cocos2d) so I decided to give back a bit to the community and share some code I made to solve a very specific problem. This might help someone out there with a similar situation but I find it interesting enough as a case study.
The game is Last Fish (more in http://www.cocos2d-iphone.org/forum/topic/12107 ) and uses a minimalist black and white style. To provide the underwater mood I wanted to add lots of tiny white points that would appear, move slowly in a random direction and disappear. Here's a screenshot of the final result (you can see this behavior animated in the trailer):


To do this I initially considered using a cocos2d particle system, however I couldn't get it to both fade in and fade out. Also I only wanted very small ambient points, and felt that it would be overkill to use individual sprites for each point. So I decided to use OpenGL points.
I created a custom node to override draw and have the points as GL_POINTS.
The node would store three arrays: one with positions, one with speeds, one of colors.
The color array is all white, but starts at opacity 0, goes up to the max opacity, then comes back to 0. It remains unchanged.
At each update the speed is applied to the positions. The place where the position/color array match is also changed on each update to give the illusion that the points are fading in then fading out. Also, at each update, the couple of points with opacity 0 (after fading out) get a new random position and speed to create a new point.
This way allows a minimal number of processing to update points and only needs two sets of calls to glColorPointer(), glVertexPointer(), glDrawArrays() to draw all points.
The method updateWithDelay: can be called manually or on its own scheduler.
I'd like to point out that the following code works only in 480x320 (haven't updated it yet for Retina Display). Please reply about any bug, optimization suggestion, or questions. Thanks.
PointsNode.h
#import "cocos2d.h"
@class GameScene;
@interface PointsNode : CCNode {
float colorStart;
float *colors;
float *points;
float *speed;
}
-(void)updateWithDelay:(ccTime)dt;
@end
PointsNode.m
#import "PointsNode.h"
#define ARC4RANDOM_MAX 0x100000000LL
#define RANDI(min,max) ((arc4random()%(max-min))+min)
#define RANDF(min,max) (((double)arc4random() / ARC4RANDOM_MAX) * (max-min) + min)
#define POINTS_NUMBER 100
#define POINTS_MAX_ALPHA 0.25f
#define POINTS_LIFE 10.0f
#define POINTS_MAX_SPEED 10.0f
@implementation PointsNode
-(id)init {
if ((self == [super init])) {
colorStart = 0;
colors = (float*)malloc(POINTS_NUMBER * 4 * sizeof(float));
int i = 0;
int t = POINTS_NUMBER * 2;
float c = 0.0f;
float dc = 2.0f * POINTS_MAX_ALPHA / (float)POINTS_NUMBER;
while(i < t) {
colors[i++]=1.0f;
colors[i++]=1.0f;
colors[i++]=1.0f;
colors[i++]=c;
c+=dc;
}
t = POINTS_NUMBER * 4;
while(i < t) {
colors[i++]=1.0f;
colors[i++]=1.0f;
colors[i++]=1.0f;
colors[i++]=c;
c-=dc;
}
points = (float*)malloc(POINTS_NUMBER * 2 * sizeof(float));
i=0;
t = POINTS_NUMBER * 2;
while (i < t) {
points[i++] = RANDF(0.0f, 480.0f);
points[i++] = RANDF(0.0f, 320.0f);
}
speed = (float*)malloc(POINTS_NUMBER * 2 * sizeof(float));
i=0;
t = POINTS_NUMBER * 2;
while (i < t) {
speed[i++] = RANDF(-1.0f*POINTS_MAX_SPEED, 1.0f*POINTS_MAX_SPEED);
speed[i++] = RANDF(-1.0f*POINTS_MAX_SPEED, 1.0f*POINTS_MAX_SPEED);
}
}
return self;
}
-(void)dealloc {
free(colors), colors=0;
free(points), points=0;
free(speed), speed=0;
[super dealloc];
}
-(void)draw {
glPointSize(2.0f);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
int cs = (int)colorStart;
int ncs = POINTS_NUMBER-cs;
glColorPointer(4, GL_FLOAT, 0, colors);
glVertexPointer(2, GL_FLOAT, 0, points + cs * 2);
glDrawArrays(GL_POINTS, 0, ncs);
glColorPointer(4, GL_FLOAT, 0, colors + ncs * 4);
glVertexPointer(2, GL_FLOAT, 0, points);
glDrawArrays(GL_POINTS, 0, cs);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);
}
-(void)updateWithDelay:(ccTime)dt {
int c1 = (int)colorStart;
colorStart += (dt * POINTS_NUMBER) / POINTS_LIFE;
int c2 = MIN((int)colorStart, POINTS_NUMBER);
if (colorStart >= POINTS_NUMBER) {
colorStart = 0.0f;
}
int i = 0;
int t = POINTS_NUMBER * 2;
while (i < t) {
points[i] += speed[i] * dt;
i++;
}
if (c1 == c2) {
return;
}
//update points between c1 and c2
for (i=c1; i<c2; i++) {
points[i*2] = RANDF(0.0f, 480.0f);
points[i*2+1] = RANDF(0.0f, 320.0f);
speed[i*2] = RANDF(-1.0f*POINTS_MAX_SPEED, 1.0f*POINTS_MAX_SPEED);
speed[i*2+1] = RANDF(-1.0f*POINTS_MAX_SPEED, 1.0f*POINTS_MAX_SPEED);
}
}
@end