This is what I do:
First I have a collection of Terrain Points that describe the basic shape of the terrain. This just contains 'control points'.
At the start of the level I calculate points between these control points according to a number of parameters (that vary for different levels). For example a point at (0,0) followed by a point at (10,2) might just calculate a straight line between the two, whereas points closer together may calculate curves between the two. (NB coordinates are all calculated in meters and only converted to pixels before being actually drawn)
So, now I have a collection of points that describe my terrain.
This is all done when loading the level.
Inside the 'game loop
I take the collection of 'detail points' and calculate the triangles needed to draw the 'sub terrain'. That bit is easy - each triangle comprises two adjacent points and a third point low enough down that it is just off the bottom of the screen.
Using this collection of 'triangle points' I draw the sub terrain using this method:
_subterraneanTexture = Director::getInstance()->getTextureCache()->addImage(imageName);
// Tell OpenGL this is the texture we're using
// wrap in both the S and T directions (that's X and Y for the rest of us!)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// Enable the arrays
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD);
// Give OpenGl our array of points that we want to fill
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(Vec2), _subterraneanFloorTextureTrianglePoints);
// Give OpenGl the array of points in the texture we want to use to fill the above array of points
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, 2, GL_FLOAT, GL_FALSE, sizeof(Vec2), _subterraneanFloorTexturePoints);
// Tell OpenGl to draw the arrays we gave it
glDrawArrays(GL_TRIANGLE_STRIP, 0, _numberOfFloorSubterraneanTextureTrianglePoints);
Now, using the original collection of detail points, I position sprites to form the surface.
This, I admit, was tricky!
I use a long, thin sprite, and here's the code for drawing sprite(s) between two adjacent points of my surface...
auto pt0 = pFrom->getLocation();
auto pt1 = pTo->getLocation();
// Get the distance between the two points in Meters
float distance = pt0.getDistance(pt1);
// If the distance is greater than the width of our surface sprite, then we need to draw many sprites
double dx = pt1.x - pt0.x;
double dy = pt1.y - pt0.y;
float normal = atan(dy / dx);
dy = WIDTHOFSURFACESPRITEINMETERS * sin(normal);
dx = WIDTHOFSURFACESPRITEINMETERS * cos(normal);
// dx and dy now store x and y distance for the surface sprite, in meters
normal = CC_RADIANS_TO_DEGREES(normal);
auto surfaceFrom = pt0;
Vec2 p = Utils::screenPointFromWorldPoint(surfaceFrom );
p += pFrom->getOffset(); // Add on the offset which is specified i teh pList in m but handled internally in pixels
SpriteFrameCache* frameCache = SpriteFrameCache::getInstance();
SpriteFrame* frame = frameCache->getSpriteFrameByName(pFrom->getSurfaceSpriteName());
Sprite* sprite = Sprite::createWithSpriteFrameName(pFrom->getSurfaceSpriteName());
sprite->setAnchorPoint(Vec2(0,0)); // anchor bottom left so surface is right on Box2d surface (except for applying any offset)
// TODO: set the sprites anchor point so different sprites align differently with the surface
// sprite.anchorPoint = from.surfaceSpriteOffset;
cocos2d::Rect rect = sprite->getTextureRect();
float width = MIN(distance, WIDTHOFSURFACESPRITEINMETERS);
rect.size.width = Utils::screenFromMeters(width);
sprite->setRotation(-1 * normal);
distance -= WIDTHOFSURFACESPRITEINMETERS;
surfaceFrom.x += dx;
surfaceFrom.y += dy;
while (distance > 0);
So, I end up with a gl drawn subterrain topped by sprites.
I actually repeat this for a roof, too.
there's a video on Youtube of me editing a level while developing this