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

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();
}

Related

Simple variables in AS3 not increasing properly

Alright, so i've got some code that says that if a hit test is preformed, a variable is increased by 1. However, when I run it, I get crazy numbers like 1,1,1,2,5,3,2,5,2,3,4,1,1
There's no pattern, it's just random. Here's the code:
public function loop(e:Event)
{
y += speed;
if (y > stage.stageHeight)
{
setupAsteroid(false);
}
//hittest for the asteroid
if (hitTestObject(target))
{
stageRef.addChild(new Explosion(stageRef, x, y));
trace("Hit");
kills ++;
trace(kills);
if(kills == 3){
trace("Success");
}
}
I've included all the code in the function used, and not all of it is necessary to the hit test as you can see. also, the variable is declared correctly as
private var kills:Number = 0;
inside the same .as file and class.
You add an Explosion to the stage but there is no sign of you removing anything from the stage, or at least stopping the check for hitTest. Also I'm guessing that your function loop(e:Event) listens for an ENTER_FRAME event and in that case if you're not removing anything from the stage that hitTest is going to be returning true every frame and creating a new Explosion every frame. Why not just add a public variable to target, like public var destroyed:boolean = false; and reconstruct your hitTest to read if(hitTestObject(target) && !target.destroyed) and in the body of your conditional just add target.destroyed = true;
More code would definitely help though.

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.

Simulate Mouse Click in AS3

I am working on an AS3 project and I am struggling with one particularly fragile part which will need a lot of refactoring in the near future. Just unit testing separate classes in isolation does not catch all issues we are running into. For example, we might forget to disable mouse events on a transparent overlay and thereby block all clicks on a button. Therefore, I am trying to write a test that simulates real user input.
I have tried to manually send a MouseEvent to the stage at the correct position:
stage.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, true, 380, 490, stage));
Since the stage has no click event handler, I expected the event to propagate through the hierarchy to the button that will actually handle it (as it does when I physically click the mouse). However, it doesn't.
I know that I could just dispatch the event on the button, but that will not detect if the object is somehow obstructed. Is there some way to simulate mouse events, such that they will properly propagate through the hierarchy?
Edit:
I managed to do it by re-implementing the propagation behavior of Flash:
Edit 2:
My previous solution didn't work if there was a partly transparent overlay with a click handler, like a Sprite with a few Shapes in it. The problem is that the hitTestPoint method returns true even if the object in question is completely transparent at that point. Therefore, I modified it to check the actual pixel value:
private function clickObject(obj:DisplayObject) : void
{
var relPos:Point = new Point(obj.width / 2, obj.height / 2);
var globalPos:Point = obj.localToGlobal(relPos);
simulateClick(obj.stage, globalPos);
}
private function simulateClick(obj:InteractiveObject, globalPos:Point) : Boolean
{
// first, check if we have any children that would rather handle the event
var container:DisplayObjectContainer = obj as DisplayObjectContainer;
if (container != null)
{
if (container.mouseChildren)
{
for (var i:int = 0; i < container.numChildren; ++i)
{
var child:DisplayObject = container.getChildAt(i);
var interactive:InteractiveObject = child as InteractiveObject;
if (interactive != null)
{
if (simulateClick(interactive, globalPos))
{
// if we have found a handler in the children, we are done
return true;
}
}
}
}
}
if (!obj.mouseEnabled) {
return false;
}
if (obj.hitTestPoint(globalPos.x, globalPos.y))
{
var localPos:Point = obj.globalToLocal(globalPos);
// check if object is visible at the clicked location
var pixel:BitmapData = new BitmapData(1, 1);
pixel.draw(obj, new Matrix(1, 0, 0, 1, -localPos.x, -localPos.y));
var color:uint = pixel.getPixel32(0, 0);
if ((pixel.getPixel32(0, 0) & 0xff000000) != 0)
{
// if yes, dispatch the click event
var e:MouseEvent = new MouseEvent(MouseEvent.CLICK, true, true, localPos.x, localPos.y, obj);
obj.dispatchEvent(e);
return true;
}
}
return false;
}
Unfortunately, there is still at least one case not covered: If the object is a mask for another object. I have no idea how to check for this, since it could be mask anywhere in the display hierarchy. I would have to traverse the whole tree and check every single display object to find this out.
So, my question remains: Isn't there an easier way to do this?
I've had issues with events in AS3 as well. I've found that the best way is to have the eventListeners added to the same object that's dispatching the events. In your case, adding the .addEventListener to the stage and sending the function as a function on a child clip. eg:
stage.addEventListener(MouseEvent.CLICK, object.object.clicked);
I hope this may help. I've used this method with success in the past.
You can use stage.getObjectsUnderPoint(new Point(pointerX , pointerY )); function , that will return You array with objects . Than remove overlay object and last instance in array should be deepest DisplayObject.
note: last instance can be graphic or such thing , so You should loop through parent objects to find nearest InteractiveObject .
Also , dont forget that parent objects can have mouseChildren = false or mouseEnabled = false

AS3 Event Race Condition

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"));
}