Advertisement

Create a Stylish 3D Interface with Papervision3D: Effects and Submenus

by

In the second and final part of this tutorial, we'll complete our 3D menu interface with special effects and submenus, including 3D components like radio buttons.

Continued from Part 1: Structure...


Also available in this series:

  1. Create a Stylish 3D Interface with Papervision3D: Structure
  2. Create a Stylish 3D Interface with Papervision3D: Effects and Submenus

Step 38: Creating a Custom Distortion Effect in 3D

Go back to the preview link and stare at MainMenu3D for a few seconds to see this effect in action. This class will apply distortion to all the DisplayObject3Ds inside MainMenu3D. Its list of responsibilities are:

  1. generate a new custom Bitmap drawing to use as DisplacementMapFilter for every timer loop
  2. add a random Timer that loops and applies the filter to the assigned array of ViewportLayers
  3. play a sound every time the effect is applied
  4. provide external access to activate and deactivate the effect

This class will do its work without the need to be added to the display list. It also doesn't extend any other class.

Create a new class and name it DistortionEffect3D and save it with the rest of the classes. Paste the code below into it:

 
package   
{ 
    import com.greensock.TweenLite; 
    import flash.display.Bitmap; 
    import flash.display.BitmapData; 
    import flash.display.BitmapDataChannel; 
    import flash.display.Shape; 
    import flash.events.TimerEvent; 
    import flash.filters.BitmapFilter; 
    import flash.filters.BlurFilter; 
    import flash.filters.DisplacementMapFilter; 
    import flash.filters.DisplacementMapFilterMode; 
    import flash.geom.Point; 
    import flash.media.Sound; 
    import flash.media.SoundTransform; 
    import flash.utils.Timer; 
    import org.papervision3d.view.layer.ViewportLayer; 
 
    public class DistortionEffect3D 
    { 
        private var _vieportLayerArray:Array; //assigned the array of ViewportLayers passed in from the constructor 
        private var _map:BitmapData; //BitmapData that is drawn the Shape object 
        private var mapBitmap:Bitmap; //Bitmap used as mapBitmap for the DisplacementMapFilter 
        private var _filter:DisplacementMapFilter; //the filter used for the effect 
        private var _timer:Timer; //a timer between 5 and 20 seconds to generate the effect 
        private var _active:Boolean; //turns the effect on or off 
        private var _sound:Sound; //sound effect used every time the filter is applied 
        private var _shape:Shape; //used as a quick drawing tool to create horizontal lines 
         
        public function DistortionEffect3D ($vieportLayerArray:Array)  
        { 
            //start here 
       } 
    } 
}

Again, see the comments next to each property for a brief description.


Step 39: DistortionEffect3D Constructor

Add the code below inside the constructor method:

 
_vieportLayerArray = $vieportLayerArray; 
_shape = new Shape(); 
 
var customLoader:CustomLoader = CustomLoader.getInstance (); 
_sound = customLoader.getSound ('staticNoise3'); 
 
createDistortion ();

Here, the required properties are assigned and saved for reference later then createDistortion () is called.


Step 40: Effect Loop Part 1 - Preparing the Tools

This method will also be called every time the random Timer completes. Add the code below after the constructor:

 
protected function createDistortion ():void  
{ 
    _shape.graphics.clear (); 
    _shape.graphics.beginFill(0x888888); 
     
    var randomColor:uint = 0; 
     
    var divideCount:uint = uint (400 / Main.getRandomInRange (1, 5)); //assigns a slightly different drawing every time 
     
    for (var i:uint = 0; i < divideCount; i++) //draw between 80 and 400 lines on the Shape object  
    { 
        _shape.graphics.drawRect (0, 400 / divideCount * i, 400, 400 * divideCount); //adjust each line's thickness to have the same coverage 
        randomColor = randomColor == 0 ? 0xFFFFFF : 0; //if the color is white, make it black 
        _shape.graphics.beginFill (randomColor); //apply the color 
    } 
    _shape.graphics.endFill(); 
     
    _map = new BitmapData(600,600, false, 0); //make sure the distortion effect is big enough to cover all of MainMenu3D 
    _map.draw(_shape); 
     
    var blurFilter:BlurFilter = new BlurFilter (15,15); //apply blur so the effect isn't too jagged 
    _map.applyFilter(_map, _map.rect, new Point(), blurFilter); //apply the filter to the BitmapData 
     
    mapBitmap = new Bitmap(_map); //make the bitmap 
     
    _filter = new DisplacementMapFilter();  
    _filter.mapBitmap = _map; //use the image for the DisplacementMapFilter 
    _filter.componentX = BitmapDataChannel.RED; 
    _filter.componentY = BitmapDataChannel.BLUE; 
    _filter.scaleX = 0; 
    _filter.scaleY = 0; 
    _filter.mode = DisplacementMapFilterMode.CLAMP; 
     
    _timer = new Timer (Main.getRandomInRange (5000, 20000), 1); //create a new Timer 
    _timer.addEventListener (TimerEvent.TIMER_COMPLETE, onTimerComplete); 
    _timer.start (); 
}

Every time this method is called, a new BitmapData image is constructed from the Shape object. That BitmapData is then applied to a Bitmap which is then used as the base image for the distortion effect. At the end of the method, the Timer is assigned a different duration anywhere between 5 and 20 seconds and repeats only once.


Step 41: Effect Loop Part 2 - Application

Once the Timer completes, TweenLite is then used to apply the effect, play the sound, and when finished, call the createDistortion () method to start all over again. Add the code below:

 
private function onTimerComplete(e:TimerEvent):void  
{             
    if (_active) //if activated 
    { 
        var tempScaleX:Number = Main.getRandomInRange (50, 100); //random between 50 and 100 - also helps makes the filter image unique every time it is recreated 
     
        //use TweenLite to tween scaleX and scaleY of the DisplacementMapFilter 
        TweenLite.to  
        ( 
        _filter, //the target of the tween 
        .4, //duration of the tween 
        { 
           scaleX: tempScaleX, //property to tween from 0 to tempScaleX's value 
           scaleY: tempScaleX / 4, //property to tween 1/4 the strength of tempScaleX 
           onStart:playSound, //method called when the tween starts 
            onStartParams:[tempScaleX], //parameter for the playSound method 
            onUpdate:assignFilter, //function called every time the tween updates 
            overwrite:false //will get overwritten by another tween for the same target 
        }  
        ); 
         
        //use TweenLite to fade out the tween after 0.4 seconds 
        TweenLite.to  
        ( 
        _filter,  
        .4,  
        { 
           scaleX:0,  
            scaleY: 0,  
            onUpdate:assignFilter, 
            delay:.4, //let the previous tween finish before fading it out 
            overwrite:false,  
            onComplete:createDistortion //when finished, call createDistortion () to start all over 
        }  
        ); 
                 
        //clear the timer 
        _timer.stop (); 
        _timer.removeEventListener (TimerEvent.TIMER_COMPLETE, onTimerComplete); 
        _timer = null; 
    } 
    else      
    //if deactivated     
    { 
        _timer.stop (); 
        _timer.removeEventListener (TimerEvent.TIMER_COMPLETE, onTimerComplete); 
        _timer = null; 
         
        createDistortion (); //start all over 
    }     
}

If the _active property is set to true, the filter is applied, and if not, the loop is just started again skipping the effect. Let's add the 2 helper methods next:

 
private function playSound($scaleX:Number):void 
{ 
    var transform:SoundTransform = new SoundTransform ($scaleX * .01); 
    _sound.play (0, 0, transform); 
} 
 
private function assignFilter ():void 
{ 
    for each (var viewport:ViewportLayer in _vieportLayerArray) viewport.filters = [_filter]; 
}

The playSound () method plays the sound with matching volume for the intensity of the effect. $scaleX is anywhere between 50 and 100 so the sound's volume will range from 0.5 and 1. The assignFilter () method is called every time TweenLite updates and applies the transformed filter to all the ViewportLayers in _viewportLayerArray.


Step 42: External Control

As you've seen with the preview link, once a button is clicked, MainMenu3D leaves the scene and gets replaced by either the game or a submenu. When this happens, we won't need the effect until MainMenu3D is back in the scene again. The following two methods take care of this for us:

 
public function deActivate ():void { _active = false; } 
 
public function activate ():void { _active = true; }

And that concludes the DistortionEffect3D class.


Step 43: Milestone Applying Distortion to MainMenu3D

Go back to MainMenu3D's setMenuEffect () method and add the code below to it:

 
var viewportLayerArray:Array = []; 
for each (var component:UIComponent3D in this.children) 
{ 
    viewportLayerArray.push (component.interfaceLayer); 
    viewportLayerArray.push (component.textLayer); 
} 
     
_distortion = new DistortionEffect3D (viewportLayerArray);

Don't forget to uncomment the _distortion property declaration at the top of the class. All we do here is go through all the UIComponent3Ds inside MainMenu3D to access their ViewportLayers and put them all inside an array. Once all the ViewportLayers you want the distortion effect applied to have been collected, its time to instantiate DistortionEffect3D and pass in the array as the single parameter. Next, go to MainMenu3D's prepareIntro () method and add code below at the very start:

 
if (_distortion) _distortion.activate (); //if _distortion is not null, activate it

Now add the code to deactivate it when MainMenu3D is not the current menu. Go to MainMenu3D's startExit () method and add the code at the very start:

 
if (_distortion) _distortion.deActivate (); //if _distortion is not null, deactivate it

Go ahead and run the program. The sample result is linked here. You should see and hear the distortion effect repeat anywhere between 5 and 20 seconds.


Step 44: More Effects - StaticGlow3D

For now, let's focus on adding 3 more effects to improve our interface. First up, the StaticGlow3D class which will add the transparent glow for all the menus. We want our menus to look holographic and this class will provide that effect. Its responsibilities are:

  1. draw a BitmapData as the base image for the effect
  2. provide external control to animate in and out
  3. have 2 different ways to animate the effect and match how the active menu opens

Create a new class named StaticGlow3D and extend DisplayObject3D for it. Paste the code below replacing everything inside it to start:

 
package  
{ 
    import com.greensock.easing.Expo; 
    import com.greensock.TweenMax; 
    import flash.display.BitmapData; 
    import flash.display.BitmapDataChannel; 
    import flash.display.Graphics; 
    import flash.display.Sprite; 
    import flash.filters.BlurFilter; 
    import flash.geom.Point; 
    import org.papervision3d.materials.BitmapMaterial; 
    import org.papervision3d.objects.DisplayObject3D; 
    import org.papervision3d.objects.primitives.Plane; 
    import org.papervision3d.view.layer.ViewportLayer; 
    import org.papervision3d.view.Viewport3D; 
     
    public class StaticGlow3D extends DisplayObject3D  
    { 
        protected var _plane:Plane; 
        protected var _bitmapData:BitmapData; 
        protected var _alphaBitmap:BitmapData; 
        protected var _viewport:Viewport3D; 
        protected var _backgroundLayer:ViewportLayer; 
         
        public function StaticGlow3D ($name:String, $viewport:Viewport3D) 
        { 
            super ($name); 
 
            _viewport = $viewport; 
 
            init (); 
        } 
    } 
}

All the variables are declared protected for inheritance. StaticGlow3D will serve as the super class for both the BackgroundGlow3D and BackgroundNoise3D classes. Its constructor calls super () to assign the name for the DisplayObject3D. The _viewport property is then set and init () is called.


Step 45: Preparing the Visual Asset for the Effect

The init () method creates the image used for the effect. Add the code below after the constructor:

 
protected function init ():void 
{ 
    _bitmapData = new BitmapData (400, 400, true, 0); //BitmapData used as the material for the 3D Plane 
    _alphaBitmap = new BitmapData (400, 400, true, 0); //BitmapData that used to make the sides of _bitmapData fade out 
     
    var sprite:Sprite = new Sprite; //temp drawing tool 
     
    //draw a rounded rectangle 
    var g:Graphics = sprite.graphics; 
    g.beginFill (0, 1); //color is not important here since we're only copying its alpha channel 
    g.drawRoundRect (50, 50, 300, 300, 100, 100); //center to give the fade out enough room 
     
    sprite.filters = [new BlurFilter (50, 50)]; //blur the rectangle so the sides slowly fade out 
     
    _alphaBitmap.draw (sprite) //draw the Sprite into the _alphaBitmap BitmapData 
             
    g.clear (); //reuse the Graphics object to draw a new rounded rectangle 
    g.beginFill (0x6AB5FF, 1); //the color for the glow 
    g.drawRoundRect (0, 0, 400, 400, 100, 100); //draw a bigger rectangle big enough to copy all of _alphaBitmap with blur 
             
    _bitmapData.draw (sprite); //draw the new image into _bitmapData 
     
    //copy _alphaBitmap's alpha channel to apply the faded sides to _bitmapData 
    _bitmapData.copyChannel (_alphaBitmap, _alphaBitmap.rect, new Point, BitmapDataChannel.ALPHA, BitmapDataChannel.ALPHA)  
             
    var material:BitmapMaterial = new BitmapMaterial (_bitmapData); //used as BitmapMaterial 
     
    _plane = new Plane (material, 1400,1200,4,4); //create a Plane using the material 
             
    _backgroundLayer = _viewport.getChildLayer (_plane); //access the ViewportLayer property to apply filters and effects 
    _backgroundLayer.alpha = 0; //make fully invisible 
    _backgroundLayer.layerIndex = -1; //so its always in the background 
         
    addChild (_plane); //add as a child of the DisplayObject3D     
}

As you see with the comments, The 3D Plane is given a special material made out of 2 BitmapDatas. It's probably simpler if we just used a VectorShape3D object and applied blur to it, but this is fine too. You'll see how we can use this same principle later and create static effect with faded corners.


Step 46: Control and Use

Once everything has been prepared, all we have to do is externally control fading the effect in and out. Add the last 2 methods next:

 
public function flash ($sideways:Boolean = false):void 
{ 
    if ($sideways == true) 
    { 
        _backgroundLayer.scaleX = .2; //compress the image 1/5 its original width 
    } 
    else 
    { 
        _backgroundLayer.scaleY = .2; //compress the image 1/5 its original height 
    } 
     
    TweenMax.to  
    ( 
    _backgroundLayer, //target of the tween 
    .4,  
    {  
        delay:.4, //play after 0.4 seconds to synchronize with the menus 
        alpha:.2, //end value for the alpha property 
          scaleX: 1, //end value for the scaleX property - works if $sideways is true 
       scaleY:1 , //end value for the scaleY property - works for default 
       ease:Expo.easeInOut, //the type of ease used 
       overwrite:false, //cannot be overwritten 
       onComplete:TweenMax.to, //method called when the tween completes 
       onCompleteParams:[_backgroundLayer, .4, { alpha:.07 } ] //parameters passed in for the method called on onComplete 
    }  
    ); 
         
} 
         
public function fadeOut ($sideways:Boolean = false):void 
{ 
    if ($sideways == true) 
    { 
        TweenMax.to  
       ( 
       _backgroundLayer, 
       1,  
       {  
           scaleX: .2, 
             scaleY: 1, 
             ease:Expo.easeInOut,  
           overwrite:false 
        }  
        ); 
    } 
    else  
    { 
        TweenMax.to  
       ( 
       _backgroundLayer, 
       1, 
       { 
            scaleX: 1, 
           scaleY: .2, 
           ease:Expo.easeInOut, 
           overwrite:false 
        } 
       ); 
    } 
    TweenMax.to  
   ( 
   _backgroundLayer, 
   .4, 
   { 
       delay:.4, 
       alpha: 0, 
       overwrite:false 
    } 
   ); 
}

The flash () method is called before a menu appears into the scene. It checks the value of $sideways and animates based on that value. If $sideways is true, like when we go to the settings menu, the glow's scaleX property is compressed then tweened out to normal. If $sideways is left as its default value - false, like when we go to the main menu, the glow's scaleY property is compressed then tweened out to normal. The second public method, fadeOut () does the opposite and is called when a menu is closing. Only this time the image thins before fading out.


Step 47: Milestone Testing StaticGlow3D

First, go at the top of the class where properties are declared and add _staticGlow as an instance property. Go into Tester's init () method and add a call to addStaticGlow () before the call to addMainMenu (). Then add the code below after the init () method:

 
private function addStaticGlow ():void 
{ 
    _staticGlow = new StaticGlow3D ('staticglow', viewport); 
    scene.addChild (_staticGlow); 
}

Next, go inside both the addMainMenu () and the onMenuExitComplete () methods. Add a call to _staticGlow.flash () after the call to MainMenu3D's startIntro () method. Then go inside the beginning of the navigateTo () method and add a call to _staticGlow.fadeOut (). The methods should look like below:

 
private function addMainMenu ():void 
{ 
    var mm:MainMenu3D = new MainMenu3D ('menu', viewport); 
    mm.addEventListener (MainMenu3D.START_GAME_CLICKED, navigateTo); 
    mm.addEventListener (MainMenu3D.SETTINGS_CLICKED, navigateTo); 
    mm.addEventListener (MainMenu3D.ABOUT_CLICKED, navigateTo); 
    mm.addEventListener (MainMenu3D.EXIT_CLICKED, navigateTo); 
    mm.addEventListener (MainMenu3D.EXIT_COMPLETE, onMenuExitComplete); 
    scene.addChild (mm); 
     
    mm.startIntro (); 
    _staticGlow.flash (); //add this call 
} 
       
private function navigateTo(e:Event):void  
{ 
    _staticGlow.fadeOut (); //add this call 
    switch (e.type) 
    { 
        case MainMenu3D.START_GAME_CLICKED: 
        trace ('load the game and play'); 
        break; 
         
        case MainMenu3D.SETTINGS_CLICKED: 
        trace ('load settings menu'); 
        break; 
         
        case MainMenu3D.ABOUT_CLICKED: 
        trace ('load about menu'); 
        break; 
         
        case MainMenu3D.EXIT_CLICKED: 
        trace ('exit the application'); 
        break; 
    } 
} 
         
private function onMenuExitComplete (e:Event):void  
{ 
    MainMenu3D (e.target).startIntro (); 
    _staticGlow.flash (); //add this call 
}

Run the program and you should have the result shown on the link here. Alright! Next is the StaticNoise3D class.


Step 48: Applying Static Noise in PV3D

If you want to see this effect in action, go to the preview link posted above and change the Quality to best in the settings menu. This class will extend StaticGlow3D and apply some modifications. Here is the list:

  1. draw an image to use as its static noise//same as the superclass
  2. provide external control to animate in and out //same as the super class
  3. have 2 different ways to animate the effect and match how the active menu opens //same as the super class
  4. provide external access for update calls to apply random noise to the BitmapData for the effect //additional responsibility

Create a new class and name it BackgroundNoise3D, have it extend StaticGlow3D.
Copy the code below and replace the contents of the new class with it:

 
package  
{ 
    import flash.display.BitmapDataChannel; 
    import flash.geom.Point; 
    import flash.utils.getTimer; 
    import org.papervision3d.view.Viewport3D; 
     
    public class BackgroundNoise3D extends StaticGlow3D  
    { 
         
        public function BackgroundNoise3D ($name:String, $viewport:Viewport3D) 
        { 
            super ($name, $viewport); 
        } 
    } 
}

Since we're using OOP, we only need to import what we need to modify the class. The class constructor takes in the same 2 parameters and passes them both to StaticGlow3D.


Step 49: Preparation, Control and Use

As shown in the list of responsibilities, everything is pretty much the same as the super class, except for how the effect is generated and 1 additional responsibility. The update () method will take care of applying noise to the BitmapData that is used for the effect. This I think, is the smallest class in this project. Add the code below:

 
public function update ():void 
{ 
    _bitmapData.noise (getTimer (), 0, 255, 7, true); //apply noise to the BitmapData 
    _bitmapData.copyChannel (_alphaBitmap, _alphaBitmap.rect, new Point, BitmapDataChannel.ALPHA, BitmapDataChannel.ALPHA); //apply the alpha channel of _alphaBitmap 
}

You can get more information about the BitmapData.noise () method here. That's it. BackgroundNoise3D is finished! Another good example of the power of OOP.


Step 50: Milestone Applying BackgroundNoise3D

Inside Tester's init () method, add a call to addBackgroundNoise () just below the addStaticGlow method call. Copy the code below after the actual addStaticGlow () method:

 
private function addBackgroundNoise ():void 
{ 
    _backgroundNoise = new BackgroundNoise3D ('backgroundNoise', viewport); //make sure to declare as an instance variable 
    scene.addChild (_backgroundNoise); 
}

Next, it's controlled the same way as StaticGlow3D. Add the codes below according to the comment posted above each code:

 
//add after the call to _staticGlow.flash in addMainMenu () and onMenuExitComplete () methods 
_backgroundNoise.flash (); 
 
//add below the call to _staticGlow.fadeOut in navigateTo () method 
_backgroundNoise.fadeOut ();

Lastly, we need to repeatedly call its update () method in order to reflect the changes applied for the noise effect. Add the code below inside the onRenderTick () method:

 
_backgroundNoise.update ();

Don't forget to declare _backgroundNoise as an instance property of type BackgroundNoise3D at the top of the class. Now you can run the program. It should look like the link here.


Step 51: Yet Another Effect - BackgroundGlow3D

It still felt like the menu was incomplete so I thought, why not add a striped background that glows before the menus animate in. Its list of responsibilities are:

  1. draw an image to use as its striped glow //modified from the super class
  2. provide external control to animate in and out //modified from the super class
  3. play a sound every time the effect is applied //additional functionality

Create a new class and name it BackgroundGlow3D. This too will extend StaticGlow3D. Copy the code below replacing everything in the new class:

 
package  
{ 
    import com.greensock.TweenLite; 
    import flash.display.BitmapData; 
    import flash.display.BitmapDataChannel; 
    import flash.display.Graphics; 
    import flash.display.Sprite; 
    import flash.filters.BlurFilter; 
    import flash.filters.GlowFilter; 
    import flash.geom.Point; 
    import flash.media.Sound; 
    import flash.media.SoundTransform; 
    import org.papervision3d.materials.BitmapMaterial; 
    import org.papervision3d.objects.primitives.Plane; 
    import org.papervision3d.view.Viewport3D; 
 
    public class BackgroundGlow3D extends StaticGlow3D 
    { 
         
        public function BackgroundGlow3D ($name:String, $viewport:Viewport3D) 
        { 
            super ($name, $viewport); //same as the super class 
        } 
    } 
}

There are more imports involved here because of the few changes to draw the image for the effect. But the constructor remains identical.


Step 52: Preparing the Visual Asset

The dimensions are the same but this time, we're drawing lines with spaces between into the BitmapData. Add the code below after the constructor:

 
override protected function init():void 
{ 
    _bitmapData = new BitmapData (400, 400, true, 0); 
    _alphaBitmap = new BitmapData (400, 400, true, 0); 
     
    var _parentSprite:Sprite = new Sprite; 
     
    var sprite:Sprite = new Sprite; 
     
    var g:Graphics = sprite.graphics; 
    g.beginFill (0, 1); 
    g.drawRoundRect (25, 50, 350, 300, 100, 300); 
         
    sprite.filters = [new BlurFilter (50, 50)]; 
     
    _alphaBitmap.draw (sprite) 
         
    g.clear (); 
     
    //start of changes 
    var color:uint = 0xB3FFFF; 
         
    for (var i:uint = 0; i < 35; i++) 
    { 
        sprite = new Sprite; 
     
        g = sprite.graphics; 
        g.lineStyle (.5, color, .3); 
        g.moveTo (-20, 0); 
        g.lineTo (420, 0) 
     
        sprite.y = i * 11; 
        _parentSprite.addChild (sprite); 
    }         
     
    //added glow  
    _parentSprite.filters = [new GlowFilter (color, 1, 5, 5, 3)];  
   //end of changes 
 
    sprite = new Sprite; 
    g = sprite.graphics; 
    g.beginFill (0xC6FFFF, 1); //applied a different color 
    g.drawRoundRect (0, 0, 400, 400, 100, 100); 
     
    _bitmapData.draw (_parentSprite); 
    _bitmapData.copyChannel (_alphaBitmap, _alphaBitmap.rect, new Point, BitmapDataChannel.ALPHA, BitmapDataChannel.ALPHA) 
     
    var material:BitmapMaterial = new BitmapMaterial (_bitmapData); 
     
    _plane = new Plane (material, 1400,1200,4,4); 
                 
    _backgroundLayer = _viewport.getChildLayer (_plane); 
    _backgroundLayer.alpha = 0; 
    _backgroundLayer.layerIndex = -1; 
             
    addChild (_plane);                 
}

See the comment in the code where it says 'start of changes'? That part adds sprites with single lines drawn on them and adds them into _parentSprite. The _parentSprite is then used as the background for the BitmapData used as material for the 3D Plane.


Step 53: External Control

This effect will only have one external control. The flash () method will be called every time the menu is about to load. Unlike the static glow effect which stays with the menu, this effect will fade out as soon as it flashes. This makes the fadeOut () method irrelevant. Add the code below:

 
override public function flash ($sideways:Boolean = false):void 
{ 
    var snd:Sound = CustomLoader.getInstance ().getSound ('backgroundFlash'); //access from the CustomLoader instance 
    TweenLite.to  
    (_backgroundLayer, //target 
    .4, //duration 
    { 
        delay:.4, //delay 
        alpha: .4, //target property end value 
        onStart:snd.play, //play the sound when the tween starts 
        onStartParams:[0, 0, new SoundTransform (.2)], //parameters for the method called when the tween started 
        onComplete:TweenLite.to, //method called when the tween ends 
        onCompleteParams:[_backgroundLayer, .4, { alpha: 0 } ] //paramters for the method called when the tween ended 
    } 
    ); 
}

Overridden from the super class, the $sideways parameter is required but has no application. The modification here also includes playing the 'backgroundFlash' sound effect loaded from the CustomLoader instance. This completes the BackgroundGlow3D class. Since we didn't override the fadeOut () method, it is still available for this subclass. If you want to completely disable it, just override the method and leave it blank without passing a call to the superclass.


Step 54: Milestone Adding the BackgroundGlow3D

Go back inside Tester's init () method and add a call to addBackgroundGlow () just below the addBackgroundNoise () method call. Copy the code below after the addBackgroundNoise () method:

 
private function addBackgroundGlow ():void 
{ 
    _backgroundGlow = new BackgroundGlow3D ('backgroundGlow', viewport); 
    scene.addChild(_backgroundGlow); 
}

Next, go to the addMainMenu () and onMenuExitComplete () methods and add a the code below:

 
_backgroundGlow.flash ();

Again, make sure _backgroundGlow is declared as an instance property of type BackgroundGlow3D at the top of the class. every time MainMenu's startIntro () method is called, the flash () method for all the effects should be called as well. Now run the program. The result can be viewed here.

Now it's riddled with effects! =) Later, we'll add an option for turning the BackgroundNoise3D on or off.

Take a break and come back when you're ready to start building the first of two submenus, the AboutMenu3D.


Step 55: Adding an About Submenu - AboutMenu3D

This DisplayObject3D will be a modified subclass of MainMenu3D. It will contain a masked 3D scrolling text, 2 buttons to scroll this text, and a back button to go back to the main menu. Its list of responsibilities are:

  1. create 2 borders to act as casing for the contents of this menu //same as the superclass
  2. create and add the 3 buttons and add listeners for them //modified from the superclass
  3. prepare for intro animation //modified from the superclass
  4. add effects and animations //modified from the superclass
  5. add 3D scrolling text //new responsibility
  6. add a mask for the 3D scrolling text //new responsibility
  7. provide external control for playing intro and exit animations //same as the superclass
  8. manage activating and deactivating the buttons //same as the superclass
  9. dispatch the custom event triggered by the back button so that UserInterface3D can load the MainMenu3D back //additional responsibility

Go back to the preview link here and click the About button to see this menu. It looks simpler than MainMenu3D because of the fewer buttons it has but its internal working is a bit more complex. Create a new class and name it AboutMenu3D. Have it extend MainMenu3D. Once the class file is open, just copy and paste the code below replacing the content of the class.

 
package   
{ 
    import com.greensock.TweenMax; 
    import flash.display.Sprite; 
    import flash.events.Event; 
    import org.papervision3d.core.math.Number2D; 
    import org.papervision3d.events.InteractiveScene3DEvent; 
    import org.papervision3d.materials.ColorMaterial; 
    import org.papervision3d.materials.special.Letter3DMaterial; 
    import org.papervision3d.objects.primitives.Plane; 
    import org.papervision3d.typography.Font3D; 
    import org.papervision3d.typography.Text3D; 
    import org.papervision3d.view.layer.ViewportLayer; 
    import org.papervision3d.view.Viewport3D; 
     
    public class AboutMenu3D extends MainMenu3D 
    { 
        private var _text3D:Text3D; 
        private var _textLayer:ViewportLayer; 
        private var _mask:ViewportLayer; 
        private var _scrollUp:Boolean; 
        private var _scrollDown:Boolean; 
         
        public function AboutMenu3D($name:String, $viewport:Viewport3D)  
        { 
            super ($name, $viewport); 
        } 
    } 
}

The instance variables are all declared private only because we won't be extending this class any further for this project. But if think you might need to extend it later for your own projects, feel free to change the modifiers appropriately.


Step 56: Initialization

To modify the contents of this menu, properties need to be changed from the init () method. Add the code below after the constructor:

 
override protected function init ():void  
{ 
    _buttonPosY = [-250]; //we only need one 'y' position for all the buttons for this menu 
    _buttonTitles = ['< back', 'up', 'down']; //new set of buttons to use 
     
    super.init (); //calls Mainmenu3D's init method which triggers createFrame, addButtons, and setMenuEffect 
     
    createMask (); //additional responsibility 
     
    createText (); //additional responsibility 
}

New values are assigned for the inherited properties that need changing before calling the superclass' init () method. The new responsibilities are then taken care of. Next, we override the createFrame () method to change how the two borders' look. Add the code below after the init () method:

 
override protected function createFrame ():void  
{         
    _topNav = new UIComponent3D  
   ( 
   '_topNav',  
   _viewport,  
   [ 
        new Number2D (-692, 17),  
       new Number2D (-632, 55), 
       new Number2D (632, 55),  
       new Number2D (692, 17),  
       new Number2D (692, -55),  
       new Number2D (653, -55),  
       new Number2D (613, -22),  
       new Number2D (207, -22),  
       new Number2D (164, -1), 
       new Number2D (-152, -1),  
       new Number2D (-196, -22),  
       new Number2D (-610, -22),  
       new Number2D (-644, -55),  
       new Number2D (-692, -55),  
       new Number2D (-692, 17) 
    ] 
   ); 
     
    _topNav.fillColor = 0x0080FF; //change color to blue 
     
    _bottomNav = new UIComponent3D  
   ( 
   '_bottomNav',  
   _viewport,  
    [ 
        new Number2D (692, -17),  
       new Number2D (632, -55),  
       new Number2D (-632, -55),  
       new Number2D (-692, -17),  
       new Number2D (-692, 55),  
       new Number2D (-653, 55),  
       new Number2D (-613, 22),  
       new Number2D (-207, 22),  
       new Number2D (-164, 1),  
       new Number2D (152, 1),  
       new Number2D (196, 22),  
       new Number2D (610, 22),  
       new Number2D (644, 55),  
       new Number2D (692, 55),  
       new Number2D (692, -17) 
    ] 
   ); 
     
    //_bottomNav.fillColor = 0xEE5311; //change color to orange 
     
    _topNav.init (); 
    _bottomNav.init (); 
     
    addChild (_topNav); 
    addChild (_bottomNav); 
}

The borders are drawn completely differently from the superclass. Next, add the modified addButtons () method:

 
override protected function addButtons():void  
{ 
    _buttonArray = []; //assign a new array to prepare for use 
     
    var button:Button3D; //local variable 
     
    //drawing coordinates for upArrow 
    var upArrow:Array = 
                        [ 
                            new Number2D ( -69, -19),  
                           new Number2D ( -54, -39),  
                           new Number2D (0, 6),  
                           new Number2D (54, -39), 
                           new Number2D (69, -19),  
                           new Number2D (2, 39),  
                           new Number2D ( -69, -19) 
                        ]; 
                         
    //drawing coordinates for downArrow 
    var downArrow:Array =  
                        [ 
                           new Number2D ( 69, 19), 
                           new Number2D ( 54, 39),  
                           new Number2D (0, -6),  
                           new Number2D ( -54, 39),  
                           new Number2D ( -69, 19),  
                           new Number2D ( -2, -39),  
                           new Number2D ( 69, 19) 
                        ]; 
             
    var coordinatesArray:Array = [upArrow, downArrow]; //use an array for simple access to create the buttons  
     
    for (var i:uint = 0; i < _buttonTitles.length; i++) 
    { 
        if (_buttonTitles[i] == '< back')  
        { 
            button = new Button3D  
                        (_buttonTitles[i],  
                        _viewport,  
                        [ 
                            new Number2D (-182, -60),  
                            new Number2D (-93, 60),  
                            new Number2D (182, 60),  
                            new Number2D (182, -26),  
                            new Number2D (143, -26),  
                            new Number2D (105, -60),  
                            new Number2D (-182, -60) 
                        ] 
                        ); 
 
            button.x = 500; 
            button.fillColor = 0x0080FF; 
 
            button.init (); 
 
            button.text3D.x = -630; //position the text manually in the center of the button 
            button.addEventListener (InteractiveScene3DEvent.OBJECT_CLICK, onButtonClick); 
        } 
        else //if the button's name/label is not  equal to '< back'; 
        { 
            button = new Button3D (_buttonTitles[i], _viewport, coordinatesArray[i-1], false); 
            button.fillColor = 0x80FF00; 
            button.x = -75 + (i - 1) * 150 ; //center the 2 scrollers' 'x' position on the stage 
            button.noClick  = true; //remove the onButtonClick () listener 
            button.init (); 
            button.addEventListener (InteractiveScene3DEvent.OBJECT_OVER, oButtonOver); 
            button.addEventListener (InteractiveScene3DEvent.OBJECT_OUT, oButtonOut); 
        } 
         
        button.y = _buttonPosY[0]; 
        _buttonArray.push (button); 
        addChild (button); 
    }     
}

It only looks huge because of the space taken by the drawing coordinates. All it does is add the 3 buttons, position them, and assign the appropriate listeners based on the named given for them. See the comments for descriptions. The next method overrides the superclass and is left empty so the distortion effect is not applied for this menu:

 
override protected function setMenuEffect():void  
{ 
    //do nothing 
}

That takes care of all the inherited methods triggered from init (). Now, for the two additional functionalities. Add the code below:

 
private function createMask ():void 
{ 
    var material:ColorMaterial = new ColorMaterial (0xFFFFFF, 1);  
    var plane:Plane = new Plane (material, _topNav.width, 550, 10, 10);  
     
    _mask = _viewport.getChildLayer (plane); 
     
    addChild (plane); 
}

The method is called before createText () to make the mask available and ready. A 3D Plane is created with the same width as the borders and a height of 550. To use a 3D Object as mask, we need to access its ViewportLayer property. Next, add the createText () method:

 
private function createText ():void 
{ 
    var text:String = '   This describes all about the game,  the tools used to create it and its  objective. Also, this is where you  can put information about yourself  and anyone else who helped with  the project.      You can add more text here if you really need to. We have a  scroller just to make sure we have  plenty of room for what you need  to write.'; 
     
    var material:Letter3DMaterial  = new Letter3DMaterial (0xFFFFFF); 
     
    var font3D:Font3D = new Millennia; 
     
    _text3D = new Text3D (text, font3D, material); 
     
    _textLayer = _viewport.getChildLayer (_text3D); 
     
    _text3D.x = -1000; 
    _text3D.y = 200; 
    _text3D.z =  -25; 
     
    _text3D.align = 'left'; 
    _text3D.scale = .75; 
     
    _textLayer.alpha = 0; //make the text invisible  
    _textLayer.layerIndex = -1; //place it behind the buttons 
     
    addChild (_text3D); 
     
    _textLayer.mask = _mask; //apply the mask 
}

This is similar to how we created the text for Button3D or UIComponent3D. Masking is applied by assigning the 3D Plane's ViewportLayer as mask for the Text3D's ViewportLayer.


Step 57: Extending the Inherited Intro Animation

Right now, when startIntro () is called, the 2 borders and the 3 buttons will animate in the same way as MainMenu3D. Add the code below to add the 3D text when the intro starts:

 
override protected function initializeButtons():void  
{ 
    super.initializeButtons(); 
    TweenMax.to (_textLayer, .2, { alpha:1, delay:.2, overwrite:true } ); 
}

The initializeButtons () method is called when the borders reach their open positions. Check MainMenu3D's startIntro () method for a refresher. Here, we have it call the superclass' method to handle the 3 buttons then tween the 3D text in. Next, add the modified showButtons () method called from MainMenu3D's initializeButtons ():

 
override protected function showButtons ($button:Button3D):void 
{ 
    super.showButtons ($button); //triggers the button's onObjectOver through the superclass 
    _scrollDown = _scrollUp = false; //make sure to stop the 3D text from scrolling 
}

Here, we do the same to it as the superclass the we make sure that _scrollDown and _scrollUp are set to false . We had to do this here because the other 2 buttons are used for scrolling and uses onButtonOver () listeners. If those listeners get triggered by showButtons (), the 3D text will start scrolling.


Step 58: Handling Mouse Events

Everything is handled internally except when the back button is clicked. Add the code below:

 
override protected function onButtonClick (e:InteractiveScene3DEvent):void  
{ 
    switch (e.target.name) 
    { 
        case '< back': dispatchEvent (new Event (MainMenu3D.BACK_TO_MAIN)); 
        break;             
    } 
    startExit (); 
}

This informs UserInterface3D to go back to the main menu. Let's now add the scrolling functionality:

 
private function oButtonOver(e:InteractiveScene3DEvent):void  
{ 
    switch (e.target.name) 
    { 
        case 'up':  
        _scrollUp = true;  
        _scrollDown = false; 
        break; 
         
        case 'down':  
        _scrollDown = true;  
        _scrollUp = false; 
        break; 
    } 
} 
 
private function oButtonOut(e:InteractiveScene3DEvent):void  
{ 
    _scrollUp = _scrollDown = false; 
}

These two methods assign the value for _scrollUp and _scrollDown properties which will be used as marker if the text should be scrolled. You'll see later when we get to the additional onUpdate () method.


Step 59: Extending the Inherited Exit Animation

Nothing fancy for the startExit () method. We just make the 3D text invisible then let the superclass handle the rest. The hideButtons () method is overridden to make sure _scrollUp and _scrollDown are both set to false again. If not done so here, the 3D text will scroll down while the menu is removed. Add the code below:

 
override public function startExit ():void  
{             
    _textLayer.alpha = 0; 
    super.startExit (); 
} 
 
override protected function hideButtons($button:Button3D):void  
{ 
    super.hideButtons($button); //triggers the button's onObjectOver through the superclass 
    _scrollDown = _scrollUp = false; //make sure to stop the 3D text from scrolling 
}

If you're unsure how these two methods interact, go back and review MainMenu3D's startExit () method.


Step 60: Scrolling The 3D Text

Just like BackgroundNoise3D, visual changes can only be applied from the update () method.

 
public function update ():void 
{ 
    if (_scrollDown == true) { if (_text3D.y < 700) _text3D.y += 10; }; 
    if (_scrollUp == true) { if (_text3D.y > 200) _text3D.y -= 10; }; 
}

This method will get called repeatedly from UserInterface3D's onRenderTick () method. It constantly checks the values of _scrollUp and _scrollDown and adjusts the 3D text's 'y' position. That completes the AboutMenu3D class.


Step 61: Milestone Testing AboutMenu3D

Go back to Tester's init () method and comment out the calls to addBackgroundNoise and addMainMenu, then add a call to addAboutMenu () at the bottom of the method. Next add the code below the init () method:

 
private function addAboutMenu ():void 
{ 
    _aboutMenu = new AboutMenu3D ('about', viewport); 
    _aboutMenu.addEventListener (MainMenu3D.BACK_TO_MAIN, navigateTo); 
    _aboutMenu.addEventListener (MainMenu3D.EXIT_COMPLETE, onMenuExitComplete); 
    scene.addChild (_aboutMenu); 
     
    _aboutMenu.startIntro (); 
     
    _staticGlow.scaleX = 1.5; 
    _backgroundGlow.scaleX = 1.5; 
     
    _staticGlow.flash (); 
    _backgroundGlow.flash (); 
}

Don't forget to declare _aboutMenu as an instance property of type AboutMenu3D at the top of the class. As you can see, its very similar to MainMenu3D's instantiation. This time, we only listen for a MainMenu3D.BACK_TO_MAIN and scale the two effects 1.5 percent the original size. Next, modify the navigateTo () method as shown below with comments:

 
private function navigateTo(e:Event):void  
{ 
_staticGlow.fadeOut (); 
//_backgroundNoise.fadeOut (); //comment this out 
     
    switch (e.type) 
    { 
        case MainMenu3D.START_GAME_CLICKED: 
        trace ('load the game and play'); 
        break; 
         
        case MainMenu3D.SETTINGS_CLICKED: 
        trace ('load settings menu'); 
        break; 
     
        case MainMenu3D.ABOUT_CLICKED: 
        trace ('load about menu'); 
        break; 
         
        case MainMenu3D.EXIT_CLICKED: 
        trace ('exit the application'); 
        break; 
         
        case MainMenu3D.BACK_TO_MAIN: //add this new jump target 
        trace ('load MainMenu3D'); 
        break; 
    } 
}

Make sure to comment out the call to _backgroundNoise.fadeOut () method call since we're not instantiating it this time. Assign a new jump target for the MainMenu3D.BACK_TO_MAIN event at the bottom of the switch statement.

The onMenuExitComplete () will act just the same for AboutMenu3D, thanks to OOP, but you'll also need to comment out the call to _backgroundNoise.flash () inside it.

Lastly, go inside the onRenderTick () method and modify the code according to the comments as show below:

 
override protected function onRenderTick (event:Event = null):void  
{ 
    var xDist:Number = mouseX - stage.stageWidth * .5; 
    var yDist:Number = mouseY - stage.stageHeight * .5; 
     
    _camPitch += ((yDist * _rotX) - _camPitch + 90) * _easOut; 
    _camYaw += ((xDist * _rotY) - _camYaw + 270) * _easOut; 
     
    camera.orbit (_camPitch, _camYaw); 
     
    super.onRenderTick(event); 
     
    //_backgroundNoise.update (); //comment this out 
    _aboutMenu.update (); //add this call 
}

Now run the app and you should see something like this.


Step 62: Adding a Settings Submenu - SettingsMenu3D

We're down to our last menu. By far this menu is more complex than the other two. But to make this class work, we'll need to create 3 new classes - CheckBox3D, RadioButton3D, and ButtonGroup3D. So let's jump out and work on these components. Let's start with CheckBox3D. You'll see the details about SettingsMenu3D when we get to step 75.


Step 63: Creating a New Button3D Type - CheckBox3D

Go to the preview link here and play with the full screen checkbox in the settings menu. Turn it on then turn it off again by clicking on it. Once its on, you can also turn it off by hitting the escape key. It differs from Button3D in a few ways - it consumes the coordinates for drawing differently, the button stays on its 'z' axis when the mouse is over it, the instance will have an extra Boolean variable to hold the value of its current state, the button will have additional external controls to turn it on or off. Its list of responsibilities are:

  1. play sound effects for mouse over and mouse click. //inherited
  2. add an interactive plane that will trigger the mouse events. //inherited
  3. apply effects for mouse over, mouse out, and mouse click. //modified
  4. provide external control for adding and removing mouse event listeners //inherited
  5. provide external control to remove mouse click listeners //inherited
  6. provide external control to turn the button on or off //additional functionality
  7. draw a checkbox using Vectorshape3D //modified from UIComponent3D
  8. store its own state and provide access for it externally //new functionality

Create a new class named CheckBox3D, have it extend Button3D. Replace the code in it with the code below:

 
package   
{ 
    import org.papervision3d.core.math.Number2D; 
    import org.papervision3d.events.InteractiveScene3DEvent; 
    import org.papervision3d.materials.special.VectorShapeMaterial; 
    import org.papervision3d.objects.special.Graphics3D; 
    import org.papervision3d.objects.special.VectorShape3D; 
    import org.papervision3d.view.Viewport3D; 
     
    public class CheckBox3D extends Button3D 
    { 
        protected var _state:Boolean; 
         
        public function CheckBox3D ($name:String, $viewport:Viewport3D, $coordinates:Array = null, $showText:Boolean = true)  
        { 
            _coordinates = $coordinates == null ?  
            [ 
            new Number2D ( -25, 0), //x location 
            new Number2D ( -25, 0), //y location 
            new Number2D (50, 0), //width 
            new Number2D (50, 0), //height 
            new Number2D (10, 0), //ellipseWidth 
            new Number2D (10, 0) //ellipseHeight 
            ]  
            : $coordinates; 
             
            super ($name, $viewport, _coordinates, $showText); 
        } 
    } 
}

Instantiation is the same except for the values inside the _coordinates variable. For drawing, we'll use the Graphics3D.drawRoundRect () method instead of the moveTo () and lineTo () methods we used for the superclass. To maintain uniformity, we'll keep the Number2D _coordinates array and just not use its 'y' property by setting them to 0. See the comments for designation.


Step 64: CheckBox3D Initialization

Once the instance is created and the init () method is called externally, the following 3 methods are triggered. Add the modified setDimensions () method after the constructor:

 
override protected function setDimensions ():void  
{ 
    _width =_coordinates[2].x; //assign the width 
    _height = _coordinates[3].x; //assign the height 
}

Next, add the modified createInterface () method:

 
override protected function createInterface ($array:Array):void  
{             
    drawButton (); //new functionality 
     
    addInteractivePlane (); //inherited from Button3D 
}

Here, we totally skip from calling the superclass' createInterface () method. Instead, it calls the new drawButton () method and then goes back to using the superclass' addInteractivePlane () method. Next, add the modified createText () method:

 
override protected function createText ():void  
{ 
    super.createText(); 
     
    _text3D.x = -1000; //position left of the checkbox 
    _text3D.z = Button3D.OUT_POSITION; 
    _text3D.scale = .75;  
}

After letting the superclass create the text, CheckBox3D takes over and modifies its location and scale.


Step 65: Drawing the CheckBox

This method first gets called by the createInterface () method and every time the onObjectClick () is triggered. It draws the checkbox based on the current value of _state.

 
protected function drawButton ():void 
{ 
    if (_interfaceGraphic) removeChild (_interfaceGraphic); 
     
    var material:VectorShapeMaterial = new VectorShapeMaterial (); 
     
    _interfaceGraphic = new VectorShape3D (material); 
             
    var g:Graphics3D = _interfaceGraphic.graphics; 
    g.lineStyle (_lineThickness, _lineColor, _lineAlpha); 
     
    var fillAlpha:Number; 
     
    if (_state == true) fillAlpha = _fillAlpha; //show or hide the center to represent on or off 
    else fillAlpha = 0; 
    g.beginFill (_fillColor, fillAlpha); 
     
    g.drawRoundRect (_coordinates[0].x,_coordinates[1].x,_coordinates[2].x,_coordinates[3].x,_coordinates[4].x,_coordinates[5].x); //drawn differently 
 
    g.endFill (); 
     
    _interfaceLayer = _viewport.getChildLayer (_interfaceGraphic); //assign to apply mouse over effect 
     
    setLayerIndex (_interfaceLayer, _layerIndex); 
     
    addChild (_interfaceGraphic); 
    _interfaceGraphic.x = -70; //position a little to the left 
    _interfaceGraphic.z = Button3D.OVER_POSITION; //make it pop-out to improve the look 
}

It should remind you of UIComponent3D's createInterface () method. See the comments for its differences.


Step 66: Responding to Mouse Events

For mouse overs, we want the checkbox to still glow but stay on its out position. Add the overridden onObjectOver () method:

 
override public function onObjectOver (e:InteractiveScene3DEvent = null):void  
{ 
    super.onObjectOver(e); 
    this.z = Button3D.OUT_POSITION; //don't let it move closer to the camera 
}

every time the checkbox is clicked, we want it to toggle on and off. Add the code next:

 
override public function onObjectClick (e:InteractiveScene3DEvent = null):void  
{ 
    _state = ! _state; //toggle true or false 
    drawButton (); //redraw the button to represent its state 
 
    dispatchEvent (new InteractiveScene3DEvent (InteractiveScene3DEvent.OBJECT_CLICK)); //same as superclass 
    _buttonClickSound.play (0,0, _soundTransform); //play the sound same as superclass 
}

First, _state is set to its opposite value; then, the drawButton () method is called to redraw the checkbox. It then dispatches the event and plays the assigned sound for button clicks. There won't be any modifications needed for the onObjectOut () method so we skip that here.


Step 67: CheckBox3D External Access

Here comes the additional functionality to turn the button on or off externally. The turnOn () method will set the value of _state to true then call the drawButton () method:

 
public function turnOn ():void 
{ 
    _state = true; 
    drawButton (); 
}

Next add matching turnOff () method:

 
public function turnOff ():void  
{ 
    _state = false 
    drawButton ();         
}

These methods will help when you use the checkbox in a group. You'll see its application when we group radio buttons soon.

And last, add access to the checkbox's current state:

 
public function get state ():Boolean  
{  
	return _state;  
}

Step 68: Milestone Testing CheckBox3D

Go back to the init () method inside the Tester class and comment out the addAboutmenu (), addBackgroundGlow (), and the addStaticGlow () method calls. Still in the init () method, add a call to addCheckbox () at the bottom. Go into the onRenderTick () method and comment out the call to _aboutMenu.update (). Next, add the new method below the init () method:

 
private function addCheckbox ():void 
{ 
    var cb:CheckBox3D = new CheckBox3D ('checkbox', viewport); 
    scene.addChild (cb); 
     
    cb.fillColor = 0x00FF00; //changed color 
    cb.lineAlpha = .6; //modified to show the borders of the checkbox 
    cb.lineThickness = 4; //modified a little thicker 
    cb.lineColor = 0xFFFFFF; //also changed color 
     
    cb.init (); //called once all the graphic properties are set 
    cb.addEventListener (InteractiveScene3DEvent.OBJECT_CLICK, checkboxClicked); 
}

Its very important to change the lineAlpha value, remember, this defaults to 0 from the UIComponent3D superclass. Next, add the callback for the checkboxClicked listener:

 
private function checkboxClicked (e:InteractiveScene3DEvent):void  
{ 
    trace (e.target.state); 
}

We'll use this information to change the fullscreen mode later. For now, let's just do a trace statement. Now run the app and you should see something like the picture below. The trace should show the value of the checkbox's state every time the checkbox is clicked. The position feels a bit off but we'll fix that when we add it as a component for the settings menu.



Step 69: Making a RadioButton3D out of CheckBox3D

Here's the preview link again if you'd like to see how the radio button works. Go to the settings menu and play with the group of radio buttons.

Create a new class named RadioButton3D, have it extend CheckBox3D and save it. Replace the contents of the new class with the code below:

 
package   
{ 
    import org.papervision3d.core.math.Number2D; 
    import org.papervision3d.materials.special.VectorShapeMaterial; 
    import org.papervision3d.objects.special.Graphics3D; 
    import org.papervision3d.objects.special.VectorShape3D; 
    import org.papervision3d.view.Viewport3D; 
 
    public class RadioButton3D extends CheckBox3D 
    {         
        public function RadioButton3D ($name:String, $viewport:Viewport3D, $coordinates:Array = null, $showText:Boolean = true)  
        { 
            _coordinates = $coordinates == null ? [new Number2D (0,0), new Number2D (0,0), new Number2D (25,0)] : $coordinates; 
             
            super ($name, $viewport, _coordinates, $showText); 
        } 
    } 
}

There will only be 3 visual modifications from CheckBox3D so the list of responsibilities are the same. One, we need to change the scale and position of the text/label. Two, we use the Graphics3D.drawCircle () method instead of the Graphics3D.drawRoundRect () for drawing the radio button. And three, since we're using Graphics3D.drawCircle (), the setDimensions () method will have to be modified to use the same value for its width and height properties. Add the code below for the overridden setDimensions () method after the constructor:

 
override protected function setDimensions():void  
{ 
    _width =_coordinates[2].x * 2;  
    _height = _width; 
}

Next, add the modified drawButton () method:

 
override protected function drawButton ():void 
{ 
    if (_interfaceGraphic) removeChild (_interfaceGraphic); 
     
    var material:VectorShapeMaterial = new VectorShapeMaterial (); 
     
    _interfaceGraphic = new VectorShape3D (material); 
     
    var g:Graphics3D = _interfaceGraphic.graphics; 
    g.lineStyle (_lineThickness, _lineColor, _lineAlpha); 
     
    var fillAlpha:Number; 
    if (_state == true) fillAlpha = _fillAlpha; 
    else fillAlpha = 0; 
    g.beginFill (_fillColor, fillAlpha); 
     
    g.drawCircle (_coordinates[0].x,_coordinates[1].x,_coordinates[2].x); //the only difference 
     
    g.endFill (); 
     
    _interfaceLayer = _viewport.getChildLayer (_interfaceGraphic); 
     
    setLayerIndex (_interfaceLayer, _layerIndex); 
     
    addChild (_interfaceGraphic); 
    _interfaceGraphic.z = Button3D.OVER_POSITION; 
}

Now add the modified createText () method:

 
override protected function createText():void  
{ 
    super.createText(); 
 
    _text3D.x = -300; 
    _text3D.y = -50; 
    _text3D.scale = .5; //make it half the original size 
}

And that completes the RadioButton3D. Thank goodness for inheritance. You can test it if you like, just change the part where it says new CheckBox3D with new RadioButton3D inside the addCheckBox () method in Tester and you're all set. Or you can wait until we've created ButtonGroup3D to test a group of radio buttons which we're doing next.


Step 70: ButtonGroup3D

This class will act as both manager and container for the group of radio buttons. It takes in 4 required parameters and 1 optional when instantiated. The parameters are - the name/label for the group, the viewport, an array of button names, and the default state. The last optional parameter was created to provide an option for aligning the buttons either vertically or horizontally. Unfortunately, due to time constraints, I was only able to apply the horizontal alignment. I'll leave it up to you to finish with the vertical option. Following is its list of responsibilities.

  1. create text to use as its name and label
  2. create a set of radiobuttons that will work together as a group
  3. manage the buttons so that only one will be on its 'on' state at all times (while the rest are in their 'off' state)
  4. dispatch a STATE_CHANGED event when the state of the group changes (ie. when a different button is turned on)
  5. hold the value of the group's current state
  6. make each button glow just like their superclass when the menu is about to close

Create a new class named ButtonGroup3D and have it extend DisplayObject3D. Copy the code below to replace all of the content inside the new class:

 
package   
{ 
    import flash.events.Event; 
    import org.papervision3d.events.InteractiveScene3DEvent; 
    import org.papervision3d.materials.special.Letter3DMaterial; 
    import org.papervision3d.objects.DisplayObject3D; 
    import org.papervision3d.typography.Font3D; 
    import org.papervision3d.typography.Text3D; 
    import org.papervision3d.view.layer.ViewportLayer; 
    import org.papervision3d.view.Viewport3D; 
 
    public class ButtonGroup3D extends DisplayObject3D 
    { 
        public static const VERTICAL:String = 'vertical'; 
        public static const HORIZONTAL:String = 'horizontal'; 
         
        public static const STATE_CHANGED:String = 'stateChanged'; 
         
        private var _buttonNames:Array; //names of the buttons to create 
        private var _state:String; //property to hold the state for the button group 
        private var _viewport:Viewport3D; //the viewport passed in from Tester or UserInterface3D 
        private var _orientation:String; //defaults to horizontal alignment 
        private var _buttonArray:Array; //will hold reference for all the buttons created for access 
        private var _textLayer:ViewportLayer; //the text/label for the group 
         
        public function ButtonGroup3D ($name:String, $viewport:Viewport3D, $buttonNames:Array, $defaultState:String, $orientation:String = HORIZONTAL) 
        { 
            super($name); //pass the name to the DisplayObject3D 
             
            _buttonNames = $buttonNames; //assign value 
             
            _state = $defaultState; //assign value 
             
            _viewport = $viewport; //assign value 
             
            _orientation = $orientation; //assign value 
             
            init ();  
        } 
    } 
}

Again, just like the other classes during instantiation, parameters are passed in and saved, then the init () method is called to prepare its contents of components.


Step 71: Initialization - ButtonGroup3D

The init () method calls two other methods to prepare the text and the buttons for the group. Add the code below after the constructor method:

 
private function init ():void 
{ 
    createText (); 
     
    addButtons (); 
}

The next method for creating text is the same as we've done for the other classes, only the position and scale have been modified. Add the code below the init () method:

 
private function createText ():void 
{             
    var material:Letter3DMaterial  = new Letter3DMaterial (0xDDFFFF);  
     
    var font3D:Font3D = new Millennia; 
     
    var text3D:Text3D = new Text3D (name, font3D, material); 
     
    _textLayer = _viewport.getChildLayer (text3D); 
             
    text3D.x = -1000; 
    text3D.y = 510; 
             
    text3D.align = 'left'; 
     
    text3D.scale = .75; //make 3/4 the size of the original 
 
    _textLayer.layerIndex = -3; //place in the back so the button is over the text 
     
    addChild (text3D); 
}

Next. add the addButtons () method:

 
private function addButtons ():void 
{ 
    _buttonArray = []; 
    var button:RadioButton3D;  
     
    for (var i:uint =  0; i < _buttonNames.length; i++) 
    { 
        button = new RadioButton3D (_buttonNames[i], _viewport); 
         
        button.fillColor = 0xFF0000; 
        button.lineAlpha = .6; 
        button.lineThickness = 4; 
        button.fillAlpha = .6; 
        button.lineColor = 0xFFFFFF; 
         
        button.init (); 
         
        addChild (button); 
        _buttonArray.push (button); 
         
        if (button.name == _state)  
        { 
            button.turnOn (); //visually reflect an 'on' state 
            button.deActivate (); //stop listening for mouse events since its already turned on 
        } 
        else  
        { 
            button.activate (); //add mouse event listeners so that it can be turned on 
        } 
         
        if (_orientation == HORIZONTAL) //here is where we align the buttons horizontally 
        { 
            var buttonWidth:Number = button.width + 50; 
            var buttonSpacing:Number = buttonWidth * (_buttonNames.length + 1); 
            var positionX:Number = buttonSpacing / (_buttonNames.length); 
            var center:Number = buttonSpacing * -.5; 
             
            button.x = center + positionX * i; 
            button.y = 500; 
        } 
        //you can add the 'vertical' alignment functionality here (with an 'else' statement) 
         
        button.addEventListener (InteractiveScene3DEvent.OBJECT_CLICK, onButtonClick); //add a listener here so that ButtonGroup3D will know when any of its buttons are clicked. 
    } 
}

The top portion of the loop goes through the number of button names saved in _buttonNames array. It then sets each button's visual properties and adds them as children at the same time saving reference for each in _buttonArray. The middle part of the method checks if the button's name matches the assigned default state which was also passed in the constructor. If it is, the button is turned on and then deactivated from listening for events. If not, the button is left on its off state and it is activated to listen for events. The last part simply aligns the buttons horizontally. Each button is assigned a listener which will only get triggered if the button is active. ButtonGroup3D will handle this internally before it is dispatched.


Step 72: Managing Click Events in ButtonGroup3D

Below is the callback that will handle the click events for all the active buttons. Add the code after the addButtons () method:

 
private function onButtonClick (e:InteractiveScene3DEvent):void  
{ 
    _state = e.target.name; 
     
    for (var i:uint = 0; i < _buttonArray.length; i++) 
    { 
        if (e.target.name != _buttonArray[i].name)  
        { 
            _buttonArray[i].turnOff (); //visually reflect an 'off' state 
            _buttonArray[i].activate (); //start listening for mouse events so it can be turned on again 
        } 
        else  
        { 
            _buttonArray[i].deActivate ();  
        } 
    } 
    dispatchEvent (new Event (STATE_CHANGED)); 
}

Here, the state for the button group is assigned the name of the active button that was clicked. Then all the buttons are looped through to see if its the button that was clicked. If it is, it is deactivated from listening for clicks, if the button isn't the same button that was clicked, it is turned off and activates it for click events again. When the loop finishes, the STATE_CHANGED event is dispatched to inform its parent container - SettingsMenu3D.


Step 73: External Access

The flash () method will make all the buttons flash when the menu is about to close, You'll see how this is applied when we get to SettingsMenu3D. Add the code below:

 
public function flash ():void 
{ 
    for each (var do3d:DisplayObject3D in children)  
    { 
        if (do3d is Button3D) Button3D (do3d).onObjectOver (new InteractiveScene3DEvent (InteractiveScene3DEvent.OBJECT_OVER)); 
    } 
}

And finally, add the code below to provide access for the button group's current state:

 
public function get state ():String { return _state; }

Step 74: Milestone Testing ButtonGroup3D

Go back to the Tester class inside the init () method and comment out the call to addCheckbox () then add a call to addButtonGroup () below it. Paste the code below after the init () method:

 
private function addButtonGroup ():void 
{ 
    var bg:ButtonGroup3D = new ButtonGroup3D ('Example', viewport, ['one', 'two', 'three'], 'two'); 
    bg.addEventListener (ButtonGroup3D.STATE_CHANGED, onStateChange); 
    bg.x = 200; 
    bg.y = -500; 
    scene.addChild (bg); 
}

Right now, with the current setup, it's best to use between 3 and 5 characters for the button labels. When ButtonGroup3D is instantiated, it is passed the name for the button group, the viewport, an array of button names in string format which will be used as names for each button and also as different states for the button group, and the default state which could be any of the 3 strings inside the button array. Once instantiated, it needs to listen for the custom event ButtonGroup3D.STATE_CHANGED to know when an active radio button was clicked. The rest positions the ButtonGroup3D instance in the center and adds it into the scene. Next add callback for the onStateChange event listener:

 
private function onStateChange(e:Event):void  
{ 
    trace (ButtonGroup3D (e.target).state); 
}

Here, we just do a quick trace to see if ButtonGroup3D does what its supposed to. Run the app to see the result. It should look exactly like this.


Step 75: Building SettingsMenu3D

Just like AboutMenu3D, this class will also inherit from MainMenu3D. This will be our last menu and once we get this done, we'll work on the UserInterface3D class to finally put everything together. This menu will allow the user to modify a few internal settings for the flash player, add or remove an effect, and save the chosen level for the game. Its list of responsibilities are:

  1. create 2 borders to act as casing for the contents //modified
  2. create and add the buttons and add listeners for them //modified
  3. prepare for intro animation //modified
  4. add effects and animations //modified
  5. provide external control for playing intro and exit animations //same as superclass
  6. manage activating and deactivating the buttons //modified
  7. dispatch custom events triggered by the buttons so that UserInterface3D can apply changes to the flash player settings or remove an effect. //modified
  8. save game level available for external access //additional functionality
  9. save stage quality available for external access //additional functionality

Create a new class named SettingsMenu3D and have it extend MainMenu3D. Replace the contents of the class with the code below:

 
package   
{ 
    import com.greensock.easing.Expo; 
    import com.greensock.TweenMax; 
    import flash.display.StageQuality; 
    import flash.events.Event; 
    import flash.events.MouseEvent; 
    import flash.media.Sound; 
    import flash.media.SoundTransform; 
    import org.papervision3d.core.math.Number2D; 
    import org.papervision3d.events.InteractiveScene3DEvent; 
    import org.papervision3d.view.Viewport3D; 
 
    public class SettingsMenu3D extends MainMenu3D 
    { 
        public static const LEVEL_EASY:String = 'levelEasy'; 
        public static const LEVEL_NORMAL:String = 'levelNormal'; 
        public static const LEVEL_HARD:String = 'levelHard'; 
         
        private var _level:String = LEVEL_NORMAL; 
        private var _quality:String = StageQuality.MEDIUM; 
         
        private var _gameLevel:ButtonGroup3D; 
        private var _stageQuality:ButtonGroup3D; 
         
        public function SettingsMenu3D($name:String, $viewport:Viewport3D)  
        { 
            super ($name, $viewport); 
        } 
    } 
}

If you check this menu out from the preview link here, you'll see that it contains 1 checkbox which toggles the fullscreen mode, 2 radio button groups which handle the stage quality and game level, and 1 back button to return to the main menu. The first 3 properties from the top of the class declaration will serve as game levels for our program. The private property _level will store 1 of these 3 levels at all times and will have a default value of LEVEL_NORMAL. The _quality property will hold the value for stage quality and will default to StageQuality.MEDIUM. The last 2 properties will hold instances of ButtonGroup3D, 1 for the stage quality and another for the game level. The constructor only needs to pass the information to the superclass.


Step 76: Initialization for SettingsMenu3D

Once the init () method is called by the superclass' constructor, we override it from SettingsMenu3D and make changes to some inherited properties. The _buttonPosY is given only 2 'y' positions for the buttons to follow, _buttonTitles is assigned a new array containing new information to make as buttons. Then the superclass's init () method is triggered to continue the process. Add the code below the constructor method:

 
override protected function init ():void 
{ 
    _buttonPosY = [0, 200]; 
     
    _buttonTitles = ['<<','Fullscreen']; 
     
    super.init (); 
}

From the superclass' init () method, the following 3 methods are called. The first being the createFrame (), which is overridden to change the menu borders' shape and colors. Add the code after the init () method:

 
override protected function createFrame ():void  
{ 
    _topNav = new UIComponent3D  
    ( 
    '_topNav',  
    _viewport,  
    [ 
        new Number2D (0, 286),  
        new Number2D (60, 326),  
        new Number2D (60, 367),  
        new Number2D (-60, 312),  
        new Number2D (-60, -315),  
        new Number2D (60, -367),  
        new Number2D (60, -325),  
        new Number2D (0, -288),  
        new Number2D (0, 286) 
    ] 
    ); 
     
    _topNav.fillColor = 0xFF8000; 
 
    _bottomNav = new UIComponent3D  
    ( 
    '_bottomNav',  
    _viewport,  
    [ 
        new Number2D (0, -286),  
        new Number2D (-60, -326),  
        new Number2D (-60, -367),  
        new Number2D (60, -312),  
        new Number2D (60, 315),  
        new Number2D (-60, 367),  
        new Number2D (-60, 325),  
        new Number2D (0, 288),  
        new Number2D (0, -286) 
    ] 
    ); 
    _bottomNav.fillColor = 0xFF8000; 
     
    _topNav.init (); 
    _bottomNav.init (); 
     
    addChild (_topNav); 
    addChild (_bottomNav); 
}

The addButtons () method has been altered quite a bit. I've divided the method into 2 parts to make it easier to understand. It starts by getting _buttonArray ready to store references for the buttons we're about to create. _buttonTitles is then looped through and checked if the name matches '<<'. If it does, a Button3D instance is created and added at the center of the menu. Its text is centered manually inside the button's visual representation. If the name doesn't match '<<', a CheckBox3D instance is created instead and placed at the top of the menu. The instance's colors are also modified before calling the button's init () method. Still within the loop, each button is assigned a listener for clicks, positioned on 'y' axis based on the current iteration's value in _buttonPosY, saved a reference in _buttonArray and added as SettingsMenu3D's children. The second part, adds 2 ButtonGroup3Ds for control of the stage quality and game level. We also have a different listener assigned for these button groups since they need to be handled differently. Add the full code next:

 
override protected function addButtons ():void  
{ 
    //first part 
    _buttonArray = []; 
     
    var button:Button3D; 
         
    for (var i:uint = 0; i < _buttonTitles.length; i++) 
    { 
        if (_buttonTitles[i] == '<<')  
        { 
            button = new Button3D  
            ( 
            _buttonTitles[i],  
            _viewport,  
            [ 
                new Number2D (49, 113),  
                new Number2D (-49, 19),  
                new Number2D (-49, -19),  
                new Number2D (49, -113),  
                new Number2D (49, 113) 
            ] 
            ); 
             
            button.x = 450; 
                     
            button.init (); 
            button.text3D.x = -540; 
        } 
        else  
        { 
            button = new CheckBox3D (_buttonTitles[i], _viewport); 
            button.x = 200; 
            button.fillColor = 0x0080FF; 
            button.lineAlpha = .6; 
            button.lineThickness = 4; 
            button.lineColor = 0xEE5311; 
             
            button.init (); 
        } 
        button.addEventListener (InteractiveScene3DEvent.OBJECT_CLICK, onButtonClick); 
        button.y = _buttonPosY[i]; 
        _buttonArray.push (button); 
        addChild (button); 
    } 
     
    //second part     
    _gameLevel = new ButtonGroup3D ('Level', _viewport, ['easy', 'nrml', 'hard'], 'nrml'); 
    _gameLevel.addEventListener (ButtonGroup3D.STATE_CHANGED, onStateChange); 
    addChild (_gameLevel); 
    _gameLevel.x = 200; 
    _gameLevel.y = -500; 
     
    _stageQuality = new ButtonGroup3D ('Quality', _viewport, ['med', 'high', 'best'], 'med'); 
    _stageQuality.addEventListener (ButtonGroup3D.STATE_CHANGED, onStateChange); 
    addChild (_stageQuality); 
    _stageQuality.x = 200; 
    _stageQuality.y = -700; 
}

And the setMenuEffect () method, the last method called by init (). We override it here and apply no effect. I'll pass this as an optional homework to you guys if you want to use the DistortionEffect for SettingsMenu3D. Remember, it takes in an array of viewport layers to apply the filter to. Add the code below:

 
override protected function setMenuEffect():void  
{ 
    //do nothing 
}

Step 77: Handing Intro Animation - SettingsMenu3D

Since the 2 borders are opening in a horizontal motion, they are positioned a little differently than how we did it for the superclass. Both _gameLevel and _stageQuality are set fully transparent ready for the intro animation. Add the code below after the setMenuEffect () method:

 
override protected function prepareIntro ():void 
{ 
    _topNav.x = -75; 
    _bottomNav.x = 75; 
    _topNav.interfaceLayer.alpha = _bottomNav.interfaceLayer.alpha = 0; //same as superclass 
     
    for each (var button:Button3D in _buttonArray) button.visible = false; //same as superclass 
     
    //additional resposibility 
   _gameLevel.visible = false; 
   _stageQuality.visible = false; 
}

The startIntro () method only differs from the superclass by how the 2 borders open up. Here we tween the borders 'x' position to move them away from each other. Add the code next:

 
override public function startIntro ():void 
{             
    prepareIntro (); 
 
    var sound:Sound = CustomLoader.getInstance ().getSound ('menuExpand'); 
     
    TweenMax.to (_topNav.interfaceLayer, .4, { alpha:1, delay:.5, onStart:sound.play, onStartParams:[0,0,new SoundTransform (1)],overwrite:false } ); 
    TweenMax.to (_bottomNav.interfaceLayer, .4, { alpha:1, delay:.5, overwrite:false } ); 
     
    TweenMax.to (_topNav, .5, { x:-500, delay:.75, ease:Expo.easeInOut, overwrite:false } ); 
    TweenMax.to (_bottomNav, .5, { x:500, delay:.75, ease:Expo.easeInOut, overwrite:false, onComplete:initializeButtons } ); 
}

The initializeButtons () method doesn't need to change so we leave it as it is in the superclass.

The showButtons () method calls its super method and then takes over to manage the intro for the 2 ButtonGroup3Ds. Add the code next:

 
override protected function showButtons ($button:Button3D):void 
{ 
    super.showButtons ($button);  
     
    _gameLevel.visible = true; 
    _stageQuality.visible = true; 
     
    _gameLevel.flash (); //make the radio buttons inside ButtonGroup3D flash just like the back button and the checkbox 
    _stageQuality.flash (); 
}

Step 78: Handling Mouse Events - SettingsMenu3D

The onButtonClick () method will handle the clicks for the fullscreen checkbox and the back button. It has the same functionality as the superclass. If the name of the button clicked is '<<', it dispatches a MainMenu.BACK_TO_MAIN and starts the exit animation. If the fullscreen checkbox was clicked, it dispatches an InteractiveScene3DEvent.OBJECT_CLICK. Toggling fullscreen mode will be done through the UserInterface3D so all we do here is inform it about the event. Add the code below:

 
override protected function onButtonClick (e:InteractiveScene3DEvent):void  
{ 
    switch (e.target.name) 
    { 
        case '<<': dispatchEvent (new Event (MainMenu3D.BACK_TO_MAIN)); 
        startExit (); 
        break;             
         
        case 'Fullscreen': dispatchEvent (new InteractiveScene3DEvent (InteractiveScene3DEvent.OBJECT_CLICK)); 
        break; 
    } 
}

The onStateChange () method will handle clicks for the 2 button groups. Here it checks the state of the event's target and responds accordingly. Then if the target's name is 'Quality', it propagates the event up for UserInterface3D to consume. We won't need to propagate the event if it came from 'Level' since it will be checked by the Main document class before loading the sample game sprite. So we just trace it here for testing. Add the code below:

 
private function onStateChange (e:Event):void  
{ 
    switch (ButtonGroup3D (e.target).state) 
    { 
        case 'easy':  
        _level = LEVEL_EASY;     
        break; 
         
        case 'nrml':  
        _level = LEVEL_NORMAL; 
        break; 
                 
        case 'hard':  
        _level = LEVEL_HARD; 
        break; 
                 
        case 'med':  
        _quality = StageQuality.MEDIUM; 
        break; 
                 
        case 'high':  
        _quality = StageQuality.HIGH; 
        break; 
                 
        case 'best':  
        _quality = StageQuality.BEST; 
        break; 
    } 
    if (e.target.name == 'Quality') dispatchEvent (e); 
    if (e.target.name == 'Level') trace ('the game is set to: ' + ButtonGroup3D (e.target).state); //for testing purposes only         
}

Step 79: Handling Exit Animation - SettingsMenu3D

When the back button is clicked, it calls the superclass' startExit () method. We don't need to override it here since it will work the same way for SettingsMenu3D as it does for the superclass. Once the hideButtons () is called by startExit (), we flash the 2 button groups so they exit the same way as the other buttons and then they are made invisible. After the 2 button groups are handled, it passes the call to the superclass to remove the checkbox and back buttons.

 
override protected function hideButtons ($button:Button3D):void  
{ 
    _gameLevel.flash (); 
    _stageQuality.flash (); 
     
    TweenMax.delayedCall (.1, hideObject, [_gameLevel]); 
    TweenMax.delayedCall (.1, hideObject, [_stageQuality]); 
     
    super.hideButtons($button); //handle the checkbox and back button 
}

The hideObject () method is also just inherited without modification and once it completes, it calls the closeNav () method modified here. Again, it only differs from the superclass in the way the 2 borders close. Add the code below:

 
override protected function closeNav ():void 
{ 
    var sound:Sound = CustomLoader.getInstance ().getSound ('menuCollapse'); 
     
    TweenMax.to (_topNav.interfaceLayer, .2, { alpha:0, delay:.4, overwrite:false } ); 
    TweenMax.to (_bottomNav.interfaceLayer, .2, { alpha:0, delay:.4, overwrite:false, onComplete:dispatchEvent, onCompleteParams:[new Event (EXIT_COMPLETE)] } ); 
     
    TweenMax.to (_topNav, .5, { x:-75, onStart: sound.play, ease:Expo.easeInOut, overwrite:false } ); //close horizontally with the x position 
    TweenMax.to (_bottomNav, .5, { x:75, ease:Expo.easeInOut, overwrite:false } ); //close horizontally with the x position 
}

Step 80: External Access - SettingsMenu3D

Here are the few additional functions for SettingsMenu3D. Add the code below:

 
public function get gameLevel ():String  
{  
    return _level;  
} 
 
public function get stageQuality ():String  
{  
    return _quality;  
} 
 
public function get fullScreen ():Boolean  
{  
    return CheckBox3D (this.getChildByName ('Fullscreen')).state; 
}

These 3 methods provide external access for the 3 configurations stored in SettingsMenu3D. These will be accessed by the Main and UserInterface3D classes. Next, add the exitFullScreen () method:

 
public function exitFullScreen ():void 
{ 
    var cb:CheckBox3D = this.getChildByName ('Fullscreen') as CheckBox3D; 
     
    if (cb.state == true) cb.onObjectClick (); 
}

This method will get called when the user hits the escape key, that way, even if SettingsMenu3D isn't the active menu, it could still be turned off if the user decides to exit fullscreen. You'll see how this is utilized when we test SettingsMenu3D in the next step. We'll have the Main document class listen for this event so we can sync the fullscreen checkbox even if the UserInterface3D isn't active on the stage.


Step 81: Milestone Testing SettingsMenu3D

Alright, There's quite a few modifications we need to make to test SettingsMenu3D. We'll work our way from the top. Go inside Tester's package declaration by the imports and add the code below:

 
import flash.display.StageDisplayState; 
import flash.events.FullScreenEvent;

Next, go to the constructor method. Add the code below after the call to startRendering ():

 
addEventListener (Event.ADDED_TO_STAGE, onAddedToStage);

This event will let us know when the stage is available for access. Next, after the constructor, add the callback method below:

 
private function onAddedToStage (e:Event):void  
{ 
    removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); 
    stage.addEventListener (FullScreenEvent.FULL_SCREEN, onFullscreen); 
    setStageQuality (); 
}

The FullScreenEvent listener will be used to inform SettingsMenu3D to set the full screen checkbox to its 'off' state. The setStageQuality () method will set the quality of the stage based on the default stageQuality in SettingsMenu3D. Add the two methods that respond to them next:

 
private function onFullscreen(e:FullScreenEvent):void  
{ 
    //if the event's fullScreen property is false, tell settings menu to turn off the fullscreen checkbox 
    if (e.fullScreen == false) _settingsMenu.exitFullScreen ();  
} 
 
private function setStageQuality ():void 
{ 
    stage.quality = _settingsMenu.stageQuality; 
}

When you look in the Main document class, you'll see that Tester is instantiated before it is added to the stage. So applying changes to the stage quality right after instantiating SettingsMenu3D will not work. The "stage" object will simply not be available yet. So go inside the init () method for Tester and uncomment the addStaticGlow () and addBackgroundGlow () method calls. Next, comment out the call to addButtonGroup () and add a call to addSettingsMenu () below it. Then add the code below after the init () method:

 
private function addSettingsMenu ():void  
{ 
    _settingsMenu = new SettingsMenu3D ('settings', viewport); 
    _settingsMenu.addEventListener (MainMenu3D.BACK_TO_MAIN, navigateTo); 
    _settingsMenu.addEventListener (ButtonGroup3D.STATE_CHANGED, onStateChange); 
    _settingsMenu.addEventListener (InteractiveScene3DEvent.OBJECT_CLICK, toggleFullscreen); 
    _settingsMenu.addEventListener (MainMenu3D.EXIT_COMPLETE, onMenuExitComplete); 
     
    scene.addChild (_settingsMenu); 
     
    _staticGlow.scaleX = 1.25; 
    _backgroundGlow.scaleX = 1.25; 
     
    _settingsMenu.startIntro (); 
     
    _staticGlow.flash (true); 
    _backgroundGlow.flash (); 
}

Make sure _settingsMenu is declared as an instance variable of type SettingsMenu3D.

We have 4 different listeners assigned to the SettingsMenu3D instance. The navigateTo () method is triggered when the '<<' button is clicked inside SettingsMenu3D. The onStateChange () method is called when the button group named 'Quality' changes state replace the content of the onStateChange () method as shown below. The toggleFullscreen () method is triggered whenever the fullscreen checkbox is clicked and the onMenuExitComplete () method is triggered when the menu finishes its exit animation. We set the scaleX for both _staticGlow and _backgroundGlow to 1.25 times their original size to fit the menu. After calling the startIntro () method, we call the flash () methods for both _staticGlow and _backgroundGlow passing in a Boolean value of true to make them flash horizontally. Add the 2 additional callback methods for SettingsMenu3D below:

 
private function toggleFullscreen(e:InteractiveScene3DEvent):void  
{ 
    trace (_settingsMenu.fullScreen); 
    if (stage) stage.displayState = _settingsMenu.fullScreen == true ? StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL; 
} 
 
private function onStateChange(e:Event):void //this method already exists, just replace the code in it with the code here 
{ 
    stage.quality = _settingsMenu.stageQuality; //replace the contents with this statement 
}

We need to check if the stage property is available before changing its display state. We apply it here to get it ready when we use it for UserInterface3D. There, if the game is active, UserInterface3D will be removed from the stage and will temporarily lose its stage property. the onStateChange () method won't need to check for the stage since, this can only get triggered from within SettingsMenu3D. Lastly, Go to the onMenuExitComplete () method and pass in a value of true for the call to _staticGlow.flash () method. Do the same for the call to _staticGlow.fadeOut () method call inside the navigateTo () method.

Now run the program. You should have a result like the link here. For those using the Flash IDE, fullscreen will not work when you run it from Flash. You'll have to open the SWF file directly with Flash Player.


Step 82: Putting It All Together

Alas, the final class for our project. The UserInterface3D class is the biggest of them all. It will inherit from BasicView just like the Tester class. Think of this class as the main controller, everything will be handled by this class. Go back and check out the link here for the fully functional preview. Play with it for a moment to get a feel of how UserInterface3D manages everything. Here is its list of responsibilities:

  1. set up the scene - ie. set the sort mode to index sort, set the camera's properties etc...
  2. create all the menus and load each of them appropriately
  3. manage all events that come from the 3 menus and apply the changes for their designation - ie. toggle fullscreen, inform the Main document class to load the game
  4. handle the position of 1 large DisplayObject3D that will house all 3 menus
  5. add the 3 effects - background noise, background glow, and static glow and control them
  6. provide external control for loading back the main menu when the game sprite is removed
  7. provide external control for to manage the full screen checkbox in SettingsMenu3D when fullscreen is disabled while the game sprite is active

I've divided the class into two large parts, initialization and event handling. Create a new class named UserInterface3D and have it extend BasicView. Paste the code below replacing all the code inside the new class:

 
package  
{ 
    import flash.display.StageDisplayState; 
    import flash.display.StageQuality; 
    import flash.events.Event; 
    import flash.net.navigateToURL; 
    import flash.net.URLRequest; 
    import flash.system.Capabilities; 
    import flash.system.fscommand; 
    import org.papervision3d.events.InteractiveScene3DEvent; 
    import org.papervision3d.objects.DisplayObject3D; 
    import org.papervision3d.view.BasicView; 
    import org.papervision3d.view.layer.util.ViewportLayerSortMode; 
     
    public class  UserInterface3D extends BasicView 
    { 
        public static const START_GAME:String = 'startGame'; //used as a custom event dispatched when the 'start game' button is clicked from the main menu 
         
        //the following 6 properties will add 3D perspective to the active menu 
        private var _rotationX:Number = .05;  
        private var _rotationY:Number = .05; 
         
        private var _easeOut:Number = .1; 
        private var _reachX:Number = .3; 
        private var _reachY:Number = .3; 
        private var _reachZ:Number = .9; 
         
        private var _xPosArray:Array = [0, -2000, -4000]; //used as 'x' positions for each menu 
         
        private var _grandObject3D:DisplayObject3D; //large DisplayObject3D container for all the menus 
         
        private var _backgroundNoise:BackgroundNoise3D; //instance of the BackgroundNoise3D effect 
        private var _backgroundGlow:BackgroundGlow3D; //instance of the BackgroundGlow3D effect 
        private var _staticGlow:StaticGlow3D; //instance of the StaticGlow3D effect 
         
 
        private var _mainMenu:MainMenu3D; //instance of the Mainmenu3D 
        private var _settingsMenu:MainMenu3D; //instance of the SettingsMenu3D 
        private var _aboutMenu:MainMenu3D; //instance of the AboutMenu3D 
         
        private var _currentMenu:MainMenu3D; //will hold the instance for currently active menu 
        private var _targetMenu:MainMenu3D; //will hold the instance of the target menu that will replace the current menu 
         
        private var _targetX:Number; //will hold the new 'x' position _grandObject will need to move to to show the new menu 
        private var _targetScaleX:Number = 1; //scale used for the background glow, static glow, and background noise effects 
         
        private var _closeApp:Boolean; //defaults to false, if true, the application/webpage will close after the main menu closes 
        private var _loadGame:Boolean; //defaults to false, if true, the START_GAME custom event is dispatched and caught by the Main document class 
         
        public function UserInterface3D () 
        {             
            if (stage) init (); 
            else addEventListener (Event.ADDED_TO_STAGE, init);             
        } 
    } 
}

There's quite a few properties for this class but we've gone through most of them in the Tester class. See the comments for property descriptions. The constructor is set to trigger the init () method only when the stage is available. This avoids errors when trying to access the "stage" object.


Step 83: Initialization - Part 1

Once the init () method is called, it triggers a series of methods that break down the initialization process by responsibility. Add the code below after the constructor:

 
private function init (e:Event = null):void 
{ 
    removeEventListener (Event.ADDED_TO_STAGE, init); //we only need this to happen once 
     
    startRendering (); //start the PV3D rendering engine, this is handled by BasicView's superclass AbstractView 
     
    setupScene (); //set up the scene's properties 
     
    initializeEffects (); //add the 3 effects 
     
    initializeInterface (); //add the 3 menus 
     
    setStageQuality (); //modify stage properties 
}

These methods are not interchangeable, you'll run into bugs if try to call setStageQuality () before initializeInterface (). Add the setUpScene () method after the init () method:

 
private function setupScene ():void 
{ 
    camera.target = DisplayObject3D.ZERO; //set the camera's target to null - free the camera 
     
    viewport.interactive = true; //enable interactivity 
     
    viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT; //fix the sorting issue we discussed during the early part of the project 
     
    _grandObject3D = new DisplayObject3D; //add the container for the 3 menus 
    scene.addChild (_grandObject3D);  
}

This is where you can apply modification for the camera, scene, and viewport. Here, we make sure the camera is not looking at any single DisplayObject3D. For the viewport, the sort mode and interactive properties are modified to accommodate our requirements. This is also where we add the 1 DisplayObject3D that will house the 3 menus. I decided to do this to have better control of the 3 menus. The effects will be added directly into the scene since they won't need to move around like the menus would. Next, add the initializeEffects () after the setUpScene () method:

 
private function initializeEffects ():void 
{ 
    _backgroundNoise = new BackgroundNoise3D ('backgroundNoise', viewport); 
    _backgroundNoise.z = -10; 
     
    _backgroundGlow = new BackgroundGlow3D ('backgroundGlow', viewport); 
    _backgroundGlow.z = 50; 
     
    _staticGlow = new StaticGlow3D ('staticGlow', viewport); 
     
    scene.addChild (_backgroundNoise); 
    scene.addChild (_backgroundGlow); 
    scene.addChild (_staticGlow); 
}

This method simply adds the 3 effects into the scene with slight differences for their 'z' positions. Add the code below for the initializeInterface () method next:

 
private function initializeInterface ():void 
{ 
    initMainMenu (); 
     
    initSettingsMenu (); 
     
    initAboutMenu (); 
     
    addInteractivity (_mainMenu); //loads the main menu into the scene 
}

The initializeInterface () instantiates all of the menus and then loads the MainMenu3D instance into the scene. I'll discuss more about the individual methods when we get to them. Next, add the setStageQuality () method after initializeInterface ():

 
private function setStageQuality (e:Event = null):void 
{ 
    stage.quality = SettingsMenu3D (_settingsMenu).stageQuality; 
 
    switch (stage.quality) 
    { 
        case 'HIGH': 
        case 'MEDIUM': 
            scene.removeChild (_backgroundNoise); 
        break; 
         
        case 'BEST':  
		    scene.addChild (_backgroundNoise); 
        break; 
    } 
}

Here, the stage's quality is set to the default stageQuality of the SettingsMenu3D instance. This method is called every time the SettingsMenu3D instance dispatches a ButtonGroup3D.STATE_CHANGED event. It also checks if the stage's quality is set to 'BEST'; if it is, the BackgroundNoise () instance is added into the scene, if not, it is removed from the scene. We provide it as an option for the user to turn it on or off since it is processor intensive.


Step 84: Initialization - Part 2

Let's now go over adding all the menus. First, the initMainMenu (). Add the code below:

 
private function initMainMenu ():void 
{ 
    _mainMenu = new MainMenu3D ('main menu', viewport); 
     
    _mainMenu.addEventListener (MainMenu3D.START_GAME_CLICKED, navigateTo); 
    _mainMenu.addEventListener (MainMenu3D.SETTINGS_CLICKED, navigateTo); 
    _mainMenu.addEventListener (MainMenu3D.ABOUT_CLICKED, navigateTo); 
    _mainMenu.addEventListener (MainMenu3D.EXIT_CLICKED, navigateTo); 
     
    _mainMenu.x = _xPosArray[0]; 
}

Everything is the same as how we used them in the Tester class except for the 'x' position we apply to each of the menus. Also, notice we didn't add the menu into _grandObject3D. This will be done when addInteractivity () is called passing in the correct menu to load. Next add the initSettingsMenu () method:

 
private function initSettingsMenu ():void 
{ 
    _settingsMenu = new SettingsMenu3D ('settings menu', viewport); 
     
    _settingsMenu.addEventListener (MainMenu3D.BACK_TO_MAIN, navigateTo); 
    _settingsMenu.addEventListener (InteractiveScene3DEvent.OBJECT_CLICK, toggleFullscreen); 
    _settingsMenu.addEventListener (ButtonGroup3D.STATE_CHANGED, setStageQuality) 
     
    _settingsMenu.x = Math.abs (_xPosArray[1]); 
}

The 'x' position is set to the absolute value because the _grandObject3D will be the one moving towards the true value. A good explanation would be: "if the current 'x' position is 0 which is where _mainMenu is and we need to go to _settingsMenu's 'x' position of 2000, _grandObject3D will move to an 'x' position of -2000 centering _settingsMenu in the scene." Remember, all the menus are contained inside _grandObject3D with different 'x' locations. Next, add the initAboutMenu ():

 
private function initAboutMenu ():void 
{ 
    _aboutMenu = new AboutMenu3D ('about menu', viewport); 
     
    _aboutMenu.addEventListener (MainMenu3D.BACK_TO_MAIN, navigateTo); 
    _aboutMenu.x = Math.abs (_xPosArray[2]); //same as we did for _settingsMenu 
}

Once all the Menus have been added, the event flow goes back to the initializeInterface () method and calls addInteractivity (). Add the code below:

 
private function addInteractivity ($menu:MainMenu3D):void 
{             
    _currentMenu = $menu; 
     
    _grandObject3D.addChild (_currentMenu); 
 
    _currentMenu.addEventListener (MainMenu3D.EXIT_COMPLETE, loadTargetMenu); 
    _currentMenu.startIntro (); 
     
    if (_currentMenu is SettingsMenu3D)  
    { 
        _staticGlow.flash (true); 
        _backgroundNoise.flash (true); 
    } 
    else  
    { 
        _staticGlow.flash (); 
        _backgroundNoise.flash (); 
    } 
    _backgroundGlow.flash (); 
}

This is how the _mainMenu is primarily loaded into the scene. This method also gets called every time a button is clicked to change menus. It works with the loadTargetMenu () method discussed later in the event handling section. Here, the _currentMenu property is assigned the menu that needs to load. _grandObject3D then adds that menu as its only child to make it available for the scene. The menu is then added a listener for when it has fully closed. This information will allow the replacement menu to load only after the previous menu has completely exited. After calling its startIntro () method, it checks to see the menu's type; if it is of type SettingsMenu3D, it tells the _staticGlow and _backgroundNoise to both animate in horizontally; if the menu's type is anything else, the two effects will animate in vertically. And that completes the initialization section.


Step 85: Event Handling

The rest of the UserInterface3D's functionality from here on are triggered by events. Add the code that responds when the fullscreen checkbox in _settingsMenu is clicked:

 
private function toggleFullscreen(e:InteractiveScene3DEvent):void 
{     
    if (stage) 
    { 
    	stage.displayState = SettingsMenu3D (_settingsMenu).fullScreen == true ? StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL; 
	} 
}

Next, since the fullscreen can change when the user hits the escape key while the game sprite is active, (this means the UserInterface3D instance has been temporarily removed as a child of the Main document), we'll need to inform _settingsMenu to uncheck the fullscreen checkbox when this happens. Add the code below:

 
public function exitFullScreen ():void 
{ 
    SettingsMenu3D (_settingsMenu).exitFullScreen (); 
}

Now, whenever the Main document class instance detects the user exited fullscreen, the SettingsMenu3D instance is informed. We'll add this listener to the Main document class when we finish building UserInterface3D. Next, add the "level" implicit getter method:

 
public function get level ():String  
{  
    return SettingsMenu3D (_settingsMenu).gameLevel ; 
}

This will allow the Main document to access the level chosen in _settingsMenu and inform the game sprite about it. Next, add the navigateTo () method:

 
protected function navigateTo (e:Event):void  
{ 
    _backgroundNoise.fadeOut (); 
    if (_currentMenu is SettingsMenu3D) _staticGlow.fadeOut (true); 
    else _staticGlow.fadeOut (); 
     
    switch (e.type) 
    {                 
        case MainMenu3D.SETTINGS_CLICKED:  
        _targetMenu = _settingsMenu; 
        _targetScaleX = 1.25; 
        _targetX = _xPosArray[1]; 
        break; 
         
        case MainMenu3D.ABOUT_CLICKED:  
        _targetMenu = _aboutMenu; 
        _targetScaleX = 1.5; 
        _targetX = _xPosArray[2]; 
        break; 
         
        case MainMenu3D.EXIT_CLICKED:  
        _targetMenu = _mainMenu;                 
        _closeApp = true;//close the browser here 
        break; 
         
        case MainMenu3D.BACK_TO_MAIN:  
        case MainMenu3D.START_GAME_CLICKED:      
        if (e.type == MainMenu3D.START_GAME_CLICKED)  
        { 
            _loadGame = true;//load the game here 
        } 
        _targetMenu = _mainMenu; 
        _targetScaleX = 1; 
        _targetX = _xPosArray[0]; 
        break; 
    } 
}

This works the same way as we used it for the Tester class. Here, we use it to scale the effects and change the values for _closeApp and _loadGame properties. Both will be used by the loadTargetMenu () method discussed next. Add the code below:

 
public function loadTargetMenu (e:Event):void 
{             
    if (_closeApp == true)  
    { 
        var jscommand:String = "window.open('close.html','_self');";  
        var req:URLRequest = new URLRequest("javascript:" + jscommand + "void (0)"); 
         
        if (Capabilities.playerType == 'PlugIn') navigateToURL(req, "_self");  
        if (Capabilities.playerType == 'StandAlone') fscommand("quit", "true"); 
    } 
     
    if (_loadGame == true)  
    { 
        dispatchEvent (new Event (START_GAME)); 
        _loadGame = false; //set back to false 
        return;//no need to continue so stop here 
    } 
     
    _currentMenu.removeEventListener (MainMenu3D.EXIT_COMPLETE, loadTargetMenu); //remove the listener 
    _grandObject3D.removeChild (_currentMenu); //remove the current menu that just closed 
     
    _backgroundNoise.scaleX = _backgroundGlow.scaleX = _staticGlow.scaleX = _targetScaleX; //scale the effects correctly 
     
    addInteractivity (_targetMenu); //load the new target menu assigned in the navigateTo () method 
    _grandObject3D.x = _targetX; //position correctly 
}

This method gets called after the current menu has completely exited. That's why the webpage/standAlone player terminates only after the menu fades out when you click the exit button. If _loadGame is true, a UserInterface3D.START_GAME is dispatched to inform the Main document to load the game sprite. Once the event has dispatched, _loadGame is set to false again and the method is terminated prematurely. The game Sprite will be loaded and UserInterface3D will then be removed from the stage. This is also the method the Main document will call to load the UserInterface3D instance back into the stage.

Next, add the onRenderTick () method below:

 
override protected function onRenderTick (event:Event = null):void  
{         
    _backgroundNoise.update (); //update the background noise 
     
    if (_currentMenu is AboutMenu3D) AboutMenu3D (_currentMenu).update (); //if _settingsMenu is active, update it to enable text scrolling 
     
   var xDist:Number = mouseX - stage.stageWidth * .5; 
    var yDist:Number = mouseY - stage.stageHeight * .5; 
     
    var targetRotX:Number = (-yDist * _rotationX - _currentMenu.rotationX) * _easeOut; 
    var targetRotY:Number = (-xDist * _rotationY - _currentMenu.rotationY) * _easeOut; 
     
    _currentMenu.rotationX += targetRotX; 
    _currentMenu.rotationY += targetRotY; 
     
    _backgroundNoise.rotationX = _currentMenu.rotationX; 
    _backgroundNoise.rotationY = _currentMenu.rotationY; 
     
    _backgroundGlow.rotationX = _currentMenu.rotationX; 
    _backgroundGlow.rotationY = _currentMenu.rotationY; 
     
    _staticGlow.rotationX = _currentMenu.rotationX; 
    _staticGlow.rotationY = _currentMenu.rotationY; 
             
    super.onRenderTick (event); 
}

The same application from how we used it in Tester. But instead, the camera stays in the same place and everything else is rotated and moved.


Step 86: Final Additions for the Main Document Class

As I've mentioned before, The Main document class will have some part of controlling the game Sprite and the UserInterface3D. Go back in the Main class and add 3 more instance properties:

 
private var _sprite:Sprite; //will be used as the game sprite  
private var _ui3d:UserInterface3D; //will hold reference for the UserInterface3D instance 
private var _textField:TextField; //text inside the game sprite

Next, go inside the onLoad () method and comment out anything that deals with the Tester class then add the code below.

 
_ui3d = new UserInterface3D; 
_ui3d.addEventListener (UserInterface3D.START_GAME, startGame); 
addChild (_ui3d);

This simply adds a UserInterface3D instance into the stage and listens for when the start game button is clicked. Next, add the startGame () method:

 
private function startGame (e:Event):void  
{ 
    trace ('load game sprite/movieclip here...'); 
    if (! _sprite)  
    { 
        _sprite = new Sprite; 
         
        _sprite.graphics.lineStyle (1); 
        _sprite.graphics.beginFill (0x000040); 
        _sprite.graphics.drawCircle (0, 0, 100); 
                 
        if (! _textField)  
        { 
            _textField = new TextField; 
            _textField.mouseEnabled = false; 
            _textField.autoSize = TextFieldAutoSize.LEFT; 
            _textField.border = true; 
            _textField.width = 200; 
            _textField.height = 30; 
            _textField.textColor = 0xFFFFFF; 
        } 
             
        _sprite.addEventListener (MouseEvent.CLICK, activateMenu); 
        _sprite.addChild (_textField); 
         
    } 
    _textField.text = 'Player Type: ' + Capabilities.playerType + ' ' + 'Game Level: ' + _ui3d.level; 
     
    _textField.x = -_textField.width / 2; 
    _textField.y = -_textField.height / 2; 
     
    _sprite.x = stage.stageWidth / 2 ; 
    _sprite.y = stage.stageHeight / 2 ; 
    addChild  (_sprite); 
     
    _ui3d.stopRendering (); //no need to render when off the stage 
    removeChild (_ui3d); 
}

Here, we load a simple sprite that represents your game. The text within it gets the information directly from the UserInterface3D instance, as well as displaying whether the SWF is loaded in a plugin or standalone Flash Player. It is assigned a click listener so the Main document class can load the UserInterface3D instance back again when this sprite is clicked.

The method below takes care of reloading UserInterface3D:

 
private function activateMenu (e:MouseEvent):void  
{ 
    removeChild (e.target as Sprite); //remove the game sprite 
    addChild (_ui3d); 
    _ui3d.startRendering ();  
    _ui3d.loadTargetMenu (new Event (MainMenu3D.EXIT_COMPLETE)); 
}

Congratulations! You have successfully completed the project! Now run the fully functional application to see the result.


Conclusion

Like I said in my Atoms tutorial, Papervision3D is a very straightforward and powerful tool. Experiment with it and you will come up with all kinds of cool 3D effects ranging from simple simulations to sophisticated interfaces. You now have under your belt experience of creating a GUI framework loaded with effects using PV3D. Along with the source download is a simple drawing application I've put together to help with your interface design. It comes with a short note on how to use it.

I hope you enjoyed reading this tut.

As always, for any questions, suggestions, or concerns, please post a note in the comments section.

Thanks for Reading!

Advertisement