How to create 2d terrain in cocos2d-x?


#1

I want to create a 2D terrain like in this tutorial: https://www.youtube.com/watch?v=jSCsRXMiT7Q The most confusing part for me is that after generating the curve I need to map 2 textures one for the hill border and one for hill texture. How can I do this? Any tutorial in any language actually is welcome as I don’t understand the technique.


#2

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);
WorldLayer::calculateFloorTexturePoints();

// Tell OpenGL this is the texture we’re using
GL::bindTexture2D(_subterraneanTexture->getName());

// 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);

_glProgram->use();

_glProgram->setUniformsForBuiltins(_modelViewTransform);

// 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;

do
{
	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)
	sprite->setSpriteFrame(frame);

	// TODO: set the sprites anchor point so different sprites align differently with the surface
	//		sprite.anchorPoint = from.surfaceSpriteOffset;

	sprite->setPosition(p);

	cocos2d::Rect rect = sprite->getTextureRect();
	float width = MIN(distance, WIDTHOFSURFACESPRITEINMETERS);
	rect.size.width = Utils::screenFromMeters(width);
	sprite->setTextureRect(rect);
	sprite->setRotation(-1 * normal);

	sprite->setLocalZOrder(100);

	distance -= WIDTHOFSURFACESPRITEINMETERS;

	surfaceFrom.x += dx;
	surfaceFrom.y += dy;

	this->addChild(sprite);


}
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


#3

Thank you for you explanation @Maxxx. The core idea is the same as in this tutorial: http://www.raywenderlich.com/32954/how-to-create-a-game-like-tiny-wings-with-cocos2d-2-x-part-1 and you have added the sprites for boarders. Cool, thanks!


#4

Yes - I actually looked at that tutorial early on - then decided I wanted to design my levels not generate them, and I wanted to have a real surface.
I also looked at PRKit originally for the gl stuff - but had to translate it, and didn’t like the thought of the inefficiencies of using a generic solution rather than having a bit more control, so wrapped my own.
Using the sprites for borders is cool - you can really play around (for example, if you want you can animate the grass, or have different height grass etc.

Cheers


#5

@Maxxx you have a sample project I can see how it all comes together? Thanks for any ideas!


#6

UNfortunately not really- I blogged about this some years ago
https://web.archive.org/web/20140126081639/http://www.pooperpig.com/2012/09/

(I lost the original in the great server crash of 2013, but it’s been archived here)

It may be of some help?

When I looked at this originally and wanted to design levels, it was a great idea - but I since changed to a cross between generation and design - using scripting to define the level, but functions within the script to do some procedural stuff.


#7

@Maxxx I appreciated your great help man! Thanks!