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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.