Saturday, March 26, 2016

Creating Interactive Poetry



Justin Ryan

Download the .fla file here!

Media Programming is very new to me, but I have just completed my first project. I followed an online tutorial to create a soundboard using ActionScript 3.0 with Adobe Animate. Before I went into Software Development, I previously studied Music and English - and as a graduate of Music, obviously sound is something I wanted to work with, so this seemed like a good tutorial for me to start with. 

The tutorial can be found at this link: 
http://www.ilike2flash.com/2011/08/create-soundboard-in-actionscript-3.html

This tutorial will guide you to make a soundboard by creating two arrays (one for buttons and one for sounds) and uses a for loop to add event listeners to each sound. Then, in a play function, another for loop is used to link each button to its corresponding sound.

Following on from this tutorial, I created an interactive presentation of Edgar Allan Poe's poem Lenore. Basically, this project has four frames: one home frame, two frames for the poem itself, and one frame with links to websites about Edgar Allan Poe and a home button that brings the movie back to the first frame. On frames 2 and 3, the user navigates through the poem by clicking on lines of the poem. When a line is clicked, an audio file of myself reading that line is played and the next line then appears. On each frame, there are also two buttons: one plays a wind sound effect and the other plays a funeral bell sound effect. This is intended to add to the dark atmosphere of the poem. These sound effects were not created by me and can be found at the following links:

https://www.youtube.com/watch?v=wqPAAwR_32M
https://www.youtube.com/watch?v=HKTIe6piDOI

In this poem, there are two different narrators. To emphasize this, I use a different font when the narrator changes, as well as using an echo effect on the audio for one of the narrators (all the narration audio was done by me, though - even if I did lower the pitch of my voice to make myself sound more sinister!). There is background music (composed by me) that is played on a loop throughout the .swf file.

This project is intended for students or enthusiasts of literature. It brings the poem to life and keeps the user's attention as they have to navigate their own way through the poem.

If you want to be able to create something like this, then read on!

Once you've completed the above tutorial, you'll be at a good starting point. Create a new .fla file in Adobe Animate. I named my file 'Lenore'. Make sure that you save all your audio files in the same directory where you save your .fla file - this is super important! For the background, I imported an image (a photo of my local area at night, which I took myself) into the library by selecting 'File - Import - Import to library...' and selected the image I wanted to use. I renamed the default layer (Layer1) to 'background' (by right clicking on the layer name), selected the first frame and inserted a new keyframe by selecting 'Insert - Timeline - Keyframe'. I dragged the imported image onto the stage and re-sized it until I was happy with its width and height (right click the image on the stage and select 'Transform - Scale' to change its size). Once this step was done, I had to insert graphics and a button onto the stage. I had images of text prepared (using the website cooltext.com) which I imported to the library. I dragged these text images to the stage (which now had my background image and text images which read 'Lenore' and 'Edgar Allan Poe') while making sure that the first frame was still selected (I made the mistake of selecting the wrong frame and/or layer so many times during this project!). 

Obviously I also needed a button on this frame that could advance the movie on to the next frame(s)., so I selected 'Insert - New Symbol...'. I named the symbol 'Play' and then used the tools available in the tools window to draw a simple circle with a triangular play symbol inside of it. I went back to view my stage and created a new layer by selecting 'Insert - Timeline - Layer'. I selected the first frame of this layer and created a new keyframe (Insert - Timeline - Keyframe). I then dragged my 'Play' button from the library window to the stage and gave it the instance name 'playBtn' in the properties panel. Now it was time to write code!

I created a new later called 'Actions' and selected the first frame. I then opened the Actions window by selecting 'Window - Actions'. Firstly, I added code that would keep the movie on the first frame. In the Actions window, I typed 'gotoAndStop(1);' (which I later realised I could change to 'stop(1);'). This ensures that when the movie starts, it will stay on frame 1. If you've made it this far, hit CTRL + ENTER to test your movie. You should see your first frame with your background image, graphics, and play button.

Now I added my background music. Still on frame 1 of the Actions layer, I typed the following code into the Actions window:

var mySound:Sound = new Sound();
mySound.load(new URLRequest("Lenore_Final.mp3"));
mySound.play();

I later decided that I wanted the background music to play on a loop, so I altered the above code to look like this:

var mySound:Sound = new Sound();
mySound.load(new URLRequest("Lenore_Final.mp3"));
mySound.play(0, int.MAX_VALUE);

If you test your movie now, it should show you your first frame as before, but now there will be music playing! Just to specify, this doesn't play the sound on an infinite loop. The second parameter in the play function specifies how many times the sound will be played. Since we're using the max value that an integer can be, it will be played a total of 2,147,483,647 (I think) times. This may not be infinite, but it will definitely last long enough to seem infinite!

I wanted the play button to bring the movie to frame 2, so I added this code in the Actions layer of frame 1:

playBtn.addEventListener(MouseEvent.CLICK, goToPoem);

function goToPoem(e:MouseEvent):void{
gotoAndStop(2);
}

Test your movie again and make sure that this works.

At this point, I added two functions: one that would make the play button slightly bigger when the cursor is over it, and one that would bring the button back to its normal size when the cursor is moved off it. Here's the code I used (obviously in frame 1 of the Actions layer):

playBtn.addEventListener(MouseEvent.ROLL_OVER, playBigger);
playBtn.addEventListener(MouseEvent.ROLL_OUT, playNormal);

function playBigger(e:MouseEvent):void{
playBtn.scaleX += 0.05;
playBtn.scaleY += 0.05;
}

function playNormal(e:MouseEvent):void{
playBtn.scaleX -= 0.05;
playBtn.scaleY -= 0.05;


Test your movie and hopefully the play button will change size whenever you move the mouse over and/or off it. That's frame one finished (for now!).

Frame 2 is where your new knowledge from the soundboard tutorial really starts to be useful. Again, you'll need to import images for the text of the poem (again, I used cooltext.com to create my images) and import them to the library. Click on 'Insert - New Symbol', call your symbol 'Line1' and ensure that its type is 'Button'. Now, instead of drawing the symbol, drag your imported image from the library panel over to the area where you edit how your symbol looks. You can re-size it here if you wish. Go back to your stage and select frame 2 of the Buttons layer. Drag your Line1 symbol from the Library to your stage and then for simplicity give it the instance name 'Line1'. Repeat this step for each line you want to place on this stage (and obviously give each symbol and instance name a different number, eg Line1, Line2, Line3 etc).

In frame 2 of the Actions layer (in the Actions panel) create two arrays - one for your buttons and one for sounds. Put the instance names of your buttons into the buttons array and put the sounds that correspond to each line in the sound array (NB - ensure that your sounds are numbered in the same order as your Line buttons so that each sound corresponds to right line). Here's the code:

var buttonsArray:Array = new Array();
buttonsArray[0] = Line1;
buttonsArray[1] = Line2;
buttonsArray[2] = Line3;
buttonsArray[3] = Line4;
buttonsArray[4] = Line5;

var soundArray:Array = new Array();
soundArray[0] = 'Line1.mp3';
soundArray[1] = 'Line2.mp3';
soundArray[2] = 'Line3.mp3';
soundArray[3] = 'Line4.mp3';
soundArray[4] = 'Line5.mp3';

Now, you'll have to add an event listener for each button. Just like in the soundboard tutorial, I did this with a for loop:

var i:uint = 0;
for(i = 0; i < buttonsArray.length; i++){
buttonsArray[i].addEventListener(MouseEvent.CLICK, playSound);
}

Again, the playSound function is very similar to what is done in the soundboard tutorial:

function playSound(e:MouseEvent):void{
for(i = 0; i < buttonsArray.length; i++){

if(e.target == buttonsArray[i]){
var mySound:Sound = new Sound();
mySound.load(new URLRequest(soundArray[i]));
mySound.play();
                }
         }
}

Test your movie. If you click on a line, it should play the sound that corresponds to that line.

Now, for my project, I decided that I wanted each line to fade in because I felt that it would create a more eerie effect for this particular poem. So, when the movie goes to frame 2, the first line fades in. When the first line is clicked, the second line fades in. When the second line is clicked, the third line fades in, and so on. I achieved this using a tween. Here's my tween for the first line (put this code in the Actions layer on frame 2):

var t:Tween = new Tween(Line1, "alpha", Strong.easeIn, 0, 1, 3, true);

Now, when the movie gets to frame 2, the first line should fade in at a regular speed. Now, we want to write some code that will do this for each line when the line before it is clicked. To do this, we need to make sure these lines aren't visible when frame 2 is enetered. Below the above line of code, add this:

Line2.visible = false;
Line3.visible = false;
Line4.visible = false;
Line5.visible = false;

Now, in the playSound function, alter the code so that the function looks like this:

function playSound(e:MouseEvent):void{
for(i = 0; i < buttonsArray.length; i++){

if(e.target == buttonsArray[i]){
var mySound:Sound = new Sound();
mySound.load(new URLRequest(soundArray[i]));
mySound.play();
if(buttonsArray[i+1] != null && buttonsArray[i+1].visible == false){
buttonsArray[i+1].visible = true;
var myTween:Tween = new Tween(buttonsArray[i+1], "alpha",                                                       Strong.easeIn, 0, 1, 9, true);
}
                 }
         }
}

The tween here creates a slower fade in effect, as (in my opinion) these lines do not need to appear as quickly as the first. Test your movie. Each time you click on a line, the line after it should slowly fade in.

Now you need to add a button that brings you to the next frame. Insert a new symbol and call it 'Next'. Make sure that its type is 'Button'. For this symbol, I created a circle with three smaller circles inside it. Go back to your stage and select frame 2 of the Buttons layer. Drag your new symbol over to the stage and place it underneath your line buttons. Give it the instance name 'next1'. Like your other buttons, ensure that it is not visible:

next1.visible = false;

Alter the playSound function again so that it looks like this:

function playSound(e:MouseEvent):void{
for(i = 0; i < buttonsArray.length; i++){

if(e.target == buttonsArray[i]){
var mySound:Sound = new Sound();
mySound.load(new URLRequest(soundArray[i]));
mySound.play();
if(buttonsArray[i+1] != null && buttonsArray[i+1].visible == false){
buttonsArray[i+1].visible = true;
var myTween:Tween = new Tween(buttonsArray[i+1], "alpha",                                                       Strong.easeIn, 0, 1, 9, true);
}
if(e.target == buttonsArray[4]){
next1.visible = true;
var tt:Tween = new Tween(next1, "alpha", Strong.easeIn, 0, 1, 9, true);
}
         }
}

Test your movie, when the last line is clicked, your new button should slowly fade in. Add more code to frame 2 of the actions layer so that this button will bring the movie to frame 3 when clicked:

next1.addEventListener(MouseEvent.CLICK, goToNext);

function goToNext(e:MouseEvent):void{
gotoAndStop(3);
}

Test your movie and ensure that everything works.

At this point, I added code that increased the size of each button in frame 2 whenever the cursor moves over it (just like the play button in frame 1). Here's the code for that (add this in frame 2 of the Actions layer):

next1.addEventListener(MouseEvent.ROLL_OVER, makeBigger);
next1.addEventListener(MouseEvent.ROLL_OUT, makeNormal);

for(i = 0; i < buttonsArray.length; i++){
buttonsArray[i].addEventListener(MouseEvent.ROLL_OVER, makeBigger);
buttonsArray[i].addEventListener(MouseEvent.ROLL_OUT, makeNormal);

}

function makeBigger(e:MouseEvent):void{
if(e.target == next1){
next1.scaleX += 0.05;
next1.scaleY += 0.05;
}

else{
for(i = 0; i < buttonsArray.length; i++){
if(e.target == buttonsArray[i]){
buttonsArray[i].scaleX += 0.05;
buttonsArray[i].scaleY += 0.05;
}
}
  }
}

function makeNormal(e:MouseEvent):void{
if(e.target == next1){
next1.scaleX -= 0.05;
next1.scaleY -= 0.05;
}
else{
for(i = 0; i < buttonsArray.length; i++){
if(e.target == buttonsArray[i]){
buttonsArray[i].scaleX -= 0.05;
buttonsArray[i].scaleY -= 0.05;
}
}
 }
}

Again, test your movie to ensure this works.

Select frame 3 of the Buttons layer and add the rest of your lines. In the Actions layer on frame 3, add these buttons to your button array. Also, add your sounds to your sound array. As before, use a for loop to add event listeners to each button. Add a tween so that the first line fades in when the movie gets to frame 3. Add another next button and attach event listeners to it. Create a function that brings the movie to frame 4 when this button is clicked. Here is the complete code for frame 3 of the Actions layer:

import flash.events.MouseEvent;

Line7.visible = false;
Line8.visible = false;
Line9.visible = false;
Line10.visible = false;
Line11.visible = false;
next2.visible = false;

next2.addEventListener(MouseEvent.ROLL_OVER, makeBigger);
next2.addEventListener(MouseEvent.ROLL_OUT, makeNormal);
next2.addEventListener(MouseEvent.CLICK, gotoFinal);

function gotoFinal(e:MouseEvent):void {
gotoAndStop(4);
}

var ttween:Tween = new Tween(Line6, "alpha", Strong.easeIn, 0, 1, 3, true);

buttonsArray[5] = Line6;
buttonsArray[6] = Line7;
buttonsArray[7] = Line8;
buttonsArray[8] = Line9;
buttonsArray[9] = Line10;
buttonsArray[10] = Line11;
soundArray[5] = 'Line6.mp3';
soundArray[6] = 'Line7.mp3';
soundArray[7] = 'Line8.mp3';
soundArray[8] = 'Line9.mp3';
soundArray[9] = 'Line10.mp3';
soundArray[10] = 'Line11.mp3';

for(i = 5; i < buttonsArray.length; i++){
buttonsArray[i].addEventListener(MouseEvent.CLICK, playSound);
buttonsArray[i].addEventListener(MouseEvent.ROLL_OVER, makeBigger);
buttonsArray[i].addEventListener(MouseEvent.ROLL_OUT, makeNormal);
}

Alter the playSound function in frame 2 of the Actions layer so that it looks like this:

function playSound(e:MouseEvent):void{
for(i = 0; i < buttonsArray.length; i++){
if(e.target == buttonsArray[i]){
var mySound:Sound = new Sound();
mySound.load(new URLRequest(soundArray[i]));
mySound.play();
if(buttonsArray[i+1] != null && buttonsArray[i+1].visible == false){
buttonsArray[i+1].visible = true;
var myTween:Tween = new Tween(buttonsArray[i+1], "alpha", Strong.easeIn, 0, 1, 9, true);
}
if(e.target == buttonsArray[4]){
next1.visible = true;
var tt:Tween = new Tween(next1, "alpha", Strong.easeIn, 0, 1, 9, true);
}
if(e.target == buttonsArray[10]){
next2.visible = true;
var buttonTween:Tween = new Tween(next2, "alpha", Strong.easeIn, 0, 1, 9, true);
}
}
}
}

As well as this, you'll need to change the makeBigger and makeNormal functions:

function makeBigger(e:MouseEvent):void{
if(e.target == next1){
next1.scaleX += 0.05;
next1.scaleY += 0.05;
}
else if(e.target == next2){
next2.scaleX += 0.05;
next2.scaleY += 0.05;
}
else{
for(i = 0; i < buttonsArray.length; i++){
if(e.target == buttonsArray[i]){
buttonsArray[i].scaleX += 0.05;
buttonsArray[i].scaleY += 0.05;
}
}
  }
}

function makeNormal(e:MouseEvent):void{
if(e.target == next1){
next1.scaleX -= 0.05;
next1.scaleY -= 0.05;
}
else if(e.target == next2){
next2.scaleX -= 0.05;
next2.scaleY -= 0.05;
}
else{
for(i = 0; i < buttonsArray.length; i++){
if(e.target == buttonsArray[i]){
buttonsArray[i].scaleX -= 0.05;
buttonsArray[i].scaleY -= 0.05;
}
}
 }

Test your movie.

Now we're on to the final frame!

I imported four images of text (from cooltext.com, of course). I then created four new symbols with these images - one graphic, and three buttons. Select frame 4 of the background layer and add the graphic and buttons to the stage. Create code in frame 4 of the actions layer that enables the buttons to send the user to three different websites, makes the buttons fade in and changes the size of the buttons when the cursor moves over them. Finally, you'll need to create a button that brings the movie back to the first frame. Insert a new symbol and use the tools to draw an icon (I drew a circle with a house symbol in it), and drag this symbol to the stage. Add code that brings the movie to the first frame when this button is clicked. Your code for frame 4 should now look like this:

import flash.events.MouseEvent;
import flash.net.URLRequest;
import fl.transitions.Tween;

var poeArray:Array = new Array;

poeArray[0] = poe1;
poeArray[1] = poe2;
poeArray[2] = poe3;
poeArray[3] = home1;


for(i = 0; i < poeArray.length; i++){
var poeTween2:Tween = new Tween(poeArray[i], "alpha", Strong.easeIn, 0, 1, 3, true);
poeArray[i].addEventListener(MouseEvent.ROLL_OVER, makePoeBigger);
poeArray[i].addEventListener(MouseEvent.ROLL_OUT, makePoeNormal);
}

poe1.addEventListener(MouseEvent.CLICK, goToStories);
poe2.addEventListener(MouseEvent.CLICK, goToMuseum);
poe3.addEventListener(MouseEvent.CLICK, goToPoets);
home1.addEventListener(MouseEvent.CLICK, goHome);

function goToStories(e:MouseEvent):void {
navigateToURL(new URLRequest('http://www.poestories.com'), "_blank");
}

function goToMuseum(e:MouseEvent):void {
navigateToURL(new URLRequest('http://www.poemuseum.org'), "_blank");
}

function goToPoets(e:MouseEvent):void {
navigateToURL(new URLRequest('http://www.poets.org/poetsorg/poet/edgar-allan-poe'), "_blank");
}

function makePoeBigger(e:MouseEvent):void {
for(i = 0; i < poeArray.length; i++){
if(e.target == poeArray[i]){
poeArray[i].scaleX += 0.05;
poeArray[i].scaleY += 0.05;
}
}
}

function makePoeNormal(e:MouseEvent):void {
for(i = 0; i < poeArray.length; i++){
if(e.target == poeArray[i]){
poeArray[i].scaleX -= 0.05;
poeArray[i].scaleY -= 0.05;
}
}
}

function goHome(e:MouseEvent):void {
gotoAndPlay(1);
}


Ok, we're very nearly done! Create a new layer called 'Effects'. Insert two new symbols with the type 'Button': one for a wind sound effect and one for a funeral bell sound effect. You can draw whatever you want, but I drew three wavy lines for the wind effect and a cross for the funeral bell effect. On the first frame of the Effects layer, add a new keyframe and then drag your new symbols over to the stage and place them somewhere where they look neat. Give them the instance names 'wind1' and 'bell1'. In frame 1 of the Actions layer, add the following code:

var isBellPlaying:Boolean;
var bellSound:Sound = new Sound();
bellSound.load(new URLRequest("bell.mp3"));
var bellChannel:SoundChannel = bellSound.play();
bellChannel.stop();

var isWindPlaying:Boolean;
var windSound:Sound = new Sound();
windSound.load(new URLRequest("wind.mp3"));
var windChannel:SoundChannel = windSound.play();
windChannel.stop();

cross1.addEventListener(MouseEvent.ROLL_OVER, makeCrossBigger);
cross1.addEventListener(MouseEvent.ROLL_OUT, makeCrossNormal);
cross1.addEventListener(MouseEvent.CLICK, playBell);

wind1.addEventListener(MouseEvent.ROLL_OVER, makeWindBigger);
wind1.addEventListener(MouseEvent.ROLL_OUT, makeWindNormal);
wind1.addEventListener(MouseEvent.CLICK, playWind);

function playBell(e:MouseEvent):void {
if(isBellPlaying != true){
bellChannel = bellSound.play(0, int.MAX_VALUE);
isBellPlaying = true;
}

else{
bellChannel.stop();
isBellPlaying = false;
}
}

function playWind(e:MouseEvent):void {
if(isWindPlaying != true){
windChannel = windSound.play(0, int.MAX_VALUE);
isWindPlaying = true;
}

else{
windChannel.stop();
isWindPlaying = false;
}
}

function makeCrossBigger(e:MouseEvent):void {
cross1.scaleX += 0.05;
cross1.scaleY += 0.05;
}

function makeCrossNormal(e:MouseEvent):void {
cross1.scaleX -= 0.05;
cross1.scaleY -= 0.05;
}

function makeWindBigger(e:MouseEvent):void {
wind1.scaleX += 0.05;
wind1.scaleY += 0.05;
}

function makeWindNormal(e:MouseEvent):void {
wind1.scaleX -= 0.05;
wind1.scaleY -= 0.05;
}

Test your movie. When you click on either of your new buttons, it should play the corresponding sound effect. When you click it again, it should stop playing the sound effect.

At this point, I THOUGHT I was done. Nope. There was an issue - when I went from the last frame back to the first frame, the background music would start playing over itself again which sounded horrible. So, I introduced a boolean value that changes based on whether or not the background music is already playing. So alter your code in frame 1 of the actions layer so that it looks like this:

var isThemePlaying:Boolean;

if(isThemePlaying != true){
var mySound:Sound = new Sound();
mySound.load(new URLRequest("Lenore_Final.mp3"));
mySound.play(0, int.MAX_VALUE);
isThemePlaying = true;
}

Test your movie. If it works, congratulations! Go buy some celebratory chocolate (it's what I did).


Ok, so here's what I found difficult:

  • Getting the sound to loop. I'm still not 100%  satisfied with how I solved this, but it's something I want to work on in the future.
  • I would have liked to get all sounds that correspond to the poem to stop when one is clicked - that way there wouldn't be two or more voices at any time. I tried working with sound channels and boolean values, but I couldn't find a solution.
  • Getting the stage to be an appropriate size for the content was trickier than I thought it would be.
Here's what I found easy:
  • Adding event listeners.
  • Moving from frame to frame.
  • Using external sound files.
Here's what I learned:
  • For loops are amazing for adding event listeners to multiple buttons.
  • I'm way more confident with navigating the timeline now.
  • How to loop sound (kind of).
  • How to use tweens.
  • Screaming into a pillow is not the best solution when something doesn't work (although it does help).
EDIT:
In order to embed my .swf file into this blog post, I had to change my project so that it uses internal audio files instead of external audio files. I imported the sound files by selecting 'File - Import - Import to library...' and selected the files I wanted to import. The, I had to give every sound a class name by right-clicking it in the library panel, selecting properties, selecting the ActionScript tab, checking the box that says 'Export for ActionScript', and typing a name into the Class field. Then my code had to be changed slightly.

Frame 1 Actions:

if(isThemePlaying != true){
var mySound:Sound = new (Lenore_Final);
mySound.play(0, int.MAX_VALUE);
isThemePlaying = true;
}

The code that deals with the wind and bell sound effects has also been changed to be like the above code.

Frame 2 Actions:

var soundArray:Array = new Array();
soundArray[0] = Line1_Sound;
soundArray[1] = Line2_Sound;
soundArray[2] = Line3_Sound;
soundArray[3] = Line4_Sound;
soundArray[4] = Line5_Sound;

function playSound(e:MouseEvent):void{
for(i = 0; i < buttonsArray.length; i++){
if(e.target == buttonsArray[i]){
var mySound:Sound = new (soundArray[i]);
mySound.play();
if(buttonsArray[i+1] != null && buttonsArray[i+1].visible == false){
buttonsArray[i+1].visible = true;
var myTween:Tween = new Tween(buttonsArray[i+1], "alpha", Strong.easeIn, 0, 1, 9, true);
}
if(e.target == buttonsArray[4]){
next1.visible = true;
var tt:Tween = new Tween(next1, "alpha", Strong.easeIn, 0, 1, 9, true);
}
if(e.target == buttonsArray[10]){
next2.visible = true;
var buttonTween:Tween = new Tween(next2, "alpha", Strong.easeIn, 0, 1, 9, true);
}
}
}
}

The same changes are made in Frame 3 of the Actions layer.

No comments:

Post a Comment