Sometimes in 2D game development we need to make use of good old-fashioned OpenGL primitives. With these we can make minimaps, heads up displays, and special effects like bullet tracers and lightning blasts to name a few. In the following scene I've created a simple figure using all of the primitive drawing functions supplied by Cocos2d as well as one I've tweaked and added.
/* Create a solid circle */ void ccDrawSolidCircle( CGPoint center, float r, float a, NSUInteger segs, BOOL drawLineToCenter) { //Check to see if we need to draw a line to the center int additionalSegment = 1; if (drawLineToCenter) additionalSegment++; const float coef = 2.0f * (float)M_PI/segs; GLfloat *vertices = calloc( sizeof(GLfloat)*2*(segs+2), 1); if( ! vertices ) return; //Calculate line segments for(NSUInteger i=0;i<=segs;i++) { float rads = i*coef; GLfloat j = r * cosf(rads + a) + center.x; GLfloat k = r * sinf(rads + a) + center.y; vertices[i*2] = j * CC_CONTENT_SCALE_FACTOR(); vertices[i*2+1] =k * CC_CONTENT_SCALE_FACTOR(); } vertices[(segs+1)*2] = center.x * CC_CONTENT_SCALE_FACTOR(); vertices[(segs+1)*2+1] = center.y * CC_CONTENT_SCALE_FACTOR(); //Draw our solid polygon glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glVertexPointer(2, GL_FLOAT, 0, vertices); glDrawArrays(GL_TRIANGLE_FAN, 0, segs+additionalSegment); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_TEXTURE_2D); //Free up memory free( vertices ); } @implementation ShapeLayer -(void) draw { //Set line width. glLineWidth(4.0f); //Set point size glPointSize(16); //Enable line smoothing glEnable(GL_LINE_SMOOTH); //Draw a blue quadratic bezier curve glColor4ub(0, 0, 255, 255); ccDrawQuadBezier(ccp(100,0), ccp(240,70), ccp(380,0), 10); //Draw a hollow purple circle glColor4ub(255, 0, 255, 255); ccDrawCircle(ccp(240,160), 125.0f, 0.0f, 100, NO); //Draw a solid red lines glColor4ub(255, 0, 0, 255); ccDrawLine(ccp(170,220), ccp(220,190)); ccDrawLine(ccp(260,190), ccp(310,220)); //Draw a green point glColor4ub(0, 255, 0, 255); ccDrawPoint(ccp(200,180)); ccDrawPoint(ccp(280,180)); //Draw a turquoise solid circle glColor4ub(0, 128, 255, 50); ccDrawSolidCircle(ccp(200,180), 25.0f, 0.0f, 20, NO); ccDrawSolidCircle(ccp(280,180), 25.0f, 0.0f, 20, NO); //Draw a brown hollow circle glColor4ub(64,32, 0, 255); ccDrawCircle(ccp(200,180), 25.0f, 0.0f, 100, NO); ccDrawCircle(ccp(280,180), 25.0f, 0.0f, 100, NO); //Draw brown lines glColor4ub(64,32, 0, 255); ccDrawLine(ccp(225,180), ccp(255,180)); ccDrawLine(ccp(305,180), ccp(370,160)); ccDrawLine(ccp(175,180), ccp(110,160)); //Draw an orange polygon glColor4ub(255, 128, 0, 255); CGPoint vertices[5]={ ccp(230,150),ccp(240,160),ccp(250,150),ccp(245,140),ccp(235,140) }; ccDrawPoly(vertices, 5, YES); //Draw a yellow cubic bezier curve glColor4ub(255, 255, 0, 255); ccDrawCubicBezier(ccp(170,90), ccp(220,150), ccp(260,50), ccp(320,100), 10); //Restore original values glLineWidth(1); glDisable(GL_LINE_SMOOTH); glColor4ub(255,255,255,255); glPointSize(1); } @end -(CCLayer*) runRecipe { ShapeLayer *layer = [[ShapeLayer alloc] init]; [layer setPosition:ccp(0,0)]; [self addChild:layer z:2 tag:0]; return self; }
This recipe shows us how to use each primitive drawing function:
In order to use OpenGL drawing routines we must override the following method of a
CCNode
:-(void) draw;
As stated in
CCNode.h
, overriding this method gives us control of underlying OpenGL drawing routines. The following OpenGL statements are implicit:glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_TEXTURE_2D);
To overload this method we create a class named
ShapeLayer
which inherits fromCCLayer
, and therefore fromCCNode
. Once attached to the scene this overriddendraw
method will be called once every cycle.The following primitive drawing functions are available in Cocos2d:
void ccDrawPoint( CGPoint point ); void ccDrawPoints( const CGPoint *points, NSUInteger numberOfPoints ); void ccDrawLine( CGPoint origin, CGPoint destination ); void ccDrawPoly( const CGPoint *vertices, NSUInteger numOfVertices, BOOL closePolygon ); void ccDrawCircle( CGPoint center, float radius, float angle, NSUInteger segments, BOOL drawLineToCenter); void ccDrawQuadBezier(CGPoint origin, CGPoint control, CGPoint destination, NSUInteger segments); void ccDrawCubicBezier(CGPoint origin, CGPoint control1, CGPoint control2, CGPoint destination, NSUInteger segments);
On top of all this we have tweaked
ccDrawCircle
to createccDrawSolidCircle
as follows:void ccDrawSolidCircle( CGPoint center, float r, float a, NSUInteger segs, BOOL drawLineToCenter);
Because we are controlling these OpenGL render calls for each frame this technique works well when used in a real-time minimap. We will explore this in a later recipe.
If you are planning to use primitive drawing extensively you may want to consider using the Vertex Buffer Object OpenGL extension
. Using the GL functions glGenBuffers
, glBindBuffer
, and glBufferData
you can put vertex and other information into video memory rather than system memory. This can drastically improve performance depending on the situation. For more information view the section Best Practices for Working with Vertex Data in the Apple Developer document OpenGL ES Programming Guide for iOS located at http://developer.apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html.