Archive for the 'Flash' Category

Flash constant frame rate

Friday, December 8th, 2006

A common problem in flash games, especially if there are some heavy graphics effects, is to handle the frame rate correctly. Even if it is possible to set a desired frame rate, the flash player will only try to keep it, but the actual frame rate may vary.
I've googled for some way to solve this problem, and I've found two different solutions.

One is to set the fps to somethings high, like 100fps, and then try to slow down the frame rate to the desired value with a busy-loop:

Actionscript:
  1. starttime = newDate()
  2. onEnterFrame=function(){
  3.    var currtime=new Date()
  4.    var ttime=currtime-starttime
  5.    while(ttime<1000/DESIREDFRAMERATE){
  6.       var currtime = new Date()
  7.       var ttime=currtime-starttime
  8.    }
  9.    starttime=new Date()
  10. }

In this way, however, some flash player's activities like garbage collection will be slowed down too, and this often lead to browser crashes. This is better explained in an interesting comment in a post on the Andrè Michelle blog by the user Troy Gilbert.

So the better way is to set the fps to something high, and then check when the interval 1000/frames_per_seconds is lapsed:

Actionscript:
  1. function onEnterFrame() {
  2.         var dtime:Number = getTimer() - this.lastFrameTime;
  3.         if(dtime> (1000/FRAMERATE)) {
  4.             var skipCount = 0;
  5.             while(dtime> (1000/FRAMERATE) && skipCount <MAX_FRAME_SKIP) {
  6.                 skipCount++;
  7.                 this.onEnterRealFrame();
  8.                 this.lastFrameTime += 1000/FRAMERATE;
  9.                 dtime = getTimer() - this.lastFrameTime;
  10.             }
  11.             this.lastFrameTime = getTimer();
  12.         }
  13.     }

onEnterRealFrame has the game logic, for example players movement and MAX_FRAME_SKIP prevent the player to hang too much.

We check if the time of a frame (1000/FRAMERATE) is lapsed, so we call onEnterRealFrame. We will call it how many times as the number of frames skipped from the last onEnterRealFrame call.
For example, if the fps is 10, and our game has a slow down for 300ms, it will call onEnterRealFrame for 3 times (300/(1000/10)) to recover the time lost.

However this solution has a problem: animated MovieClips will run out of sync. This can be solved either using tween engines like Zigo, or using a sound clip for synchronization.

Scroll browser window inside Flash

Tuesday, November 14th, 2006

Updated: fixed some typos

I've found that resizing a flash movie to fit his content has a little drawback: the mouse wheel doesn't scroll the browser window when the movie has focus, and in my case the movie has width: 100%.
Luckily I've found a nice javascript to scroll the page.

Add this javascript to the page:

JAVASCRIPT:
  1. function getScrollXY() {
  2.   var scrOfX = 0, scrOfY = 0;
  3.   if( typeof( window.pageYOffset ) == 'number' ) {
  4.     //Netscape compliant
  5.     scrOfY = window.pageYOffset;
  6.     scrOfX = window.pageXOffset;
  7.   } else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
  8.     //DOM compliant
  9.     scrOfY = document.body.scrollTop;
  10.     scrOfX = document.body.scrollLeft;
  11.   } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
  12.     //IE6 standards compliant mode
  13.     scrOfY = document.documentElement.scrollTop;
  14.     scrOfX = document.documentElement.scrollLeft;
  15.   }
  16.   return [ scrOfX, scrOfY ];
  17. }

and in your movie:

Actionscript:
  1. var mouseListener:Object = new Object();
  2. mouseListener.onMouseWheel = function(delta:Number) {
  3.   getURL("javascript:window.scroll(getScrollXY()[0], getScrollXY()[1] + "+(0-delta*8)+");");
  4. };
  5. Mouse.addListener(mouseListener);

I multiply to 8 the delta to adjust the mouse sensitivity. Sadly there isn't a way to find the user's system sensitivity.
Maybe a better way can be to scroll directly to a specific point in the page:

Actionscript:
  1. mouseListener.onMouseWheel = function(delta:Number) {
  2.   if(delta> 0) {
  3.     getURL("javascript:window.scroll(getScrollXY()[0], "+this.bottom+");");
  4.   }
  5.   else {
  6.     getURL("javascript:window.scroll(getScrollXY()[0], "+this.top+");");
  7.   }
  8. };

Resize Flash at runtime

Saturday, November 11th, 2006

If your content is bigger than the average screen size, using a flash scrollbar for the entire document is really slow and not user-friendly, so its better to enlarge flash to fit the content and scroll the entire page with the browser's scroll bar.

Put the flash object in a div:

HTML:
  1. ...
  2. <SCRIPT LANGUAGE="JavaScript">
  3. <!--
  4. function newSize(width,height) {
  5.     if(document.all && !document.getElementById) {
  6.       document.all['myflash'].style.pixelWidth = width;
  7.       document.all['myflash'].style.pixelHeight = height;
  8.     }else{
  9.         document.getElementById('myflash').style.width = width;
  10.         document.getElementById('myflash').style.height = height;
  11.     }
  12. }
  13. //-->
  14. </script>
  15. </head>
  16. <div id="myflash" style="position:relative; width:100%; height:100%; z-index:1; min-width:1000px;">
  17.  ...
  18. </object>
  19. </div>

and in the movie do:

Actionscript:
  1. getURL("javascript:newSize('1000px', '100%')");

Other info here

Flash max connections

Saturday, November 11th, 2006

Recently I'm making a Flash photo gallery for my dad and I'm trying to optimize the loading of many pictures at once.
The question is: how many concurrent download can Flash make?
Google seems to have no answer, so I decided to make a small test to find it out by myself.

So I've made an swf that load 4 pictures simultaneously, and the result is in line with the HTTP specifications of max 2 connections per server.
More exactly, Flash use the browser configuration, in Firefox is Network.http.max-connections-per-server in about:config.
Can it be improved?
It is possible to use the old trick of loading images through multiples domain: for example static1.foo.com, static2.foo.com. This increase the maximum connection limit until network.http.max-connections

Default values in Firefox 1.5 are 8 for max-connections-per-server and 24 for max-connections.

You can try it by yourself:
Load from a single domain
Load from two domain

Sources:
Single domain
Two domain

Simple CakeAMFPhp How To

Wednesday, November 8th, 2006

It's been a while since I last wrote, sorry! I've been busy with work, thesis and some other suffs (which I'll be posting about in the next days).
One of those is AMFPhp: a method to connect Flash with Php.
Why AMF instead of JSON or XML? Basically because AMF is more mature than JSON, offer a ready-made and really complete set of classes, many utilities like the connection debugger component, a great Php implementation, and, above all, CakeAMFPhp. (AMF is also a binary protocol on top of HTTP, so performance should be better than XML)
CakeAMFPhp is the integration of AMFPhp and Cake, a Rails-inspired php framework. With Cake I've quickly build a prototype with the Bake command-line utility. Then I've started implementing the methods for the AMF calls. Thanks to the great database abstraction, every method is only few lines long.

Since CakeAMFPhp is a beta, I've found some problems, for which I've found a quick fix that I'll explain shortly.

To start a CakeAMFPhp project, simply download the required libraries as explained in this tutorial. (If you are new to Cake you should also see this)
Then in your controller, for example GalleriesController, put this:

PHP:
  1. vendor("cakeamfphp/amf-core/util/MethodTable");
  2. vendor('cakeamfphp/amf-core/adapters/lib/Arrayft');
  3. vendor('cakeamfphp/amf-core/adapters/lib/Arrayf');
  4.  
  5. class GalleriesController extends AppController
  6. {
  7. ...
  8. function GalleriesController()
  9. {
  10.   //AMF:
  11.   $this->methodTable = MethodTable::create(__FILE__);
  12.   parent::__construct();
  13. }

In the line 11 we call the automatic method table constructor of AMFPhp: it read the javadoc-like comments in the file and do all the magic:

PHP:
  1. /**
  2. * @desc Return the list of public galleries
  3. * @access remote
  4. * @pagesize 25
  5. */
  6. function getGalleries($offset = 0, $limit = 25) {
  7.   $data = $this->Gallery->findAll(null, null, 'created DESC', $limit, $offset, 0);
  8.   return $this->_arrayft($data, 'Gallery');
  9. }
  10.  
  11. function getGalleries_count() {
  12.   return $this->Gallery->findCount();
  13. }

The @access remote tag specify that the getGalleries method is callable from flash:

Actionscript:
  1. var ser = new Service("http://localhost/cake_gateway.php", null, 'GalleriesController', null , null);
  2. var pc:PendingCall = ser.getGalleries();
  3. pc.responder = new RelayResponder(this, "handleResult", "handleError");

The getGalleries_count() method is needed for the pageable recordset:

Actionscript:
  1. function handleResult (re:ResultEvent) {
  2.   var rs = RecordSet(re.result);
  3.   rs.addEventListener('modelChanged', this); //Listen for updateItems
  4.   this.totalItems = rs.getLength();
  5.  
  6.   for(var i:Number=startFrom; i <l; i++) {
  7.     item = rs.getItemAt(i);
  8.     if(i <rs.getNumberAvailable()) { //It is already fetched
  9.     ... //Process a row
  10.   }
  11. }

getGalleries return only the first 25 row from the database. When the data is loaded in flash, rs.getLength() method return the value from getGalleries_count(). getItemAt return an item or a future, we check if the item is loaded in the line 8. If the item isn't from the ones loaded, it is automatically requested from the server when we call getItemAt. When it arrives, the modelChanged method is called:

Actionscript:
  1. function modelChanged (info:Object) {
  2.   var rs = this.rs;
  3.   var item:Object;
  4.   if(info.eventName == "updateItems") {
  5.     for(var i:Number=info.firstItem; i <= info.lastItem; i++) {
  6.       item = rs.getItemAt(i);
  7.       target.addItem(item);
  8.     }
  9.   }
  10. }

in the info object there are the rows received, you can configure Flash to get only the item required or the page containing it.
That's all, simply add the @access remote tag, a *methodname*_count method and it works.
You can find more information on pageable recordsets here.

The _arrayft is required for the issue I've talk about before: CakeAMFPhp 0.6 does not support sending recorset to flash, so this method is required to transform the data returned by the Cake database abstraction layer into a AMF-compatible recordset:

PHP:
  1. function _arrayft($data, $table) {
  2.   $r = array();
  3.   if(!empty($data[$table])) {
  4.     //Multi table query:
  5.     if(is_array($data[$table][0])) {
  6.       //Nested table:
  7.       foreach($data[$table] as $t) {
  8.         $r[] = $t;
  9.       }
  10.     }
  11.     else {
  12.     //Sigle record:
  13.       foreach($data[$table] as $t) {
  14.         $r[] = $t;
  15.       }
  16.     }
  17.   }
  18.   else {
  19.     foreach($data as $t) {
  20.       array_push($r, $t[$table]);
  21.     }
  22.   }
  23.   return new Arrayf($r, array_keys($r[0])  );
  24. }

For performance reason, it is suggested to use a 0 'recursive' value in the find(..) call.

Some useful tips on Flash, caching and Php

Wednesday, September 13th, 2006

Making the test for some strange flash cache behaviour I've learned some useful tips:

You can be sure a movie will be loaded appending a query string to the file name:

Actionscript:
  1. loadMovie("clip.swf?123", target);

 

You can force the user to reload a movie when you release a new version:

Actionscript:
  1. clipname= "cache"+VERSION+"-clip.swf";
  2. loadMovie(clipname, target); //Loaded
  3. //After the first load the movie is cached:
  4. loadMovie(clipname, target2); //Cached

And use a .htaccess file (ModRewrite required):

Apache:
  1. RewriteEngine on
  2. RewriteCond %{REQUEST_URI} cache([0-9]+)-(.*)\.swf(.*)$
  3. RewriteRule ^cache([0-9]+)-(.*)\.swf(.*)$ $2\.swf [L]

Only when you change the VERSION variable (pass it with flashVars) the movie will be reload, otherwise it will be loaded from the browser cache.
 

You can make cacheable a file downloaded through php:

PHP:
  1. header('Last-Modified: '.date( "D, j M Y G:i:s \G\M\T", filemtime($filename) ));

Be sure to set the correct content type:

PHP:
  1. $filename= "clip.swf";
  2. $fp = @fopen($filename,"r");
  3. if($fp){
  4.     header('Last-Modified: '.date( "D, j M Y G:i:s \G\M\T", filemtime($filename) ));
  5.     header('Content-Length: '.filesize($filename));
  6.     header('Connection: close');
  7.     header('Content-Type: application/x-shockwave-flash');
  8.  
  9.     while (!feof($fp)){
  10.         echo(fgets($fp, 4096));
  11.         ob_flush();
  12.     }
  13.     fclose($fp);
  14. }
  15. else
  16. {
  17.     header("HTTP/1.0 404 Not Found");
  18. }

Without Last-Modified or ETag headers flash doesn't cache the files.

Strange Flash cache behaviour and attachMovieAnywhere solution

Monday, September 11th, 2006

Some days ago I've read about an old problem:
if I load a swf with some identifiers, I can't "attachMovie" them anywhere, but only in the loaded movie:

Actionscript:
  1. //If you do:
  2. myClip.loadMovie("libraryfile.swf");
  3. //then you can attach movie only in this scope: "myClip":
  4. myClip.attachMovie('clip1', 'foo', myClip.getNextHighestDepth());
  5.  
  6. //this doesn't works:
  7. _root.attachMovie('clip1', 'foo', _root.getNextHighestDepth());

This can be really a problem if you decide to move some assets to external files too late in the development, probably you'll have to rewrite substantial part of the application or it can be just impossible.

The simplest way to load an external resource and still be able to attach movies everywhere is to "Import for runtime sharing" it, but in this way you can't control via actionscript when to load or change what file to load (for example, to change the theme).

The solution:

Since I've noted some strange behaviour loading files in the flash ide, I've opened my trusty Ethereal sniffer and I've found something interesting:
When flash load a clip and cache it, it will not try to reload it until the page is refreshed.
What does it mean?

Actionscript:
  1. var mcLoader:MovieClipLoader = new MovieClipLoader();
  2. //This send to server: GET /libraryfile.swf HTTP/1.1
  3. mcLoader.loadClip("libraryfile.swf", target);
  4.  
  5. var mcLoader2:MovieClipLoader = new MovieClipLoader();
  6. //This don't send nothing, No bandwidth waste :)
  7. mcLoader2.loadClip("libraryfile.swf", target2);

In the above example, after the first load, every time I'll load "libraryfile.swf" in the movie, flash will take it from the memory and not from the server.
So I can load it everywhere I need it without using any bandwidth. In this way I can attach the loaded resources everywhere in the movie.
The next time the page is loaded, flash will ask the server for the file only the first time.

This can be a problem when we want to check if an swf was modified, in this case we have to put a ? in the filename to be sure the file will be reloaded:

Actionscript:
  1. //This is _always_ reloaded:
  2. clipName = "clip.swf?"+ Math.random();

AttachMovieAnywhere:

This simple function can be useful to attach a loaded movieclip anywhere:

Actionscript:
  1. MovieClip.prototype.attachMovieAnywhere = function(file:String, idName:String, newName:String, depth:Number, initObject:Object ) {
  2.         if(depth == undefined) depth = getNextHighestDepth();
  3.     var parent:MovieClip = this;
  4.     var container:MovieClip = this.createEmptyMovieClip(newName, depth);
  5.     var mcLoader:MovieClipLoader = new MovieClipLoader();
  6.     var listener:Object = new Object();
  7.     listener.onLoadInit = function (mc) {
  8.         mc.attachMovie(idName, newName, mc.getNextHighestDepth());
  9.         parent[newName] = parent[newName][newName];
  10.     }
  11.     mcLoader.addListener(listener);
  12.     mcLoader.loadClip(file, container);
  13. }

The usage is simple:

Actionscript:
  1. var container:MovieClip = this.createEmptyMovieClip("container", getNextHighestDepth());
  2. container.attachMovieAnywhere('foo.swf', 'clip', 'foo');
  3. //One frame later:
  4. container.foo._x = 100;

The main difference from attachMovie is that the attached movieclip will not be avaible until the next frame.

Conclusions:

It is a bug? I don't think, this isn't much different form how a browser works: after the first occurrence of <img src="img.jpg"/>, it will not try to reload "img.jpg" if another <img src="img.jpg"/> is found.

So why I haven't found it before? :)
Maybe because I have always thought that "loadClip" would have loaded file, maybe receiving a "HTTP1/1 304 Not Modified" reply, instead of silently take the cached one, who knows!

I'm sure there are plenty of experienced flash developers out there that already know this, but I've found many questions about this problem and I hope this can be useful to someone :)

 

To be sure that this stuff works I've made a test for it, it was useful to me to better understand how the flash cache mechanism works and to test it on many platform.
Feel free to leave a comment if something doesn't works.
(It is sufficient to view this post to participate, the results will be stored for statistic purpose only)

Object Explorer extension

Saturday, July 22nd, 2006

Ok, I've finished the object/class viewer and I've named it Object Explorer.
You can find the Object Explorer project page here or download the extension for the flash ide here.

For everyone interested in making his own extension: useful link on making flash extension.

Reference Explorer

Sunday, July 16th, 2006

Page outdated, for the new version of this application visit: Object Explorer

Ok, the exams are gone, and I'm ready to came back to my projects!

Finally, I've (almost) completed a reference explorer for flash: this application show the object tree in a swf, it can be useful while debugging and it can help find circular references:
in Flash up to 7 circular references doesn't allow objects to be deallocated by the garbage collector. A nice explanation of the problem can be found in this Grant Skinner's blog entry.

Although this issue doesn't exist for the new Flash 8 garbage collector, it is still present in Flash Lite 2.x.
A reference explorer can also help refactoring or be useful to visually debug your flash projects and to explore object dependencies, in future I could extend it to show information useful for debugging like object variables and class hierarchies.

Usage:

  • Download it and click on main.html
  • Type the name of the swf you want to load, you can see the movie in a window
  • When you are ready click "Analyze" to show the object diagram
  • Red objects contain a circular reference, the name of the variable holding that reference is showed in a box
  • After each object name you can see the class name

 

Download source and swf:
ReferenceExplorer.zip

Try it online with a test swf: (or your own if you set proper cross domain policies)
Reference Explorer

 

Reference Explorer at work:

 

Some links on garbage collection in Flash Lite:
Flash Lite memory management (LiveDocs)
Comparison between Java ME and Flash Lite
Flash Lite memory management

Must-read for AS3 developer

Tuesday, July 11th, 2006

André Michelle in his blog point to a great article about AS3 internals by Gary Grossman, the creator of Action Script.