0
votes

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 ) );
        }


    }
}
1

1 Answers

1
votes

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.

  1. 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.

  2. 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.

  3. 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.