TEST_ND2D_feature

Fullscreen support in Stage3D was like pulling teeth at first. For this reason, I’ve put together a few observations during the course of this painful process to shave off a few hours of research for you (and save you from self-inflicted pain with your keyboard or banging your head against your desk):

  • When toggling between Fullscreen and Standard mode, it looks like an intermediate Event.RESIZE is fired before the stage is actually resized to the Fullscreen state. For example, if my Standard size was 800 x 600, and my Fullscreen size was 1920 x 1080, I would sometimes get the event fired at 800 x 639.33333 (if I remember correctly).
  • Since it takes some User Input to change to Fullscreen mode (ex: Mouse or Keyboard due to a Flash Player security feature), when you assign stage.displayState = StageDisplayState.FULL_SCREEN it will fire off an Event.RESIZE event. Most developers would think that stage.stageWidth and stage.stageHeight would contain the desired results immediately after this assignement… but that is not the case! For this reason, I’ve chosen to intercept any Event.RESIZE events during a User initiated Fullscreen toggle and just fire one off manually when the User Input finishes. (shown in more details below)
  • It’s best to set the stage.align = StageAlign.TOP_LEFT and stage.scaleMode = StageScaleMode.NO_SCALE for this to work correctly.
  • When the stage is resized, the Context3D will have to be reconfigured too because since we are displaying in NO_SCALE (at 100% actual size), we have to tell the context3D.configureBackBuffer(...) method what are the new Fullscreen dimensions we want to occupy. Also, it will be important to place the used Stage3D‘s x and y coordinates so the GPU renders nicely in the center of the screen (since this approach involves scaling up proportionally from whatever size the stage was originally).

Confusing? Alright, maybe some code will clarify this matter.

I have a Fullscreen class that handles some typical operations during a Fullscreen toggle. Here’s a preview of the important chunk that manually dispatches the Event.RESIZE after I’ve stored the values that I need:

		//This static callback-method can be assign to any User-Input events
		public static function callbackToggleFullscreen(e:Event=null):void {
			_IS_RESIZING =	true;
			
			if (isFullscreen) {
				_STAGE.displayState =	StageDisplayState.NORMAL;
			} else {
				_STAGE.displayState =	StageDisplayState.FULL_SCREEN;
				
				//AFTER RESIZE:
				if (!_FULLSCREEN_SIZE) {
					_FULLSCREEN_SIZE =	getStageRect();
				}
			}
			
			_IS_RESIZING =	false;
			
			_STAGE.dispatchEvent(new Event(Event.RESIZE));
		}

Instead of writing a callback-method each time I want to trigger the Fullscreen mode from a button or keyboard shortcut, I can drop this in my target’s
.addEventListener( MouseEvent.CLICK, Fullscreen.callbackToggleFullscreen) (as you’ll see in the implementation further down).

There is a couple more methods that are key to making this work:

		//This is like an initializing method (important to call early in your app!):
		public static function setupStage(pStage:Stage = null):void {
			if (_STAGE) {
				return;
			}
			
			if (!pStage) {
				pStage =	BigPMediator.STAGE;
			}
			
			_STAGE =			pStage;
			
			_STAGE.align =		StageAlign.TOP_LEFT;
			_STAGE.scaleMode =	StageScaleMode.NO_SCALE;
			
			_STANDARD_SIZE =	getStageRect();
			
			Listener.AddListener(Event.RESIZE, onStageResize, _STAGE, null, 1);
		}
		
		//Event callback method for intercepting resize events while resizing in fullscreen-mode:
		private static function onStageResize(e:Event):void {
			if (!_IS_RESIZING) {
				return;
			}
			
			e.stopImmediatePropagation();
			e.stopPropagation();
			e.preventDefault();
		}

Regarding the Context3D reconfiguration – the only reason I know about this is thanks to some guy who told me to check up ND2D’s forum thread on the same issue I was experiencing:

  1. Stackoverflow: Question that I started.
  2. Pastebin.com: Source code that I found for the back-buffer fix (by Rolpege).

I’ve made some modifications to it, but you should really thank Rolpege (I have no idea who he is… well… he’s my hero now, I know THAT much!). It is basically an extension to ND2D‘s World2D class.

package com.bigp.nd2d_extensions
{
	import de.nulldesign.nd2d.display.World2D;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3DRenderMode;
	import flash.events.Event;
	import flash.geom.Rectangle;
	/**
	 * @author Rolpege, edited by Pierre Chamberlain
	 */
	public class WorldDynamic2D extends World2D
	{
		public static var WIDTH:Number = 800;
		public static var HEIGHT:Number = 600;
		
		protected var firstResizeTime:Boolean = true;
		
		public function WorldDynamic2D(pFrameRate:uint=60, pAntiAlias:uint=2)
		{
			super(Context3DRenderMode.AUTO, pFrameRate);
			
			antialiasing = pAntiAlias;
		}
		
		override protected function addedToStage(event:Event):void
		{
			stage.align=StageAlign.TOP_LEFT;
			stage.scaleMode=StageScaleMode.NO_SCALE;
			
			WIDTH =	stage.stageWidth;
			HEIGHT = stage.stageHeight;
			
			super.addedToStage(event);
		}
		
		override protected function resizeStage(e:Event = null):void
		{
			if (firstResizeTime)
			{
				if(!context3D) return;
				var rect:Rectangle = bounds ? bounds : new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
				stage.stage3Ds[stageID].x = rect.x;
				stage.stage3Ds[stageID].y = rect.y;
				
				context3D.configureBackBuffer(rect.width, rect.height, antialiasing, false);
				camera.resizeCameraStage(rect.width, rect.height);
			}
			else
			{
				var zoom:Number = Math.min(stage.stageWidth / WIDTH, stage.stageHeight / HEIGHT);
				context3D.configureBackBuffer(WIDTH * zoom, HEIGHT * zoom, antialiasing, false);
				
				var stage3D:Stage3D = stage.stage3Ds[stageID];
				stage3D.x = (stage.stageWidth - (WIDTH * zoom)) * 0.5;
				stage3D.y = (stage.stageHeight - (HEIGHT * zoom)) * 0.5;
			}
			
			firstResizeTime = false;
		}
	}
}

Although I won’t get into the full details of it, you should create a class that manages the layout of your DisplayObjects (ex: lock a sprite’s position relative to a screen corner or edge). It will be very important if you plan to have a HUD made with standard DisplayObjects (Sprite, MovieClip, Button, TextField, etc.). I’ve created such a class called LayoutManager that does just this for me.

Here’s a re-post on how I’ve implemented the above classes to correctly toggle between Fullscreen and Standard display mode in an ND2D example:
package  
{
	import com.bigp.display.AbstractMain;
	import com.bigp.display.Fullscreen;
	import com.bigp.managers.LayoutManager;
	import com.bigp.nd2d_extensions.TextureAtlasHelper;
	import com.bigp.nd2d_extensions.WorldDynamic2D;
	import de.nulldesign.nd2d.display.Scene2D;
	import de.nulldesign.nd2d.display.Sprite2D;
	import de.nulldesign.nd2d.materials.texture.Texture2D;
	import de.nulldesign.nd2d.materials.texture.TextureAtlas;
	import flash.display.BitmapData;
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	/**
	 * @author Pierre Chamberlain
	 */
	[SWF(frameRate="60",width="640",height="480",backgroundColor="0x000000")]
	public class TestTexture extends AbstractMain 
	{
		private var _ballTexture:Texture2D;
		private var _ballAtlas:TextureAtlas;
		private var _ball:Sprite2D;
		
		private var _world:WorldDynamic2D;
		private var _scene:Scene2D;
		
		/////////////////////////////////////////////// MAIN ENTRY:
		
		public override function main():void {
			setupWorld();
			setupFullScreenButton();
		}
		
		/////////////////////////////////////////////// PRIVATE:
		
		private function setupWorld():void {
			_world =	new WorldDynamic2D(60, 0);
			_world.addEventListener(Event.INIT, onWorldInit);
			
			addChild(_world);
			
			_world.start();
		}
		
		private function setupScene():void {
			_scene =	new Scene2D();
			
			_scene.backGroundColor =	0x222244;
			_scene.addChild( setupBeachBall() );
			
			_world.setActiveScene( _scene );
			
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onUpdate);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseHandler);
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseHandler);
		}
		
		private function setupBeachBall():Sprite2D {
			_ball =	new Sprite2D(_ballTexture);
			_ball.setSpriteSheet( _ballAtlas );
			
			return _ball;
		}
		
		private function setupFullScreenButton():void 
		{
			var button:Sprite =	new Sprite();
			var g:Graphics =	button.graphics;
			
			var clrBlack:uint =	0x000000;
			var clrWhite:uint =	0xffffff;
			
			var coordinates:Array =	[
				[clrBlack, 0, 0, 30, 30],
				[clrWhite, 5, 5, 20, 20],
				[clrBlack, 7, 7, 16, 16],
				[clrBlack, 0, 10, 30, 10],
				[clrBlack, 10, 0, 10, 30]
			];
			
			for (var c:int; c < coordinates.length; c++) {
				var command:Array = coordinates1;
				g.beginFill(command[0]);
				g.drawRoundRect(command[1], command[2], command[3], command[4], 6, 6);
				g.endFill();
			}
			
			button.useHandCursor =	true;
			button.buttonMode =		true;
			button.x =				stage.stageWidth - button.width - 10;
			button.y =				stage.stageHeight - button.height - 10;
			button.addEventListener(MouseEvent.CLICK, Fullscreen.callbackToggleFullscreen);
			
			addChild( button );
			
			//Sets the button to always be aligned to the bottom-right corner
			LayoutManager.alwaysAlignTo( button, LayoutManager.ALIGN_BOTTOM_RIGHT );
		}
		
		/////////////////////////////////////////////// EVENT-LISTENERS:
		
		private function onWorldInit(e:Event):void {
			TextureAtlasHelper.load("textures/BeachBall.plist", onDataLoaded, 60);
		}
		
		private function onDataLoaded( pBitmapData:BitmapData, pAtlas:TextureAtlas ):void {
			_ballTexture =	Texture2D.textureFromBitmapData(pBitmapData);
			_ballAtlas =	pAtlas;
			
			setupScene();
		}
		
		private function onUpdate(e:MouseEvent):void {
			var stage3DLocation:Point =	Fullscreen.getMouseLocation();
			_ball.x =	stage3DLocation.x;
			_ball.y =	stage3DLocation.y;
			
			tracer(stage.stageWidth + " : " + stage.stageHeight);
			
			e.updateAfterEvent();
		}
		
		private function onMouseHandler(e:MouseEvent):void {
			_ball.scaleX =	// follow on next line...
			_ball.scaleY =	e.type == MouseEvent.MOUSE_DOWN ? 2.0 : 1.0;
		}
	}
}

And here’s the demo:

This area requires
Adobe FlashPlayer version 11 or above.
iOS Devices are not currently supported.