Create a terrain like the one in Tiny Wings with Flash and Box2D – adding textures

Talking about Tiny Wings game, Actionscript 3, Box2D and Flash.

If you are following the series about the creation of a Tiny Wings-like terrain, you should know one of the most popular requests once I placed a car running on the hills was how to get rid of the debug draw graphics and use your own textures.

First, you need a seamless texture, or a texture without seam at least horizontally. I suggest you to pick a texture of the same length of the hills (640 pixels in this case) or a texture which width divides the lenght of the hills.

I used a rock texture taken from 40 watt.

Then, as soon as you place the polygons representing the hill slices, you also have to draw on a sprite the same shape you are giving your hill.

This will act as a mask for your seamless texture. And obviously remember to remove both the mask and the texture when they leave the screen to the left side.

This is what you will get:

Use UP and DOWN arrow keys to control the cart, and LEFT/RIGHT to balance it while in the air.

This is the fully commented source code:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import Box2D.Dynamics.Joints.*;
	public class Main extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,10),true);
		private var worldScale:int=30;
		// variables which will be used to determine key pressed
		private var left:Boolean=false;
		private var right:Boolean=false;
		private var up:Boolean=false;
		private var down:Boolean=false;
		// the body of the cart
		private var cart:b2Body;
		// wheels motor speed
		private var motorSpeed:Number=0;
		// front and rear wheels revolute joints
		private var rearWheelRevoluteJoint:b2RevoluteJoint;
		private var frontWheelRevoluteJoint:b2RevoluteJoint;
		// random hill's height
		private var nextHill:Number=140+Math.random()*200;
		// build another hill when stage's x position is lower than this value
		private var buildNextHillAt:Number=0;
		// this is the seamless texture
		private var seamlessTexture:seamlessTextureMc;
		// this is the sprite which will be use to mask the samless texture
		private var theMask:Sprite;
		// this is just a container
		private var hillSprite:Sprite=new Sprite();
		// background sky
		private var sky:skyMc=new skyMc();
		public function Main():void {
			// first I add the sky
			addChild(sky);
			// then the debug draw sprite
			debugDraw();
			// an dfinally the cointainer of the hills
			addChild(hillSprite);
			// we start with two hills
			nextHill=drawHill(10,0,nextHill);
			nextHill=drawHill(10,640,nextHill);
			addEventListener(Event.ENTER_FRAME,updateWorld);
			stage.addEventListener(KeyboardEvent.KEY_DOWN,keyPressed);
			stage.addEventListener(KeyboardEvent.KEY_UP,keyReleased);
			// add the cart
			var carBodyDef:b2BodyDef = new b2BodyDef();
			carBodyDef.type=b2Body.b2_dynamicBody;
			carBodyDef.position.Set(320/worldScale,50/worldScale);
			carBodyDef.userData=new Object();
			var box:b2PolygonShape = new b2PolygonShape();
			box.SetAsBox(30/worldScale,10/worldScale);
			var boxDef:b2FixtureDef = new b2FixtureDef();
			boxDef.density=0.5;
			boxDef.friction=3;
			boxDef.restitution=0.3;
			boxDef.filter.groupIndex=-1;
			boxDef.shape=box;
			cart=world.CreateBody(carBodyDef);
			cart.CreateFixture(boxDef);
			// wheel shape
			var wheelShape:b2CircleShape=new b2CircleShape(12/worldScale);
			// wheel fixture
			var wheelFixture:b2FixtureDef = new b2FixtureDef();
			wheelFixture.density=1;
			wheelFixture.friction=3;
			wheelFixture.restitution=0.1;
			wheelFixture.filter.groupIndex=-1;
			wheelFixture.shape=wheelShape;
			// wheel body definition
			var wheelBodyDef:b2BodyDef = new b2BodyDef();
			wheelBodyDef.type=b2Body.b2_dynamicBody;
			// real wheel
			wheelBodyDef.position.Set(cart.GetWorldCenter().x-(16/worldScale),cart.GetWorldCenter().y+(15/worldScale));
			var rearWheel:b2Body=world.CreateBody(wheelBodyDef);
			rearWheel.CreateFixture(wheelFixture);
			// front wheel
			wheelBodyDef.position.Set(cart.GetWorldCenter().x+(16/worldScale),cart.GetWorldCenter().y+(15/worldScale));
			var frontWheel:b2Body=world.CreateBody(wheelBodyDef);
			frontWheel.CreateFixture(wheelFixture);
			// rear joint
			var rearWheelRevoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef();
			rearWheelRevoluteJointDef.Initialize(rearWheel,cart,rearWheel.GetWorldCenter());
			rearWheelRevoluteJointDef.enableMotor=true;
			rearWheelRevoluteJointDef.maxMotorTorque=10000;
			rearWheelRevoluteJoint=world.CreateJoint(rearWheelRevoluteJointDef) as b2RevoluteJoint;
			// front joint
			var frontWheelRevoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef();
			frontWheelRevoluteJointDef.Initialize(frontWheel,cart,frontWheel.GetWorldCenter());
			frontWheelRevoluteJointDef.enableMotor=true;
			frontWheelRevoluteJointDef.maxMotorTorque=10000;
			frontWheelRevoluteJoint=world.CreateJoint(frontWheelRevoluteJointDef) as b2RevoluteJoint;
		}
		private function drawHill(pixelStep:int,xOffset:Number,yOffset:Number):Number {
			var hillStartY:Number=yOffset;
			var hillWidth:Number=640;
			var hillSliceWidth=hillWidth/pixelStep;
			var hillVector:Vector.;
			var randomHeight:Number=Math.random()*100;
			// creates a new masking sprite
			theMask = new Sprite();
			// adjusts x property according to hill's offset
			theMask.x=xOffset;
			// a simple line style
			theMask.graphics.lineStyle(1,0xffffff);
			// creates a new texture sprite
			seamlessTexture = new seamlessTextureMc();
			// adds the sprite to the container
			hillSprite.addChild(seamlessTexture);
			// adjust x property with the same concept seen before
			seamlessTexture.x=xOffset;
			// sets the mask
			seamlessTexture.mask=theMask;
			if (xOffset!=0) {
				hillStartY-=randomHeight;
			}
			for (var j:int=0; j();
				// once I create the Box2D slices, I also paint the mask sprite
				theMask.graphics.beginFill(0xffffff,1);
				theMask.graphics.moveTo(j*pixelStep,480);
				hillVector.push(new b2Vec2((j*pixelStep+xOffset)/worldScale,480/worldScale));
				theMask.graphics.lineTo(j*pixelStep,hillStartY+randomHeight*Math.cos(2*Math.PI/hillSliceWidth*j));
				hillVector.push(new b2Vec2((j*pixelStep+xOffset)/worldScale,(hillStartY+randomHeight*Math.cos(2*Math.PI/hillSliceWidth*j))/worldScale));
				theMask.graphics.lineTo((j+1)*pixelStep,hillStartY+randomHeight*Math.cos(2*Math.PI/hillSliceWidth*(j+1)));
				hillVector.push(new b2Vec2(((j+1)*pixelStep+xOffset)/worldScale,(hillStartY+randomHeight*Math.cos(2*Math.PI/hillSliceWidth*(j+1)))/worldScale));
				theMask.graphics.lineTo((j+1)*pixelStep,480);
				hillVector.push(new b2Vec2(((j+1)*pixelStep+xOffset)/worldScale,480/worldScale));
				theMask.graphics.endFill();
				var sliceBody:b2BodyDef=new b2BodyDef ;
				var centre:b2Vec2=findCentroid(hillVector,hillVector.length);
				sliceBody.position.Set(centre.x,centre.y);
				for (var z:int=0; z, count:uint):b2Vec2 {
			var c:b2Vec2 = new b2Vec2();
			var area:Number=0.0;
			var p1X:Number=0.0;
			var p1Y:Number=0.0;
			var inv3:Number=1.0/3.0;
			for (var i:int = 0; i < count; ++i) {
				var p2:b2Vec2=vs[i];
				var p3:b2Vec2=i+11000) {
				motorSpeed=1000;
			}
			// setting wheels motor speed
			rearWheelRevoluteJoint.SetMotorSpeed(motorSpeed);
			frontWheelRevoluteJoint.SetMotorSpeed(motorSpeed);
			world.Step(1/30,10,10);
			world.ClearForces();
			for (var currentBody:b2Body=world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
				// getting cart x position
				if (currentBody.GetUserData()!=null) {
					// adjusting stage position to keep cart in the vertical middle of the stage
					x=320-currentBody.GetPosition().x*worldScale;
					// checking if it's time to add a new hill
					if (x<=buildNextHillAt) {
						buildNextHillAt-=640;
						nextHill=drawHill(10,- buildNextHillAt+640,nextHill);
					}
				}
				if (currentBody.GetPosition().x*worldScale<(x*-1)-640) {
					world.DestroyBody(currentBody);
				}
			}
			// have to remove mask and texture once the leave the screen...
			for (var i:int = 0; i=hillSprite.getChildAt(i).x+640) {
					hillSprite.removeChildAt(i);
				}
			}
			// ... and update backgound position
			sky.x=-x;
			world.DrawDebugData();
		}
	}
}

Download the full source code and give me feedback.