Create a game like Tiny Wings

Tiny Wings is really a cute game and very fun to play thanks to physics content! It uses Box2D. If you’ve never played the game, give it a quick look :

Generate those type of hills with a physics engine is complicated for a novice. You’ll find cool tutorials on Emanuele Feronato’s website using Box2D and an other on Lorenzo Nuvoletta’s website using Nape.

Someone asks me if it was possible to create this type of game with the Citrus Engine, absolutely! So let’s go for a quick tutorial.

Firstly, all hails to Lorenzo! The physics hills and the sprites creation come from his algorithm.
This is what you will get : demo.
To see the physics debug view, open the console with the key tab, then write :

set nape visible true

And press enter. You will see Nape’s debug view and the hero. You can jump with a click.
All the source code is accessible on the new CE’s GitHub for examples.

I won’t explain the algorithm. If you have any questions on it, ask Lorenzo. He will certainly answer better than me. However I’ll explain how it has been added to the Citrus Engine.

This the Main class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package games.tinywings {
 
	import com.citrusengine.core.StarlingCitrusEngine;
 
	/**
	 * @author Aymeric
	 */
	public class Main extends StarlingCitrusEngine {
 
		public function Main() {
 
			setUpStarling(true);
 
			state = new TinyWingsGameState();
		}
	}
}

The GameState. Here you init your physics world, and create the hero and the hills :

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
package games.tinywings {
 
	import com.citrusengine.core.StarlingState;
	import com.citrusengine.math.MathVector;
	import com.citrusengine.physics.nape.Nape;
 
	import flash.geom.Rectangle;
 
	/**
	 * @author Aymeric
	 */
	public class TinyWingsGameState extends StarlingState {
 
		private var _nape:Nape;
		private var _hero:BirdHero;
 
		private var _hillsTexture:HillsTexture;
 
		public function TinyWingsGameState() {
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			_nape = new Nape("nape");
			//_nape.visible = true;
			add(_nape);
 
			_hero = new BirdHero("hero", {radius:20});
			add(_hero);
 
			_hillsTexture = new HillsTexture();
 
			var hills:HillsManagingGraphics = new HillsManagingGraphics("hills", {sliceHeight:200, sliceWidth:70, currentYPoint:350, registration:"topLeft", view:_hillsTexture});
			add(hills);
 
			view.setupCamera(_hero, new MathVector(stage.stageWidth /2, stage.stageHeight / 2), new Rectangle(0, 0, int.MAX_VALUE, int.MAX_VALUE), new MathVector(.25, .05));
		}
 
		override public function update(timeDelta:Number):void {
			super.update(timeDelta);
 
			// update the hills here to remove the displacement made by StarlingArt. Called after all operations done.
			_hillsTexture.update();
		}
	}
}

Now let’s focus on the Hills creation. Hills is a new Nape’s object added to the Core of the Citrus Engine. It extends NapePhysicsObject. Since the Citrus Engine separates logic/physics from view/art, we don’t want to manage graphics in this core class. So we remove all the content related to graphics :

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
package com.citrusengine.objects.platformer.nape {
 
	import nape.geom.Vec2;
	import nape.phys.Body;
	import nape.phys.BodyType;
	import nape.shape.Polygon;
 
	import com.citrusengine.objects.NapePhysicsObject;
 
	/**
	 * This class creates perpetual hills like the games Tiny Wings, Ski Safari...
	 * Write a class to manage graphics, and extends this one to call graphics function.
	 * For more information, check out CE's Tiny Wings example.
	 * Thanks to <a href="http://www.lorenzonuvoletta.com/create-an-infinite-scrolling-world-with-starling-and-nape/">Lorenzo Nuvoletta</a>.
	 */
	public class Hills extends NapePhysicsObject {
 
		/**
		 * This is the height of a slice. 
		 */
		public var sliceHeight:uint = 600;
 
		/**
		 * This is the width of a slice. 
		 */
		public var sliceWidth:uint = 30;
 
		/**
		 * This is the height of the first point.
		 */
		public var currentYPoint:Number = 200;
 
		/**
		 * This is the width of the hills visible. Most of the time your stage width. 
		 */
		public var widthHills:Number = 550;
 
		/**
		 * This is the physics object from which the Hills read its position and create/delete hills. 
		 */
		public var rider:NapePhysicsObject;
 
		protected var _slicesCreated:uint;
		protected var _currentAmplitude:Number;
		protected var _nextYPoint:Number;
		protected var _slicesInCurrentHill:uint;
		protected var _indexSliceInCurrentHill:uint;
		protected var _slices:Vector.<Body>;
		protected var _sliceVectorConstructor:Vector.<Vec2>;
 
		public function Hills(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function initialize(poolObjectParams:Object = null):void {
 
			super.initialize(poolObjectParams);
 
			_prepareSlices();
		}
 
		protected function _prepareSlices():void {
 
			_slices = new Vector.<Body>();
 
			// Generate a rectangle made of Vec2
			_sliceVectorConstructor = new Vector.<Vec2>();
			_sliceVectorConstructor.push(new Vec2(0, sliceHeight));
			_sliceVectorConstructor.push(new Vec2(0, 0));
			_sliceVectorConstructor.push(new Vec2(sliceWidth, 0));
			_sliceVectorConstructor.push(new Vec2(sliceWidth, sliceHeight));
 
			// fill the stage with slices of hills
			for (var i:uint = 0; i < widthHills / sliceWidth * 1.2; ++i) {
				_createSlice();
			}
		}
 
		protected function _createSlice():void {
 
			// Every time a new hill has to be created this algorithm predicts where the slices will be positioned
			if (_indexSliceInCurrentHill >= _slicesInCurrentHill) {
				_slicesInCurrentHill = Math.random() * 40 + 10;
				_currentAmplitude = Math.random() * 60 - 20;
				_indexSliceInCurrentHill = 0;
			}
			// Calculate the position of the next slice
			_nextYPoint = currentYPoint + (Math.sin(((Math.PI / _slicesInCurrentHill) * _indexSliceInCurrentHill)) * _currentAmplitude);
			_sliceVectorConstructor[2].y = _nextYPoint - currentYPoint;
			var slicePolygon:Polygon = new Polygon(_sliceVectorConstructor);
			_body = new Body(BodyType.STATIC);
			_body.shapes.add(slicePolygon);
			_body.position.x = _slicesCreated * sliceWidth;
			_body.position.y = currentYPoint;
			_body.space = _nape.space;
 
			_pushHill();
		}
 
		protected function _pushHill():void {
 
			_slicesCreated++;
			_indexSliceInCurrentHill++;
			currentYPoint = _nextYPoint;
 
			 _slices.push(_body);
		}
 
		protected function _checkHills():void {
 
			if (!rider)
				rider = _ce.state.getFirstObjectByType(Hero) as Hero;
 
			var length:uint = _slices.length;
 
			for (var i:uint = 0; i < length; ++i) {
 
				if (rider.body.position.x - _slices[i].position.x > widthHills * 0.5 + 100) {
 
					_deleteHill(i);
					--i;
					_createSlice();
 
				} else
					break;
			}
		}
 
		protected function _deleteHill(index:uint):void {
 
			_nape.space.bodies.remove(_slices[index]);
			_slices.splice(index, 1);
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			_checkHills();
		}
 
		/**
		 * Bodies are generated automatically, those functions aren't needed.
		 */
		override protected function defineBody():void {
		}
 
		override protected function createBody():void {
		}
 
		override protected function createMaterial():void {
		}
 
		override protected function createShape():void {
		}
 
		override protected function createFilter():void {
		}
 
		override protected function createConstraint():void {
		}
	}
}

We’ve cutted the algorithm in several methods to be able to manage graphics from a subclass.
This is the class which manage graphics :

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
package games.tinywings {
 
	import com.citrusengine.objects.platformer.nape.Hills;
 
	/**
	 * @author Aymeric
	 */
	public class HillsManagingGraphics extends Hills {
 
		public function HillsManagingGraphics(name:String, params:Object = null) {
			super(name, params);
		}
 
		override protected function _prepareSlices():void {
 
			if (view)
				(view as HillsTexture).init(sliceWidth, sliceHeight);
 
			super._prepareSlices();
		}
 
		override protected function _pushHill():void {
 
			if (view)
				(view as HillsTexture).createSlice(_body, _nextYPoint, currentYPoint);
 
			super._pushHill();
		}
 
		override protected function _deleteHill(index:uint):void {
 
			(view as HillsTexture).deleteHill(index);
 
			super._deleteHill(index);
		}
 
 
	}
}

We just make some calls to view’s functions. And so finally, the view class :

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
package games.tinywings {
 
	import nape.phys.Body;
 
	import starling.display.Image;
	import starling.display.Sprite;
	import starling.events.Event;
	import starling.textures.Texture;
 
	import flash.display.BitmapData;
	import flash.geom.Matrix;
 
	/**
	 * @author Aymeric
	 */
	public class HillsTexture extends Sprite {
 
		private var _groundTexture:Texture;
		private var _sliceWidth:uint;
		private var _sliceHeight:uint;
 
		private var _images:Vector.<Image>;
 
		private var _flagAdded:Boolean = false;
 
		public function HillsTexture() {
		}
 
		public function init(sliceWidth:uint, sliceHeight:uint):void {
 
			_sliceWidth = sliceWidth;
			_sliceHeight = sliceHeight;
 
			_groundTexture = Texture.fromBitmapData(new BitmapData(_sliceWidth, _sliceHeight, false, 0xffaa33));
 
			_images = new Vector.<Image>();
 
			addEventListener(Event.ADDED, _added);
		}
 
		private function _added(evt:Event):void {
 
			_flagAdded = true;
 
			removeEventListener(Event.ADDED_TO_STAGE, _added);
		}
 
		public function update():void {
 
			// we don't want to move the parent like StarlingArt does!
			if (_flagAdded)
				this.parent.x = this.parent.y = 0;
		}
 
		public function createSlice(rider:Body, nextYPoint:uint, currentYPoint:uint):void {
 
			var image:Image = new Image(_groundTexture);
			addChild(image);
 
			_images.push(image);
 
			var matrix:Matrix = image.transformationMatrix;
            matrix.translate(rider.position.x, rider.position.y);
            matrix.a = 1.04;
            matrix.b = (nextYPoint - currentYPoint) / _sliceWidth;
            image.transformationMatrix.copyFrom(matrix); 
		}
 
		public function deleteHill(index:uint):void {
 
			removeChild(_images[index], true);
			_images.slice(index, 1);
		}
	}
}

And that’s it! Our scrollable world with hills is working!

To have exactly the same feeling that Tiny Wings there is always lots of work to achieve :
– make the terrain more rolling.
– don’t apply a linear velocity to your hero, just a basic force and then change its mass between screen touched or not.
– create nice graphics using procedural palettes algorithms.

When I’ve some time, I will work on the Box2D version thanks to Emanuele’s tutorial and polish this version.

24 thoughts on “Create a game like Tiny Wings

  1. Absolutely! Create a class which extends Hero and override update function. Then just add a constant velocity, example (using Nape) :

    override public function update(timeDelta:Number):void {
    	
    	var velocity:Vec2 = _body.velocity;
    	velocity.x = 100; //you can change it
    
    	_body.velocity = velocity;
    }
  2. Thanks for the great tutorial!
    Just found a bug/typo in HillsTexture deleteHill()
    slice should be splice, this was causing a memory leak as the array was continuously growing

  3. Hi I’ve been working on a game that implements this tutorial but I’m running into a problem where I don’t see the fill colour of the hill slices. I have to turn on the visibility of nape in order to see where my hills are going. Everything else works perfectly!

    _groundTexture = Texture.fromBitmapData(new BitmapData(_sliceWidth, _sliceHeight, false, 0xffffff));

    Any idea what might be going wrong? I’m stumped.

  4. When I run the example, based on the latest updates, I get this error:

    _nape is null


    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at Walls::WallElement/_createSlice()
    at Walls::WallElement/_prepareSlices()
    at Walls::WallElement/initialize()
    at citrus.core::CitrusObject()
    at citrus.objects::APhysicsObject()
    at citrus.objects::NapePhysicsObject()
    at Walls::WallElement()
    at GameState/initialize()
    at citrus.core.starling::StarlingCitrusEngine/handleEnterFrame()

    I tried moving the _prepareSlices call to an override of addPhysics. This causes a different error once my hero intersects with the wall: ‘b’ is null in NapeContactListener.as: onInteractionBegin()

    Any idea what I might be doing wrong?

  5. Found the issue, I copied the Hills file from this post Since I didn’t see it here: https://github.com/alamboley/Citrus-Engine-Examples/tree/master/src/games
    I’m now using the latest from the Citrus Engine repo, which I didn’t see before.

    Still having the other issue though, trying to figure that out now. When my hero object hits the hill, it throws an error saying ‘b’ is null in NapeContactListener.as: onInteractionBegin()

    I made a Gist of what I have so far if you get a chance to check it out:
    https://gist.github.com/astrism/6a0b9f64d7b24ebeb565

  6. Hey Astrism. I didn’t test your code, however it seems you’re calling manually the update function on each object (GameState line 123), which is not needed! It may be the problem too.

  7. Hey Aymeric, I removed some of the extra calls to update, are there special rules around this? I notice that your texture class’ update method is getting called from the StarlingState.

    Changing the updates didn’t fix my bug, but I finally found it! When changing the hills to walls I removed this line:
    _body.userData.myData = this;
    I dropped it back in and everything is back to normal. Thanks for your help!

  8. Each object extending CoitrusObject has their update method called if it is enabled (thanks to the boolean).
    “External” class update method should be called manually ;)

  9. Hey, great tutorial! This and the original from Lorenzo helped a lot! I´m totally new to starling and citrus. How to make the hero follow the terrain inclination? Like, if it were a car, it should adapt itself to the floor. Do citrus create a nape space by default?

    Thanks in advance for any help!

  10. Hey Bruno, thanks :) You will probably change this function : _body.allowRotation = false;

    No you’ve to create one:
    _nape = new Nape(“nape”);
    add(_nape);

    Hope that help!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>