AS3 Event Race Condition - actionscript-3

I'm having some trouble solving a race condition that exists when I instantiate new MovieClip classes with some custom events that fire back before I can store the instances in an associative object.
Main Class
var pages:Object = {
"page1":"page1",
"page2":"page2"
};
for(var pageName:String in pages)
{
pages[pageName] = buildPage(pageName, onReady);
}
function buildPage(pageName:String, onReady:Function)
{
var newPage:MovieClip = new (getDefinitionByName(pageClass) as Class)();
newPage.addEventListener("PAGE_READY", onReady);
newPage.dispatchEvent(new Event("PAGE_CREATE"));
return newPage;
}
function onReady(e:Event)
{
for(var pageName:String in pages)
{
trace(typeof pages[pageName]);
}
}
Page 1 & 2 Classes extends MovieClip
function pageX()
{
this.addEventListener("PAGE_CREATE",this.onCreate);
}
function onCreate(e:Event)
{
this.graphics.beginFill(0xFF0000);
this.graphics.drawRect(0,0,200x200);
this.graphics.endFill();
this.dispatchEvent(new Event("PAGE_READY"));
}
Unfortunatley when this all fires off I get:
string
string
object
string
The original string object still exists before I can store the MovieClip instance for later reference. Any suggestions on how to beat this?

The code you posted does not depict a standard class.
Is this code on the movieclip frames or in separate files?
But as far as posted code I can see your issue is with the PAGE_CREATE event firing before the object has been added to the array.
your are doing
newPage.dispatchEvent(new Event("PAGE_CREATE"));
before you return the object.
You did not post the full classes so I really can't help you rewrite your code but, from what you posted I would remove the buildPage method completely and put the code in loop then dispatch the event after the object has been added to the array.
Also, another reason, best practice would be not to call a function from a loopif that function does not have a lot of code in it.
for(var pageName:String in pages)
{
var newPage:MovieClip = new (getDefinitionByName(pageClass) as Class)();
newPage.addEventListener("PAGE_READY", onReady);
pages[pageName] = newPage;
newPage.dispatchEvent(new Event("PAGE_CREATE"));
}

Related

as3 - Detect if object is moved to another object using timer

I was creating a shooting game that the target is appear and disappear any seconds. I don't know how use the codes and I don't know how to detect the object to object when using if statement.
Here's my codes:
import flash.utils.Timer;
var dummySX: Number = dummyS.x;
var dummySY: Number = dummyS.y;
var targetTimeStart: Timer = new Timer(1000);
targetTimeStart.start();
targetTimeStart.addEventListener(TimerEvent.TIMER, targetTimeStartNow);
function targetTimeStartNow(e: TimerEvent): void {
target.x = dummySX;
target.y = dummySY;
targetTimeStart.stop();
}
function detect(): void {
if ((target.x == dummySX) && (target.y == dummySY)) {
trace("DETECTED");
}
}
or
function detect(): void {
if (target.hitTestObject(dummyS)==true) {
trace("DETECTED");
}
}
Thanks!!
There are several ways to detect hit between objects.
Let's say you have 2 display objects (do1 and do2)
First of all there is the do1.hitTestObject(do2). This method is very fast and works well for rectangular objects. You can get some visually "false" results if the rectangular objects are rotated or you use it with other than rectangular objects.
Next there is the do1.hitTestPoint(do2.x, do2.y, shapeFlag). This method is slower but more accurate, especially when you set the shapeFlag to true. It tests the hit against a single point (x & y coordinates).
There is a third method (hitTest) that implies you use BitmapData objects and that is even more precise and is specific the the BitmapData class, but much harder to process (being able to ignore transparent pixels).
A fourth option would be to do a custom implementation of the hit detection, but it seems a bit off to detail that here.
Going over your code, I see you create a 1 second Timer object and start it, after which you add a listener with the targetTimeStartNow function. In that function you move the target object to the same position of the dummyS object and after that you stop the targetTimeStart Timer object. That means that you have only 1 entry in that function.
The detect function seems that is never called from your code, although both calls should trace DETECTED.
The first detect function that you've written mimics the hitTestPoint version.
function detect(): void {
if ((target.x == dummySX) && (target.y == dummySY)) {
trace("DETECTED");
}
}
This is the same with:
function detect():void {
if(target.hitTestPoint(dummySX, dummySY, false)) {
trace("DETECTED");
}
}
It'd be better to use this variant (in order to use the shape of the object, and not their bounding box):
function detect():void {
if(target.hitTestPoint(dummySX, dummySY, true)) {
trace("DETECTED");
}
}
The second detect function uses the hitTestObject and it's ok. You could use it without explicitly == true:
function detect(): void {
if(target.hitTestObject(dummyS)) {
trace("DETECTED");
}
}
The only thing is to call it in code in order to get it executed. So somewhere after the target object was moved just add:
detect();
Depending on your needs, you could add it to the targetTimeStartNow method (or add an EnterFrame listener, or create another Timer object):
function targetTimeStartNow(e: TimerEvent): void {
target.x = dummySX;
target.y = dummySY;
targetTimeStart.stop();
detect();
}

want to change the code actionscript2 to actionscript3?

i am newbie to flash.i need to change the below actionscript code to actionscript 3.0 code.
i am currently working on drag and drop. so i want to duplicate the movieclip while dragging i found the code on internet but it is actionscript 2.0 so please convert it to as3. the box is a instance name of a movieclip.
the code blocks are:
var num:Number = 0
box.onPress = function(){
num++
duplicateMovieClip(box ,"box"+num, _root.getNextHighestDepth())
_root["box"+num].startDrag();
}
box.onReleaseOutside = function(){
trace(_root["box"+num])
stopDrag();
}
If you dont want to use seperate .as file, follow this steps:
1- assign AS linkage to box movieClip (in library panel):
2- Select frame 1 on the timeline, and paste this code in the Actions panel:
var boxes:Array=[];
//var box:Box=new Box();
//addChild(box);
box.addEventListener(MouseEvent.MOUSE_DOWN,generateBox);
function generateBox(e:MouseEvent):void{
var newBox:Box=new Box();
newBox.x = e.target.x;
newBox.y = e.target.y;
newBox.startDrag();
newBox.addEventListener(MouseEvent.MOUSE_UP,stopD);
newBox.addEventListener(MouseEvent.MOUSE_DOWN,startD);
boxes.push(newBox);
addChild(newBox);
}
function startD(e:MouseEvent):void{
e.target.startDrag();
}
function stopD(e:MouseEvent):void{
e.target.stopDrag();
}
Unfortunately, there's no duplicateMovieClip analog in AS3, so you'll have to create a Class for your box movieClip template. Let's say it will be called BoxTemplate. (You can google how to create Classes for your library object). Add a Class with this name and add this code (event subscription in the constructor and a private event listener). You'll get something like this:
package
{
public class BoxTemplate
{
public function BoxTemplate()
{
addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
}
private function onMouseUp(e:MouseEvent):void
{
stopDrag();
}
}
Leave your present instance of this symbol on the stage. This is your code in the frame:
import flash.event.MouseEvent
box.addEventListener(MouseEvent.CLICK, onClick);
function onClick(e:MouseEvent):void
{
var newBox:BoxTemplate = new BoxTemplate();
newBox.x = e.target.x;
newBox.y = e.target.y;
addChild(newBox);
newBox.startDrag();
}
It will allow you to infinitely clone your boxes. Of course, you can add all of them in the array to keep the references.

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame
I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.
The only way I could think of doing this was to have an initial phase in your project which will:
Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.
The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like #Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.
I have set this up and it works well - I'll paste each class and try explain what they do.
So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:
public class Main extends MovieClip
{
private var _userFrameRate:int;
private var _frames:Vector.<Frame> = new <Frame>[];
public function Main()
{
_userFrameRate = stage.frameRate;
stage.frameRate = 120;
addEventListener(Event.FRAME_CONSTRUCTED, _assess);
}
public function getFrame(index:int):Frame
{
return _frames[index - 1];
}
private function _assess(e:Event):void
{
var frame:Frame = new Frame(this);
_frames.push(frame);
if(currentFrame === totalFrames)
{
removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
gotoAndStop(1);
stage.frameRate = _userFrameRate;
ready();
}
else play();
}
public function ready():void
{
// Start here.
// There is a MovieClip on frame 10 with the instance name 'test'.
// We can get the width of it like this.
trace( getFrame(10).define("test").property("width") );
}
}
This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.
Next we have the Frame class, which serves to hold information about children related to a frame:
public class Frame
{
private var _main:Main;
private var _content:Object = {};
public function Frame(main:Main)
{
_main = main;
update();
}
public function update():void
{
_content = {};
for(var i:int = 0; i < _main.numChildren; i++)
{
var target:DisplayObject = _main.getChildAt(i);
// This will be explained below.
var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");
_content[target.name] = definition;
}
}
public function define(name:String):ObjectDefinition
{
return _content[name];
}
}
It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.
The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:
public class ObjectDefinition
{
private var _definition:Object = {};
public function ObjectDefinition(target:DisplayObject, ...properties)
{
for each(var i:String in properties)
{
_definition[i] = target[i];
}
}
public function property(property:String):*
{
return _definition[property];
}
}
You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).
Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:
var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));
Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.
You have to listen to a specific event before you can ask for the information.
clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);
function frameReadyHandler(event:Event):void
{
clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
var width = clip.width;
}
The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.
You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.
A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.
If you know the maximum size of the MovieClip, you may try this:
// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();
// Move to second frame
movie.gotoAndStop(2);
// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);
// Draw second frame
bd.draw(movie);
// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);
trace(movieBounds); // (x=42, y=15, w=32, h=33)

How to hitTestObject MC items of an array with a MC target?

I have items in an array, each is a movieclip, which play at delayed intervals of 1 second and a target movieclip (carTarget) controlled by arrow keys where the aim is to avoid a collision with the array items (obstacleArray[i]). I have been attempting to create a hitTestObject statement to notify when there is a collision. However I have received errors such as parameter hitTestObject most be non-null and cannot figure out how to resolve it.
//obstacles
function randomSort(a:*, b:*):Number
{
if (Math.random() < 0.5) return -1;
else return 1;
}
var obstacleArray:Array = [obstacleCar,obstacleCar2,obstacleCar3];
obstacleArray.sort(randomSort);
trace(obstacleArray);
trace(obstacleArray.length);
//OBSTACLE START DELAY
var timerPlay:Timer = new Timer(1000,1);
timerPlay.addEventListener(TimerEvent.TIMER, ontimerPlay);
timerPlay.start();
var i:int = 0;
var timerDelay:Timer = new Timer(1000, 3);
function ontimerPlay(evt:TimerEvent):void{
obstacleArray[i].addEventListener(Event.ENTER_FRAME,checkHitTest);
//FOR EACH OBSTACLE
timerDelay.addEventListener(TimerEvent.TIMER, ontimerDelay);
timerDelay.start();
}
function checkHitTest(event:Event):void{
if(carTarget.hitTestObject(obstacleArray[i]))
{
trace("HIT!");
}
}
function ontimerDelay(evt:TimerEvent):void{
obstacleArray[i].play();
trace(obstacleArray[i]);
i++;
}
Most likely, the problem is in this block of code:
function checkHitTest(event:Event):void{
if(carTarget.hitTestObject(obstacleArray[i]))
{
trace("HIT!");
}
}
After i changes with the timer calls, then obstacleArray[i] will be null or worse - another object other than the one you need to check.
You should change this to:
function checkHitTest(event:Event):void{
if(carTarget.hitTestObject(event.target))
{
trace("HIT!");
}
}
event.target will get the current obstacle calling the checkHitTest from Enterframe event
It is very likely that hitTestObject does not exist at the moment you're trying to use it. From the code you shared, I do not see where you create it in your code. So, assuming carTarget is an object on the stage and that it has the property or object hitTestObject initialized when you want to use it -assuming those things, then you need to make sure that when this code is running, that object still exists.

Dynamically Handling Events with Function Expression

I have a class which exposes literally dozens of events(before you get of on a tangent about whether that's good/bad design, just know that I didn't make that class). The event object of each event(eventParam in the below code) always has a toDebugString function, that basically creates a string containing all of the event object's property values:
propertyName1: propertyValue1
propertyName2: propertyValue2
propertyName3: propertyValue3
It works so far as creating all of the panels, with the title of each panel being the name of the event. However, the big problem is that all of events end up in the TextArea of the last panel. So there is something I don't understand about the anonymous method. It's as if each iteration of the loop uses the same function, and on the last iteration of the loop it decides that the debugPanel that was just created will be the one that all instances of that function will reference. In other words, a new unique debugSubPanel and TextArea is created in each iteration of the loop, but there is only one debugResponseListener event handler shared by all iterations of the loop. So my question is, how can I dynamically create the event handler function dynamically so that it stays associated with the debugSubPanel that I want it to?
public function debugPanelCreated(event:FlexEvent)
{
//iterate through all of the events exposed by mClient.ResponsesDispatcher
//where key is the name of the event
for (var key:String in mClient.ResponsesDispatcher.respMap)
{
//for each event, create a panel containing a text box
var debugSubPanel:Panel = new Panel();
debugSubPanel.title = debugSubPanel.label = key;
var debugSubPanelTextArea:TextArea = new TextArea();
debugSubPanel.addChild(debugSubPanelTextArea);
var debugResponseListener:Function =
function (eventParam :Object) : void
{
//use debugString function to write the properties
//of eventParam to the text box
debugSubPanelTextArea.text = eventParam .toDebugString();
};
//listen to this event:
mClient.ResponsesDispatcher.addEventListener(key,debugResponseListener);
//add the panel for this event
debugPanel.addChild(debugSubPanel);
}
}
Actionscript includes a feature called closures, which means that when you create an inner function and call it, the variables of its parent function are still available. (This is how debugResponseListener = function() ... works at all.) The issue is that a closure is only created when that function is called, and it uses the variable values from their last setting.
You can get around this by making a function that returns the listener function you want.
function makePanelListener(debugSubPanelTextArea:TextArea) : Function
{
return function(eventParam :Object) : void {
//use debugString function to write the properties
//of eventParam to the text box
debugSubPanelTextArea.text = eventParam .toDebugString();
}
}
and in your original code:
var debugResponseListener:Function = makePanelListener(debugSubPanelTextArea);
(There's a little explanation of what's going on in Explaining JavaScript scope and closures, look for the section called "The Infamous Loop Problem". More on closures at jibbering.)
This is the hack I came up with. I really don't like it, but it'll work for now. Open to suggestions still.
public class ResponseDispatcherToDebugStringHelper
{
public var textArea:TextArea;
public function responseToDebugStringHandler(eventParam:Object) : void
{
//use debugString function to write the properties
//of eventParam to the text box
textArea.text = eventParam.toDebugString();
}
}
public function debugPanelCreated(event:FlexEvent)
{
//iterate through all of the events exposed by mClient.ResponsesDispatcher
//where key is the name of the event
for (var key:String in mClient.ResponsesDispatcher.respMap)
{
//for each event, create a panel containing a text box
var debugSubPanel:Panel = new Panel();
debugSubPanel.title = debugSubPanel.label = key;
var debugSubPanelTextArea:TextArea = new TextArea();
debugSubPanel.addChild(debugSubPanelTextArea);
var helper:ResponseDispatcherToDebugStringHelper =
new ResponseDispatcherToDebugStringHelper();
helper.textArea = debugSubPanelTextArea;
//listen to this event:
mClient.ResponsesDispatcher.addEventListener(key,helper.responseToDebugStringHandler);
//add the panel for this event
debugPanel.addChild(debugSubPanel);
}
}