Simple Platform Game Basics Part 4a
A simple game with a horse. Using the WithAnimationsSubscriber class.


Most of the elements in this example were seen in part 3.
However for the animations of the horse we use a WithAnimationsSubscriber.
It is useful when using MovieClips instead of individual images to act as the animations
that go with the currentAnimation values of a IWithAnimations implementor.



Hurdles.as
package Tutorials
{
	import com.actiontad.basicGameObjects.SimplePlatformEngine;
	import com.actiontad.basicGameObjects.SimplePlatformWalker;
	import com.actiontad.basicGameObjects.WithAnimationsSubscriber;
	import com.actiontad.basicGameObjects.basicStationaryObject;
	import com.actiontad.basicGameEvents.GameObjectEvents;
	import com.actiontad.gameUtils.TextSplashScreen;
	
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	
	
	public class Hurdles extends SimplePlatformEngine
	{
		private var horse:SimplePlatformWalker;
		private var horseBody:WithAnimationsSubscriber;
		
		[Embed(source = "images/horseHurdles2.png")]
		private var landHitableImage:Class;
		
		[Embed(source = "images/nightTrees2.jpg")]
		public static var backgroundImage:Class;
		
		private var theBackground1:DisplayObject;
		private var theBackground2:DisplayObject;
		private var land:basicStationaryObject;//The only different thing about a basicStationaryObject is that its looperDelay is default 0.
		private var quickInstructions:TextSplashScreen;
		
		
		/**
		 * 
		 *  Simple platform basics Part 4a - Hurdles, adding a background scrolling image, and using a png file for the land. 
		 *  						Also shows using a WithAnimationsSubscriber.
		 * 
		 *  In this example we will add a background to the platform game, and use a png instead of drawing the land ourselves.
		 *  The png file is a 5000 by 800 transparent png with ground and stone like hurdle things.
		 * 
		 *  We can get away with using such big files because of how the SimplePlatformEngine works.
		 *  It only shows the part of the container that is visible on screen, the reset of the container is invisible if out of view.
		 * 
		 *  This example is not like the Samus examples, this time we have some premade MovieClips that we will use
		 *  as the body for the horse.
		 * 
		 *  The WithAnimationsSubscriber class is set up to automatically switch the visibility of 2 Sprites it holds.
		 *  leftImageGroup and rightImageGroup.
		 *  We can add any DisplayObject/s to these groups, 
		 *  and when its IWithAnimations source (the horse in this example) faces* left or right, the images change accordingly.
		 * 
		 *  (*As defined by the words in each currentAnimation, if they contain 'left', or 'right')
		 * 
		 * 
		 *  If we add MovieClips to any of the groups their frame rate will become based off the IWithAnimations looperDelay, if its also a BasicGameObject.
		 *  We can also define which frame any MovieClips should stop on if the currentAnimation (from the IWithAnimations source) is one that stops animation.
		 *  (As defined by stillString and stillFrame in WithAnimationsSubscriber)
		 * 
		 *  Each WithAnimationsSubscriber has a set of functions that match the common currentAnimation values. 
		 *  The lowercase currentAnimation value from its IWithAnimation source is continually called as a method of the WithAnimationsSubscriber if it has such a method,
		 *  and if the callCurrentAnimation property is true on the WithAnimationsSubscriber. (default is false)
		 * 
		 *  By default none of those functions do anything. And if you make custom animations (on an animations object)
		 *  you would need to extend a WithAnimationsSubscriber and create your custom methods on it that match your custom currentAnimation possible values.
		 *  (In the samus example we made 2 custom values, "hurt" and "surf")
		 *  Therefore, in most cases, you will want to extend WithAnimationsSubscriber so that you can add more functionality to it.
		 * 
		 *  This example shows a quick way to use one without extending it, when we have premade MovieClips.
		 * 
		 *  If you do not have MovieClips, or need more commands to follow currentAnimation values,
		 *  you should always extend WithAnimationsSubscriber first, then use that new subclass as the follower of the IWithAnimations implementor.
		 *  
		 * 
		 */
		public function Hurdles() {
			super();
			horse = new SimplePlatformWalker(false,false,true,2000);
			horse.jumpStrength = horse.initialJumpStrength = 15;
			horse.speed = 1;
			horse.maxVelocity = 10;
			horse.friction = .8;
			horse.gravity = 2;
			
			
			//see the ComboKeys class in com.actiontad.gameUtils and basicUserControlledObject in com.actiontad.basicGameObjects
			//basicUserControlledObject (part of the super class chain of SimplePlatformWalker) has a key object that is an instance of the ComboKeys class.
			//It also dispatches combo and key down events when keys are pressed or combos happen.
			horse.combosToRespondTo.push([39, 39, 39]);
			horse.combosToRespondTo.push([37, 37, 37]);
			horse.addEventListener(GameObjectEvents.COMBO + "393939", speedUp); 
			horse.addEventListener(GameObjectEvents.COMBO + "373737", speedDown); 
			
			horseBody = new WithAnimationsSubscriber(horse, null, "normal", 0);
			horseBody.cacheAsBitmap = false;
			horseBody.callCurrentAnimation = false;
			land = new basicStationaryObject();
			land.addChild(new landHitableImage());//landHitableImage was embedded above with Embed meta data tag
			quickInstructions = new TextSplashScreen(null, "left", null, 650, 350);
			quickInstructions.color = 0x000000;
			
			with (horse.graphics) {
				beginFill(0xffffff, .001);
				drawRect(0, 0, 88, 89);
			}
			
			//beginGame();
			//beginGame needs to be called to start the game.
			//In this case it will be called from the class with the ScreenOrganizer in Part 4c
			
		}
		public function beginGame(e:Event = null):void {
			if (!container.contains(land)) {//most of the function only happens once
				container.addChild(land);
				land.y = 520;
				horseBody.leftImageGroup.addChild(new horseRunLeft());//MovieClip width 151 height 98 from a swc or the library
				horseBody.rightImageGroup.addChild(new horseRunRight());
				
				container.addChild(horse);
				horse.animationDispatcher.pause();
				container.addChild(horseBody);
				
				theBackground1 = new backgroundImage();//embedded above
				theBackground2 = new backgroundImage();
				addChildAt(theBackground1, 0);
				theBackground2.x = theBackground1.width;
				theBackground1.y = theBackground2.y = 0;
				addChildAt(theBackground2, 0);
				
				//Sometimes putting the background movement on its own timer would be better for performance.
				//In this case, it was better to let it tap into the global loop.
				addEventListener(GameObjectEvents.LOOP, moveBackground);
				
				addChild(quickInstructions);
				horseBody.addEventListener(GameObjectEvents.LOOP, followHorse);
				horse.addEventListener(GameObjectEvents.KEY + 84 + GameObjectEvents.UP, backToTitleScreen);
			}
			quickInstructions.wordShowTime = this.innerDispatcher.frameOffsetCalc(10);//show the words for about 10 seconds
			quickInstructions.words = 
			"Press right fast 3 times to speed up, \n left fast 3 times to slow back down.\n Press t to go back to title screen.";
			horse.x = 40; horse.y = 20; theBackground1.y = theBackground2.y = 0;
		}
		private function backToTitleScreen(e:Event):void {
			HurdlesMain.theScreenOrg.changeScreen(0);
		}
		/**
		 *  A simple background movement scheme.
		 * 
		 * @param	e
		 */
		private function moveBackground(e:Event):void {
			var spee:int = 2;
			if (horse.directionX == 1  ) {
				theBackground1.x -= spee;
				theBackground2.x -= spee;
			}
			if (horse.directionX == -1  ) {
				theBackground1.x += spee;
				theBackground2.x += spee;
			}
			if (horse.directionY == 1 && theBackground1.y > -(theBackground1.height - 350) ) {
				theBackground1.y -= spee;
				theBackground2.y -= spee;
			}
			if (horse.directionY == -1 && theBackground1.y < 0 ) {
				theBackground1.y += spee;
				theBackground2.y += spee;
			}

			if (theBackground1.x <= -(theBackground1.width)) {
				theBackground1.x = (theBackground1.width-1);
			}
			if (theBackground2.x <= -(theBackground1.width)) {
				theBackground2.x = (theBackground1.width-1);
			}

			if (theBackground1.x > 0) {
				theBackground2.x = theBackground1.x - (theBackground1.width-1);
			}
			if (theBackground2.x > 0) {
				theBackground1.x = theBackground2.x - (theBackground1.width-1);
			}
			
		}
		/**
		 *  The horseBody follows the horse.
		 *  And is offset the height by 10 for the images specifics.
		 * @param	e
		 */
		private function followHorse(e:Event):void {
			horseBody.x = horse.x + horse.width / 2;
			horseBody.y = horse.y + (horse.height / 2) + 10;
		}
		private function speedUp(e:Event):void {
			quickInstructions.words = "speed up";
			horse.removeEventListener(GameObjectEvents.LOOP, keepSpeedingDown);
			horse.addEventListener(GameObjectEvents.LOOP, keepSpeedingUp);
		}
		private function keepSpeedingUp(e:Event):void {
			if (horse.speed < 10) horse.speed += 1 else 
				horse.removeEventListener(GameObjectEvents.LOOP, keepSpeedingUp);
		}
		private function speedDown(e:Event):void {
			quickInstructions.words = "slow down";
			horse.removeEventListener(GameObjectEvents.LOOP, keepSpeedingUp);
			horse.addEventListener(GameObjectEvents.LOOP, keepSpeedingDown);
		}
		private function keepSpeedingDown(e:Event):void {
			if (horse.speed > 1) horse.speed -= 1 else
				horse.removeEventListener(GameObjectEvents.LOOP, keepSpeedingDown);
		}
		
	}
}

Next: Part 4b

Part 4b is the title screen class we will use to begin the game.
In part 4c the main class is constructed and a ScreenOrganizer is used to switch between title screen and engine.

part 1 part 2 part 3 part 4b part 4c