I'm going crazy with this. I have a few bears on the stage and have listed them in an array. I want them to change their animation frame when clicked as long as they are not on the "down" animation frame to begin with. Below is the code. The problem occurs below the //check if they get hit line.
//put the bears in an array
var bearsArray: Array = new Array();
for (var i=0; i<numChildren;i++) {
if (getChildAt (i) is bear_mc) {
bearsArray.push(getChildAt(i));
}
}
//move them up and down
addEventListener(Event.ENTER_FRAME,upAndDown);
function upAndDown(event:Event){
if (Math.random() < 0.02){
var randomBear = Math.floor(Math.random() * 9);
bearsArray[randomBear].gotoAndPlay("popup");
}
}
//check if they get hit
for (var j:int = 0; j < bearsArray.length; j++){
bearsArray[j].addEventListener(MouseEvent.CLICK, hitBears);
}
function hitBears(e:MouseEvent){
for (var k: int=0; k<numChildren; k++){
if (bearsArray[k].currentFrame != "down"){
trace("clicked");
bearsArray[k].gotoAndPlay("surprised");
}
}
}
currentFrame returns an integer, not string. If you want to use string, you have to use currentFrameLabel or currentLabel.
EDIT: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/MovieClip.html#currentLabel
If you want to control only one bear, remember that you don't need to cycle through them (actually that would serve no purpose as you would be checking every one of them). The better (and correct) approach would be this:
function hitBears(e:MouseEvent){
var bear:MovieClip = e.currentTarget as MovieClip;
if(bear.currentLabel != "down") { //or currentFrameLabel, depends how your mc is set up
trace("clicked");
bear.gotoAndPlay("surprised");
}
}
I got it working by checking a hittest with the mouse instead of just checking the click. That works for some reason.
Related
I want to create a stacking Game. Where when you tap the screen for instance a block falls down and a new one appears where the other one originally was. Now when the User taps the screen again the same block falls down and if aligned correctly stacks on top of the first one so one and so one. Keep stacking until you miss.
I thought creating an array and pushing each new object to that array would be able to hitTest between each new one etc and have them stack on each other. I realized I don't quite understand how to go about doing this. New instances are created so I got that down. Here is my code so far:
private function engineLogic(e:Event):void
{
stackingHandler();
}
private function stackingHandler():void
{
for (var i:int = 0; i < aCatArray.length; i++)
{
var currentCat:mcCats = aCatArray[i];
//HIT TEST CATS
}
trace("NUMBER OF CATS: " + aCatArray.length);
}
private function onTap(e:MouseEvent):void
{
//Move Down
TweenLite.to(cats, 1.0, {y:(stage.stageHeight / 2) + 290, onComplete: addCats});
}
private function addCats():void
{
//Create Instance
cats = new mcCats();
//Add Objects
addChild(cats);
//Push to Array
aCatArray.push(cats);
}
I would appreciate any help from you guys. Maybe if you can push me in the right direction. Thank you in advance!
It looks like the cats variable holds the object that is currently falling?
In that case you'd do something like this:
private function stackingHandler():void
{
for (var i:int = 0; i < aCatArray.length; i++)
{
if(cats.hitTestObject(aCatArray[i])) {
// collision detected!
// kill the Tween
// set the y position of the `cats` object
// so it appears on top of the object it collided with (`aCatArray[i]`)
// (it may have moved slightly past the object before doing this check)
}
}
}
So you're looping through the array and hit testing cats against every object in the array one at a time.
It might make more sense to use a basic gravity simulation, or just linearly increasing the y value instead of using a Tween, but you didn't ask about that.
You might also want to set a flag for whether or not an object is currently falling and use that to determine whether or not to run the stackingHandler. Otherwise, you'll just be continually hit testing all the objects when nothing is moving.
This is how I was able to fix it. Creating a double for loop. Checking if they are equal to each other continue and check for hitTest:
private function stackingHandler():void
{
for (var i:int = 0; i < aCatArray.length; i++)
{
var currentCat:mcCats = aCatArray[i];
for (var j:int = 0; j < aCatArray.length; j++)
{
var newCat:mcCats = aCatArray[j];
if (currentCat == newCat) continue;
//Hit Test between Objects
if (newCat.hitTestObject(currentCat.mcHit) && newCat.bFlag == false)
{
//Stop Moving
newCat.stopMoving();
trace("HIT");
if (highScore == 0)
{
addCats();
trace("ADD CATS 1");
}else
{
TweenLite.delayedCall(0.6, addCats);
trace("ADD CATS 2");
}
//Add Points
highScore ++;
trace(highScore + " Score");
//Set Flag boolean
newCat.bFlag = true
}
}
}
}
I've added the basic targets and applying drag and drop for my puzzle pieces, now Im having trouble making the shuffling aspect. As in, after the player completes or opens up the fla, each time will start the puzzle pieces in random places of the stage. I understand using arrays for shuffling somehow but Im not sure exactly how to achieve this. I've stored the instance of my 19 puzzle pieces inside the array but now I have no idea what to do with this array. Other tutorials were abit out of my league and leaves my head scratching.
Just started doing coding for flash professional so yeah, any help with the shuffling movie clips ie the puzzles pieces would be greatly appreciated.
Heres's my code, Im not posting the whole thing since from P1 to P19 is basically copy pasting:
import flash.events.Event;
stage.addEventListener(Event.ENTER_FRAME, EntFrame)
function EntFrame(e: Event) : void
{
P1.addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag);
function fl_ClickToDrag(event:MouseEvent):void
{
P1.startDrag();
}
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop);
function fl_ReleaseToDrop(event:MouseEvent):void
{
P1.stopDrag();
}
if (T1.hitTestObject(P1.Tar1))
{
P1.x = 313.15;
P1.y = 242.75;
}
P19.addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag_19);
function fl_ClickToDrag_19(event:MouseEvent):void
{
P19.startDrag();
}
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop_19);
function fl_ReleaseToDrop_19(event:MouseEvent):void
{
P19.stopDrag();
}
if (T19.hitTestObject(P19.Tar19))
{
P19.x = 624.35;
P19.y = 455.60;
}
}
Here is what I hope is more holistic answer.
First, ditch those inline functions. Right now you make an ENTER_FRAME listener and inside that function you have inline function defined. This means every frame tick (which is tied to your frame rate, not the main timeline), those functions are going to get created again, and since you are adding them as handlers for listeners, they will stay in memory forever.
Here is a way you code this, showing ways to reduce redundancy and get rid of those memory leaks. This assumes the following:
You have 19 objects on the stage called T1 - T19, that represent the possible locations the pieces can go.
You have 19 pieces on the stage called P1 - P19, that, and the numbers correlate to the T locations as per the correct location of the piece.
//let's create a function to randomize the piece location
function seedPieces() {
//create an array consisting of the integers 1 - 19
var unusedSpaces:Vector.<int> = new Vector.<int>;
var i:int;
for (i = 1; i <= 19; i++) {
//populate that array
unusedSpaces.push(i);
}
var curLocation:DisplayObject; //helper var for the loop below
var curPiece:Sprite; //helper var for the loop below
//loop 19 times (from 1 - 19) - one iteration for each piece
for (i = 1; i <= 19; i++) {
curPiece = this["P" + i] as Sprite; //you can get the piece this way, or use an array if you've made one, like `pieces[i];`
trace(curPiece.name);
//splice removes and returns the item at the specified index (in this case a random number between 0 and arrays length less 1) - the second parameter is amount of items to remove (just 1 for this case)
curLocation = this["T" + unusedSpaces.splice(int(Math.random() * unusedSpaces.length), 1)] as DisplayObject;
trace(" ",curLocation.name);
//move the piece to the random location:
curPiece.x = curLocation.x;
curPiece.y = curLocation.y;
}
}
//NOW, as an aside, you should use a loop to add all your listeners for the sake of sanity - if you have them in an array, loop through that, or use the sloppy way like this:
for (var i:int = 1; i <= 19; i++) {
Sprite(this["P" + i]).addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag);
}
//create a var to hold any piece that is currently being dragged, so you know which piece to stop drag on later
var currentDraggingItem:Sprite;
seedPieces();
function fl_ClickToDrag(event:MouseEvent):void
{
//assign this clicked item to the currentDraggingItem var
currentDraggingItem = event.currentTarget as Sprite;
//bring this one to the front
currentDraggingItem.parent.addChild(currentDraggingItem);
//you can use this one click handler for all pieces
//the piece that was actually clicked, is referenced by event.currentTarget
currentDraggingItem.startDrag();
//add the mouse up listener now that the mouse is currently DOWN
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop);
//listen every frame while dragging
stage.addEventListener(Event.ENTER_FRAME, EntFrame);
}
function fl_ReleaseToDrop(event:MouseEvent):void
{
//if currentDraggingItem has a value, stop drag it
if (currentDraggingItem) {
currentDraggingItem.stopDrag();
//send to the back
currentDraggingItem.parent.addChildAt(currentDraggingItem,0);
}
//remove the mouse up and enter frame listener now that the mouse is UP
stage.removeEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop);
stage.removeEventListener(Event.ENTER_FRAME, EntFrame);
if(checkComplete()){
//game over, do something
}
}
function EntFrame(e: Event) : void
{
//this will snap the peice to the correct spot when the mouse is touching the correct spot
if(currentDraggingItem){
if (this[currentDraggingItem.name.replace("P","T")].hitTestPoint(mouseX,mouseY))
{
currentDraggingItem.x = this[currentDraggingItem.name.replace("P","T")].x;
currentDraggingItem.y = this[currentDraggingItem.name.replace("P","T")].y;
}
}
}
function checkComplete():Boolean {
//use a loop to go through all your pieces and check if they are in the right spot. Again, you could have them in an array, or do it the lazy way
for (var i:int = 1; i <= 19; i++) {
if (!this["T"+i].hitTestObject(this["P"+i]))
{
return false;
}
}
return true;
}
Well, in general you can shuffle with the following code:
var shuffledVector:Vector.<someClass> = new Vector.<someClass>;
while (originalVector.length > 0) {
shuffledVector.push(originalVector.splice(Math.random() * originalVector.length, 1)[0]);
}
Longer, explained version:
var shuffledVector:Vector.<someClass> = new Vector.<someClass>; //We will store our shuffled vector in here
var randomIndex:int; //Random index from the originalVector
var resultVector:Vector.<someClass>; //result from the originalVector.splice(...) function
var randomElement:someClass; //Random element from the originalVector
while (originalVector.length > 0) { //We will reduce the size of the originalVector until the originalVector is empty.
randomIndex = Math.random() * originalVector.length; //Calculate a random index within the range of the originalVector from 0 to originalVector.lenght-1 (note that the range decreases by one on every loop)
randomVector = originalVector.splice(randomIndex, 1); //Use splice to remove one element at the randomly choosen index, we will receive a vector with the removed element...
randomElement = randomVector[0]; //...so we need to access the element
shuffledVector.push(randomElement); //Add the randomly choosen element to our shuffled vector
}
I've written the code for a vector as i suggest to use a vector instead of an array, but the principle behind it is the same for an array.
In your case the originalVector is a vector filled with your P1-P19 Movieclips and someClass would be MovieClip. The originalVector is empty at the end and could be replaced with the shuffled one and of course it would make a lot more sense if you put the code in a seperate function like this:
function Shuffle(originalVector:Vector.<someClass>) : void {
var shuffledVector:Vector.<someClass> = new Vector.<someClass>;
while (originalVector.length > 0) {
shuffledVector.push(originalVector.splice(Math.random() * originalVector.length, 1)[0]);
}
originalVector = shuffledVector;
}
Offtopic, but important for further coding: Someone else already mentioned, that it is not good to add EventListeners on every frame, because it is absolutely unnecessary. You only need to add the Listeners once. Your code is very repetitive, you should use a function which accepts a MovieClip, x and y then call that function 19 times.
e.g.:
function setUpMovieClip(MC:MovieClip, x:int, y:int) : {
MC.addEventListener(MouseEvent.MOUSE_DOWN, clickToDrag);
//more code...
}
within the clickToDrag function you can access the MovieClip which was clicked via the event.target property:
function clickToDrag(e:MouseEvent) : {
e.target.startDrag();
//more code...
}
I hope you get the idea.
I'm sorry if it's asking for too much, but I'm too confused by now. I'm making this really simple shooter game for my nephew in AS3. It all seems to be working just fine, except for one really annoying error that keeps popping up every second or third time the game is launched.
IT is Error #1009: Cannot access a property or method of a null object reference. The problem is always with parent.removeChild(this) command in the relevant class (EnemyClass, BulletClass or MissileClass). This happens in two cases: either when checkFinishConditions method in Main is called and the EnemyClass instance needs to be deleted. So if I get the #1009 error does this mean the instance has already been deleted? The second situation is when inst.hitTestObject(enemyInstance) is checked in Main class. Does this mean the EnemyClass instance has already been deleted somehow? I'm totally lost here to be honest.
private function checkCollision():void
{
//loop through missiles
for (var i:int = 0; i < aMissileArray.length; i++) {
//get the current missile
var currentMissile:missileClass = aMissileArray[i];
//loop through enemies
for (var j:int = 0; j < aEnemyArray.length; j++) {
var thisEnemy:EnemyClass = aEnemyArray[j];
if (currentMissile.hitTestObject(thisEnemy)) {
var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);
addChild(thisExplode);
currentMissile.destroyThis();
aMissileArray.splice(i,1);
thisEnemy.deleteEnemy();
aEnemyArray.splice(j, 1);
aDamageArray.splice(j, 1);
scoreValueText += 1;
j--;
i--;
}
//break;
}
}
//loop through bullets
for (var l:int = 0; l < aBulletArray.length; l++) {
//get the current missile
var currentBullet:BulletClass = aBulletArray[l];
//loop through enemies
for (var k:int = 0; k < aEnemyArray.length; k++) {
var currentEnemy:EnemyClass = aEnemyArray[k];
if (currentBullet.hitTestObject(currentEnemy)) {
currentBullet.destroyThis();
aBulletArray.splice(l, 1);
aDamageArray[k] -= 1;
l--;
if (aDamageArray[k] < 1) {
//create an explosion
var thisBulletExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);
addChild(thisBulletExplode);
currentEnemy.deleteEnemy();
aEnemyArray.splice(k, 1);
aDamageArray.splice(k, 1);
scoreValueText += 1;
k--;
}
break;
}
}
}
}
There is alot of code to look over, but one potential issue I see is here :
for each(var currentDieEnemy:EnemyClass in aEnemyArray) {
aEnemyArray.splice(0, 1);
currentDieEnemy.deleteEnemy();
}
The potential issue is that you are 'assuming' that the order of this loop is sequential based on the actual index order of the Array. You might want to stick a trace in there to verify that is actually what is happening, because I believe it's possible that they can be out of order.
See this question for details -- For-Each Loop AS3: Is the direction guaranteed?
So imagine the scenario where the 3rd item in the array is first and you splice the item at the 0 index. Now, you have an item in the array that is removed from the display list, but not from the array. So what happens when you get to that item ? Pretty much what you are describing will happen since the parent property is null, since you already removed it from the display list.
The way to fix that is to do something like this :
while (aEnemyArray.length > 0)
{
var currentDieEnemy:EnemyClass = aEnemyArray[0];
currentDieEnemy.deleteEnemy();
aEnemyArray.splice(0,1);
}
I found another issue in this block of code :
for (var j:int = 0; j < aEnemyArray.length; j++) {
var thisEnemy:EnemyClass = aEnemyArray[j];
if (currentMissile.hitTestObject(thisEnemy)) {
var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);
addChild(thisExplode);
currentMissile.destroyThis();
aMissileArray.splice(i,1);
thisEnemy.deleteEnemy();
aEnemyArray.splice(j, 1); // now array composition is different.
aDamageArray.splice(j, 1);
scoreValueText += 1;
}
}
When you loop through an Array and splice an item at the loop index, you have to realize what happens to the composition of the array. Here's an example
Suppose enemy3 needs to be spliced because of a collision. Given your code, this is what happens :
Before (j == 2)
(0) enemy1
(1) enemy2
(2) enemy3
(3) enemy4
(4) enemy5
AFTER
(0) enemy1
(1) enemy2
(2) enemy4
(3) enemy5
So when the loop continues, j will now increment and equal 3.
The result is that enemy4 doesn't get evaluated as the composition of the array collapses to fill the hole created by the splice and you didn't adjust your loop index variable.
So, what you can do in that situation is simply decrement the loop index variable at the end of the block like so :
aEnemyArray.splice(j, 1); // now array composition is different.
aDamageArray.splice(j, 1);
scoreValueText += 1;
j--; // decrement loop index variable.
So while this is one solution, the main point to take away here is the change in the composition of your Array after a splice.
Yep, you are splicing your objects from arrays in a weird way. First, you are traversing the array forwards and splicing in the loop. This can lead to issues of several bullets not trigger collisions when they should, or say not move. See, if you are running your loop for bullets at index i, once you call bullets.splice(i,1); the bullet that was to be iterated next becomes at position i, then you increment i and that bullet remains unprocessed.
Next, you have nested loops, and in the inner loop you are removing an object from OUTER loop. This means once you did a removal of the outer loop's object, your inner loop is now invalidated, you have to preemptively terminate inner loop.
//loop through bullets
for (var l:int = aBulletArray.length-1; l>=0; l--) {
// first, traverse both arrays backwards
//get the current missile
var currentBullet:BulletClass = aBulletArray[l];
//loop through enemies
for (var k:int = aEnemyArray.length-1; k>=0; k--) {
var currentEnemy:EnemyClass = aEnemyArray[k];
if (currentBullet.hitTestObject(currentEnemy)) {
currentBullet.destroyThis();
aBulletArray.splice(l, 1);
aDamageArray[k] -= 1;
if (aDamageArray[k] < 1) {
//create an explosion
var thisExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);
addChild(thisExplode);
currentEnemy.deleteEnemy();
aEnemyArray.splice(k, 1);
aDamageArray.splice(k, 1);
scoreValueText += 1;
}
// and since we don't have "currentBullet" anymore, do this
break;
}
}
}
Fix all the other iterations through your arrays where you do splicing, like I did in this part of code, and you should avoid 1009 errors in these loops.
Also, you should not post your entire project code, but instead post only relevant parts, say an entire function that's reported as throwing an error, and explain which line is producing the error - it's written as number in the error's stack trace.
I have an issue here. Every five seconds the spawnEnemies is fired. It creates a movieclip, sets it position and adds it to the stage and an array. In the function checkBullet I check if the bullet hits one of the enemies. If it does, the bullet and the enemy are removed from the stage, and the enemy is spliced from the array. But an error is thrown once I hit an enemy:
TypeError: Error #1010: A term is undefined and has no properties.
If I comment out the arrayEnemies.splice(i,1) line, it works fine but then it isn't removed from the array. This is in itself not an issue, but naturally I don't like to keep unnecessary data in my array. Any help on this?
function checkBullet(event:Event) {
if(stage.contains(mcBullet)) {
for(var i:int = 0; i < arrayEnemies.length; i++) {
if(arrayEnemies[i].hitTestPoint(mcBullet.x, mcBullet.y, true)) {
stage.removeChild(mcBullet);
this.removeChild(arrayEnemies[i]);
arrayEnemies.splice(i,1);
bulletOnStage = false;
}
}
if(mcBullet.x > 800) {
stage.removeChild(mcBullet);
bulletOnStage = false;
}
}
}
function spawnEnemies(event:TimerEvent) {
var enemie:MovieClip = new box_mc();
enemie.name = "mcBox" + event.currentTarget.currentCount;
enemie.x = 850;
enemie.y = Math.floor(Math.random()*(1+155)+255);
addChild(enemie);
arrayEnemies.push(enemie);
}
function moveEnemies(event:Event) {
for(var i:int = 0; i < arrayEnemies.length; i++) {
arrayEnemies[i].x -= 5;
}
}
This will be caused by working on an array that you are interating through, you should hold a ref to the stuff you want to remove then do it after the loop.
Your problem is that if the bullet hits two enemies, you try to remove it from the stage twice. This will throw an ArgumentError.
If you need to test against all enemies (assuming multiple enemies can be hit by the same bullet), you also need to decrement i when you remove an element from your enemy array.
function checkBullet(event:Event) {
if(stage.contains(mcBullet)) {
if(mcBullet.x > 800) {
stage.removeChild(mcBullet);
bulletOnStage = false;
}
for(var i:int = 0; i < arrayEnemies.length; i++) {
if(arrayEnemies[i].hitTestPoint(mcBullet.x, mcBullet.y, true)) {
if(stage.contains(mcBullet)){
stage.removeChild(mcBullet);
}
this.removeChild(arrayEnemies[i]);
arrayEnemies.splice(i,1);
bulletOnStage = false;
i--;
}
}
}
}
Bit of an older question but thought I'd throw in my answer too for anyone that might end up here.
Like Neil said, editing an array that you're itterating through (in this case arrayEnemies) can cause concurrent update problems.
My prefered solution is to use a seperate toRemove array and remove them after the update, for example:
var enemies:Array();
//Lets assume at some point this is populated with Enemy objects
function update():void
{
var toRemove:Array = new Array();
//May want to keep and clear an array instead to avoid creating a new one each update
foreach(var enemy:Enemy in enemies)
{
enemy.update();
if(enemy.markedForRemoval())
toRemove.push(enemy);
}
foreach(var enemy:Enemy in toRemove)
enemies.splice(enemies.indexOf(enemy), 1);
}
I've got an array of sprites which I'm animating by incrementing their rotationX property. What I want is for them all to disappear once the last item in the array has come full circle. The problem is that their rotation speeds are being generated by a randomized function, so I can't just go to the end of the array to find the last one. Each time it will be a different one.
So I have an array of Sprites:
for(var i:int=0; i<arrSprites.length; i++)
{
addChild(arrSprites[i]) ;
}
Then I have my event listener:
addEventListener(Event.ENTER_FRAME, loop);
And my handler:
private function loop(e:Event):void
{
for(var i:int=0; i<arrSprites.length; i++)
{
var currentSprite:Sprite = arrSprites[i];
if(currentSprite.rotationX < 361) //this will detect the first one
//to finish but I want the last
{
currentSprite.rotationX += arrSprites[i].speed; //random speed
}
else
{
deleteTheSprites(); //removes all sprites and does other stuff
}
}
}
There's got to be an elegant way to do this. Anyone know what it is?
Thanks,
David
private function loop(e:Event):void
{
var finished : int = 0; // will count the number of sprites finished
for each (var current:Sprite in arrSprites)
{
if (current.rotationX < 361) current.rotationX += current.speed;
else if (++finished == arrSprites.length) deleteTheSprites(); // executes only if all sprites have finished
}
}