Tutorial: Flash skier game

These are notes on an ActionScript 3.0 version of the skier game I did using ActionScript 2.0. The idea is to use the left and right arrow keys to guide a skier down the slopes, going though each gate. The implementation demonstrates

  • re-positioning of one movie clip instance representing the slope of the hill at each TIMER interval event. This makes it look like the skier is skiing down the slope. Actually, the slope instance is moving up the screen under the skier instance.
  • using the keyboard KEY_UP event to capture the left or right arrow keys to move the skier. I used a start up button to trigger a function that generates the gates, starts the timer event monitoring AND made sure the focus was on the slope to detect the key presses.
  • dynamically created movie clips, using movie clip symbols with Properties setting for Export for ActionScript, to put gates on the slope. Each gate is actually a pair of instances. In skier3, one appears as the visible gate and the other is the gate plus areas outside the gate. It is this second instance that is checked using hitTestPoint to see if the skier has gone outside a gate.It is bad for the player if this happens. In skier4, I try a different approach, the companion instance is a region between the gate poles. The call to hitTestPoint checks if the current gate in-between region has been crossed.This is good for the player. All the gates and the gate companions are stored in arrays.
  • use of a dynamic text field to display either that a gate was missed OR the elapsed time from the start to the finish.

The tricky step for me in this application was checking for missing a gate. At first blush (my term), this meant checking for something that wasn't there. So, I created a companion for each gate that included regions on either side of the gate proper. My code then made these instances have alpha set to zero. The hitTestPoint method could be used, but nothing was visible. To keep the gate and outsidegate in the same position, I made the outsidegate instances children of the gate instances. This is the strategy used in skier3. However, I then had a new idea. I would make a companion piece be the in-between areas of the gate and have a variable holding the current gate index. If and when the current gate inside was hit, the code would increment this variable. If the skier reaches the end and the variable, appropriately named currentgate, was the value of the number of gates, indicating that all had been 'hit' in turn, then the time is shown. Otherwise, the number of the missed gate, more precisely first missed gate, is displayed. For both these strategies, I need to make the areas big enough, namely have enough height, so the skier will hit them. Keep in mind that the skier / slope movement is discrete, that is, in jumps. You don't want the skier to jump over the bad region and be deemed okay for that strategy or jump over the good region and be deemed bad in the other strategy.

A good strategy when creating a game is to not have the full challenge of playing the game when testing it. The idea is to position the gates randomly on the slope. However, to make it easier, I kept the vertical positioning evenly spaced down the slope and I put in a line, which I can comment out, fixing the horizontal position.

I include the source code of four applications:

  1. skier1 shows a skier moving down the slope. The Output panel shows when the startup is complete, when the function moveskier is invoked, and when the skier stops.
  2. skier2 shows gates and indicates when a gate has been hit in the Output panel. The Output panel also indicates when the ski run is Complete.
  3. skier3 shows gates and if a gate has been missed, stops the skier and displays the number of the missed gate. If the skier reaches the end, the message displayed is Success.
  4. skier4 shows the gates and indicates that a gate has been missed only when the skier is off the slope.

These, even the last one, are only stubs for real applications. Please let me see your improved skier application!

There are 2 matters that do 'cry out' for improvement.

  • The vertical displacement is constant and yet we know from experience AND trigonometry that zig-zag motion takes more time. The code needs to be changed to decrease the re-positioning of the slope to an appropriate amount based on the horizontal change of the skier.
  • Pressing the start button creates new gates, even if the skier is moving down the slope. A pesky player may press the start button many times. The desired action would be to stop the skier, wipe out the existing gates, create new ones, and re-position the skier at the top of the slope.

Implementation

You can, of course, skip to the coding of skier3 or skier4, but you will benefit from at least skimming the explanation of skier1 and skier2.

Moving down the slope: skier1

I made this application by creating two movie clip symbols: skier and slope and moving both to the Stage. I also used a button from the Common Libraries/Buttons. To make sure the skier and the button were on top of the slope, I created 4 layers: ground, skier, actions, and interface.

The slope instance essentially is a tall rectangle: 400 wide and 2000 high. I drew some lines on it to make it possible to detect movement. The screen shot shows the symbol in the Library:

Bring an instance of the slope to the Stage, the ground layer, and give it the name slope.

The skier in this program is a crude drawing. Notice that the origin is in the center of the figure.

Bring an instance of the skier to the Stage, the skier layer, and give it the name skier.

Use Window/Common Libraries/Buttons to select a button. Bring it to the Stage, the interface layer. Give it the name startbtn.

Selecting the actions layer, use Window/Actions to get the Actions panel.

The code consists of

  • import statements to access the Timer features and the Keyboard features
  • global variables for the slopespeed and the stimer Timer
  • call to startbtn.addEventListener to set up the response to pressing the start button. The function for this is startup
  • definition of the startup function. It includes code to start the timer and to enable listening for the keys
  • definition of movedown function. This is the event handler for the timer event
  • definition of moveskier function. This is the event handler for the keys.

Keep in mind that the slope is moving, not the skier. To determine that the skier has skied off the hill, the code checks if the slope.y value is less than 1900. This and other values can be modified for different effects.

The moveskier function has code that checks the event.keyCode value to determine if the LEFT key or RIGHT key has been pressed. The horizontal (x) attribute of the skier is increased or decreased appropriately.

With that explanation, here is the code:

import flash.utils.Timer;

import flash.ui.Keyboard;

var slopespeed:int = 5;

var stimer:Timer = new Timer(20);

startbtn.addEventListener(MouseEvent.CLICK,startup);

function startup(event) {

addEventListener(KeyboardEvent.KEY_UP,moveskier);

stimer.addEventListener(TimerEvent.TIMER, movedown);

stimer.start();

trace("done with startup");

}

function movedown(event) {

slope.y -=slopespeed;

if (slope.y<-1900) {

stimer.stop();

trace("stopped");

}

}

function moveskier(event) {

trace("in moveskier");

if (event.keyCode == Keyboard.LEFT) {

skier.x -= 5;

}

else if (event.keyCode == Keyboard.RIGHT) {

skier.x += 5;

}

}

Moving down snowy slope, with gates: skier2

For the next application, I improved (somewhat) the look of the skier and the snowy slope by copy and paste of sections of photographs. to do this, I did need to Modify/Bitmap/Trace bitmap. For the slope, after getting a rectangle using a screen capture program on a photo image, I stretched it by changing the width and the height. You can continue with the skier and slope of skier1. The new symbol for skier2 is the gate.

In order to create new gates to place on the slope during runtime, you need to right click on gate in the Library, select Properties, and then click on Export for ActionScript. I changed the Class name to be Gate with a capital G. This is in keeping with the practice to make class names start with capitals. No separate class definition is needed. You simply say okay when Flash gives a message saying there is not class definition and one will be created.

The code is much like skier1, with the addition of variables relating to the creation of the gates and coding to check for hitting the gates. The gate instances are added by ActionScript as children of the slope instance. This is important, because it means that the gates will move when the slope instance moves. The array gates will hold all the created gate instances. The movedown function has code that checks for the skier origin being over the material of each of the gate instances.

import flash.utils.Timer;

import flash.ui.Keyboard;

var slopespeed:int = 5;

var numberofgates:int = 6;

var sizeofslope:int = slope.height;

var stimer:Timer = new Timer(20);

var gates:Array = new Array();

startbtn.addEventListener(MouseEvent.CLICK,startup);

function startup(event) {

for (var i:int=0;i<numberofgates;i++) {

var ngate:Gate = new Gate();

gates.push(ngate);

slope.addChild(ngate);

ngate.x = Math.random()*300;

ngate.y = slope.y+ i*(sizeofslope/numberofgates);

}

addEventListener(KeyboardEvent.KEY_UP,moveskier);

stimer.addEventListener(TimerEvent.TIMER, movedown);

stimer.start();

}

function movedown(event) {

slope.y -=slopespeed;

if (slope.y<-1900) {

stimer.stop();

trace("Complete");

}

for (var i:int =0;i<gates.length;i++) {

if (gates[i].hitTestPoint(skier.x,skier.y,true)) {

trace("hit gate "+i);

}

}

}

function moveskier(event) {

//trace("in moveskier");

if (event.keyCode == Keyboard.LEFT) {

skier.x -= 5;

}

else if (event.keyCode == Keyboard.RIGHT) {

skier.x += 5;

}

}

Checking for skiing outside of the gates: skier3

As indicated earlier, the approach I took in this program was to check if the skier collided with a companion to the visible gate that included the poles and the flag, but also regions to the left and to the right. I created this by right clicking on gate in the Library and clicking Duplicate. I named the copy, outsidegate and added rectangles on either side to represent the forbidden regions. Here is my outsidegate symbol:

As with the other gate, you need to right click, Properties, and click to Export to ActionScript. Again, I changed the default class name to Outsidegate to suit the convention. Again, the default class definition works; no new class definition needs to be written.

In the interface layer, use the T tool to create a dynamic text field and name it result.

This application builds on the last. The additions include another array for the outsidegate instances. NOTE: the gates array is not used, but I kept it just in case I had reason to use it in the future. Each created gate instance is positioned and added to the display list as a child of the slope instance as in skier2. The companion outsidegate instance is added as a child of the correspondinggate instance. This means they move together. The alpha for this instance is set to 0. This makes it transparent but still present for the hitTestPoint calculations.

As soon as an outsidegate and skier collision occurs, the timer event is stopped and a message appears.

The code is:

import flash.utils.Timer;

import flash.ui.Keyboard;

var slopespeed:int = 5;

var numberofgates:int = 6;

var sizeofslope:int = slope.height;

var stimer:Timer = new Timer(20);

var gates:Array = new Array();

var outsidegates:Array = new Array();

startbtn.addEventListener(MouseEvent.CLICK,startup);

function startup(event) {

for (var i:int=0;i<numberofgates;i++) {

var ngate:Gate = new Gate();

gates.push(ngate);

slope.addChild(ngate);

var nogate:Outsidegate = new Outsidegate();

outsidegates.push(nogate);

ngate.addChild(nogate);

nogate.alpha = 0;

ngate.x = Math.random()*250;

//ngate.x = 180; //uncomment to make it easy for testing

ngate.y = slope.y+ i*(sizeofslope/numberofgates) + Math.random()*100;

}

addEventListener(KeyboardEvent.KEY_UP,moveskier);

stimer.addEventListener(TimerEvent.TIMER, movedown);

stimer.start();

}

function movedown(event) {

slope.y -=slopespeed;

for (var i:int =0;i<gates.length;i++) {

if (outsidegates[i].hitTestPoint(skier.x,skier.y,true)) {

stimer.stop();

result.text="Missed gate "+String(i+1);

}

}

if (slope.y<-1900) {

stimer.stop();

result.text = "Success";

}

}

function moveskier(event) {

//trace("in moveskier");

if (event.keyCode == Keyboard.LEFT) {

skier.x -= 5;

}

else if (event.keyCode == Keyboard.RIGHT) {

skier.x += 5;

}

}

Do make use of the tactic to make the gates be in fixed positions to make testing easier.

Checking for skiing through the gates: skier4

This application builds on what came before. However, this time, delete the outsidegate symbol if you have created it. As before, right click and make a duplicate of the gate symbol. Name it insidegate and use Properties to make it Export for Actionscript. Change the class name to Insidegate. In between the poles, create a thick rectangle. Then erase the poles. The screen shot shows what insidegate symbol looks like and the Properties panel:

The coding, shown below, will create insidegate objects in place of the outsidegate objects. Again, these will be made children of the corresponding gate objects and will have alpha set to 0. A new global variable, currentgate, will indicate the next gate to be passed through. If a gate is missed, it will not be indicated until the skier is down off the slope.

import flash.utils.Timer;

import flash.ui.Keyboard;

var currentgate:int = 0;

var slopespeed:int = 5;

var numberofgates:int = 6;

var sizeofslope:int = slope.height;

var stimer:Timer = new Timer(20);

var gates:Array = new Array(); //not used

var insidegates:Array = new Array();

startbtn.addEventListener(MouseEvent.CLICK,startup);

function startup(event) {

for (var i:int=0;i<numberofgates;i++) {

var ngate:Gate = new Gate();

gates.push(ngate);

slope.addChild(ngate);

var nigate:Insidegate = new Insidegate();

insidegates.push(nigate);

ngate.addChild(nigate);

nigate.alpha = 0;

ngate.x = 120+ Math.random()*80;

//ngate.x = 200; //to make it easier to test

ngate.y= slope.y+ i*(sizeofslope/numberofgates) + 100 ;

//probably need some randomness vertically, also

}

addEventListener(KeyboardEvent.KEY_UP,moveskier);

stimer.addEventListener(TimerEvent.TIMER, movedown);

stimer.start();

}

function movedown(event) {

slope.y -=slopespeed;

if (currentgate<numberofgates) // check for hitting gate

if (insidegates[currentgate].hitTestPoint(skier.x,skier.y,true))

{

currentgate++;

}

if (slope.y<(-1900)){

if (currentgate>=numberofgates) {

result.text = "Success";}

else {

result.text = "Missed gate "+String(currentgate+1);

}

}

}

function moveskier(event) {

if (event.keyCode == Keyboard.LEFT) {

skier.x -= 5;

}

else if (event.keyCode == Keyboard.RIGHT) {

skier.x += 5;

}

}

Enjoy!