Actionscript 3, adding an audio slider to my game - actionscript-3

so I've really been struggling trying to add an audio slider to my game the last couple days and I've about had it, so, let me preface this by saying I have tried EXTREMELY hard and I would love to fully understand the solution, but at this point I really just want the solution, so the more specific you can be the better.
I've thought of a number of approaches, but my actionscript skills have been too faulty to pull any of them off. Let me explain my setup a bit. I have a fully functional class that is an audio slider. It plays music, slides, does everything perfect. Now, I'm sticking it in my document class. I thought it was easiest to call this function right at the very beginning (as my title screen shows up), EVEN THOUGH I don't want music to be playing right now at the title screen. So I thought I would call it, then just simply not play the music yet. So now I have it called from my document class. Now, I want to be able to click on the options button and then addChild the audio slider. I have actually accomplished this and it works. Finally....the hard part. I want to click the play game button and then have the music start playing WHILE BEING LINKED to the options slider which has the possibility of already being moved. Really...just a standard audio slider.
I'm really stuck on linking the options screen audio slider to the music actually playing in the game. I thought I would call the Volume() function (my audio slider), and then once my playscreen starts up, call it again and then just run a method of the Volume() class from within my playscreen class. This seems to lead to problems though...and this is where I lose it. By calling the Volume() class again, am I totally reinstating my audio? Like does this mean that I'm erasing everything I have done with my audio slider in the options panel? It seems to be...because it just starts playing at the default value.
So here's some detailed code of what I'm doing. I was thinking of using navigation events, but like I said, I'm not the greatest flash programmer yet and when I tried using them before I ran into trouble. Here are my main concerns in the code.
1) Calling new_Volume in my playScreen class (called SimpleMenuMain). Does this wipe out everything I've done in my document class when I called Volume()?
2) Towards the end of the Volume() class, I made a stopMusic function. Whenever I call it from my document class, it only works if I have not yet pressed play game, further leading me to believe I'm erasing whatever I do in the options panel before pressing play game.
3) Should I be using navigation events (custom events)? If so, how exactly do I do it? I understand how to use them in general (I've already used 5 in this game), but all of my navigation events go to my document class, so I'm guessing there's some subtlety with them I'm not quite understanding. I have a custom events class..I'll post that as well.
Thank you for all the help =) this has been killing me. I will wittle down these classes to the relavent info.
Document Class
package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.ProgressEvent;
public class SMGDocClass extends MovieClip
{
public var playScreen:SimpleMenuMain;
public var titleScreen:TitleScreen;
public var gameOver:GameOver;
public var loadingProgress:LoadingProgress;
public var optionScreen:OptionScreen;
public var new_Volume:Volume;
public function SMGDocClass()
{
loadingProgress = new LoadingProgress();
loadingProgress.x = 0;
loadingProgress.y = 0;
addChild( loadingProgress );
loaderInfo.addEventListener( Event.COMPLETE, onCompletelyDownloaded,false,0,true );
loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgressMade,false,0,true );
}
public function showTitleScreen():void
{
titleScreen = new TitleScreen();
titleScreen.addEventListener(NavigationEvent.START,onRequestStart,false,0,true);
titleScreen.addEventListener(NavigationEvent.OPTIONS,onRequestOptions,false,0,true);
titleScreen.x = 0;
titleScreen.y = 0;
addChild(titleScreen);
removeChild(loadingProgress);
new_Volume = new Volume();
new_Volume.stopMusic();
stage.focus = playScreen;
stage.stageFocusRect = false;
}
public function onStickman1Death(stickman1Event:Stickman1Event):void
{
var finalScore:Number = playScreen.getFinalScore();
var finalClockTime:Number = playScreen.getFinalClockTime();
gameOver = new GameOver();
gameOver.addEventListener(NavigationEvent.RESTART,onRequestRestart,false,0,true);
gameOver.addEventListener(NavigationEvent.MAINMENU,onRequestMainMenu,false,0,true);
gameOver.x = 5;
gameOver.y = 6;
gameOver.setFinalScore( finalScore );
gameOver.setFinalClockTime( finalClockTime );
addChild(gameOver);
new_Volume.stopMusic();
removeChild(playScreen);
playScreen = null;
}
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
playScreen = new SimpleMenuMain();
playScreen.addEventListener( Stickman1Event.DEAD, onStickman1Death,false,0,true );
playScreen.x = 0;
playScreen.y = 0;
addChild( playScreen );
dispatchEvent(new Stickman1Event(Stickman1Event.DEAD));
removeChild(titleScreen);
titleScreen = null;
stage.focus = playScreen;
stage.stageFocusRect = false;
}
public function restartGame():void
{
playScreen = new SimpleMenuMain();
playScreen.addEventListener(Stickman1Event.DEAD, onStickman1Death,false,0,true);
playScreen.x = 0;
playScreen.y = 0;
addChild(playScreen);
removeChild(gameOver);
gameOver = null;
}
public function onRequestMainMenu( navigationEvent:NavigationEvent):void
{
titleScreen = new TitleScreen();
titleScreen.addEventListener(NavigationEvent.START,onRequestStart,false,0,true);
titleScreen.addEventListener(NavigationEvent.OPTIONS,onRequestOptions,false,0,true);
titleScreen.x = 0;
titleScreen.y = 0;
while(numChildren > 0)
{
removeChildAt(0);
}
addChild(titleScreen);
stage.focus = playScreen;
stage.stageFocusRect = false;
}
public function onCompletelyDownloaded( event:Event ):void
{
gotoAndStop(3);
showTitleScreen();
}
public function onProgressMade( progressEvent:ProgressEvent ):void
{
loadingProgress.setValue( Math.floor( 100 * loaderInfo.bytesLoaded / loaderInfo.bytesTotal ) );
}
public function onRequestOptions(navigationEvent:NavigationEvent):void
{
optionScreen = new OptionScreen();
optionScreen.addEventListener(NavigationEvent.MAINMENU,onRequestMainMenu,false,0,true);
optionScreen.x = 0;
optionScreen.y = 0;
addChild(optionScreen);
removeChild(titleScreen);
}
}
}
Play Screen Class
package {
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.ui.Mouse;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.events.Event;
import flash.media.SoundChannel;
public class SimpleMenuMain extends MovieClip {
public var gameTimer:Timer;
public var stickman1:Stickman1;
public var new_Volume:Volume;
public function SimpleMenuMain() {
currentLevelData = new LevelData( 1 );
setBackgroundImage();
new_Volume = new Volume();
gameTimer = new Timer(25);
gameTimer.addEventListener(TimerEvent.TIMER, onTick, false, 0, true);
gameTimer.start();
addEventListener( Event.ADDED_TO_STAGE, onAddToStage );
}
public function onAddToStage(event:Event):void
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPress,false,0,true);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyRelease,false,0,true);
}
public function onTick(timerEvent:TimerEvent):void
{
var stickman1HasBeenHit:Boolean = false;
var i:int = army1.length - 1;
var enemy1:Enemy1;
while ( i > -1 )
{
enemy1 = army1[i];
if (enemySpeed == 1)
{
enemy1.moveABit(3,-1.5);
}
if (enemySpeed == 2)
{
enemy1.moveABit(3.6,-1.8);
}
if ( PixelPerfectCollisionDetection.isColliding( stickman1, enemy1, this, true ) )
{
getFinalScore();
gameTimer.stop();
bgmSoundChannel.stop();
stickman1HasBeenHit = true;
}
if ( enemy1.y > 400 )
{
removeChild( enemy1 );
army1.splice( i, 1 );
}
i = i - 1;
}
var i:int = army2.length - 1;
var enemy2:Enemy2;
while ( i > -1 )
{
enemy2 = army2[i];
if (enemySpeed2 == 1)
{
enemy2.moveABit(6,-3);
}
if (PixelPerfectCollisionDetection.isColliding(stickman1, enemy2, this, true ) )
{
gameTimer.stop();
bgmSoundChannel.stop();
stickman1HasBeenHit = true;
}
if ( enemy2.y > 400 )
{
removeChild( enemy2 );
army2.splice( i, 1 );
}
i = i - 1;
}
if (stickman1HasBeenHit)
{
dispatchEvent(new Stickman1Event(Stickman1Event.DEAD));
}
}
}
Volume Class
package {
import flash.display.Sprite;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.net.URLRequest;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.geom.Rectangle;
public class Volume extends Sprite {
public var snd:Sound = new Sound();
public var channel:SoundChannel = new SoundChannel();
//URLRequest=new URLRequest("solitude.wav");
//Make sure you pass URLRequest an audio file on your computer.
public var req:BackgroundMusic = new BackgroundMusic();
public var boundary:Rectangle;
public var sprite:Sprite;
public var slider:Sprite;
public var xPos:Number;
public var yPos:Number;
public var vol:Number;
/*
Our request is loaded into the sound object and plays through
our channel. Volume is initially set at 50% and passed as a
transformation to our our channels soundTransform property
(a fancy way of saying volume). The init() function is called.
*/
public function Volume() {
this.addEventListener(Event.ADDED_TO_STAGE, onStage,false,0,true);
startMusic();
}
/*
The init function creates and draws a rectangle and circle
to the stage and centers them based on the height and
width of the stage. In addition, a rectangle object is
created to 'contain' the sliding circle, like an imaginary box.
We pass -100 as the x value because it is added relative
to our sprite. If we set its x value at 0, or the sprites default x
value,the boundary would stop and start at the slider sprite. Change
-100 to 0 in the rectangle object to get a better idea of its use.
*/
public function onStage(e:Event):void
{
//We remove it immediately so that it doesn't get called multiple times
//As the instance is added to the display list tree
this.removeEventListener(Event.ADDED_TO_STAGE, onStage);
xPos = 320;
yPos = 132;
/* Now that we have a reference to the stage, let's go ahead and create our slider */
init();
}
public function init():void {
sprite = new Sprite();
channel.stop();
sprite.graphics.beginFill(0x999999);
sprite.graphics.drawRect(xPos,yPos,100,5);
sprite.graphics.endFill();
addChild(sprite);
sprite.x-=sprite.width/2;
slider = new Sprite();
slider.graphics.beginFill(0xFF0000);
slider.graphics.drawCircle(xPos+50,yPos, 15);
slider.graphics.endFill();
addChild(slider);
slider.addEventListener(MouseEvent.MOUSE_DOWN, dragSlider);
stage.addEventListener(MouseEvent.MOUSE_UP, stopSlider);
boundary=new Rectangle(-100,0,100,0);
}
/*
dragSlider runs when the use holds the mouse button down. A
startDrag method is used on our sprite where we specify boundary
as our dragging limits. A new event handler designed
to change the mouse volume is subsequenlty called per frame, where
the slider.x property determines volume.
*/
public function dragSlider(event:MouseEvent):void {
slider.startDrag(false,boundary);
slider.removeEventListener(MouseEvent.CLICK, dragSlider);
slider.addEventListener(Event.ENTER_FRAME, changeVolume);
}
/*
Stops dragging and removes the event listener to save on space. Again,
volume will be based on the sliders current x position, which is
constantly being recalculated per frame because we used an
ENTER_FRAME event.
*/
public function stopSlider(event:MouseEvent):void {
slider.stopDrag();
slider.removeEventListener(MouseEvent.MOUSE_UP, stopSlider);
}
/*
This function is constantly recalculating the vol variable
based on the sliders x position, relative to the length of
our rectangle. Creates a decimal range from 0 to 1, where 1
represents 100% volume and 0 represents mute. Anything exceeding
100% causes distortion.
*/
public function changeVolume(event:Event):void {
vol=1+Math.round(slider.x)/100;
channel.soundTransform=new SoundTransform(vol);
}
public function onBackgroundMusicFinished(event:Event):void
{
channel = req.play();
channel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished );
vol=1;
channel.soundTransform=new SoundTransform(vol);
}
public function startMusic():void
{
channel=req.play();
channel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished,false,0,true );
vol=1;
channel.soundTransform=new SoundTransform(vol);
}
public function playMusic():void
{
channel = req.play();
}
public function stopMusic():void
{
channel.stop();
}
}
}
Navigation Event Class
package
{
import flash.events.Event;
public class NavigationEvent extends Event
{
public static const RESTART:String = "restart";
public static const START:String = "start";
public static const MAINMENU:String = "mainmenu";
public static const OPTIONS:String = "options";
public static const STOPMUSIC:String = "stopmusic"
public function NavigationEvent( type:String, bubbles:Boolean = false, cancelable:Boolean = false )
{
super( type, bubbles, cancelable );
}
public override function clone():Event
{
return new NavigationEvent( type, bubbles, cancelable );
}
public override function toString():String
{
return formatToString( "NavigationEvent", "type", "bubbles", "cancelable", "eventPhase" );
}
}
}
Options Screen Class
package {
import flash.display.MovieClip;
import flash.display.SimpleButton;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.ui.Mouse;
import flash.net.SharedObject;
import flash.events.Event;
public class OptionScreen extends MovieClip {
public var mainMenuButton:SimpleButton;
private var new_Volume:Volume;
public var myEvent:Event;
public function OptionScreen() {
Mouse.show();
new_Volume = new Volume();
addChild(new_Volume);
mainMenuButtonOptions.addEventListener(MouseEvent.CLICK, onClickMainMenu,false,0,true);
}
public function onClickMainMenu(mouseEvent:MouseEvent):void
{
dispatchEvent( new NavigationEvent(NavigationEvent.MAINMENU));
}
}
}
Finally....Title Screen Class
package
{
import flash.display.MovieClip;
import flash.display.SimpleButton;
import flash.events.MouseEvent;
import flash.ui.Mouse;
public class TitleScreen extends MovieClip
{
public var startButton:SimpleButton;
public function TitleScreen()
{
Mouse.show();
startButton.addEventListener( MouseEvent.CLICK, onClickStart,false,0,true );
titleOptionsButton.addEventListener( MouseEvent.CLICK, onClickOptions,false,0,true );
}
public function onClickStart( event:MouseEvent ):void
{
dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
}
public function onClickOptions( event:MouseEvent ):void
{
dispatchEvent( new NavigationEvent( NavigationEvent.OPTIONS ) );
}
}
}

You can change the volume on individual SoundChannels, or globally on the SoundMixer class. Your current implementation is the former, and defined every time you instantiate your class.
See Adobe's Controlling sound volume and panning.
You can also set global volume and pan values for all sounds at once
using the soundTransform property of the SoundMixer class, as the
following example shows:
SoundMixer.soundTransform = new SoundTransform(1, -1);
You could also store your volume and reference it as a global/static variable.
public static var soundLevel:Number = 100;
public function changeVolume(event:Event):void {
soundLevel = 1+Math.round(slider.x)/100;
channel.soundTransform=new SoundTransform(soundVolume);
}
This way, any slider object changes the same variable. Of course, then you're left with needing to update N number of soundObjects to the new soundLevel, which is why using the SoundMixer is a better solution.
No. The new instance of Volume class doesn't overwrite the SoundChannel previously created. However, because Volume is a DisplayObject, if its only reference was on the stage, and starting your game takes to you a different frame, the old Volume object would be removed from the DisplayList and could be garbage collected.
Definitely sounds like you're moving between frames, in which case the older Volume object would be removed from DisplayList and GC'ed. This comes up a lot, but I'll reiterate: don't use stage frames. There are better (less headache inducing) ways of programming what you need.
Your choice of Nav events is discretional. You could just as easily write a nav class which handles all input events as they come from the system, and appropriately call your methods. Personally, I don't like Flash style events. There's often other data I want to pass to my methods apart from the usual variable suspects, thereby leading to clutter induced by custom events. You could also clear that up with global state tracking, and simply reference your current state from your generic methods to determine their response to any given nav event.

Related

Actionscript making intro of a game

I'm making a game in full classes. so the timeline is empty. But I want to use another scene for the game intro, after the intro, it will proceed to the main menu of the game which I have created. Anyone got an idea? I haven't found any since a week ago... I don't really know about how to operate scenes from code in classes. Please help. Thanks!
Here is the main code :
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.MovieClip;
public class Main extends Sprite
{
public var field:Array;
//CALL EVERY CLASS
public var _money:Money = new Money();
public var _gold:Gold;
public var _hero:Hero;
public var _enemy:Enemy;
private var _pause:MovieClip = new Pause();
private var pauseLayer:MovieClip = new PauseLayer();
private var ts:TitleScreen;
public function Main():void
{
ts = new TitleScreen();
addChild(ts);
}
//GAME FUNCTION
public function onStage():void
{
_hero = new Hero(this);
_enemy = new Enemy(this);
_gold = new Gold(this);
setupField();
_gold.goldSet(stage);
_money.addText(stage);
_hero.displayHero(stage);
_enemy.displayEnemy(stage);
setPause();
_pause.addEventListener(MouseEvent.CLICK, pauseGame);
}
private function setPause():void
{
addChild(_pause);
_pause.x = 620;
_pause.y = 50;
_pause.buttonMode = true;
}
private function pauseGame (e:MouseEvent):void
{
stage.frameRate = 0;
addChild(pauseLayer);
pauseLayer.alpha = 0.5;
pauseLayer.parent.setChildIndex(pauseLayer,numChildren-1);
}
//SET UP FIELD ARRAY
private function setupField():void
{
var fieldSprite:Sprite=new Sprite();
addChild(fieldSprite);
fieldSprite.graphics.lineStyle(4);
field=new Array();
for (var a:int=0; a<6; a++)
{
field[a]=new Array();
for (var b:int=0; b<10; b++)
{
field[a][b]=0;
}
}
//DRAW FIELD
for (var i:int=0; i<5; i++)
{
for (var j:int=0; j<9; j++)
{
fieldSprite.graphics.drawRect(75+65*j,50+75*i,65,75);
}
}
}
}
}
Titlescreen class :
package
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.display.Sprite;
public class TitleScreen extends Sprite
{
private var playBtn:MovieClip = new Resume();
public function TitleScreen()
{
playBtn.x = 50;
playBtn.y = 50;
playBtn.addEventListener(MouseEvent.CLICK, Play);
}
private function Play(e:MouseEvent):void
{
trace("a");
}
}
}
The most simple way would be using wrapper Sprites to hold each set of objects you probably want to be available as whole, say main menu, upgrades, storyline, etc etc. Then you just shift them in and out of display list to display corresponding "scene" with your Main class responsible of transition flow. But to do this you need to shift your game core functionality out of Main class into say Game class. That's how I have done the same thing in my game:
public class Main extends MovieClip {
private var so:SharedObject;
private var ui:UserInterface;
private var ts:TitleScreen;
private function init(e:Event = null):void
{
ui = new UserInterface();
ts = new TitleScreen();
ts.newButtonClicked = newGame;
ts.loadButtonClicked = loadGame;
ui.gotoMapBehavior = wentToMap;
addChild(ts);
}
Here, UserInterface is a class that has gaming logic inside, and TitleScreen is a main menu class. The functions are callbacks in Main:
private function newGame():void {
removeChild(ts); // hide title
if (!contains(ui)) addChild(ui);
SoundManager.playMusic(SoundManager.MUSIC_LEVELSELECT);
}
private function loadGame():void {
newGame();
ui.loadBattle(); // this should make UI load the battle from shared object (either supplied or received by itself)
}
private function wentToMap():void {
// we have just executed "go to map" from UI
removeChild(ui);
addChild(ts);
checkSO();
SoundManager.playMusic(SoundManager.MUSIC_INTRO);
}
The actual gaming logic does not interact with Main at all, except for shared object which is common for the entire project, but the link is received normally via SharedObject.getLocal(someName). The code is ugly, but could do for starters.
Save your game as SWF and make another project with timeline-animated intro. When the intro ends, make your project to load your game. Loader class can load other swf files. So, you don't need to edit your game classes.

AS3 - Having trouble with a basic game class

So I am creating a space shooter game. My document class is Engine and it looks like this:
package Classes
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
public class Engine extends MovieClip
{
private var startMenu:StartMenu;
private var numberOfStars:int = 80;
public static var enemyList:Array = new Array();
private var spaceShip:Ship;
private var hud:HUD;
public function Engine()
{
startMenu = new StartMenu();
stage.addChild(startMenu);
startMenu.x = (stage.stageWidth / 2);
startMenu.y = (stage.stageHeight / 2);
}
private function startGame()
{
stage.removeChild(startMenu)
spaceShip = new Ship(stage);
stage.addChild(spaceShip);
spaceShip.x = (stage.stageWidth / 2);
spaceShip.y = (stage.stageHeight / 2);
spaceShip.addEventListener("hit", shipHit);
hud = new HUD(stage); //create the HUD
stage.addChild(hud); //and display it.
for (var i:int = 0; i < numberOfStars; i++)
{
stage.addChildAt(new Star(stage), 1);
}
addEventListener(Event.ENTER_FRAME, createFighter);
}
}
So as you can see I am calling on another class called StartMenu. This is where I am having trouble: Here is the code (Or lack there of)
package Classes
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.*;
public class StartMenu extends MovieClip
{
public function StartMenu()
{
button1.addEventListener(MouseEvent.CLICK, buttonClicked);
}
private function buttonClicked(e:MouseEvent)
{
}
}
}
(Ignore the indentation errors, it is correct in the real code)
Okay so imagine a button being displayed on the screen. This button is part of the StartMenu Class and is listening for a MouseEvent.CLICK.
Once the button is clicked I need to somehow travel back to the Engine class and call the function startGame() , but I can't just do Engine.startGame() , I have tried setting the function to a public function, and I have tried setting the function to a public static function. no luck. HELP PLEASE?? Any method will be fine, I just need a way for this class to go to the startGame function once the button is clicked!
Probably the quickest way to do this is to add an Engine variable into the StartMenu class and pass the engine through the start menu's constructor. Here's a short code sample:
StartMenu
public class StartMenu extends MovieClip
{
private var _engine:Engine // add a new variable to the start menu class
public function StartMenu(engine:Engine) // add a new parameter to the constructor
{
_engine = engine; // set the variable to the value passed through the constructor
button1.addEventListener(MouseEvent.CLICK, buttonClicked);
}
private function buttonClicked(e:MouseEvent)
{
_engine.startGame()
}
}
Engine
public function Engine()
{
startMenu = new StartMenu(this);
// pass through the current instance of engine using the this keyword
...
}
public function startGame() // change private to public
{
...
}
I hope that helps
In your Engine.as class, you can put :
public static var instance:Engine;
public static function getInstance():Engine
{
return instance as Engine;
}
and in constructor of engine class put :
instance = this;
now you can use instace of Engine class and all the public functions and variables anywhere in your project by :
Engine.getInstance().startGame();
It can help you.
There are two types of solving such a case. One is using parent reference or specific reference to call a certain function, as Ethan Worley andwered, the other is using a customizable public clicker setter like this:
public class StartMenu extends MovieClip
{
private var button1:MovieClip; // or whatever type your button is
private var startGameFunction:Function;
public function StartMenu()
{
// some initialization code if needed, including allocating button1
startGameFunction=null;
button1.addEventListener(MouseEvent.CLICK, buttonClicked);
}
public function set startGameClicked(value:Function):void {
if (value==startGameFunction) return; // nothing to set
startGameFunction=value;
}
private function buttonClicked(e:MouseEvent)
{
if (startGameFunction) startGameFunction(); // if there's a function assigned, call it
}
}
Engine class:
public function Engine()
{
startMenu = new StartMenu();
startMenu.startGameFunction=this.startGame;
// no "()" here, as we are giving a function reference
...
}
public function startGame() // change private to public
{
...
}
I am a bit surprised that no one mentioned an Events based approach yet. That's what I would have used for such a requirement, since I don't really find the idea of passing an entire class instance for just a function call to be that appealing (that would mean that I may be a bit biased towards this approach so please feel free to point out the drawbacks it has, if any).
Inside your Engine class:
public function Engine()
{
startMenu = new StartMenu();
startMenu.addEventListner('StartGame', startGame);
stage.addChild(startMenu);
..
}
private function startGame(e:Event)
{
startMenu.removeEventListner('StartGame', startGame);
..
}
Inside your StartMenu class:
private function buttonClicked(e:MouseEvent)
{
this.dispatchEvent(new Event('StartGame'));
..
}

Error #1009 utilizing event listener in external class file

(New to AS3/Flash so go easy on me if I'm oblivious to something...)
Trying to utilize external class files to create a continuous scrolling background image. I got it to work by putting it in the document class file, but trying to put it in its own external class file and calling it from the document class file brings up the error in my title.
Document Class File:
package {
import flash.display.MovieClip;
import org.masteringmoneybasics.piggy._class_BG
public class Main extends MovieClip {
public function Main() {
//Create instance of background class
new _class_BG();
}
}
}
External Class File:
package org.masteringmoneybasics.piggy {
import flash.display.*
import flash.events.Event
import flash.display.Bitmap;
import flash.display.BitmapData;
public class _class_BG{
//BG Variables
var scrollSpeed:uint = 6;
var bgLeft:Bitmap
var bgRight:Bitmap
[Embed(source="../../../assets/side_of_mountain.png")]
private var bgImage:Class;
public function _class_BG() {
//This adds two instances of the background to the stage
bgLeft = new bgImage();
bgRight = new bgImage();
bgLeft.height = 500;
bgRight.height = bgLeft.height;
bgLeft.width = 1300;
bgRight.width = bgLeft.width;
bgLeft.x = 0;
bgRight.x = bgLeft.width;
addChild(bgLeft);
addChild(bgRight);
//Adds an event lsitener to the stage
stage.addEventListener(Event.ENTER_FRAME, moveScroll); //<<<<<< ERROR HERE
}
public function moveScroll(e:Event):void{
bgLeft.x -= scrollSpeed;
bgRight.x -= scrollSpeed;
if(bgLeft.x < -bgLeft.width){
bgLeft.x = bgRight.x + bgRight.width;
}else if(bgRight.x < -bgRight.width){
bgRight.x = bgLeft.x + bgLeft.width;
}
}
}
}
If I remove the stage. reference in the event listener, it runs without errors but the images don't appear on the stage like they are supposed to.
What am I doing wrong?
You tried to In Main class external class initialized in stage. In fact, stage not arrived. see a your _class_BG, addChild() is a little wrong. because you not check Main class, perfectly added stage.
FlashBuilder this problem must be careful. first added to stage on Main class after external class(related DisplayObject) fully loaded or initialized.
refer a following code.
In _class_BG Class Carefully addEventListener(Event.ADDED_TO_STAGE,init);
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
var sp:_class_BG = new _class_BG();
addChild(sp);
}
}
}
package {
import flash.display.*;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
public class _class_BG extends Sprite {
//BG Variables
private var scrollSpeed:uint = 6;
private var bgLeft:Bitmap
private var bgRight:Bitmap
[Embed(source="../asset/myTestImage.png")]
private var bgImage:Class;
public function _class_BG()
{
if(!stage)
addEventListener(Event.ADDED_TO_STAGE, init);
else
init();
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
//This adds two instances of the background to the stage
bgLeft = new bgImage();
bgRight = new bgImage();
bgLeft.height = 500;
bgRight.height = bgLeft.height;
bgLeft.width = 1300;
bgRight.width = bgLeft.width;
bgLeft.x = 0;
bgRight.x = bgLeft.width;
addChild(bgLeft);
addChild(bgRight);
//Adds an event lsitener to the stage
stage.addEventListener(Event.ENTER_FRAME, moveScroll);
}
public function moveScroll(e:Event):void{
bgLeft.x -= scrollSpeed;
bgRight.x -= scrollSpeed;
if(bgLeft.x < -bgLeft.width){
bgLeft.x = bgRight.x + bgRight.width;
}else if(bgRight.x < -bgRight.width){
bgRight.x = bgLeft.x + bgLeft.width;
}
}
}
}
Only the top level Displayable has access to scene. What's more, it is read-only, which means that you cannot pass it through the original stage attribute.
The simplest way to go would be... I don't know, perhaps passing the stage to your constructor? The relavant parts of the constructor would be:
public function _class_BG(myStage : Stage) {
// SNIP
//Adds an event lsitener to the stage
myStage.addEventListener(Event.ENTER_FRAME, moveScroll);
}
And in Main (in which you do have acces to the stage):
public class Main extends MovieClip {
public function Main() {
//Create instance of background class
addChild(new _class_BG(stage));
}
}
You should think about some other means of building your logic, passing stage around will get hairy quickly. But it should work.
EDIT:
stage -> myStage; also, addChild in Main().

Adding eventlisteners in main document class for external classes

I have a small project I'm trying to help learn as3. It is a variation from the book Foundation Game Design with Actionscript 3.0. I am using an fla only to have a document class. All art is loaded within the as files. In the book, he just put all the code in the document class, I followed along and it worked as expected. I am trying to break out the code into separate classes to get a handle on OOP. One class makes a background - Background.as, one makes a character - Character.as, and one makes a button, which I instantiate 6 times for 6 different buttons - GameButton.as. And of course there is GameWorld.as which is the document class. Everything loads and shows up as expected. However when I try and add an eventListener for the buttons, I don't get any response. I have tried putting the eventListener in the GameButton.as and also tried it in the GameWorld.as neither of which has worked. Also I pass a reference to the stage when instantiating the various classes, because when I tried to addChild in the GameWorld.as, nothing would show up. I searched the site and found something similar, but it didn't seem to help. Thank you in advance for any advice you my have. Here is the code:
GameWorld.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.DisplayObject
import flash.events.MouseEvent;
import GameButton;
import Character;
import Background;
[SWR(width = "550", height = "400", backgroundColor = "#FFFFFF", frameRate = "60")]
public class GameWorld extends Sprite
{
//public variables
//Background
public var gameBackground:Background;
//Character
public var catCharacter:Character;
//Buttons
public var upButton:GameButton;
public var downButton:GameButton;
public var growButton:GameButton;
public var shrinkButton:GameButton;
public var vanishButton:GameButton;
public var spinButton:GameButton;
public function GameWorld ()
{
//Add the background to the stage
gameBackground = new Background("../images/background.png", stage);
//Add the character(s) to the stage
catCharacter = new Character("../images/character.png", stage);
//Set initial character position
catCharacter.CharacterPos(225, 150);
//Add the buttons to the stage
upButton = new GameButton("../images/up.png", stage, 25, 25);
downButton = new GameButton("../images/down.png", stage, 25, 85);
growButton = new GameButton("../images/grow.png", stage, 25, 145);
shrinkButton = new GameButton("../images/shrink.png", stage, 425, 25);
vanishButton = new GameButton("../images/vanish.png", stage, 425, 85);
spinButton = new GameButton("../images/spin.png", stage, 425, 145);
//Button event handlers
upButton.addEventListener(MouseEvent.CLICK, upButtonHandler);
}
public function upButtonHandler(event:MouseEvent)
{
trace("You clicked the up button!");
catCharacter.CharacterMove(15);
}
}
}
GameButton.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.MouseEvent;
public class GameButton extends Sprite
{
//public variables
public var stageRef:Stage;
public var urlRequest:URLRequest;
public var gameButtonLoader:Loader;
public var gameButtonSprite:Sprite;
//Constructor
public function GameButton (urlRequest:String, stageRef:Stage, xPos:Number, yPos:Number)
{
this.stageRef = stageRef
this.urlRequest = new URLRequest();
gameButtonLoader = new Loader();
gameButtonSprite = new Sprite();
this.urlRequest.url = urlRequest;
gameButtonLoader.load(this.urlRequest);
gameButtonSprite.addChild(gameButtonLoader);
this.stageRef.addChild(gameButtonSprite);
gameButtonSprite.buttonMode = true;
gameButtonSprite.x = xPos;
gameButtonSprite.y = yPos;
}
}
}
Character.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
public class Character
{
//private variables
private var stageRef:Stage;
private var urlRequest:URLRequest;
private var characterLoader:Loader;
private var characterSprite:Sprite;
//public variables
public var character_x_pos:Number;
public var character_y_pos:Number;
//Constructor
public function Character (urlRequest:String, stageRef:Stage)
{
this.stageRef = stageRef;
this.urlRequest = new URLRequest();
characterLoader = new Loader();
characterSprite = new Sprite();
this.urlRequest.url = urlRequest;
characterLoader.load (this.urlRequest);
characterSprite.addChild (characterLoader);
this.stageRef.addChild (characterSprite);
characterSprite.mouseEnabled = false;
}
//Set the position of the character
public function CharacterPos(xPos:Number, yPos:Number):void
{
characterSprite.x = xPos;
characterSprite.y = yPos;
}
//Move the position of the character
public function CharacterMove( yPos:Number):void
{
characterSprite.y -= yPos;
}
}
}
Background.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
public class Background
{
//Private variables
private var stageRef:Stage;
private var urlRequest:URLRequest;
private var backgroundLoader:Loader;
private var backgroundSprite:Sprite;
//Constructor
public function Background (urlRequest:String, stageRef:Stage)
{
this.stageRef = stageRef;
this.urlRequest = new URLRequest();
backgroundLoader = new Loader();
backgroundSprite = new Sprite();
this.urlRequest.url = urlRequest;
backgroundLoader.load (this.urlRequest);
backgroundSprite.addChild (backgroundLoader);
this.stageRef.addChild (backgroundSprite);
backgroundSprite.mouseEnabled = false;
}
}
}
All art is loaded within the as files.
This is not an approach I recommend. There's a reason God gave us the Flash IDE--and it's not to write code! Any time you're spending on layout and viduals in code is just wasted, unless you have an actual requirement to change the visuals at runtime. The fact that your paths are all hard-coded suggests that you don't have that requirement.
So let's step back and imagine that you have a Symbol that contains 6 Symbols that you've created as just Flash buttons (when you select Button as the Symbol type). These will be SimpleButtons, but in the Class below we're just going to type them as DisplayObject. The Class doesn't care what they are, but using Simplebutton gives them up, over, down and hit states that require no code.
Note that the below assumes you have "automatically declare stage instances" off, which is IMO the best way to do things.
package view {
public class NavBar extends Sprite {
//because you put these on stage in the Symbol, they will be available in the constructor
public var upButton:DisplayObject;
public var downButton:DisplayObject;
public var growButton:DisplayObject;
public var shrinkButton:DisplayObject;
public var rotateButton:DisplayObject;
public var vanishButton:DisplayObject;
//makes it easier to do the same setup on all buttons
protected var allButtons:Vector.<DisplayObject> = <DisplayObject>([upButton, downButton, growButton, shrinkButton, rotateButton, vanishButton]);
public function NavBar() {
super();
for each (var btn:DisplayObject in allButtons) {
btn.buttonMode = true;
btn.mouseChildren = false;
btn.addEventListener(MouseEvent.CLICK, onButtonClick);
}
}
protected function onButtonClick(e:MouseEvent):void {
switch (e.target) {
case upButton:
dispatchEvent(new CharacterEvent(CharacterEvent.UP));
break;
case downButton:
dispatchEvent(new CharacterEvent(CharacterEvent.DOWN));
break;
case growButton:
dispatchEvent(new CharacterEvent(CharacterEvent.GROW));
break;
case shrinkButton:
dispatchEvent(new CharacterEvent(CharacterEvent.SHRINK));
break;
case rotateButton:
dispatchEvent(new CharacterEvent(CharacterEvent.ROTATE));
break;
case vanishButton:
dispatchEvent(new CharacterEvent(CharacterEvent.VANISH));
break;
default:
break;
}
}
}
}
Note that there's zero layout code. This code is dependent on a custom Event Class. I'm going to write that Event Class so that it always bubbles. That way, it can be dispatched anywhere on the display list and received at the top level:
package control {
class CharacterEvent extends Event {
public static var UP:String = 'characterUp';
public static var DOWN:String = 'characterDown';
public static var GROW:String = 'characterGrow';
public static var SHRINK:String = 'characterShrink';
public static var ROTATE:String = 'characterRotate';
public static var VANISH:String = 'characterVanish';
public function CharacterEvent(type:String) {
super(type, true, true);//always bubbles
}
public function clone():Event {
return new CharacterEvent(type);
}
}
}
Now, if you want to manually handle instantiation of the Symbol that has view.NavBar as its base class, it will look like this:
package {
public var navBar:NavBar;
class GameWorld {
public function GameWorld() {
try {
var navClass:Class = getDefinitionByName('NavBarSymbol') as Class;
} catch (e:Error) {
trace('You need to have a Library symbol called NavBarSymbol');
}
if (navClass) {
navBar = new navClass() as NavBar;
//unnecessary layout code here
//Note that this is NOT the responsibility of the child Class!
addChild(navBar);
}
//instantiate character
...
//by listening to the whole Document, you can add other things that
//dispatch Character events on the display list, like a keyboard listener
addEventListener(CharacterEvent.UP, moveCharacterUp);
//listeners for the rest of the character events...
}
public function moveCharacterUp(e:CharacterEvent):void {
//character move logic
}
//other handlers
}
}
Personally, I'd just add the navBar to the stage, and then there's no need to manage it at all (not even reference it with a variable), simply add the event listeners for the various character events.
The root of your problem doesn't seem to be the character code. However, I'm going to give you a few "best practice" pointers about it.
The convention in AS3 is for Class members (properties and methods) to be camel case starting with a lower case letter. So, characterPos() and characterMove().
Your Class already contains character in the name, so really these should just be pos() and move() (though there's no need now to shorten position()).
The only thing your child Classes are doing with their references to the parent are adding themselves. They don't need and shouldn't have a reference to the parent for this purpose. It is the parent's responsibility to add the Children (or the responsibility of the Flash Player if you use the stage).
That said, you could give your Character a reference to the parent Class typed as IEventDispatcher and allow the Character to listen to this channel. This concept is called an event bus.
Note that the reason that so many people do what you're doing is that Adobe failed to document how to properly use OOP with the timeline. Unfortunately, by the time a few of us started documenting that around late 2009/early 2010, the damage was done and everyone assumed that if you wanted to write good code you had to pretend the timeline and stage didn't exist.
I know I've covered a lot of ground, and probably most of what I said directly contradicts what you thought you knew, so please don't hesitate to ask any questions you might have.

Initialize Assets after Preload

Whenever I export the .swf file of my Flash game, I am receiving "TypeError: Error #1009: Cannot access a property or method of a null object reference.", along with a Runtime Shared Library Preloading Warning for my preloader. I have my timeline organized so that the first and third frames are both empty along with a stop(); command in the Actions layer. The second frame contains a single MovieClip that contains all of my exported assets, which are going to be initialized in the third frame of the timeline. None of my assets, except for the preloader, are exported in the first frame. What changes should I make to my Document Class for it to initialize the assets in the third frame?
Document Class:
package com.gameEngine.documentClass
{
import flash.events.*;
import flash.display.*;
import flash.geom.Point;
import com.gameEngine.assetHolders.*;
import com.gameEngine.assetHolders.Levels.*;
public class Document extends MovieClip
{
private static var _document:Document;
private var preloader:Preloader;
public var mcMain:Player;
public var restartButton:RestartButton;
public var spawnArea:SpawnArea;
public var level_1:Level_1;
public var level_2:Level_2;
public var level_3:Level_3;
public function Document()
{
addEventListener(Event.ADDED_TO_STAGE, init);
_document = this;
preloader = new Preloader(390, this.loaderInfo);
this.addChild(preloader);
preloader.addEventListener("loadComplete", loadAssets);
preloader.addEventListener("preloaderFinished", showLogo);
mcMain = new Player(this);
restartButton = new RestartButton(this);
spawnArea = new SpawnArea();
level_1 = new Level_1(this);
level_2 = new Level_2(this);
level_3 = new Level_3(this);
this.addChild(restartButton);
this.addChild(spawnArea);
this.preloader.x = 400;
this.preloader.y = 250;
restartButton.x = 822.95;
restartButton.y = 19;
spawnArea.x = 400;
spawnArea.y = 250;
trace ("Document Class Initialized");
// constructor code
}
public static function getInstance():Document
{
return _document;
}
private function loadAssets(event:Event):void
{
this.play();
}
private function showLogo(event:Event):void
{
this.removeChild(preloader);
}
public function init(event:Event)
{
if (stage.contains(spawnArea))
{
addChild(mcMain);
}
mcMain.x = spawnArea.x;
mcMain.y = spawnArea.y;
}
}
}
Preloader Class:
package com.gameEngine.assetHolders
{
import com.gameEngine.documentClass.*;
import flash.display.*;
import flash.events.*;
public class Preloader extends MovieClip
{
private var fullWidth:Number;
public var loaderInfo:LoaderInfo;
public function Preloader(fullWidth:Number = 0, loaderInfo:LoaderInfo = null)
{
this.fullWidth = fullWidth;
this.loaderInfo = loaderInfo;
addEventListener(Event.ENTER_FRAME, checkLoad);
}
private function checkLoad (event:Event):void
{
if (loaderInfo.bytesLoaded == loaderInfo.bytesTotal && loaderInfo.bytesTotal != 0)
{
dispatchEvent(new Event("loadComplete"));
phaseOut();
}
updateLoader(loaderInfo.bytesLoaded / loaderInfo.bytesTotal);
}
private function updateLoader(num:Number):void
{
progressBar.width = num * fullWidth;
}
private function phaseOut():void
{
removeEventListener(Event.ENTER_FRAME, checkLoad);
progressBar.gotoAndPlay(2);
if (progressBar.currentFrame == progressBar.totalFrames)
{
phaseComplete();
}
}
private function phaseComplete() : void
{
dispatchEvent(new Event("preloaderFinished"));
}
}
}
You have a lot of race conditions going on here. Many of these events could occur at relatively random times in relation to one another . . . you have to think asynchronously. That is, there can be no assumption that any object exists. E.g., in Document.init(), you check is if the spawnArea exists, but it is almost guaranteed not to at that point, and you never check for it again.
Without making any specific changes, I can recommend a generic solution. For any object (objB) you want loaded after another object (objA) is loaded, have objB created in the objA's ADDED_TO_STAGE handler. A simple example would be:
var objA:Whatever;
var objB:WhateverElse;
[...]
objA = new Whatever();
objA.addEventListener(Event.ADDED_TO_STAGE, objAAddedHnd);
[...]
public function objAAddedHnd(event:Event)
{
// remove the event, if no longer needed:
objA.removeEventListener(Event.ADDED_TO_STAGE, objAAddedHnd);
objB = new WhateverElse();
objB.addEventListener(Event.ADDED_TO_STAGE, objBAddedHnd);
}
[...]
public function objBAddedHnd(event:Event)
{
// remove the event, if no longer needed:
objB.removeEventListener(Event.ADDED_TO_STAGE, objBAddedHnd);
// and so on . . .
}
At this point, it shows that you would need to plan the timeline of object creation.