Escape Theory Gaming For Life… views are mine, and not affiliated with The White Agency or Nomad

3Jun/101

Flexperiment 1: Physics of a Bouncing, throwable ball.

Here is a simple bit of code that demonstrates simulated physics of a ball being thrown (written with my colleague Anthony http://www.creatistblog.com/):

The keystone of this experiment is the ENTER FRAME event function that tracks the motion of the ball, applying horizontal 'drag' and vertical 'gravity' as well as spelling out the boundaries of the ball's movement area. A simple hack forces the ball to stop in its tracks when it is at a certain speed and distance from the 'ground' or bottom of the screen. Understanding the physics of something like a bouncing ball helps me build on top of it with more features (such as a true collision detection system, or adding wind).

package {

 import flash.display.Graphics;
 import flash.display.Sprite;
 import flash.events.*;

 [ SWF( frameRate="60", width="420", height="420", backgroundColor="0x000000" ) ]
 public class BouncingBall extends Sprite {

 private var maxSpeed:Number = 100;
 private var radius:Number = 25;
 private var vx:Number;
 private var vy:Number;
 private var ball:Sprite;
 private var previousMouseX:Number=0;
 private var previousMouseY:Number=0;
 private var currentMouseX:Number;
 private var currentMouseY:Number;
 private var throwX:Number;
 private var throwY:Number;

 public function BouncingBall() {
 ball = new Sprite();
 ball.buttonMode=true;
 ball.addEventListener(MouseEvent.MOUSE_DOWN, ballDownHandler);
 ball.addEventListener(MouseEvent.MOUSE_UP, ballUpHandler);
 addChild( ball );
 ball.x = Math.random() * ( stage.stageWidth - radius );
 ball.y = Math.random() * ( stage.stageHeight - radius );
 vx = Math.random() * maxSpeed + 0.5;
 vy = Math.random() * maxSpeed + 0.5;

 var g:Graphics = ball.graphics;
 g.beginFill( 0xFF0000 );
 g.drawCircle( 0, 0, radius );
 g.endFill();

 addEventListener( Event.ENTER_FRAME, onClipEnterFrame, false, 0, true );
 }

 private function onClipEnterFrame( p_event:Event ):void {

 currentMouseX = this.mouseX;
 currentMouseY = this.mouseY;

 ball.x += vx;
 ball.y += vy;
 if ( vy != 0 ) vy += 0.5;
 if ( vx != 0 ) vx += (0-vx)*0.015;

 if ( ( ball.x <= radius && vx < 0 ) || ( ball.x >= stage.stageWidth - radius && vx > 0 ) ) {
 vx = -vx * 0.6;
 }
 if ( ( ball.y <= radius && vy < 0 ) || ( ball.y >= stage.stageHeight - radius && vy > 0 ) ) {
 vy = -vy * 0.6;    
 }

 if ( Math.abs( vx ) < 0.01 ) vx = 0;
 if ( Math.abs( vy ) < 0.1 && ball.y>=stage.stageHeight-radius-2){
 vy = 0;
 ball.y=stage.stageHeight-radius;
 }

 throwX = currentMouseX - previousMouseX;
 throwY = currentMouseY - previousMouseY;

 previousMouseX = currentMouseX;
 previousMouseY = currentMouseY;

 }

 public function ballDownHandler(e:MouseEvent):void{
 ball.startDrag(true);
 vy = vx = 0;
 }

 public function ballUpHandler(e:MouseEvent):void{
 ball.stopDrag();
 vx = throwX;
 vy = throwY - 0.6;
 }
 }
}
27May/101

Tween Class Tweens Freezing Halfway Through Animation

When creating multiple Tween instances using the Flash Tween classes, animations randomly freeze.

Error type: Bug

Error class: -

How to Induce error:

Using the built-in Flash Tween classes, create multiple tweens on one or more objects.

When these tweens are all firing , Flash randomly 'cleans up' the tween instance while it is still animating. In effect, the animation appears to freeze.

Further tween instances can unfreeze the animation, but clearly this is too annoying to ignore!

Solution:

We can store the tween instance in an Array, and clear the instance when the tween is no longer required. This forces the Flash garbage collector to stop destroying your tween instance:

var tweensArray:Array = []; var tween_count:Number = 0; function tweenFunc():void{ tweensArray[tween_count] = new Tween(myObj, "alpha", Strong.easeOut, myObj.alpha, 1, 0.5, true); tween_count++; }
function fadeContent(showWhichNumber):void{

//tween class has FREEZING bug, it removes tweens before they complete when many tweens occur - this is RANDOM.
//in order to stop the removal, we store the tweens in an array, and wipe them out on each changeContent call.
//remove this fix at your own risk!

if(currentContentNumber == showWhichNumber){
tweensArray[tween_count] = new Tween(this['content'+ (showWhichNumber).toString()], "alpha", Strong.easeOut, this['content'+ (showWhichNumber).toString()].alpha, 1, 0.5, true);
tween_count++;
}else{
tweensArray[tween_count] = new Tween(this['content'+ (showWhichNumber).toString()], "alpha", Strong.easeOut, this['content'+ showWhichNumber].alpha, 0, 0.5, true);
tween_count++;
}
}

19Apr/100

Interface Programming, using ‘implements’ instead of ‘overrides’

Suppose we are a Beastmaster, and have created  a class MonsterFarm whose list will contain several species of fantastic creatures. We want to create a Centaur and Human class, and populate the farm with one instance of each:

Human:

package
{
        public class Human
        {
                public function Human()
                {
                        //human here
                }
        }
}

Centaur:

package
{
        public class Centaur
        {
                public function Centaur()
                {
                        //Centaur here
                }
        }
}

MonsterFarm:

package
{
        public class MonsterFarm
        {
                var monsters:Array = [];

                public function MonsterFarm()
                {
                        trace('monster farm is open!');

                        var centaur:Centaur = new Centaur();
                        var human:Human = new Human();
                        monsters.push(centaur);
                        monsters.push(human);
                }
        }
}

We want to be able to call upon all the creatures within the farm to speak, so both the Human and Centaur will need a 'speak' method. Additionally, the farm will need to treat all creatures similarly, without having to know their Type.

The most straightforward solution would be to extend both the Human and Centaur classes from a common class like Creature, and then move the speak function into that class.

The problem now is that Centaurs and humans speak differently, so we'd probably create another speak() method in both the Centaur and human classes that override the method in Creature.

This is all well and good, but what happens if Centaur didn't extend from Creature, but instead extended from Monster (which in turn extends from EnemyCreature)? Since classes can only extend from a single class, we would need to work out an elaborate encapsulation and overriding method chain for all the speak-able creatures. Worse still, when we call all our creatures to speak() in the farm, we may no longer be able to call all Creatures anymore, but instead call their common parent which can only safely be assumed as Object.

Enter interfaces!

Unlike classes, interfaces define their methods and not their implementation, which means that it is up to each class to implement the methods themselves. This means that a class can implement multiple interfaces.

For our Centaur and Human, we create a new interface named ICharacterActions which will contain a speak() method:

package
{
        public interface ICharacterActions
        {
                function speak();
        }
}

Both the Centaur and Human classes can now 'implement' this interface, and promise to supply its methods similarly to an override (without the 'override' syntax). These must also be public methods!
Human:

package
{
        public class Human implements ICharacterActions
        {
                public function Human()
                {
                }
                public function speak() {
                        trace('Good Morrow!');
                }
        }
}

Centaur (note that this is extending from a class unrelated to Human!):

package
{
        public class Centaur extends Monster implements ICharacterActions
        {
                public function Centaur()
                {
                }
                public function speak() {
                        trace('neigh!');
                }
        }
}

Although the Centaur and Human classes have different parents, the MonsterFarm can still call them all to speak() by simply to their common interface: ICharacterActions

//in MonsterFarm
for each(var monster:ICharacterActions in monsters) {
        monster.speak();
}
//traces:
//neigh!
//Good Morrow!

With interfaces, we have opened up a myriad of possibilities for the MonsterFarm - we can populate it with any kind of creature, with only the speakable ones implementing the respective interface. We can also add additional interfaces and refine them further like 'ISpeak', 'IFight' so that speaking creatures need not also be fighting creatures, and vice-versa. The performance is also much quicker than overrides.

The catch?

Implementing an interface is like setting a contract - a creature that implements the ICharacterActions interface must contain all the methods defined in the interface or it will cause an error:

1044: Interface method * in namespace * not
implemented by class *.

Methods in interfaces must not have any curly braces '{}' that signify their implementation, so no 'default code' can be executed here (which doesn't matter, because you need to implement the method to avoid the 1044 error above anyway).

For a working example of today's files, download the 'Interface Programming Example' file from the files page.

9Feb/100

Finding the base URL of your Flash

One of the most useful 'basic' tools of a Flash developer is to be able to find the URL of your Flash application, and it is very simple:

//in timeline
trace(loaderInfo.url);
//this returns the path of the Flash application
stop();

The reason it is useful is because you can make your application behave differently depending on which environment it is in.

Your application may be expecting a flashvar which is only available if the Flash was embedded in an HTML. In order to test your functionality within the Flash IDE instead of creating two separate projects, you create some logic based on the URL of the movie:

//in timeline
import flash.display.LoaderInfo;
var myVar:String;
<pre>var paramObj:Object;</pre>
if(loaderInfo.url.indexOf('file://')==0){
//if the URL begins with 'file://' it means we're working locally
//locally, flashvar is not going to be available
//we'll give it a default value
myVar = 'foo';
}else{
//we assume that not working locally means we're working live
//get flashvar from HTML
paramObj = LoaderInfo(root.loaderInfo).parameters;

//set myVar to equal myLiveVar from the flashvar
myVar = paramObj.myLiveVar;
}

stop();
25Jan/102

SWFObject Error passing URI’s and HTML code into flashvars

When passing URL's or HTML code through SWFObject flashvars, some characters cause errors.

Error type: Pitfall

Error class: -

How to Induce error:

Flash file, published as 'example.swf', 550 x 400:

//in timeline
import flash.text.TextField;
import flash.display.LoaderInfo;

//this is for Flashvars
var paramObj:Object = LoaderInfo(root.loaderInfo).parameters;
var myTextField:TextField = new TextField();
myTextField.width = 400;

if(paramObj['testVar']){
        trace('found flashvar');
        myTextField.text = paramObj['testVar'];
}else{
        trace('no flashvar found');
        myTextField.text = 'no flashvar found';
}

addChild(myTextField);
stop();

HTML:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>swfObject demo</title> <script type="text/javascript" src="swfobject.js"></script> </head> <body bgcolor="#ffffff"> <div id="flashcontent"> This text is replaced by the Flash movie. </div> <script type="text/javascript"> var so = new SWFObject("swfObject_flashvar_error.swf", "swfObject_flashvar_error", "550", "400", "8", "#ffffff"); so.addVariable("testVar", "Lots & lots & lots & lots of ampersands"); so.write("flashcontent"); </script> </body> </html>

Ensure that swfObject.js is in the correct location (same folder as the .swf and .html in the above example).

If you are getting the 'no flashvar found' message, make sure you are not accessing the .html through your file system (you will know this if the start of your URL is 'file://' or something similar).

We expect to see the words 'Lots & lots & lots & lots of ampersands.' in the Flash, but if you have the correct file structure on a live environment, you'll find that you can only see the word 'Lots', this is because SWFObject doesn't like the '&' character.

This poses a huge issue with words encoded in different languages, and URL's which typically require several non-standard characters

Solution:

Simply use the encodeURIComponent() function in the HTML on your flashvar values:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>swfObject demo</title> <script type="text/javascript" src="swfobject.js"></script> </head> <body bgcolor="#ffffff"> <div id="flashcontent"> This text is replaced by the Flash movie. </div> <script type="text/javascript"> var so = new SWFObject("swfObject_flashvar_error.swf", "swfObject_flashvar_error", "550", "400", "8", "#ffffff"); so.addVariable("testVar", encodeURIComponent("Lots & lots & lots & lots of ampersands")); so.write("flashcontent"); </script> </body> </html>

The encodeURIComponent is available for all Mozilla-style browsers, Safari, Opera, Firefox, Chrome and IE versions 5.5 and above. For more information on this workaround, visit the SWFObject FAQ.