Install Circle Chain on your iPhone for free and get the source code!! 645 downloads to go - last updated: April 21, 2012

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.

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (12 votes, average: 5.00 out of 5)
Loading ... Loading ...
Flash Templates provided by Template Monster are pre-made web design products developed using Flash technology.
They can be easily customized to meet the unique requirements of your project.
Be my fan on Facebook and follow me on Twitter! Exclusive content for my Facebook fans and Twitter followers

This post has 14 comments

  1. Chris

    on October 4, 2011 at 7:48 pm

    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?

  2. Flopsie

    on October 4, 2011 at 8:06 pm

    @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.. :/

  3. Ben Reynolds

    on October 5, 2011 at 2:56 am

    Awesome effect, this tutorial series is really starting to come together!

  4. Rick

    on October 5, 2011 at 4:09 am

    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

  5. Emanuele Feronato

    on October 5, 2011 at 8:29 am

    It’s not Box2D itself, probably it’s the debug draw and its transparences.

  6. Julian

    on October 5, 2011 at 11:10 am

    @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.

  7. Scott

    on October 5, 2011 at 4:26 pm

    @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.

  8. Jeetendra Chauhan

    on October 5, 2011 at 5:34 pm

    it’s Awesome, thanks again Emanuele

  9. Chris

    on October 5, 2011 at 10:41 pm

    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

  10. Scott

    on October 5, 2011 at 11:27 pm

    yeah if you figure out how to do the bike part, let me know please :) because thats the only part im confused on.

  11. Law

    on October 6, 2011 at 7:03 pm

    This is very cool. Thanks for your hard work

  12. Scott

    on October 7, 2011 at 2:39 pm

    Emanuel whats next for this series?

  13. Ivan Kuckir

    on October 31, 2011 at 8:15 pm

    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)

  14. pWEN

    on March 29, 2012 at 8:56 pm

    What version of Box2D is this? I don’t seem to have an .ApplyTorque() function.