I am having trouble with hit detection - actionscript-3

I am having trouble with getting my hit detection to work in as3. I am making a flappy bird like game and when the player dies and goes back to frame 2, we get error #1009. I am new to this so if you could make this as simple as possible it would be much appreciated.Thank you! Here is my code.
import flash.events.Event;
stage.addEventListener(MouseEvent.CLICK, fl_MouseClickHandler);
function fl_MouseClickHandler(event:MouseEvent):void
{
player.y += -100;
}
var randomX:Number = Math.random() * 550;
player.x = 50;
player.y = 50;
var speed:Number = 5;
player.addEventListener(Event.ENTER_FRAME, moveDown);
function moveDown(e:Event):void {
e.target.y += speed;
if(e.target.y >= 400) {
{
gotoAndStop(2);
player.removeEventListener(Event.ENTER_FRAME, moveDown);
}
}
}
var gravity = 8;
stage.addEventListener(Event.ENTER_FRAME, movewt);
function movewt(e:Event):void
{
wt.x = wt.x - 5;
}
stage.addEventListener( Event.ENTER_FRAME, handleCollision)
function handleCollision( e:Event ):void
{
if(player.hitTestObject(wt))
{
gotoAndStop(2);
stage.removeEventListener(MouseEvent.CLICK, fl_MouseClickHandler);
player.removeEventListener(Event.ENTER_FRAME, moveDown);
stage.removeEventListener(Event.ENTER_FRAME, movewt);
stage.removeEventListener( Event.ENTER_FRAME, handleCollision);
}
}

Problem
When this code goes to frame 2
if(e.target.y >= 400)
{
{
gotoAndStop(2);
player.removeEventListener(Event.ENTER_FRAME, moveDown);
}
}
it only removes the ENTER_FRAME event listener from player, all other handlers persist, namely:
fl_MouseClickHandler
movewt
handleCollision
The latter two are executed on their own, which makes them the problematic ones, because the objects that they access are not necessarily present on frame 2.
movewt accesses wt, handleCollision accesses both player and wt. If either player or wt does not exist on frame 2, they are null.
Solution
Don't use frames! While it appears to be very easy to build navigations based on frames, you quickly run into problems like the one at hand. Frames were made for animations and they are great at doing that, so use them for that. How to not use frames is beyond the scope of this answer, there are many resources on the web on how to do that. Most game frameworks don't even have frames and use state machines for that purpose. It's highly recommended to take a look at how game frameworks handle this.
Another problem is the inconsistent way you switch between frames. Sometimes you remove one event listener, sometimes you remove some others...did you just forget to remove all of them? Why do you have this duplicated logic in the first place? If you have different states of your game (say for example, the game itself, which is currently on frame 1 (or whatever frame the code you posted is on) and some game over screen at frame 2), then you should have a clearly defined way to transition between the two states.
If you want to go to the game over state, have one function that does this and do all the cleanup in that function:
function gameOver():void
{
gotoAndStop(2);
stage.removeEventListener(MouseEvent.CLICK, fl_MouseClickHandler);
player.removeEventListener(Event.ENTER_FRAME, moveDown);
stage.removeEventListener(Event.ENTER_FRAME, movewt);
stage.removeEventListener( Event.ENTER_FRAME, handleCollision);
}
Now, whenever you want to change to that state, be it because if the end of the level or because of a collision, call that function:
import flash.events.Event;
var speed:Number = 5;
var gravity = 8;
var randomX:Number = Math.random() * 550;
player.x = 50;
player.y = 50;
stage.addEventListener(Event.ENTER_FRAME, movewt);
stage.addEventListener( Event.ENTER_FRAME, handleCollision);
player.addEventListener(Event.ENTER_FRAME, moveDown);
stage.addEventListener(MouseEvent.CLICK, fl_MouseClickHandler);
function fl_MouseClickHandler(event:MouseEvent):void
{
player.y += -100;
}
function moveDown(e:Event):void
{
e.target.y += speed;
if(e.target.y >= 400)
{
gameOver();
}
}
function movewt(e:Event):void
{
wt.x = wt.x - 5;
}
function handleCollision(e:Event):void
{
if(player.hitTestObject(wt))
{
gameOver();
}
}
function gameOver():void
{
gotoAndStop(2);
stage.removeEventListener(MouseEvent.CLICK, fl_MouseClickHandler);
player.removeEventListener(Event.ENTER_FRAME, moveDown);
stage.removeEventListener(Event.ENTER_FRAME, movewt);
stage.removeEventListener( Event.ENTER_FRAME, handleCollision);
}
Again, ideally you'd use a framework that provides classes that represent game states. This allows for clearly defined transitions between states:
As the code stands now, you have to remember that gameOver() should be called in order to make a proper transition to the game over state and you could still do it in another way. With a library, there's no "other way", only one. But for now, gameOver() will do and should solve your problem.

Related

stop() caller not working properly

I am making a brick breaker game with three frames. The first frame is the start screen, the second frame is the game itself, and the third frame is the "game over" screen (with a try again button). When I hit "Start game" the program jumps to the second frame and stops. If you fail to hit the ball with the racket, the program jumps to frame three.
My problem occurs here, because the program instantly jumps to the second frame again. Any idea why the stop(); caller fails to work? I have tried to remove all content from the last frame (except for the stop(); caller), but it still just skips back to frame 2.
I really can't figure out why this is happening. I am using Adobe Flash Professional CC. The only actionscript on frame 3 are "stop();". This is the entire code block on frame 2:
import flash.events.KeyboardEvent;
import flash.display.Stage;
import flash.events.Event;
import flash.ui.Keyboard;
import fl.transitions.Tween;
import fl.transitions.easing.*;
trace(currentFrame);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
this.addEventListener(Event.ENTER_FRAME, moveBall);
var rackert: bar = new bar();
rackert.name = "rackert";
rackert.y = 740;
rackert.x = 640;
addChild(rackert);
var ball: circle = new circle();
ball.y = 80;
ball.x = 640;
addChild(ball);
var ballXSpeed: Number = 12; //X Speed of the Ball
var ballYSpeed: Number = 12; //Y Speed of the Ball
function keyDown(e: KeyboardEvent) {
var key: uint = e.keyCode;
var step: uint = 35;
switch (key) {
case Keyboard.LEFT:
if (rackert.x > 0) {
var myTween: Tween = new Tween(rackert, "x", Regular.easeOut, rackert.x, rackert.x - step, 0.2, true);
} else rackert.x = 0;
break;
case Keyboard.RIGHT:
if (rackert.x + rackert.width < 1000) {
var myTween2: Tween = new Tween(rackert, "x", Regular.easeOut, rackert.x, rackert.x + step, 0.2, true);
} else rackert.x = 1000 - rackert.width;
break;
}
}
var gameOver: Boolean = false;
function moveBall(event: Event): void {
ball.x += ballXSpeed;
ball.y += ballYSpeed;
if (ball.x >= 1000 - (ball.width / 2)) {
ballXSpeed *= -1;
}
if (ball.x <= 0 + (ball.width / 2)) {
ballXSpeed *= -1;
}
if (ball.y >= stage.stageHeight) {
if (gameOver == false) {
gotoAndStop(3);
this.removeEventListener(Event.ENTER_FRAME, moveBall);
stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDown);
gameOver = true;
rackert.visible = false;
}
}
if (ball.y <= 22) {
ballYSpeed *= -1;
}
if (ball.hitTestObject(rackert)) {
calcBallAngle();
}
}
function calcBallAngle(): void {
var ballPosition: Number = ball.x - rackert.x;
trace("Position: " + ballPosition);
var hitPercent: Number = (ballPosition / (rackert.width - ball.width)) - .7;
trace("percent: " + hitPercent);
ballXSpeed = hitPercent * 10;
ballYSpeed *= -1;
}
function getRandom(min: Number, max: Number): Number {
return min + (Math.random() * (max - min));
}
Change this:
if (gameOver == false) {
gotoAndPlay(3); //gotoAndPlay(); caller
gameOver = true;
rackert.visible = false;
}
To:
if (gameOver == false) {
gotoAndStop(3); //gotoAndPlay(); caller
gameOver = true;
rackert.visible = false;
}
Difference is goToAndStop(). The default behavior is to "loop" an animation, so you tell it to go to frame 3 (last frame) and it "plays" through that frame back around to 1, then 2, where you most likely have a frame script that calls stop(); to stop the play head.
Update
I believe you that you're calling stop(); in frame 3. It seems like it should work and indeed it actually is, it's just not working on the object that you're expecting it to work on. Since you're using a frame script, stop(); is being called on the InteractiveObject who's scope the frame script is inside of. Let me clarify.
Frame 3 Of Stage
-> Child on frame three called FrameScriptsArePITA
-> Double click FrameScriptsArePITA and write a frame script "stop()", the script will do nothing but stop FrameScriptsArePITA from playing.
Watch your scope. That's part of why frame scripts are... best to avoid. Using your own DocumentClass and hooking everything in your design view into corresponding classes will make things easier to solve in AS3.
I finally found the issue. I had a timer event on frame 1, which caused the bug. I simply used removeEventListener for the timer function where i skip to frame 2. As Technick Empire said, you should always be cleaning up anything including even listeners as they can even interfere with the garbage collector and cause memory leaks.

Problems getting keyboard input in AS3

So I'm workin on a flash project where I want keyboard input. In the stage there's an instance "Car" seen from above which is supposed to be rotate and drive direction of rotation. This is what I've put together so far in AS3:
//Required stuff
import flash.ui.Keyboard;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.display.Stage;
import flash.display.MovieClip;
Car.addEventListener(Event.ENTER_FRAME, this.RunGame);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
//Variables
var keys:Array = []
var vDrive:Number = 3; //Car's current base speed
var vx:Number = 0; //Speed along x axis
var vy:Number = 0; //Speed along y axis
var vMax:Number = 30; //Top speed
var vRot:Number = 3; //Rotation speed
var vAcc:Number = 1.1; //Factor for acceleration
var vDeAcc:Number = 0.90; //Factor for de-acceleration
//Game Loop
RunGame();
function RunGame():void
{
// Drive forwards
if (keys[Keyboard.UP])
{
if (vDrive < vMax)
vDrive += vAcc;
}
// Reverse
if (keys[Keyboard.DOWN])
{
if (vDrive > vMax)
vDrive *= vAcc;
}
// Turn right
if (keys[Keyboard.RIGHT])
{
Car.rotation += vRot;
}
// Turn left venstre
if (keys[Keyboard.LEFT])
{
Car.rotation -= vRot;
}
//Movement
// Friction
vDrive *= vDeAcc;
//Calculating movement vector
vx = vDrive * Math.cos(toRad(Car.rotation));
vy = vDrive * Math.sin(toRad(Car.rotation));
//Update car position
Car.x -= vx ;
Car.y -= vy;
}
However, when I run the program, the arrow keys don't seem to do anything.
I also get the following compiler warnings for both "onKeyDown" and "onKeyUp":
Migration issue: The onKeyDown event handler is not triggered
automatically by Flash Player at run time in ActionScript 3.0. You
must first register this handler for the event using addEventListener
( 'keyDown', callback_handler)
Trying to add what it suggested just makes errors saying callback_handler ain't defined.
I'm now stuck trying to figure out how to make the keyboard input work. Anyone know?
You are currently missing the functions for the key listeners. You have added the listeners to the stage here:
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
Now you just need to create the functions:
function onKeyDown( e:KeyboardEvent ):void {
//add our key to the keys array
keys[e.keyCode] = e.keyCode;
}
function onKeyUp( e:KeyboardEvent ):void {
//if the key is in the keys array, set the value to null
keys[e.keyCode] = null;
}
But there is another problem here:
Car.addEventListener(Event.ENTER_FRAME, this.RunGame);
You do not need the this.RunGame, just RunGame will do, but you should get an error this method needs a parameter of type Event, so your RunGame() definition should look like this:
function RunGame(e:Event):void
Then you wouldn't call RunGame(), it is called each frame while tied to the event listener.
Edit: Please note that there are many ways to handle key events, my answer will work with your current implementation.

Why are my array movie clips getting harder to click when they get faster?

I am making a game where insects come down from the top of the screen, and the user must kill them. The insects are in an array. Each time the user kills them, the score goes up..after a while the insects get faster and faster. When they get faster, some of them don't get killed when you click them. You have to click multiple times for them to die. I want them to get killed in one click, but this isn't working when they get faster!
function makeEnemies():void
{
var chance:Number = Math.floor(Math.random() * 150);
if (chance <= + level)
{
//Make sure a Library item linkage is set to Enemy...
tempEnemy = new Enemy();
//Math.random(); gets a random number from 0.0-1.0
tempEnemy.x = Math.round(Math.random() * 1000);
addChild(tempEnemy);
enemies.push(tempEnemy);
tempEnemy.speed = enemyBaseSpeed + ((level - 1) * speedLevelInc);
}
}
function moveEnemies():void
{
var tempEnemy:MovieClip;
for (var i:int =enemies.length-1; i>=0; i--)
{
tempEnemy=enemies[i];
if (tempEnemy.dead)
{
score++;
score++;
roachLevel.score_txt.text = String(score);
enemies.splice(i,1);
}
else // Enemy is still alive and moving across the screen
{
//rotate the enemy between 10-5 degrees
tempEnemy.rotation += (Math.round(Math.random()*.4));
//Find the rotation and move the x position that direction
tempEnemy.x -= (Math.sin((Math.PI/180)*tempEnemy.rotation))*tempEnemy.speed;
tempEnemy.y += (Math.cos((Math.PI/180)*tempEnemy.rotation))*tempEnemy.speed;
if (tempEnemy.x < 10)
{
tempEnemy.x = 11;
}
if (tempEnemy.x > stage.stageWidth - offset)
{
tempEnemy.x = stage.stageWidth - offset;
}
if (tempEnemy.y > stage.stageHeight)
{
removeEnemy(i);
lives--;
roachLevel.lives_txt.text = String(lives);
}
}
}
}
function removeEnemy(id:int)
{
removeChild(enemies[id]);
enemies.splice(id,1);
}
There is also code inside the insect.
import flash.events.MouseEvent;
import flash.display.MovieClip;
import fl.motion.Animator;
import flash.events.*;
play();
var MainTimeLine = MovieClip(root);
var mysound:squish = new squish();
this.addEventListener(MouseEvent.CLICK, kill);
this.dead = false;
function kill(e:MouseEvent):void
{
this.dead=true;
mouseChildren=false
mysound.play();
gotoAndPlay(21);
this.removeEventListener(MouseEvent.CLICK, kill);
flash.utils.setTimeout(removeSelf,2000);
}
function removeSelf():void
{
this.parent.removeChild(this);
}
You shouldn't remove an enermy from the array while iterating it.
You make enemies.splice(i,1); in your loop iterating from enemies.length to 0. While you changing your array size, you don't adjust the loop condition.
I think your main issue might be that you are reusing your insects, maybe pooling them. If you do this, you need to make sure that you are adding the eventListener for the click again when recycling.
If you are adding the listener in the constructor, that will only execute when the insect is created, not when you recycle him.
Your issue is the setTimeOut(), which is causing a memory leak. Using a timer is much safer, but if you must use it, keep a reference to the call and clear it when you no longer need it.
Also, the code you posted doesn't show where you're adding a listener to MainTimeline or parent, but if you are you need to remove that as well before the insect can be garbage collected.

How to remove a function or event stop it from stage.addEventListener in as3

I´m trying to make object follow the mouse in as3.
My wish is when I roll over a movieclip(btn1) I want the function that make object follow the mouse(my_object) stop until I roll out of it.
HERE IS THE SCRIPT:
btn1.addEventListener(MouseEvent.ROLL_OVER, JD);
function JD(event:MouseEvent):void{
stage.removeEventListener(Event.ENTER_FRAME, follow_me);
}
btn1.addEventListener(MouseEvent.ROLL_OUT, kk);
function kk(event:MouseEvent):void{
play();
}
stage.addEventListener(Event.ENTER_FRAME,follow_me)
function follow_me(event:Event):void {
var dx:int = bracketL.x - mouseX;
var dy:int = bracketL.y - mouseY;
my_object.x -= dx / 9+5;
my_object.y -= dy /9;
}
Even I roll over the btn1, the my_object does not stop, it still follow the mouse !!
WHAT SHOULD I DO ?
Using a different approach for smoother animation, you could create a enter frame handler that checks for a paused state variable.
Each frame, your object is animated following the mouse cursor; however, if the mouse rolls over your button the object is paused from tracking the mouse.
Example SWF
CS6 FLA source code
CS5 FLA source code
Code:
import flash.events.Event;
import flash.events.MouseEvent;
var paused:Boolean = false;
addEventListener(Event.ENTER_FRAME, frameHandler);
button.addEventListener(MouseEvent.ROLL_OVER, buttonOverHandler);
button.addEventListener(MouseEvent.ROLL_OUT, buttonOutHandler);
function buttonOverHandler(event:MouseEvent):void
{
paused = true;
}
function buttonOutHandler(event:MouseEvent):void
{
paused = false;
}
function frameHandler(event:Event):void
{
if (!paused)
{
object.x -= (object.x - mouseX) * 0.1;
object.y -= (object.y - mouseY) * 0.1;
}
}

How to get this code to show movieclip correctly?

I have the following code and it works fine, except I want it to play the Explosion movieclip in the library when an EnemyShip disappears but I only want it to play once and then disappear but not quite sure how to do it (I've tried a few things and it either makes the explosion animation loop and the ships don't disappear, which I believe is as a result of putting it inside the kill function, or I get other ArgumentErrors).
var speed:Number;
var shot = new ShotSound();
var explosion = new Explosion();
this.x = 800;
this.y = Math.random() * 275 + 75;
speed = Math.random()*5 + 9;
addEventListener("enterFrame", enterFrame);
addEventListener(MouseEvent.MOUSE_DOWN, mouseShoot);
function enterFrame(e:Event)
{
this.x -= speed;
if(this.x < -100)
{
removeEventListener("enterFrame", enterFrame);
stage.removeChild(this);
}
}
function kill()
{
stage.addChild(this);
explosion.x = this.x;
explosion.y = this.y;
removeEventListener("enterFrame", enterFrame);
stage.removeChild(this);
shot.play();
}
function mouseShoot(event:MouseEvent)
{
kill();
}
Thank for for any help I may receive.
You need some script to fire when the Explosion reaches it's final frame. A few ways you can do this:
1.
Dispatch an event on the last frame of your explosion animation. eg. this.dispatchEvent(new Event(Event.COMPLETE)); then listen for that event:
var explosion = new Explosion();
addChild(explosion);
explosion.addEventListener(Event.COMPLETE, removeExplosion);
function removeExplosion(e:Event):void {
MovieClip(e.currentTarget).stop();
MovieClip(e.currentTarget).removeEventListener(Event.COMPLETE, removeExplosion);
removeChild(MovieClip(e.currentTarget));
}
2.
Have the explosion remove itself on the last frame of the animation. eg. if(this.parent) parent.removeChild(this);
3.
If you can't or don't want to modify the explosion timeline, use the undocumented AddFrameScript command:
var explosion = new Explosion();
addChild(explosion);
explosion.addFrameScript(explosion.totalFrames-1, function():void {
explosion.stop();
if(explosion.parent){
explosion.parent.removeChild(explosion);
}
});
Here is a tip with your setup:
add a function called removeMe (or similiar) to avoid redundant code (so you call this function on kill or when the ship goes out of bounds
function removeMe(e:Event = null):void {
this.removeEventLIstener(Event.ENTER_FRAME,enterFrame);
if(this.parent){
this.parent.removeChild(this);
}
//any other cleanup that's required
}
Next, an updated kill function:
function kill(){
var explosion = new Explosion();
explosion.addEventListener(Event.COMPLETE, removeMe); //you need to dispatch this event on the last frame of the explosion timeline as shown below
addChild(explosion); //add it to the ship so it stays with it as the ship moves
//not sure what shot.play() does and if that belongs here.
}
//on the last frame of your explosion timeline:
stop();
dispatchEvent(new Event(Event.COMPLETE));
For starters, you're never adding explosion to the display list.
Change
var explosion = new Explosion();
stage.addChild(this);
to
var explosion = new Explosion();
stage.addChild(explosion);
Once you've done that, you'll want to add a listener to explosion to find out when it has finished playing:
explosion.addEventListener(Event.ENTER_FRAME, onExplosionProgress);
function onExplosionProgress(e:Event):void
{
if(explosion.currentFrame == explosion.totalFrames)
{
// explosion has reached end of its timeline
explosion.removeEventListener(Event.ENTER_FRAME, onExplosionProgress);
explosion.stop();
stage.removeChild(explosion);
}
}