DGM6108 Tutorial: Creating an Interactive Narrative

By Matt Carrano, Northeastern University

 

This tutorial discusses how to create a simple interactive narrative presentation using Flash and ActionScript.  A Flash movie of this type will be suitable for posting to the web for providing a topical presentation of a specific subject matter.  For our sample narrative, we will construct a short presentation on Boston history.  Version 1 of this tutorial uses a scrolling timeline metaphor to allow the user to navigate between a set of MovieClips that each contain content relative to a single decade of Boston history.  See the movie file below.

This movie is created using nested movie clips.  Each slideshow that presents a single decade of Boston history is a MovieClip object that walks the user through a decade of Boston history using that movie clipÕs internal timeline.  Open the Flash source file (BostonHistory.5.fla) corresponding to the first version of the tutorial and open one of the "Boston" movieclips from the Library. Below is the timeline for one of these MovieClip symbols.

Notice that timeline tweens are used to fade-up each page as it is revealed (this can easily be accomplished by tweening the alpha property of the page.  In the actions layer is code to stop the timeline at frames 1, 50, and 100.  A next button placed on the stage moves to the next page by invoking the play() method each time it is clicked.

Creating the scrolling timeline

On the main timeline, we want to scroll to bring the appropriate movie clip into view that corresponds to the decade that the user clicks on in the primary navigation bar across the top of the stage.  To accomplish this, we set up the stage as illustrated below.

 

 

The stage holds some static text to form the page header, five button objects that form our navigation bar, and one large MovieClip named timeline_mc.  This MovieClip is a container for the home page and four nested MovieClip objects representing each decade of Boston history.  The width of timeline_mc is 3500 pixels, or five time the stage width.  By scrolling timeline_mc left or right, we can bring a new page into view.  Think of the stage here as a 500 x 700 pixel window that defines the user viewable area.  While content falling to the right or left of the stage exists on the display list, it will be invisible to the user since it falls outside of this window.

Now letÕs turn to the ActionScript code needed to make this interactive.  This code resides in Frame 1 of the main timeline.

First, we declare two variables that will be used later to control the timeline animation.

var currentPage:MovieClip;

var targetX:Number;  //holds target x value for moving timeline

 

Next, we declare a constant for the Òeasing factor.Ó  The use f this constant will be explained later.

const EASE_FACTOR:Number = 8;        

 

Our primary navigation will be controlled by the five buttons arranged across the top of the stage.  Add an event listener to each one that calls a common event handler function named clicked.  This function is not as complicated as it looks.  First, it determines which button was clicked by querying the event object e.  It then includes a series of if clauses that test for the button that was clicked.  A reference to the movie clip object within timeline_mc that corresponds to that button is saved in the variable currentPage for future reference, and the variable targetX is set to hold position of the left edge of timeline_mc relative to the start of the target page.  Remember that we want to shift timeline_mc to the left to bring content originally residing off of the right hand side of the stage into view, that means targetX must be an increasingly large negative number as the decades progress.  The third step within each of these if clauses is to add a frame event listener to call the function movePage which will animate the movement of timeline_mc to the left or right depending on the difference between its current position and the value held in targetX.  The final part of the clicked  function is designed to reset the target page movie clip to itÕs first frame.

 

The code for the primary navigation buttons is shown below.

 

home_btn.addEventListener (MouseEvent.CLICK, clicked);

boston1_btn.addEventListener (MouseEvent.CLICK, clicked);

boston2_btn.addEventListener (MouseEvent.CLICK, clicked);

boston3_btn.addEventListener (MouseEvent.CLICK, clicked);

boston4_btn.addEventListener (MouseEvent.CLICK, clicked);

 

function clicked (e:Event):void {

            var buttonClicked:SimpleButton = SimpleButton(e.target);

            if (buttonClicked == home_btn) {

                        currentPage = null;

                        targetX = 0;

                        this.addEventListener(Event.ENTER_FRAME, movePage);

            }

            else if (buttonClicked == boston1_btn) {

                        currentPage = timeline_mc.boston1_mc;

                        targetX = -700;

                        this.addEventListener(Event.ENTER_FRAME, movePage);

            }

            else if (buttonClicked == boston2_btn) {

                        currentPage = timeline_mc.boston2_mc;

                        targetX = -1400;

                        this.addEventListener(Event.ENTER_FRAME, movePage);

            }

            else if (buttonClicked == boston3_btn) {

                        currentPage = timeline_mc.boston3_mc;

                        targetX = -2100;

                        this.addEventListener(Event.ENTER_FRAME, movePage);

            }

            else {

                        currentPage = timeline_mc.boston4_mc;

                        targetX = -2800;

                        this.addEventListener(Event.ENTER_FRAME, movePage);

            }

            if (currentPage != null)  {

                        currentPage.gotoAndStop(1);

            }

}

 

The remaining function, movePage, handles the animation of the movie clip timeline_mc.  Remember that this function gets called on every frame.  It first tests whether the difference between the current x position of timeline_mc and targetX is more than 1 pixel.  If so, we will move timeline_mc to the left or right by a amount that is proportional to the distance between these two points divided by the ease factor.  This technique is called Òeasing.Ó  We could simply move timeline_mc with constant velocity until is reaches its target destination, but this would not create a very realistic looking motion effect.  Using an easing technique, motion will slow as we approach the target.  The constant EASE_FACTOR gives us the ability to control the rate of this easing, the larger the value, the longer it will take to reach our desitination.  You may be wondering why we test for timeline_mc.x being within 1 pixel of targetX.  Why not continue animating until they are equal?  Since the amount that we move timeline_mc is always proportional to the distance between timeline_mc.x and targetX, we will never actually reach targetX.  Therefore, to make this work, we will stop the animation when we are one pixel from the target.  Yes, this will introduce some error into the position of timeline_mc.x over time, but we can live with that for the purpose of this problem.

 

When out target position has been reached, we simply remove the frame event listener (to stop further animation) and start the target movie clip playing.  The complete code for the function movePage is given below.

 

function movePage(evt:Event):void        {

            if (Math.abs(targetX - timeline_mc.x) > 1)

                        timeline_mc.x += (targetX - timeline_mc.x)/EASE_FACTOR;

            }

            else       {

            this.removeEventListener(Event.ENTER_FRAME, movePage);

                        if (currentPage != null)  {

                                    currentPage.play();

                        }

            }

}

                       

Another navigation model

The next part of this tutorial presents a slightly more complex navigation model.  In this model, each movie clip representing a decade of Boston history is placed on the stage as a thumbnail and acts as itÕs own navigation button.  When the user clicks on that movie clip object, it moves to the center of the stage and scales up to itÕs full size for viewing.  When the user clicks Close, the clip shrinks back to thumbnail size and returns to its original position on the stage.

 

 

To explore this implementation, download and open the Flash souce file BostonHistory.7.fla. As in the previous example, we start by declaring several variables and constants that will be used throughout the program.

 

const EASE_FACTOR:Number = 4;

var targetX:Number;

var targetScale:Number;

 

var currentPage:MovieClip = null;

 

var currentPageOriginX:Number;

var currentPageOriginY:Number;

var currentPageOriginScaleX:Number;

var currentPageOriginScaleY:Number;

 

var hCenter:Number = stage.stageWidth/2;

var vCenter:Number = stage.stageHeight/2;

 

The variables targetX and targetScale will be used by the animation functions that follow.  Besides saving a reference to the page currently in view, we also want to retain its original position on the stage.  This information will be used when animating the clip back after it is closed.  The variables currentPageOriginX, currentPageOriginY, currentPageOriginScaleX, and currentPageOriginScaleY serve that purpose.  Finally, we want to calculate and save the center point of the stage.  This will be our target for the first animation step when we open a page.

 

Next, create an instance of the Close button and give it a position.  We will not add this to the stage until later.

 

var close_btn:SimpleButton = new CloseButton();

close_btn.x = 560;

close_btn.y = 57;

 

The next block of code sets up the content pages that were placed on the stage at authoring time to behave as roll-over button objects.

 

//Make the thumbnails 75% transparent

boston1_mc.alpha = 0.75;

boston2_mc.alpha = 0.75;

boston3_mc.alpha = 0.75;

boston4_mc.alpha = 0.75;

 

 

//Make the pages look like button (hand cursor) and disable any mouseevents until the page is opened

boston1_mc.buttonMode = true;

boston2_mc.buttonMode = true;

boston3_mc.buttonMode = true;

boston4_mc.buttonMode = true;

 

boston1_mc.mouseChildren = false;

boston2_mc.mouseChildren = false;

boston3_mc.mouseChildren = false;

boston4_mc.mouseChildren = false;

 

We start by giving them a bit of translucency so we can bring them up in opacity to create our roll-over effect.  Next, set them to take on button-like behavior, and finally, ensure that objects nested inside the movie clips themselves will not take mouse events until the page is opened.  Without setting .mouseChildren = false, a mouse click on one of the movie clips will be first intercepted by the object at the lowest level of the display list, which might prevent it from ever being seen at the level of the main timeline.

 

Next, we define two functions addEventListeners() and removeEventListeners() that add and remove all the necessary event listeners from the content movie clips.  This includes event handlers for both roll-over and click events.  Enclosing this code within a function is useful because it will allow us to enable or disable the clips themselves using a single instruction.  This will come in handy later as you will see.

 

function addEventListeners():void {

 

            //Add mouse over handlers for each button

            boston1_mc.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

            boston2_mc.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

            boston3_mc.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

            boston4_mc.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

           

           

            //Add mouse out handlers for each button

            boston1_mc.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            boston2_mc.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            boston3_mc.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            boston4_mc.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

           

            //Add mouse click handlers for each button

            boston1_mc.addEventListener(MouseEvent.CLICK, mouseClickHandler);

            boston2_mc.addEventListener(MouseEvent.CLICK, mouseClickHandler);

            boston3_mc.addEventListener(MouseEvent.CLICK, mouseClickHandler);

            boston4_mc.addEventListener(MouseEvent.CLICK, mouseClickHandler);

           

 

}

 

function removeEventListeners():void {

 

boston1_mc.removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

boston2_mc.removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

boston3_mc.removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

boston4_mc.removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

           

 

//Remove mouse out handlers for each button

            boston1_mc.removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            boston2_mc.removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            boston3_mc.removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

            boston4_mc.removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);

           

           

            //Remove mouse click handlers for each button

            boston1_mc.removeEventListener(MouseEvent.CLICK, mouseClickHandler);

            boston2_mc.removeEventListener(MouseEvent.CLICK, mouseClickHandler);

            boston3_mc.removeEventListener(MouseEvent.CLICK, mouseClickHandler);

            boston4_mc.removeEventListener(MouseEvent.CLICK, mouseClickHandler);

           

}

 

addEventListeners();

 

By calling the addEventListeners() function when we are done, we are initializing the program to start will all of the movie clips enabled.

 

The next two functions handle the roll-over effects.

 

//This is called when the mouse is over the button

function mouseOverHandler(e:MouseEvent):void {

            //Get the button from the event

            var button:MovieClip = MovieClip(e.target);

            //Fully opaque

            button.alpha = 1;

}

 

//This is called when the mouse moves away from the button

function mouseOutHandler(e:MouseEvent):void {

            //Get the button from the event

            var button:MovieClip = MovieClip(e.target);

            //75% transparent

            button.alpha = 0.75;

}

 

Rolling over the clip sets its alpha property to 1 (or 100%), and rolling out sets the opacity back to 75%. 

 

The click handler function, mouseClickHandler, kicks off the animation effect to open a page.

 

function mouseClickHandler(e:MouseEvent):void {

            removeEventListeners();

            var button:MovieClip = e.target as MovieClip;

            currentPage = button;

            currentPage.alpha = 1;

 

            //Save its original coordinates and size

            currentPageOriginX = currentPage.x;

            currentPageOriginY = currentPage.y;

            currentPageOriginScaleX = currentPage.scaleX;

            currentPageOriginScaleY = currentPage.scaleY;

 

            this.setChildIndex(currentPage, numChildren-1);

            targetX = hCenter;

            this.addEventListener(Event.ENTER_FRAME, movePage);

}

 

First, it disables further mouse events on the thumbnails by calling removeEventListeners().  Next, it determines which thumbnail was clicked by querying the target of the event object, assigns it to currentPage, and makes it fully opaque.  Before starting any animation, we need to save the pageÕs current position and size so we can restore it later.  Finally, we push the current page to the top of the display list (so it will display over other pages) and begin animating it to move to stage center by adding a frame event listener. 

 

The next set of functions creates the animation effects.  For this version of the application, animation must take place in two steps.  The first step is to move the movie clip object, represented as a thumbnail to the center of the stage.  The second step is to scale the clip back to its fill size and enable it for viewing.  Here is the first animation function.

 

function movePage(evt:Event):void        {

            if (Math.abs(targetX - currentPage.x) > 1)

                        currentPage.x += (targetX - currentPage.x)/EASE_FACTOR;

            }

            else       {

                        this.removeEventListener(Event.ENTER_FRAME, movePage);

                        if (targetX == hCenter)  {                     

                                    targetScale = 1;                                    //scale up to 100%

                                    this.addEventListener(Event.ENTER_FRAME, scaleUpPage);

                        }

                        else       {                                                                                                                                 

currentPage.x = currentPageOriginX

addEventListeners();

                        }

            }

}

 

This function is written in a way that it will work for either moving the page to the center of the stage or restoring it to its original position.  It begins by using an identical easing technique to the sliding timeline example shown first.  This code successively moves the target page to within 1 pixel of the center of targetX.  When the movement is complete, it removes the frame event listener, and tests the value of targetX.  If targetX is center stage, then we must be opening the page, in this case we add a new frame event listener to begin animating the size of the page (based on the way we set this up, it is assumed that the thumbnail represents the content movie clip scaled down to something less than 100%).  Otherwise we must have been restoring the page back to its original position on the stage.  In this case we are done with the animation and can re-enable the thumbnails and wait for the next mouse event.  You may be wondering why the statement currentPage.x = currentPageOriginX?  This will correct for the 1 pixel error and force the page back to its starting point.  Without this, the positioning error will become noticeable after a couple of iterations for this particular layout.

 

Next, we add a function for scaling up the page to its full size.

 

function scaleUpPage(evt:Event):void    {

            if (Math.abs(targetScale - currentPage.scaleX) > .001)    { 

currentPage.scaleX += (targetScale - currentPage.scaleX)/EASE_FACTOR;

            }

            if (Math.abs(targetScale - currentPage.scaleY) > .001)    { 

currentPage.scaleY += (targetScale - currentPage.scaleY)/EASE_FACTOR;

            }

            else       {                                  //done scaling

                        this.removeEventListener(Event.ENTER_FRAME, scaleUpPage);

                        currentPage.play();

                        currentPage.mouseChildren = true;

                        //Make the Close button visible and add an event listener to it

                        this.addChild(close_btn);

                        close_btn.addEventListener(MouseEvent.CLICK, closeClicked);

            }

}

 

Again, this uses an easing technique to scale the page to within 0.1% of its full size (held by the variable targetScale).  Note that this must be done on both the x and y dimensions at the same time.  When the animation is done, we enable the current page by setting it playing and allowing it to accept mouse events.  As a final step, we want to add the Close button to the stage and attach an event listener to it.  From the point of view of the main timeline, Nothing more will happen until the user clicks close, which will kick off the animation in the reverse direction.  (The user may interact directly with the current page movie clip, but those mouse events are handled inside the nested clip and never propagate to the main timeline level.)

 

Now we need to add the code that will restore the page to itÕs original size when the Close button is clicked.  First, we write the following event handler function that is called when the user clicks the button.

 

function closeClicked(e:Event):void {

            currentPage.mouseChildren = false;

            targetScale = currentPageOriginScaleX;

            this.addEventListener(Event.ENTER_FRAME, scaleDownPage);

            close_btn.removeEventListener(MouseEvent.CLICK, closeClicked);

            this.removeChild(close_btn);

}

 

We begin by disabling the page so it will no longer take mouse events and setting the target scaling value back to the pageÕs initial thumbnail size (saved in currentPageOriginScaleX before we started opening the page).  Next, add another frame event listener that will start the animation to shrink the page, and finally, remove the Close button from the stage.

 

The last function we need is called scaleDownPage.  The frame event listener added above calls this function. 

 

function scaleDownPage(evt:Event):void            {

            if (Math.abs(targetScale - currentPage.scaleX) > .001)    { 

currentPage.scaleX += (targetScale - currentPage.scaleX)/EASE_FACTOR;

            }

            if (Math.abs(targetScale - currentPage.scaleY) > .001)    { 

currentPage.scaleY += (targetScale - currentPage.scaleY)/EASE_FACTOR;

            }

            else       {                      //done scaling

                        this.removeEventListener(Event.ENTER_FRAME, scaleDownPage);

                        currentPage.scaleX = currentPageOriginScaleX;

                        currentPage.scaleY = currentPageOriginScaleY;

                        //reset the movie clip to first frame

                        currentPage.gotoAndStop(1);

 

                        //Move the page back to its original position

                        targetX = currentPageOriginX;

                        this.addEventListener(Event.ENTER_FRAME, movePage);

            }

}

 

Like the scaleUpPage function, it uses easing to scale the page to within 0.1% of itÕs target size.  When done, the page will be forced back to its original size (to correct for the easing error), and the movie clip will be reset to itÕs first frame.  The last step is to set a frame event listener to call movePage with targetX set to the original position of the thumbnail sized page on the stage.

 

The key lesson of this second navigation model is that your can create rather complex animations by chaining a number of movements together.  But in all cases, the basic technique of using frame events and easing to animate (or ÒtweenÓ) a specific property prevails.