Create a terrain like the one in Tiny Wings with Flash and Box2D – adding more bumps
I want to show you an improved version of the Tiny Wings terrain you’ve already seen at Create a terrain like the one in Tiny Wings with Flash and Box2D – adding textures.
This time hills have more bumps and are more irregular, getting a little closer to the original look and feel.
I also placed a sphere you can drive with left and right arrow keys to move along the terrain, and my movieMonitor to show you the performance.
Move the ball with LEFT and RIGHT arrows.
This is the source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | 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 const degreesToRadians:Number=0.0174532925; private var world:b2World=new b2World(new b2Vec2(0,10),true); private var worldScale:int=30; private var left:Boolean=false; private var right:Boolean=false; private var nextHill:Number=240; private var realHeight:Number=240; private var realWidth:Number=0; private var buildNextHillAt:Number=0; var theSphere:b2Body; private var gameCanvas:Sprite=new Sprite(); public function Main():void { var sphereShape:b2CircleShape=new b2CircleShape(12/worldScale); var sphereFixture:b2FixtureDef = new b2FixtureDef(); sphereFixture.density=1; sphereFixture.friction=3; sphereFixture.restitution=0.1; sphereFixture.filter.groupIndex=-1; sphereFixture.shape=sphereShape; var sphereBodyDef:b2BodyDef = new b2BodyDef(); sphereBodyDef.type=b2Body.b2_dynamicBody; sphereBodyDef.position.Set(320/worldScale,0); sphereBodyDef.userData=new Object(); theSphere=world.CreateBody(sphereBodyDef); theSphere.CreateFixture(sphereFixture); addChild(gameCanvas); stage.addChild(new movieMonitor()); debugDraw(); while (realWidth<1280) { nextHill=drawHill(10,realWidth,nextHill); } addEventListener(Event.ENTER_FRAME,updateWorld); stage.addEventListener(KeyboardEvent.KEY_DOWN,keyPressed); stage.addEventListener(KeyboardEvent.KEY_UP,keyReleased); } private function drawHill(pixelStep:int,xOffset:Number,yOffset:Number):Number { var hillStartY:Number=yOffset; var hillWidth:Number=120+Math.ceil(Math.random()*26)*20; realWidth+=hillWidth; var numberOfSlices=hillWidth/pixelStep; var hillVector:Vector.<b2Vec2>; var randomHeight:Number; if (xOffset==0) { randomHeight=0; } else { do { randomHeight=Math.random()*hillWidth/7.5; } while (realHeight+randomHeight>600); } realHeight+=randomHeight; hillStartY-=randomHeight; for (var j:int=0; j<numberOfSlices/2; j++) { hillVector=new Vector.<b2Vec2>(); hillVector.push(new b2Vec2((j*pixelStep+xOffset)/worldScale,480/worldScale)); hillVector.push(new b2Vec2((j*pixelStep+xOffset)/worldScale,(hillStartY+randomHeight*Math.cos(2*Math.PI/numberOfSlices*j))/worldScale)); hillVector.push(new b2Vec2(((j+1)*pixelStep+xOffset)/worldScale,(hillStartY+randomHeight*Math.cos(2*Math.PI/numberOfSlices*(j+1)))/worldScale)); hillVector.push(new b2Vec2(((j+1)*pixelStep+xOffset)/worldScale,480/worldScale)); 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<hillVector.length; z++) { hillVector[z].Subtract(centre); } var slicePoly:b2PolygonShape=new b2PolygonShape ; slicePoly.SetAsVector(hillVector,4); var sliceFixture:b2FixtureDef=new b2FixtureDef ; sliceFixture.shape=slicePoly; var worldSlice:b2Body=world.CreateBody(sliceBody); worldSlice.CreateFixture(sliceFixture); } hillStartY-=randomHeight; if (xOffset==0) { randomHeight=0; } else { do { randomHeight=Math.random()*hillWidth/5; } while (realHeight-randomHeight<240); } realHeight-=randomHeight; hillStartY+=randomHeight; for (j=numberOfSlices/2; j<numberOfSlices; j++) { hillVector=new Vector.<b2Vec2>(); hillVector.push(new b2Vec2((j*pixelStep+xOffset)/worldScale,480/worldScale)); hillVector.push(new b2Vec2((j*pixelStep+xOffset)/worldScale,(hillStartY+randomHeight*Math.cos(2*Math.PI/numberOfSlices*j))/worldScale)); hillVector.push(new b2Vec2(((j+1)*pixelStep+xOffset)/worldScale,(hillStartY+randomHeight*Math.cos(2*Math.PI/numberOfSlices*(j+1)))/worldScale)); hillVector.push(new b2Vec2(((j+1)*pixelStep+xOffset)/worldScale,480/worldScale)); sliceBody=new b2BodyDef ; centre=findCentroid(hillVector,hillVector.length); sliceBody.position.Set(centre.x,centre.y); for (z=0; z<hillVector.length; z++) { hillVector[z].Subtract(centre); } slicePoly=new b2PolygonShape ; slicePoly.SetAsVector(hillVector,4); sliceFixture=new b2FixtureDef ; sliceFixture.shape=slicePoly; worldSlice=world.CreateBody(sliceBody); worldSlice.CreateFixture(sliceFixture); } hillStartY=hillStartY+randomHeight; return (hillStartY); } private function findCentroid(vs:Vector.<b2Vec2>, 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+1<count?vs[int(i+1)]:vs[0]; var e1X:Number=p2.x-p1X; var e1Y:Number=p2.y-p1Y; var e2X:Number=p3.x-p1X; var e2Y:Number=p3.y-p1Y; var D:Number = (e1X * e2Y - e1Y * e2X); var triangleArea:Number=0.5*D; area+=triangleArea; c.x += triangleArea * inv3 * (p1X + p2.x + p3.x); c.y += triangleArea * inv3 * (p1Y + p2.y + p3.y); } c.x*=1.0/area; c.y*=1.0/area; return c; } private function keyPressed(e:KeyboardEvent):void { switch (e.keyCode) { case 37 : left=true; break; case 39 : right=true; break; } } private function keyReleased(e:KeyboardEvent):void { switch (e.keyCode) { case 37 : left=false; break; case 39 : right=false; break; } } private function debugDraw():void { var worldDebugDraw:b2DebugDraw=new b2DebugDraw(); var debugSprite:Sprite = new Sprite(); gameCanvas.addChild(debugSprite); worldDebugDraw.SetSprite(debugSprite); worldDebugDraw.SetDrawScale(worldScale); worldDebugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit); worldDebugDraw.SetFillAlpha(0.5); world.SetDebugDraw(worldDebugDraw); } private function updateWorld(e:Event):void { if (left) { theSphere.ApplyTorque(-0.5); } if (right) { theSphere.ApplyTorque(0.5); } world.Step(1/30,10,10); world.ClearForces(); for (var currentBody:b2Body=world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) { if (currentBody.GetUserData()!=null) { gameCanvas.x=320-currentBody.GetPosition().x*worldScale; gameCanvas.y=240-currentBody.GetPosition().y*worldScale; if (gameCanvas.x<=-realWidth+800) { while (realWidth<-gameCanvas.x+1280) { nextHill=drawHill(10,realWidth,nextHill); } } } if (currentBody.GetPosition().x*worldScale<(gameCanvas.x*-1)-640) { world.DestroyBody(currentBody); } } world.DrawDebugData(); } } } |
No need to download anything, simply copy and paste in any Tiny Wings source code you’ll find around the blog.
Net time, I’ll try to texture the hills with something decent.
They can be easily customized to meet the unique requirements of your project.


























This post has 14 comments
Chris
The irregular hills are very cool, and look very nice.
On a similar note:
I was just playing one of those motorcycle games last night, where you try to keep it from flipping over and hitting the rider on the head as you try to get to the end of the level.
Any idea how these games create their terrain? They’ve obviously been created by hand (vs programatically), but I’m not sure how they would draw it out, store the series of points, and create “solid” objects from it?
Flopsie
@Chris, yeah, I tried to make a similar game, but I don’t want a random generated terrain. As I’m not so familiar with box2D I gave up any attempts.. :/
Ben Reynolds
Awesome effect, this tutorial series is really starting to come together!
Rick
Is AS3 Box2D not ready for iOS? I took your above code and simply set right=true (so it would start to run) and commented out the performance meter.
It was very choppy on my iPad2
Emanuele Feronato
It’s not Box2D itself, probably it’s the debug draw and its transparences.
Julian
@Rick of cause it is. Check out Haxe (haxe.org) to compile as3 into a native iOS app. It runs very smooth then on android, iOS and webos.
Scott
@Chris Im not sure exactly how to make the bike part of it. But youtube AS3 sidescroller devnote has a good tut on this. So once you have this you would have to make the bike part. Which would not have be the same as the player. Each wheel would have to have controll.
Jeetendra Chauhan
it’s Awesome, thanks again Emanuele
Chris
Awesome, thanks Scott!
It makes sense that a per-pixel test would probably be most straightforward, and actually work ;)
I’ll have to test out if it is fast enough for what I need :D
Scott
yeah if you figure out how to do the bike part, let me know please :) because thats the only part im confused on.
Law
This is very cool. Thanks for your hard work
Scott
Emanuel whats next for this series?
Ivan Kuckir
It’s better to use “isEdge” property of b2Body (or something), there are just 2 points, no polygons. It is much faster then. I used it here http://www.ivank.net/clients/moto/ (little demo of motorbike)
pWEN
What version of Box2D is this? I don’t seem to have an .ApplyTorque() function.