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.
Related
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();
}
I'm making a game as a project for school and have recently encountered a little problem in as3. I'm making a game where you are maneuvering ship avoiding asteroids, and just added the function so that when you hit an asteroid your ship is removed from the stage via stage.removeChild. Everything is fine until you actually hit something, then this error comes up(and I mean alot, like it keeps repeating itself for as long as the game is on):
ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.
at flash.display::DisplayObjectContainer/removeChild()
at Function/com.asgamer.basics1:Engine/private:loop/com.asgamer.basics1:krash()[C:\Users\nti\Desktop\Ship game\com\asgamer\basics1\Engine.as:54]
Here's the code(I marked out line 54):
package com.asgamer.basics1
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
public class Engine extends MovieClip
{
private var numStars:int = 80;
private var enemyList:Array = new Array();
private var ourShip:Ship;
public function Engine() : void
{
ourShip = new Ship(stage);
stage.addChild(ourShip);
ourShip.x = stage.stageWidth / 2;
ourShip.y = stage.stageHeight / 2;
for (var i:int = 0; i < numStars; i++)
{
stage.addChildAt(new Star(stage), stage.getChildIndex(ourShip));
}
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
private function loop(e:Event) : void
{
if (Math.floor(Math.random() * 14) == 5)
{
var enemy:Asteroid = new Asteroid(stage);
enemy.addEventListener(Event.REMOVED_FROM_STAGE, removeEnemy, false, 0, true);
enemyList.push(enemy);
stage.addChild(enemy);
stage.addEventListener(Event.ENTER_FRAME, krash);
function krash(e:Event):void{
if (enemy.hitTestObject(ourShip)==true){
stage.removeChild(ourShip); <-------------------------- LINE 54
}
}
}
}
private function removeEnemy(e:Event)
{
enemyList.splice(enemyList.indexOf(e.currentTarget), 1);
}
}
}
Please do remember that I'm sort of a beginner to coding, which would explain other possible "faults" in the code. :)
The problem is, as was mentioned a while ago, is that you don't clean up your ourShip after actually removing it from stage. See, once you call stage.removeChild() the ship is no longer on stage, but collision check still goes because you didn't remove the enter frame listener. You should add the remove listener statement to the code branch where you remove the ship.
stage.addEventListener(Event.ENTER_FRAME, krash);
... // other code
function krash(e:Event):void{
for each (var enemy in enemyList)
if (enemy.hitTestObject(ourShip)==true){
stage.removeChild(ourShip);
stage.removeEventListener(Event.ENTER_FRAME, krash); // THIS line
}
}
EDIT: first, move the addEventListener(Event.ENTER_FRAME, krash) line to the point where you add a loop listener, because you don't want to have more than a single one of these, and second, make a complete loop for hitTestObject versus enemyList. The code above is updated.
Note: Some validity checks may need to be present in the listener code, for example, if ourShip is already removed you might just return from the event listener function, or skip the hit test checks. This will also help you once you'll start advancing to a single master enter frame listener - it's generally faster having one function as event listener than many for the same event, and also helps you consolidate every bit of code you want to be executed in response for a particular event in one place, instead of searching where did you place that statement that bugs out. This also helps to not fiddle with adding/removing listeners on a regular basis, although sometimes playing with listeners can do you better.
Problem is caused by the fact that you're trying remove ourShip from the stage several times. More precisely on each Event.ENTER_FRAME event.
I'm trying to set the depth of every MovieClip contained in one big MovieClip called 'map'.
To do this, I made a function that runs on event ENTER_FRAME, i.e :
public function set_depth(map:MovieClip) {
var arr:Array = [];
for(var i=1;i<=map.numChildren;i++) {
arr.push({ "name" : map.getChildAt(i-1).name, "y" : map.getChildAt(i-1).y});
}
arr.sortOn("y", Array.NUMERIC);
for(var h=0;h<arr.length;h++) {
map.setChildIndex(map.getChildByName(arr[h].name), h);
}
}
What the function basically does is to sort MovieClip based on its y property and set the MovieClip index
based on the ordererd list of the MovieClips (the y property would eventually change on its own so the
index order will change too). The problem is, I can't seem to change the 'map' MovieClip frame
while the 'set_depth' function continously runs, for example I wanted to change the map current frame everytime
I pressed the Shift key which looked like this :
public function whenKeyUp(e:KeyboardEvent) {
if(e.keyCode == 16) {
trace('Shift pressed');
map.gotoAndStop(2);
}
}
Nothing's happened everytime I pressed Shift, eventhough the trace function gets called. However, if the
'set_depth' function gets commented/deleted, the map MovieClip changed its frame to 2 without any problem.
So, is there anyhting wrong with the code? Can I somehow fix it so that the map MovieClip can change
its frame while the set_depth function runs? Or is there any other solutions? Thanks in advance. Pardon my English
Assuming setDepth(map) is called from function yourEnterFrame(e: Event)..
Change this to
public function whenKeyUp(e:KeyboardEvent) {
if(e.keyCode == 16) {
map.removeEventListener(Event.ENTER_FRAME, yourEnterFrame);
map.addEventListener(Event.ENTER_FRAME, frameHunt);
trace('Shift pressed');
map.gotoAndStop(2);
}
}
And please add this
public function frameHunt(e: Event){
var map:MovieClip = MovieClip(e.target);
if( map.currentFrame != 2) return;
map.removeEventListener(Event.ENTER_FRAME, frameHunt);
map.addEventListener(Event.ENTER_FRAME, yourEnterFrame);
}
I have a student who is working on a Tower Defense game in AS3 and has an issue that has stumped me. He is using hitTestObject to change the direction that a movieClip is moving. The movieClip has its own timeline with frames for the different directions that the object is facing and a linked .as file with the code for the behavior of the object.
When he calls gotoAndStop to change the internal frame of the movieClip, the removed event is triggered, but the object stays on the screen and no longer moves.
All of my searches find answers about removing objects, but I have not seen anything about preventing an object from removing itself.
The following code is a loop triggered by an ENTER_FRAME event in the .as class file for the movieClip object:
private function eFrame(event:Event):void
{
if (_root.isPaused == false)
{
//MOVING THE ENEMY
this.x += speed * xDir;
this.y -= speed * yDir;
if (health <= 0)
{
_root.currency += 4;
this.parent.removeChild(this);
}
if (this.x > 770)
{
this.parent.removeChild(this);
_root.health -= 10;
_root.gotHit = true;
}
//checking if touching any invisible markers
for (var i:int=0; i<_root.upHolder.numChildren; i++)
{
//the process is very similar to the main guy's testing with other elements
var upMarker:DisplayObject = _root.upHolder.getChildAt(i);
if (hitTestObject(upMarker))
{
yDir = 1;
xDir = 0;
this.gotoAndStop(3);
}
}
for (i=0; i<_root.downHolder.numChildren; i++)
{
//the process is very similar to the main guy's testing with other elements
var downMarker:DisplayObject = _root.downHolder.getChildAt(i);
if (hitTestObject(downMarker))
{
yDir = -1;
xDir = 0;
this.gotoAndStop(7);
}
}
for (i=0; i<_root.rightHolder.numChildren; i++)
{
//the process is very similar to the main guy's testing with other elements
var rightMarker:DisplayObject = _root.rightHolder.getChildAt(i);
if (hitTestObject(rightMarker))
{
yDir = 0;
xDir = 1;
this.gotoAndStop(6);
}
}
for (i=0; i<_root.leftHolder.numChildren; i++)
{
//the process is very similar to the main guy's testing with other elements
var leftMarker:DisplayObject = _root.leftHolder.getChildAt(i);
if (hitTestObject(leftMarker))
{
yDir = 0;
xDir = -1;
this.gotoAndStop(2);
}
}
}
}
private function remove(event:Event):void
{
trace("remove");
removeEventListener(Event.ENTER_FRAME, eFrame);
_root.enemiesLeft -= 1;
}
}
When the gotoAndStop line executes, the frame of the movieClip changes and then the code jumps directly to a function that is triggered by the REMOVED event.
Does anyone have an idea why the REMOVED event might be triggered by this code?
Thank you for your help.
The REMOVED Event is triggered by anything that is removed from the stage inside the MovieClip or Sprite that is containing it, if I'm not mistaken. And especially with MovieClips that have animation, things get removed and added everytime, for instance if some part of the animation ends on the timeline, or at keyframes.
Event.REMOVED_FROM_STAGE is dispatched only when the container itself is removed from stage. Maybe that's causing your confusion? I can't see from your code example exactly what event type you're listening for.
Where are you adding the remove-listener?
Without more information, I would guess that you are listening to a clip inside an animation, and that it's not there on all frames (or, maybe even more likely - that the instance is being swapped out for another, identical one, by flash pro. This can happen depending on in what order you added keyframes, the alignment of the moon and fluctuations in the ionosphere. It's easiest fixed by simply removing all key-frames and then re-creating them. And then never using flash pro for anything ever again.)
So in my game, every 'enemy' movieclip creates a textfield that represents the name of the enemy. The problem is, I want my collision detect engine to detect a collision with the enemy itself, not the textfield.
This is the code that I have currently have on my collision class for detecting a collision with the enemy:
for(var i = 0;i < _enemies.length; i++)
{
if(CollisionEngine.isColliding(_laser, _enemies[i]._enemyNameTextField, _animation))
{
}
else if(CollisionEngine.isColliding(_laser, _enemies[i], _animation))
{
_enemies[i].kill();
_animation._killedEnemy = true;
}
}
The first if clause checks for a collision with the enemy's text field. The else if checks for a collision with the enemy.
The problem with this current implementation is that if the 'laser' hits the textfield first, passes through it, and hits the enemy, it's not detected as a collision.
Any idea on how I could work around this?
Change the order of the checks:
if(CollisionEngine.isColliding(_laser, _enemies[i], _animation))
{
if(!CollisionEngine.isColliding(_laser, _enemies[i]._enemyNameTextField, _animation))
{
_enemies[i].kill();
_animation._killedEnemy = true;
}
}
Note that the above code is equivalent to:
if(CollisionEngine.isColliding(_laser, _enemies[i], _animation) && !CollisionEngine.isColliding(_laser, _enemies[i]._enemyNameTextField, _animation))
{
_enemies[i].kill();
_animation._killedEnemy = true;
}
Alternatively, you can explicitly define a hitArea on all of your enemies so that collisions with the text field aren't considered collisions.
in your enemy have a method like:
public function getCollision():Sprite{
//Where this.body is the main animation for the clip/Hittest area
return this.body
}
or something that returns just the active hit area, then you can run:
if(CollisionEngine.isColliding(_laser, _enemies[i].getCollision(), _animation))