Fullscreen preloader - 3D tunnel ( Esquimo + actionscript )

There are quite a few tutorials on the web about preloaders and I guess the topic will be still valid because there always be a need of loading a bigger portion of data (such as online games, developed applications/websites based on flash) and it might take some time while downloading. We must be aware of different internet speed in several regions of the world. At some locations internet providers offer few times slower broadband than in others. The quality of internet access may be also different on some mobile platform. Thus using preloaders is still topical. In this tutorial we will try a bit variant attempt. I will show you how to make a simple fullscreen 3D animation using Esquimo and adding few lines of actionscript. Esquimo is a 3D animation editor with graphics interface that produces SWF as an output file, you can download demo version from here.

Here is what the final effect looks like. I prepared 3 examples (scroll the page to the end to find source files to download ). Click on the miniature to see it working:

Esquimo preloader - example #1 Esquimo preloader - example #2 Esquimo preloader - example #3

3D background animation

First let's focus on making animated background. Above examples has been made with Esquimo version 1.3.2 but obviously you can use it any newer version if available. The main idea is creating torus or more of them, make it rotating ( to make an illusion of flying through a tunnel) and add some gentle camera motion.

Example #1

Create new Esquimo file and add a Torus to the scene. We can stay with default size of the torus but will change some of parameters to make it looking like wired and dotted techno style object.Change following parameters:
Parameters
Sides 1:
60
Sides 2:
20
Radius 1 lines:
ON
Radius 2 lines:
ON
Double sided:
ON
Surface->Fill
Shader:
Solid color
Fill color:
#00072D
Surface->Lines
Visible:
Yes
Thickness:
1.6
Color:
#00003A
Surface->Points
Visible:
Yes
Size:
3
Color:
#0C00FF


You won't notice any interesting effect in default views. We have to go inside of the torus. Create a Target Camera and place it more or less as on the figure below: Positioning the camera inside of torus

Press SHIFT+W to toggle wiremode for viewports. You don't have to tightly keep mentioned parameters and positions. Possibly you may get more interesting effects playing a bit with objects parameters. You don't have to precisely follow camera and light positioning either. This is only to show you how it was made.

In order to heighten speed impression we are going to change camera's field of view from default 30 to 110. We can also get an nice extra effect by squeezing the torus in Z axis to around 50% of its original size.

Camera view after modifications

Now, toggle wire mode off (SHIFT+W) to see the surface of the torus. Camera view should look like on the picture below. You may need to position camera a bit to achieve similar view.

Generated surface of torus

Now let's make it moving. And here is the tricky part. Instead moving camera round through inside of the torus we will rotate torus causing visually the same effect - less work to do :). Before we start to animate anything we are going to set overall parameters of the animation. Go to Scene tab and change a few options.

General scene settings

60 FPS will ensure smooth playing. 800 frames will be enough to reserve some time for camera motion. The summary motion will consist of torus rotating and camera moving over the plane which is perpendicular to the flight direction. The size of animation ( set on 640x300 pixels ) doesn't matter in our case as we are going to display it on fullscreen. This is why Enable resizing option is checked. This options ensures that the output SWF will fit available area of its HTML container. When you preview your animation ( press F5 ) the SWF is playing in specified scene size which is 640x300 in this case, but you can easy check what it is looking like by maximize the preview window. Make sure you have got Camera view active when you preview the animation.

All right, let's spin the torus round. Select torus and switch to "rotate object" mode ( press 4 key or click the rotate object icon. Put an animation key in first frame because there is no key set by default. Click on key icon next to timeline slider or press K key. It may be helpful to watch adding keys in timeline view. Click on Timeline tab to display it. Once you have the first rotation key set go to last frame (800) and put another one. You can either press K and adjust the value of angle or rotate the torus in main view what add the animation key automatically ( as long as the "automatic keying" icon is on ). Set the angle of rotation for the torus to 720° for Z axis.

Adding rotation keys for torus

When you preview your animation you should be able to see effect like here. Not bad, but we can make a little bit more interesting. We are going to add camera and light motion. Move the camera over the torus cross-section plane approximately every 200 frames. Watch the Camera view to keep the camera inside the torus through the whole animation period. We don't want any blinking or clipping objects on the screen.

Adding a camera motion in torus cross-section plane

Additionally I added small rotation of camera at each key. At this stage your animation may look like this. The camera is moving pretty smoothly but you might notice kind of bouncing at key frames. This is because of interpolation algorithm for rotation keys which is set to Linear by default. In order to achieve going through key frames perfectly smoothly you have to change parameter Spline type for each torus rotation key from Linear to Bezier.

Change of interpolation algorithm for animation keys

And check it after the change. You may come across a problem of adjusting bezier handlers at first and last frame to get the trajectory smooth in the whole range. Check the part of Esquimo manual - Time axis, keyframes to find out on how to work it around.

Last thing we are going to add is a light motion. The default light is already in the scene and it's placed in the centre of the scene. Let's add our own light - create a Point light - and give it some motion trajectory as well as for camera. Just move it on different positions around the torus pipe every 200th frame so that the inside of the torus is lighten from different angle as the animation goes.

Adding a moving light source

Here is the the result after adding flying light source and here is an Esquimo source file . Of course this is only my attempt and you may don't like it. If you find the scene too dark or you just don't like it I recommend to play with settings a bit. Few changes may can make this scene totally different.

Below I will briefly go through the process of creation second and third example animation. If you don't need it you can jump to the part about scripting as the complete preloader requires bit of AS3.




Esquimo preloader - example #2

Example #2

Way of rotating the torus All examples of the tutorial are based on toruses animation. Obviously you can design some animation using any objects, the more time you take for it the more interesting effects you get. I just wanted to show you that you can make some animation within few minutes with no big experience in 3d animation. The second example animation is really easy do make. Go to Scene tab, set a length of animation (Total frames) to 200 frames and Background color to #4D0049. Create the torus and give it parameters as on the figure below. After you set it add animation keys at first and last frame to make the torus spinning round along its Z axis continuously.

Esquimo 3D - Torus parameters settings

Then duplicate a torus by copying ( CTRL+D ). When duplicating dialog window pops up select a Copy radiobutton. Make sure the option Duplicate splines is marked. On such created object when you make any change over it (Torus 2) it won't apply to its original (Torus 1). I've made a simple trick to create kind of echo rings. The only changes you need to do on Torus 2 is a change of Radius 2 from 0.300 to 0.320, trim down the transparency to 40 and set the value of Z axis rotation to 22. That makes the second torus going a little bit slower.

Setting a rotation for the second torus

Finally add the camera to the scene. Create a Free camera and place it more or less as on the figure below. Actually the position and angle of the camera is meaningless. This is only my proposal. Move the camera around and play with different angles and field of view parameter if you want to. My settings are not mandatory

Camera placement - 3d preloader background animation scene
You can grab Esquimo scene file - here




Esquimo preloader - example #3

Example #3

The third example is also based on spinning two toruses where one is slightly rotated in relation to other. To main action is performed by moving camera. Let's try to recreate it. Create a new empty scene and go to Scene tab and set up a overall scene parameters first. Set total animation length to 400 frames and Background color to #D7D7D7. Remember to check Enable resizing option as the animation will be stretched to fullscreen.

So, One more time, create a torus and give it parameters as follows:

Setting a parameters for the torus

Animate newly created torus by setting rotation keys at first and last frame with values respectively (0,0,0) and (0,0,360). Duplicate the Torus 1 as a copy. Mark Duplicate splines option.
The only adjustment to make for copied torus is a change of Offset (axis 1) parameter and turning on the surface filling by solid color #FCFCFC instead of displaying lines.

Adjusting parameters for the duplicated torus

The last thing I did for this example was adding a camera with simple movement. I just made Target camera moving there and back facing center of the torus. It might be hard to explain the placement of camera position keys and it might be easier for you to analyse the scene directly in Esquimo. Here is the scene file.

Decorating with actionscript

A complete preloader needs a bit scripting. I used a FlashDevelop ( free open sourced code editor ) for this tutorial. I find FlashDevelop excellent tool for code creation. If you are not familiar with it yet, check some info about it - www.flashdevelop.org/. Of course you can use any your favourite editor and as3 compiler if you like. Adobe Flash Professional ( CS5 and above ) will do the job as well.

Ok, let's set up our environment first. Open FlashDevelop and create new AS3 project. Your folders structure should look like on the picture below.


Folders structure of newly created AS3 project Folders structure after adding assets and libraries
Folders structure of newly created AS3 project Folders structure after adding all assets and libraries

As you likely know we put our code into src/Main.as file. Hovewer, before you put your first line of code set necessary project setting.

FlashDevelop - project settings

If you own Adobe Flash Professional in version CS5 or higher you can change "Compilation Target" option from Application to Other IDE. I've been using Flex to compile the project. If you don't know how to configure Flex to work together with FlashDevelop check documentation chapters about installation and configuration.

Make sure that Flex on your machine will compile to SWF that uses Stage3D compatible routines. Find flex-config.xml file and check following lines:

Flex-config.xml settings

Here is the full source code ( content of Main.as file ). I explain fragments which needs additional commment below.

  1. package
  2. {
  3.     import flash.display.DisplayObject;
  4.     import flash.display.MovieClip;
  5.     import flash.display.Sprite;
  6.     import flash.display.Loader;
  7.     import flash.events.Event;
  8.     import flash.events.ProgressEvent;
  9.     import flash.filters.GlowFilter;
  10.     import flash.net.URLRequest;
  11.     import flash.text.TextField;
  12.     import flash.text.TextFormat;
  13.     import flash.text.TextFieldAutoSize;
  14.     import flash.geom.Matrix;
  15.     import flash.utils.getTimer;
  16.     import com.eclecticdesignstudio.motion.Actuate;
  17.     import com.eclecticdesignstudio.motion.easing.Quad;
  18.  
  19.     /**
  20.      * ...
  21.      * ninevectors 2012
  22.      */
  23.    
  24.     public class Main extends Sprite
  25.     {
  26.        
  27.         [Embed( source = "../lib/xenotron.ttf", fontName = 'Xenotron', embedAsCFF = 'false')]
  28.         private var myFont:Class;
  29.        
  30.         private var esquimoSWFLoader:Loader;
  31.         private var esquimoSWF:MovieClip;
  32.         private var counter:Sprite, rBox:Sprite,  text1:TextField, text2:TextField;
  33.         private var myLoader:Loader;
  34.        
  35.         public function Main():void
  36.         {
  37.             var myurl:URLRequest = new URLRequest("flash/option1.swf");
  38.             esquimoSWFLoader = new Loader();
  39.             esquimoSWFLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, SWFloaded);
  40.             esquimoSWFLoader.load(myurl);
  41.         }
  42.        
  43.         public function SWFloaded(e:Event):void
  44.         {
  45.             esquimoSWF = MovieClip(esquimoSWFLoader.content);
  46.             addChild(esquimoSWF);
  47.             esquimoSWF.addEventListener("Esquimo3D.InitializationFinished", esquimoInitialized);
  48.         }
  49.        
  50.         public function esquimoInitialized(e:Event):void
  51.         {
  52.             prepareCounterGraphics();
  53.             prepareLoading();
  54.         }
  55.        
  56.         public function prepareLoading():void
  57.         {
  58.             myLoader = new Loader();
  59.             myLoader.load(new URLRequest ("http://www.yourhostdomain.com/test_picture.jpg?t="+getTimer()));
  60.             myLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, updateProgress)
  61.             myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, contentLoaded);
  62.             myLoader.contentLoaderInfo.addEventListener(Event.OPEN, initProgressAnimation);
  63.         }
  64.        
  65.         public function initProgressAnimation(e:Event):void
  66.         {
  67.             Actuate.tween(text1, 0.5, { alpha:0.5 }, false).repeat().reflect();
  68.         }
  69.        
  70.         public function updateProgress(pe:ProgressEvent):void
  71.         {
  72.             text2.text = String(Math.floor(100 * pe.bytesLoaded / pe.bytesTotal))+"%";
  73.         }
  74.        
  75.         public function contentLoaded(e:Event):void
  76.         {
  77.             Actuate.tween(text2, 0.5, { alpha:0 }, true);
  78.             text1.text = "ready..."
  79.             Actuate.tween(text1, 1, { alpha:1, x:75, y:12}, true);
  80.             Actuate.transform (text1, 1).color (0xFFFFFF, 1);
  81.             var myGlow:GlowFilter = new GlowFilter(0xffffff, 1, 20, 20, 1, 3);
  82.             text1.filters = [myGlow];
  83.             Actuate.effects (text1, 0.5).filter (GlowFilter, { strength : 0.1 } ).repeat().reflect();
  84.             Actuate.tween(rBox, 3, { alpha:0 } ).delay(2);
  85.         }
  86.        
  87.         public function prepareCounterGraphics():void
  88.         {
  89.             counter = new Sprite();
  90.            
  91.             rBox = new Sprite();
  92.             rBox.graphics.beginFill(0x000000, 1);
  93.             rBox.graphics.lineStyle(1, 0x575ff, 1);
  94.             rBox.graphics.drawRoundRect(0, 0, 230, 35, 20, 20)
  95.             var counterGlow:GlowFilter = new GlowFilter();         
  96.             counterGlow.color = 0x2222ff;
  97.             counterGlow.blurX = counterGlow.blurY = 40;
  98.             counterGlow.quality = 3;
  99.             rBox.filters = [counterGlow];
  100.             rBox.alpha = 0.3;
  101.            
  102.             var myTextFormat1:TextFormat = new TextFormat();
  103.             myTextFormat1.font = "Xenotron";
  104.             myTextFormat1.size = 14;
  105.             myTextFormat1.color = 0x4575ff;
  106.             text1 = new TextField();
  107.             text1.text = "Loading";
  108.             text1.embedFonts = true;
  109.             text1.autoSize = TextFieldAutoSize.LEFT;
  110.             text1.defaultTextFormat = myTextFormat1;
  111.             text1.setTextFormat(myTextFormat1);
  112.             text1.x = 20;
  113.             text1.y = 15;
  114.            
  115.             var myTextFormat2:TextFormat = new TextFormat();
  116.             myTextFormat2.font = "Xenotron";
  117.             myTextFormat2.size = 30;
  118.             myTextFormat2.color = 0xffffff;
  119.             text2 = new TextField();
  120.             text2.embedFonts = true;
  121.             text1.autoSize = TextFieldAutoSize.LEFT;
  122.             text2.defaultTextFormat = myTextFormat2;
  123.             text2.setTextFormat(myTextFormat2);
  124.             text2.x = 120;
  125.             text2.y = 6;
  126.            
  127.             skewIt(text1, 12);
  128.             skewIt(text2, 12);
  129.            
  130.             addChild (counter);
  131.             counter.addChild(rBox);
  132.             counter.addChild(text1);
  133.             counter.addChild(text2);
  134.            
  135.             stage.addEventListener(Event.RESIZE, onScreenResize);
  136.             stage.dispatchEvent( new Event (Event.RESIZE));
  137.         }
  138.        
  139.         public function onScreenResize(e:Event):void
  140.         {
  141.             counter.x = stage.stageWidth - 300;
  142.             counter.y = stage.stageHeight - 80;
  143.         }
  144.        
  145.         public function skewIt(target:DisplayObject, deg:Number):void
  146.         {
  147.             var mtx:Matrix = new Matrix();
  148.             mtx.c = -deg * Math.PI/180;
  149.             mtx.concat(target.transform.matrix);
  150.             target.transform.matrix = mtx;
  151.         }
  152.     }
  153. }

lines 3-17: Importing necessary classes. FlashDevelop takes care of it and adds needed "import" line with relevant path to library where the class definition is placed for the class member which is used first time. It applies to standard flash libraries. I had to add a few manually but I mention about it below

lines 27-28: I was browsing www.dafont.com - great online font library and found cool looking font called xenotron. You can grab it here : http://www.dafont.com/xenotron.font. Copy it into lib folder. If you need to embed non-standard fonts into your project it requires using [embed] metatag and attaching it as a class to your project. There is an issue with embedding font by Flex ( too long story to tell it here ), but you can easily fix it adding embedAsCFF = 'false'. Otherwise you might get an error: Error: Unable to transcode xenotron.ttf

lines 30-33: Some of elements that are referred to in several functions are declared here to assure the scope within the whole class.

lines 35-41: Initialization of Loader that loads the Esquimo SWF with background animation. This is going to be the first layer, on top of which another elements will be adding. You need to copy the SWF that was exported by Esquimo into location which you can refer to later. I created a flash folder in bin and placed the exported file (option1.swf) in there. The bin folder is where your project will be compiled. Here are located files that you will upload to your web hosting server.

lines 43-48: After the SWF with animation is loaded is good to give some time to graphics card for initialization. Esquimo internal engine will also need a short time to initialize. Here is when Esquimo3D.InitializationFinished Esquimo's built in event comes handy. Using it allows to prevent other content displaying before Esquimo animation starts. The schema below shows the initialization process flow:

Initialization process flow

Once Esquimo is initialized the function prepareCounterGraphics is called. AS3 drawing API is mostly using here and I guess it doesn't need deeper explanations as they are dozens of tutorials about drawing using flash API on the net. Though lines 135-136 may require extra comment. It is to keep progress indicator sticked to the right bottom corner of the screen regardless of screen resolution. Position of the indicator will be adjusted during resizing of browser window - onScreenResize event is invoked then.
stage.addEventListener(Event.RESIZE, onScreenResize); - this line is instead of traditional init position commands. It calls the onScreenResize function once what places progress indicator in right position.

After all initialization routines loading process begins. I wanted to animate the progress indicator to bring a bit life into it. I used for animation Actuate library which I much prefer over the Adobe standard tween classes. Again, I wouldn't like to lenghten this tutorial and recommend you to visit Actuate project's page. You can find really easy follow a tutorial there that allow you to start using it in few minutes.

Using Actuate library requires adding extra lines at importing area:

import com.eclecticdesignstudio.motion.Actuate;
import com.eclecticdesignstudio.motion.easing.Quad;

Of course you have to copy all necessary files to src folder

Before you start testing the code you need to know that main loaded file must be refered to by using http request. You can not test it locally. Loaded file has to be of format which is accepted by compiler. It may be a picture, a sound file or .flv movie for instance. In order to prevent caching of loaded file by browser I'm adding a current time as a parameter of request ( line 59 ). This common trick is very helpful while testing preloaders.

In earlier part of the tutorial we went through process of creation 3 examples. You can use any of them as the background animation. The only thing you will need to adjust are colors of progress indicator elements ( fonts, frame, glow, etc ) to match colors in the background.

Putting everything together - adding HTML

The only thing is left is to create a basic page that displays the preloader. I think these are basics that everyone knows or at least can find using google. The only issues which needs to be highlighted in terms of the tutorial concern some of parameters of javascript that embeds the flash file. It doesn't matter whether you use SWFobject, Gedi's embedding script or any other. Just make sure that wmode parameter is set to direct or gpu and width and height to 100%. The listing below is what is looks like with use of Gedi's embedding script

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style>
    body, html{ background:#000; padding:0; margin:0; height:100% }
</style>
<script type="text/javascript" src="scripts/gedi_create_flash.js"></script>
</head>
    <body>
        <div id="preloader" style="height:100%; width:100%; position:fixed">
            <script type="text/javascript">
                createFlash("flash/demo1.swf","100%","100%","#000000","gpu","","idFlasha","preloader","","");
            </script>
        </div>
    </body>
</html>

Download the package containing all the resources for this tutorial.

3d_fullscreen_preloader_files.zip - (FlashDevelop project files, HTML examples and Esquimo scene files)

I hope you liked it and these informations will give you good basics to build outstanding 3d preloaders. Thanks for reading!