Teleporter, treadmill and cannon

After my game school project (Kinessia), I’m still doing some experiments with the Citrus Engine.

On its latest update, there was some new objects :
– a powerful particle system.
– new objects : missile, reward and reward box.

In this tutorial, I wil explain how to create new specific object which extends a Citrus Engine object.
At the end, we will have : a teleporter, a treadmill and a cannon that fires missile !
An ugly example.

Now let’s start coding ! I start with my new classes and the state at the end.

A teleporter is simply a Sensor which can move PhysicsObject, it has a destination, and a waiting time :

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
package {
 
	import com.citrusengine.objects.PhysicsObject;
	import com.citrusengine.objects.platformer.Sensor;
 
	import flash.utils.clearTimeout;
	import flash.utils.setTimeout;
 
	/**
	 * @author Aymeric
	 */
	public class Teleporter extends Sensor {
 
		public var endX:Number;
		public var endY:Number;
		public var object:PhysicsObject;
 
		public var time:Number = 0;
		public var needToTeleport:Boolean = false;
 
		protected var _teleporting:Boolean = false;
 
		private var _teleportTimeoutID:uint;
 
		public function Teleporter(name:String, params:Object = null) {
			super(name, params);
		}
 
		override public function destroy():void {
 
			clearTimeout(_teleportTimeoutID);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (needToTeleport) {
 
				_teleporting = true;
 
				_teleportTimeoutID = setTimeout(_teleport,time);
 
				needToTeleport = false;
			}
 
			_updateAnimation();
		}
 
		protected function _teleport():void {
 
			_teleporting = false;
 
			object.x = endX;
			object.y = endY;
 
			clearTimeout(_teleportTimeoutID);
		}
 
		protected function _updateAnimation():void {
 
			if (_teleporting) {
				_animation = "teleport";
			} else {
				_animation = "normal";
			}
		}
	}
}

A treadmill is a Platform with contacts. Instead of using a simple Platform, I use a MovingPlatform. So we can have a treadmill moving platform, that’s crazy πŸ˜€ A treadmill increase velocity of its passengers.

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
package {
 
	import Box2DAS.Dynamics.b2Body;
 
	import com.citrusengine.objects.platformer.MovingPlatform;
 
	/**
	 * @author Aymeric
	 */
	public class Treadmill extends MovingPlatform {
 
		public var speedTread:Number = 3;
		public var startingDirection:String = "right";
 
		public var enableTreadmill:Boolean = true;
 
		public function Treadmill(name:String, params:Object = null) {
			super(name, params);
 
			if (startingDirection == "left") {
				_inverted = true;
			}
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (enableTreadmill) {
 
				for each (var passengers:b2Body in _passengers) {
 
					if (startingDirection == "right") {
						passengers.GetUserData().x += speedTread;
					} else {
						passengers.GetUserData().x -= speedTread;
					}
 
				}
			}
 
			_updateAnimation();
		}
 
		protected function _updateAnimation():void {
 
			if (enableTreadmill) {
				_animation = "move";
			} else {
				_animation = "normal";
			}
		}
	}
}

And the last object, the cannon, a bit more complex. It extends Platform, in most of the games you can’t move it, so it doesn’t need to be a box2d dynamic body, static is ok. A cannon has a fire rate, a direction, and it fires missiles.

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
package {
 
	import com.citrusengine.objects.PhysicsObject;
	import com.citrusengine.objects.platformer.Missile;
	import com.citrusengine.objects.platformer.Platform;
 
	import org.osflash.signals.Signal;
 
	import flash.events.TimerEvent;
	import flash.utils.Timer;
 
	/**
	 * @author Aymeric
	 */
	public class Cannon extends Platform {
 
		public var onGiveDamage:Signal;
 
		public var fireRate:Number;
		public var startingDirection:String = "right";
 
		public var missileWidth:uint = 20;
		public var missileHeight:uint = 20;
		public var missileSpeed:Number = 2;
		public var missileAngle:Number = 0;
		public var missileExplodeDuration:Number = 1000;
		public var missileFuseDuration:Number = 10000;
		public var missileView:String = "";
 
		protected var _firing:Boolean = false;
 
		private var _timer:Timer;
 
		public function Cannon(name:String, params:Object = null) {
			super(name, params);
 
			onGiveDamage = new Signal(PhysicsObject);
		}
 
		override public function destroy():void {
 
			onGiveDamage.removeAll();
 
			_timer.stop();
			_timer.removeEventListener(TimerEvent.TIMER, _fire);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			_updateAnimation();
		}
 
		protected function _damage(missile:Missile, contact:PhysicsObject):void {
 
			if (contact != null) {
				onGiveDamage.dispatch(contact);
			}
		}
 
		public function startFire():void {
 
			_firing = true;
 
			_timer = new Timer(fireRate);
			_timer.addEventListener(TimerEvent.TIMER, _fire);
			_timer.start();
		}
 
		public function stopFire():void {
 
			_firing = false;
 
			_timer.stop();
			_timer.removeEventListener(TimerEvent.TIMER, _fire);
		}
 
		protected function _fire(tEvt:TimerEvent):void {
 
			var missile:Missile;
 
			if (startingDirection == "right") {
				missile = new Missile("Missile", {x:x + width, y:y, width:missileWidth, height:missileHeight, speed:missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:missileView});
			} else {
				missile = new Missile("Missile", {x:x - width, y:y, width:missileWidth, height:missileHeight, speed:-missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:missileView});
			}
 
			_ce.state.add(missile);
			missile.onExplode.addOnce(_damage);
		}
 
		protected function _updateAnimation():void {
 
			if (_firing) {
				_animation = "fire";
			} else {
				_animation = "normal";
			}
		}
	}
}

I use a Signal to dispatch event onGiveDamage. It happens if a missile hits PhysicsObject.

And finally my game state :

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
package {
 
	import Box2DAS.Dynamics.ContactEvent;
 
	import com.citrusengine.core.State;
	import com.citrusengine.objects.PhysicsObject;
	import com.citrusengine.objects.platformer.Hero;
	import com.citrusengine.objects.platformer.Platform;
	import com.citrusengine.physics.Box2D;
 
	/**
	 * @author Aymeric
	 */
	public class MyState extends State {
 
		public function MyState() {
			super();
		}
 
		override public function initialize():void {
 
			var box2d:Box2D = new Box2D("Box2d");
			//box2d.visible = true;
			add(box2d);
 
			var hero:Hero = new Hero("Hero", {x:50, width:30, height:60, view:"hero.swf"});
			add(hero);
 
			var treadmill:Treadmill = new Treadmill("Treadmill", {x:200, y:300, width:400, height:20, view:"treadmill.swf"});
			add(treadmill);
 
			treadmill.enabled = false; //remove moving platform
 
			var teleporter:Teleporter = new Teleporter("Teleporter", {x:300, y:250, width:20, height:20, view:"teleporter.swf"});
			add(teleporter);
 
			teleporter.endX = 50;
			teleporter.endY = 0;
			teleporter.time = 200;
 
			teleporter.onBeginContact.add(_teleport);
 
			Platform.Make("Platform", -5, 150, 5, 300);
 
			var cannon:Cannon = new Cannon("Cannon", {x:450, y:170, width:40, height:40, view:"cannon.swf"});
			add(cannon);
 
			cannon.fireRate = 2500;
			cannon.startingDirection = "left";
			cannon.missileFuseDuration = 4000;
			cannon.missileView = "missile.swf";
			cannon.startFire();
 
			cannon.onGiveDamage.add(_cannonHurt);
		}
 
		private function _teleport(cEvt:ContactEvent):void {
 
			if (cEvt.other.GetBody().GetUserData() is Hero) {
				cEvt.fixture.GetBody().GetUserData().object = cEvt.other.GetBody().GetUserData();
				cEvt.fixture.GetBody().GetUserData().needToTeleport = true;
			}
		}
 
		private function _cannonHurt(contact:PhysicsObject):void {
 
			if (contact is Hero) {
				Hero(contact).hurt();
			}
		}
	}
}

I handle the teleported object in the state because if I only want to teleport my Hero (so it doesn’t affect Bad Guy) it is easier. The same for cannon hurting : it affects only my Hero, missile will just explode on a Bady Guy without killing it !

As usual you can download my zip.

Flas of my new objects are not included because I didn’t save it… But you can make it really fast using my flash panels for the Citrus Engine πŸ˜‰ If you haven’t tested it yet, gives it a try !!

34 thoughts on “Teleporter, treadmill and cannon

  1. Hello,
    I installated flash panels in my adobe extension manger, but i cannot see it in window: other panels

    i am using flash pro cs5

  2. I did. I have other addons instalated. Just cannot see yours. Thats make me sad πŸ™

  3. Could you tell me if you see the files : CitrusEnginePanels.swf and CitrusEnginePanels.jsfl into :

    X:\Users\\AppData\Local\Adobe\Flash CS5\lang\
    folders : WindowSWF and Commands.

    on mac : /Users//Library/Application Support/Adobe/Flash CS5/lang/Configuration/

    And what is your lang ?

  4. Oh i can see the problem now. It was the “lang” folder. Manual installation fixed that. Nice addon, great job!

  5. Glad you succeed to install it !
    It is really annoying if there is problem on installing it with no french version.
    I copied/pasted the extension descriptor file (.mxi) from an english version.

    I will try to figure it out.

  6. I would like to test something :
    could you try to create the .zxp file thanks to the src folder ? You just need to open the .mxi in Adobe Extension Manager, it will exports a .zxp

    Uninstall the extension and reinstall it with your .zxp. Restart flash and tell me if it works πŸ˜‰

  7. Hi, all these are really helpfull. Thanks Aymeric. Just need to find one which adds a background as I am stumped as to how to do that. what kind of object would that be – based on the camera I guess is would need some physics t create paralax…Just dont get it yet

    Thanks best Paul

  8. Hi Paul, I’m not sure that I’ve understood what you said. If you are looking to create a parallax background, juste use a CitrusSprite and the parallax property πŸ˜‰

  9. Hello,
    please would you explain how the “anonymous” array works in making game objects available?

    public function CitrusEngineDemo()
    {
    super();
    var objects:Array = [Hero,Platform,Windmill,etc];

    }

    But this array never gets called or directly referenced anywhere!
    yet it is essential.

    Thanks Paul

  10. Hi Paul,

    In fact it is due to the Level Editor (Flash). If you add your platforms in it, and never import Platform in your State class there will be an error (can’t find property com.citrusengine.objects.platformer.Platform).
    I use FDT to develop, and import are automatic, so if I don’t make code with a platform it is removed. So I have created an array with all my objects and most of them are dynamic!

    I hope I was clear.

  11. Thanks yep got that!

    Please could I ask one more question:

    Using RewardBox and I don’t understand signals enough to know what the type of event is passed by the listener:

    Gem:RewardBox etc (
    Gem.onUse.add(callWhenBumped)

    private function callWhenBumped(evt:NOT SURE WHAT THIS IS!):void
    {
    do stuff
    }
    Thanks for your expertise!
    Best Paul

  12. Hi Paul,

    You define Signal’s params when you create it, for example :
    onCollect = new Signal(Reward);
    Here the signal params is an Rewad class object.

    If you create your signal like that :
    onCollect = new Signal();
    There isn’t params πŸ˜‰

    Hope that was clear!

  13. Nope, sorry gap in my brain!
    The engine comes with a RewardBox.as and this has two signals declared
    onUse = new Signal(RewardBox).
    This gets dispatched when hero bumps the reward “platform”

    So my question should perhaps have been – How do I “listen” for that
    signal and do something with it?

    Best

    Paul

  14. Oh ok.
    I have never use this classes (Reward & RewardBox), but it shouldn’t be too complex :

    var gem:RewardBox…
    gem.onUse.add(callWhenBumped);

    function callWhenBumped(rewardBox:RewardBox):void {
    // make something with my Reward Box, but don’t know what…
    }

    If you want to have access to the Reward, you shoud listen onRewardCollect’s Signal. However it seems that you can’t easily custom your reward object. Maybe you should modify the original class to have what you want.

  15. Ok now I got it going. Thanks Aymeric. Spent a moment on robert Penner signals and then realised that as you have done, this returns a RewardBox.
    put a trace in handleBeginContact and discovered that it need to be hit at 90degs (from below)! so I was not getting anywhere with the box on the reward platform on the floor!

    This Citrus Engine has really pushed my as3 skills to new levels, thanks to folks like you. Sometimes miss not having had any formal training in programming. Things change so rapidly, it is hard to keep at the edge. Spent many years with director and lingo, now this is a bit wasted, and as you comment, seem like flash is also a finite object too!

    Hope you are enjoying your current projects and thank you for your help

    Best
    Paul

  16. I’m happy that you have fixed your issue Paul. The Citrus Engine has also helped me to increase my AS3 skills, and also my programming skills : C# used events like Signal libs πŸ˜‰

    I’m sure that what you’ve learned with Director & Lingo aren’t lost, and help you today with Flash.

    In 5 years maybe Flash will not be used anymore, but our skills will not be lost! It is not too hard to learn new language when you have strong programming basis skills. At this moment it isn’t too hard for me to learn Objective-C which is not the easiest language thanks to my skills with AS3 and some with C++.

    Don’t hesitate to ask me if you have new questions, I will be happy to help you! You can also use the CE’s forum.

  17. Hey there, I am trying to add the cannon code into my project but I keep getting the error: Main Thread (Suspended: Error: Missile doesn’t have the exploded animation set up in its animations’ array) .

    I’m new to flash so I’m not sure what I’m doing wrong. I created a new cannon.as and everything seems to be reading ok. Any hints on what I can try to solve this? Thanks!

  18. Aymeric, thank you for your reply. I am using the missile.swf contained the zip file linked above. Looking at the file, the missile has 20 frames. Is there something different I need to be doing? Sorry if this is such a noob question, I really appreciate your help.

  19. Scott, does it work if you used the missile from the zip file? It has 20 frames, with two labels for the two animations. Hope that help.

  20. Using the missile in the zip file you provided is what is causing the error. I’m going to be running some more tests to see if I can solve it. So far, I’m still getting the same error.

  21. Do you have an email address I can send the zipped project to? I’d like you to take a look at the source if possible to see why it might be happening.

  22. Ok, email has been sent. I really appreciate you taking your time to look into man. I’m sure it’s something incredibly simple that’s been causing me days of headache. Great blog and engine btw!

  23. Hi, please help !

    I can’t find Box2DAS.Dynamics.ContactEvent in Citrus-Engine-master pack, where can i find it ?

    Any easier way to set object to those type i wanna teleport?

    Thanks!

  24. Thanks a lot, i found out a new way to solve it according to your example!

    var hero:Hero = new Hero(“hero”, { x:100, y:.100, height:120, width:65 } );
    add(hero);

    var enemy:Enemy = new Enemy(“enemy”, { x:600, y:100, height:65, width: 100, leftBound:100, rightBound:700 } );
    add(enemy);

    var teleporter:Teleporter = new Teleporter(“teleporter”, { x:600, y:450, endX:500, endY:150} );
    teleporter.onBeginContact.add(teleport)
    add(teleporter);

    private function teleport(c:b2Contact):void
    {

    var bodyA:b2Body = c.GetFixtureA().GetBody();
    var bodyB:b2Body = c.GetFixtureB().GetBody();

    if (bodyA.GetUserData().name == “hero” || bodyA.GetUserData().name == “enemy”)
    {
    c.GetFixtureB().GetBody().GetUserData().object = c.GetFixtureA().GetBody().GetUserData();
    }
    else
    {
    c.GetFixtureA().GetBody().GetUserData().object = c.GetFixtureB().GetBody().GetUserData();
    }

    }

Leave a Reply

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