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

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.

Related

kill unnamed generated instances after time in flash (as3)

I am making a little game, where instances are spawned and I want them to be killed after two seconds.
The problem I was running into is that the instances have a generated name, and I don't know how to talk to them after they have spawned.
I tried things like Timeout or a normal Timer, but I still can't talk to them.
function spawn(): void {
if (Math.floor(Math.random() * 70) == 0) {
plane = new Plane();
plane.x = Math.random() * (stage.stageWidth - 100) + 50;
plane.y = Math.random() * (stage.stageHeight - 100) + 20;
plane.addEventListener(MouseEvent.CLICK, shoot);
var killtimer: Timer = new Timer(2000);
killtimer.addEventListener(TimerEvent.TIMER, timerListener);
//setTimeout(kill, 2000);
addChild(plane);
killtimer.start();
}
if (Math.floor(Math.random() * 30) == 0) {
bird = new Bird();
bird.x = Math.random() * (stage.stageWidth - 100) + 50;
bird.y = Math.random() * (stage.stageHeight - 100) + 20;
bird.addEventListener(MouseEvent.CLICK, shoot);
//setTimeout(kill, 2000);
addChild(bird);
}
if (Math.floor(Math.random() * 300) == 0) {
g_bird = new Golden_bird();
g_bird.x = Math.random() * (stage.stageWidth - 100) + 50;
g_bird.y = Math.random() * (stage.stageHeight - 100) + 20;
g_bird.addEventListener(MouseEvent.CLICK, shoot);
//setTimeout(kill, 2000);
addChild(g_bird);
}
}
function timerListener(e: TimerEvent): void {
trace("Killtimer: " + flash.utils.getQualifiedClassName(e.currentTarget));
e.currentTarget.parent.removeChild(e.currentTarget); <- Problem e is the timer, not the instance
}
Can anybody help me?
I'll present you with two options.
1. (Easy-ish way) - Use dynamic properties and an Array (or Vector)
Create an Array or Vector to hold all your spawned items.
var items:Array = [];
Then, when you spawn your items, add them to the array/vector, and give them a made up property, let's call it duration and it will store the current time plus 2 seconds:
plane = new Plane();
items.push(plane);
plane.duration = flash.utils.getTimer() + 2000; //this is when the item expires and can be removed
Then, make ONE master timer that ticks 10 times per second (or however long you'd like)
var mainTimer:Timer = new Timer(100);
mainTimer.addEventListener(TimerEvent.TIMER, timerListener);
mainTimer.start();
Or instead of that, you could listen every frame: (more accurate, but less performant)
this.addEventListener(Event.ENTER_FRAME, timerListener);
In the timer tick handler (or enter frame handler), check the duration of each item and see if it needs to be removed yet:
function timerListener(e:Event):void {
//get the current time
var time:Number = flash.utils.getTimer();
//iterate backwards through all the items
for(var i:int=items.length-1;i--){
//if the current time is the same or greater
if(items[i]).time >= time){
removeChild(items[i]); //remove it from the screen
items.splice(i,0); //delete it from the array
}
}
}
2. Create a base class that does all the work
The best way to do this, would be to make a base class for all those objects who you wish to last a specific duration.
You could create a file called MyBaseClass.as next to your .fla. In that file, you could do something like this:
package {
import flash.display.Sprite;
import flash.utils.Timer;
import flash.events.Event;
import flash.events.TimerEvent;
public class MyBaseClass extends Sprite {
private var timer:Timer = new Timer(2000,1);
public function MyBaseClass():void {
this.addEventListener(Event.ADDED_TO_STAGE, addedToStage, false, 0, true); timer.addEventListener(TimerEvent.TIMER, kill,false,0,true);
}
private function addedToStage(e:Event):void {
timer.start();
}
private function kill(e:Event):void {
if(parent) parent.removeChild(this);
}
}
}
Anything that extends this base class, will kill itself after 2 seconds of being added to the display.
To extend it, right click your assets in the library (in FlashPro), and in the "export for actionscript" settings, put MyBaseClass in the base class text field.
Of course there are other ways to accomplish this as well, and you could also do some combination of the two I've shown, as having just one timer is more efficient than every item having it's own timer running.
3. Use setTimeout (not ideal)
If want to just understand how you could use setTimeout, this would be the correct usage:
plane = new Plane();
setTimeout(kill, 2000, plane);
function kill(itemToKill:DisplayObject):void {
removeChild(itemToKill);
}
one more variant is to create closure as an event listener, which will enclose the value of a plane, and after being triggered would remove itself as an event listener, and remove the plane. Just like this:
function getKillFn(plane:Plane):Function {
return function handler(event:TimerEvent):void {
Timer(event.currentTarget).removeEventListener(TimerEvent.TIMER, handler);
plane.parent.removeChild(plane);
}
}
so all that you have to change in your code is to replace this line:
killtimer.addEventListener(TimerEvent.TIMER, timerListener);
with this one:
killtimer.addEventListener(TimerEvent.TIMER, getKillFn(plane));
Or you can even create this function inplace (in spawn function):
function spawn(): void {
if (Math.floor(Math.random() * 70) == 0) {
plane = new Plane();
plane.x = Math.random() * (stage.stageWidth - 100) + 50;
plane.y = Math.random() * (stage.stageHeight - 100) + 20;
plane.addEventListener(MouseEvent.CLICK, shoot);
var killtimer: Timer = new Timer(2000);
function killPlane(event:TimerEvent):void {
killTimer.removeEventListener(TimerEvent.TIMER, killPlane);
plane.parent.removeChild(plane);
}
killtimer.addEventListener(TimerEvent.TIMER, killPlane);
//setTimeout(kill, 2000);
addChild(plane);
killtimer.start();
}
...
and one more thing: since you create a new timer on each spawn call, you should stop it and recycle in a handler, or better use TimerEvent.COMPLETE, for it to stop automatically:
var killtimer: Timer = new Timer(2000, 1);
function killPlane(event:TimerEvent):void {
killTimer.removeEventListener(TimerEvent.COMPLETE, killPlane);
killTimer = null;
plane.parent.removeChild(plane);
}
killtimer.addEventListener(TimerEvent.COMPLETE, killPlane);
hope this'll help

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.

How to loop falling object in flash AS3 once it has hit bottom of the scene

This is the code i have got so far for a single falling object. The 'DangerIN' is the instance name for the object that is falling down. The class is named 'Danger'. So how can i make it loop so it falls continuously and when it reaches certain y value it will remove it self. Also i want more than one(abount 5) objects falling down at once.
var randomX:Number = Math.random() * 550;
DangerIN.x = randomX;
DangerIN.y = 96;
var speed:Number = Math.random()*10;
DangerIN.addEventListener(Event.ENTER_FRAME, moveDown);
function moveDown(e:Event):void {
e.target.y += speed;
if(e.target.y >= 610) {
DangerIN.removeEventListener(Event.ENTER_FRAME, moveDown);
}
}
It's easy. But to do that, you first need an Array of falling stuff, and then you need to reposition your e.target to the top once it's below your threshold.
function moveDown(e:Event):void {
e.target.y += speed;
if (e.target.y >= 610) {
// reposition
e.target.x=math.random()*550;
e.target.y=96;
}
}
Assign this function to every object you want to fall down, reach bottom and reappear back up.
To remove itself you can add a following line after the removeEventListener():
parent.removeChild(this);
But it's not pretty and you probably should to it the right way:
Store all the Danger objects in the array, in the Danger class create a function like go(), moveDown() or something:
public function go():void
{
y+= speed;
}
and in the class where you create the Danger objects make a loop like this:
private function loop():void
{
for (var i:int = dangerObjArray.lenght - 1; i >= 0; i--)
{
dangerObjArray[i].go();
if (dangerObjArray[i].y >= maxY)
dangerObjArray.splice(i , 1);
}
}

Creating Multiple Levels AS3

Hey Guys so I'm having a little trouble creating Multiple levels. I'm not so sure if im creating them the right way but i have a player and goal_1, goal_2, etc.. Basically when the player hitTestObject the goal_1 i want it to go to a new function called level_2 then level_3 after that hitTest. so Level_1 works just fine the hitTest works and it initializes level_2 but when i try to hitTest the player and goal_2 or even goal_1 again it just goes through it and doesnt do anything.
I understand now that level_2 isnt being called every frame like level_1 since its not part of the Enter_Frame listener. But i cant figure out how to have multiple Enter Frame events and not have them run simultaneously. If thats even the right way to create multiple levels.
Can you see what i could do in order to make it work?
private function gameLoop(e:Event):void
{
playerShoot();
playerControl();
playerStageBoundaries();
checkEndGameCondition();
checkPlayerOffScreen();
level_1();
}
private function level_1():void
{
if(player.hitTestObject(mGoal_1))
{
trace("Goal_1 Collision");
//Remove button for constant movement
btnShootPlayer = false;
mGoal_1.destroyGoal_1();
player.destroyPlayer();
//Update High Score text
nScore += 10;
updateHighScore();
stage.removeEventListener(Event.ENTER_FRAME, gameLoop);
//Update level
nLevel++;
updatePlayerLevel();
level_2();
}else
{
checkEndGameCondition();
}
}
public function level_2():void
{
stage.addEventListener(Event.ENTER_FRAME, gameLoop);
TweenMax.to(mGoal_1, 1, {y:40, repeat:-1, yoyo:true, ease:Power0.easeInOut});
trace("Level_2 Initiated");
//Keep Text Scores initiated
updateHighScore();
updatePlayerLives();
player = new mPlayer();
stage.addChild(player);
player.x = (stage.stageWidth / 2) - 280;
player.y = (stage.stageHeight / 2);
mGoal_1 = new goal_1();
stage.addChild(mGoal_1);
mGoal_1.x = (stage.stageWidth / 2) + 300;
mGoal_1.y = (stage.stageHeight) - 35;
if (player.hitTestObject(mGoal_1))
{
trace("Level 2 Hit test works!");
nScore += 10;
updateHighScore();
}
}
I didn't read too carefully all the code, but I guess you can use a function variable. Declare it on class level (outside any function):
var _doFunction:Function;
than, instead calling level1 function, pass the reference and call the _doFunction:
_doFunction = level1;
_doFunction();//or _doFunction.call(); - see Adobes documentation
when you are done with the level1, than pass the next level:
_doFunction = level2;
P.S. don't forget to accept the answer if it helped to solve your problem.