As odd as it sounds, sometimes in a 2D game you simply just want to add some simple 3D graphics. Whether you are creating a cool 2D/3D hybrid or a simple 3D game with a 2D HUD, 3D graphics are no easy thing to produce. The complexities of a third dimension often conflict with 2D programming paradigms.
For the sake of simplicity, this recipe will show you how to create a simple colored cube and a simple textured cube. The uses of simple geometry are varied even when making a 2D game. However, more examples including shaders and 3D models are beyond the scope of this book.
#import "Vector3D.h" @interface Cube3D : CCSprite { Vector3D *translation3D; Vector3D *rotation3DAxis; GLfloat rotation3DAngle; bool drawTextured; } @property (readwrite, assign) Vector3D *translation3D; @property (readwrite, assign) Vector3D *rotation3DAxis; @property (readwrite, assign) GLfloat rotation3DAngle; @property (readwrite, assign) bool drawTextured; -(void) draw; @end @implementation Cube3D @synthesize translation3D,rotation3DAxis,rotation3DAngle,drawTextured; -(void) draw { //Vertices for each side of the cube const GLfloat frontVertices[]={ -0.5f,-0.5f,0.5f, 0.5f,-0.5f,0.5f, -0.5f,0.5f,0.5f, 0.5f,0.5f,0.5f}; const GLfloat backVertices[] = { -0.5f,-0.5f,-0.5f, -0.5f,0.5f,-0.5f, 0.5f,-0.5f,-0.5f, 0.5f,0.5f,-0.5f }; const GLfloat leftVertices[] = { -0.5f,-0.5f,0.5f, -0.5f,0.5f,0.5f, -0.5f,-0.5f,-0.5f, -0.5f,0.5f,-0.5f }; const GLfloat rightVertices[] = { 0.5f,-0.5f,-0.5f, 0.5f,0.5f,-0.5f, 0.5f,-0.5f,0.5f, 0.5f,0.5f,0.5f }; const GLfloat topVertices[] = { -0.5f,0.5f,0.5f, 0.5f,0.5f,0.5f, -0.5f,0.5f,-0.5f, 0.5f,0.5f,-0.5f }; const GLfloat bottomVertices[] = {-0.5f,-0.5f,0.5f,-0.5f,-0.5f,-0.5f,0.5f,-0.5f,0.5f, 0.5f,-0.5f,-0.5f }; //Coordinates for our texture to map it to a cube side const GLfloat textureCoordinates[] = { 0,0, 1,0, 0,1, 1,1,}; //We enable back face culling to properly set the depth buffer glEnable(GL_CULL_FACE); glCullFace(GL_BACK); //We are not using GL_COLOR_ARRAY glDisableClientState(GL_COLOR_ARRAY); //We disable GL_TEXTURE_COORD_ARRAY if not using a texture if(!drawTextured){ glDisableClientState(GL_TEXTURE_COORD_ARRAY); } //Replace the current matrix with the identity matrix glLoadIdentity(); //Translate and rotate glTranslatef(translation3D.x, translation3D.y, translation3D.z); glRotatef(rotation3DAngle, rotation3DAxis.x, rotation3DAxis.y, rotation3DAxis.z); //Bind our texture if neccessary if(drawTextured){ glBindTexture(GL_TEXTURE_2D, texture_.name); } //Here we define our vertices, set our textures or colors and finally draw the cube sides glVertexPointer(3, GL_FLOAT, 0, frontVertices); if(drawTextured){ glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates); } else{ glColor4f(1.0f, 0.0f, 0.0f, 1.0f); } glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glVertexPointer(3, GL_FLOAT, 0, backVertices); if(drawTextured){ glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates); } else{ glColor4f(1.0f, 1.0f, 0.0f, 1.0f); } glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glVertexPointer(3, GL_FLOAT, 0, leftVertices); if(drawTextured){ glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates); } else{ glColor4f(1.0f, 0.0f, 1.0f, 1.0f); } glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glVertexPointer(3, GL_FLOAT, 0, rightVertices); if(drawTextured){ glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates); } else{ glColor4f(0.0f, 1.0f, 1.0f, 1.0f); } glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glVertexPointer(3, GL_FLOAT, 0, topVertices); if(drawTextured){ glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates); } else{ glColor4f(0.0f, 1.0f, 0.0f, 1.0f); } glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glVertexPointer(3, GL_FLOAT, 0, bottomVertices); if(drawTextured){ glTexCoordPointer(2, GL_FLOAT, 0, textureCoordinates); } else{ glColor4f(0.0f, 0.0f, 1.0f, 1.0f); } glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); //We re-enable the default render state glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_CULL_FACE); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); } @end @interface Ch1_3DCubes { Cube3D *cube3d1; Cube3D *cube3d2; } @implementation Ch1_3DCubes -(CCLayer*) runRecipe { //Load a textured cube and set initial variables cube3d1 = [Cube3D spriteWithFile:@"crate.jpg"]; cube3d1.translation3D = [Vector3D x:2.0f y:0.0f z:-4.0f]; cube3d1.rotation3DAxis = [Vector3D x:2.0f y:2.0f z:4.0f]; cube3d1.rotation3DAngle = 0.0f; cube3d1.drawTextured = YES; [self addChild:cube3d1 z:3 tag:0]; //Load a colored cube and set initial variables cube3d2 = [Cube3D spriteWithFile:@"blank.png"]; cube3d2.translation3D = [Vector3D x:-2.0f y:0.0f z:-4.0f]; cube3d2.rotation3DAxis = [Vector3D x:2.0f y:2.0f z:4.0f]; cube3d2.rotation3DAngle = 0.0f; cube3d2.drawTextured = NO; [self addChild:cube3d2 z:1 tag:1]; //Schedule cube rotation [self schedule:@selector(step:)]; return self; } -(void) step:(ccTime)delta { cube3d1.rotation3DAngle += 0.5f; cube3d2.rotation3DAngle -= 0.5f; } @end
What we see here is a crash course in OpenGL ES cube rendering with a Cocos2d twist. Like when we drew OpenGL primitives, here we create another CCNode
and override its draw method to create more complex OpenGL geometry.
Texturing:
We harness a
CCSprite
method to load a texture into memory to allow us to bind that texture for 3D drawing. This process is fairly straightforward.Depth testing, sizing, and translation:
Thanks to Cocos2d's built-in depth testing, cubes will be properly ordered based on the Z property. The
translation3D.z
value affects the actual size of the cube while itstranslation3D.x
andtranslation3D.y
values affect where it is on the screen proportional totranslation3D.z
.
For more information about 3D graphics, please refer to the recipe Using Cocos3d in Chapter 8, Tips, Tools, and Ports.