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.