Trouble with addChild/removeChild and display list order in ActionScript3 - actionscript-3

I am working on a project, which includes a Lake symbol that the player can throw stones into, which in turn causes octopi to rise out of the lake in the positions that each stone hits the lake.
There is also a symbol for the splash made by the stone which will appear after the stone hits and before the octopus appears.
It is likely that there will be many octopi on the screen at the same time and they need to be ordered in the display list so that the ones that should appear further back are behind the others.
Each instance of these symbols should only play once and then be removed.
My code for this makes use of the different add/remove child method alongside for loops, conditionals and arrays which I have put together with the help of various tutorials and forums.
The problem I have is that when you click on the lake two or more times in quick succession, the stone and the splash symbols aren't removed properly and often keep looping.
Here is the code I am using. Any ideas?
var stone:Stone;
var stoneSplash:StoneSplash;
var octopus1:Octopus1;
var octopus2:Octopus2;
var whichOctopus:Array = [addOctopus1, addOctopus2];
var octopusScale:Number;
var octopusContainer:MovieClip = new MovieClip;
lake.lakeHitArea.addEventListener(MouseEvent.CLICK, onClickLake);
//Add octopusContainer to the stage's display list just above the Lake
addChildAt(octopusContainer,getChildIndex(lake) + 1);
octopusContainer.x = 0;
octopusContainer.y = 0;
function onClickLake(e:MouseEvent):void
{
trace("CLICK");
throwStone(mouseX, mouseY);
}
function throwStone(stonePositionX:int, stonePositionY:int)
{
stone = new Stone();
stone.x = stonePositionX;
stone.y = stonePositionY;
addChild(stone);
addEventListener(Event.ENTER_FRAME, removeStone);
}
function removeStone(e:Event):void
{
var count:int = numChildren;
var children:Array = [count];
//load all the children of the component into an Array
for (var i:int=0; i<count/* -1*/; i++)
{
children[i] = getChildAt(i/* + 1*/);
}
for (i=0; i<count/* - 1*/; i++)
{
if (children[i] is Stone)
{
if (children[i].currentFrameLabel == "Splash")
{
stoneSplash = new StoneSplash();
octopusContainer.addChild(stoneSplash);
stoneSplash.x = children[i].x;
stoneSplash.y = children[i].y;
}
if (children[i].currentFrameLabel == "end")
{
octopusContainer.removeChild(stoneSplash);
var positionX:int = children[i].x;
var positionY:int = children[i].y;
addOctopus(positionX, positionY);
removeChild(children[i]);
}
}
}
}
function addOctopus(positionX, positionY)
{
var o:int = Math.round(randomNumber(0,1));
whichOctopus[o](positionX, positionY);
reorderDisplayList();
addEventListener(Event.ENTER_FRAME, removeOctopus);
}
function addOctopus1(positionX: int, positionY:int):void
{
// if (whichOctopus1 == true)
// {
// var octopus:* = octopus1_1;
// }
// else
// {
// octopus = octopus1_2;
// }
octopus1 = new Octopus1();
var octopus:DisplayObject = octopus1;
octopusContainer.addChild(octopus);
octopus.x = positionX;
octopus.y = positionY;
octopusScale = randomNumber(0.5,0.85);
octopus.scaleX = octopusScale;
octopus.scaleY = octopusScale;
trace("children = " + octopusContainer.numChildren);
testPosition(octopus);
}
function addOctopus2(positionX: int, positionY:int):void
{
// if (whichOctopus2 == true)
// {
// var octopus:* = octopus2_1;
// }
// else
// {
// octopus = octopus2_2;
// }
octopus2 = new Octopus2();
var octopus:DisplayObject = octopus2;
octopusContainer.addChild(octopus);
octopus.x = positionX;
octopus.y = positionY;
octopusScale = randomNumber(0.25,0.5);
octopus.scaleX = octopusScale;
octopus.scaleY = octopusScale;
trace("children = " + octopusContainer.numChildren);
testPosition(octopus);
}
function testPosition(octopus:Object):void
{
trace(octopus)
for (var i:int = 0; i < 200; i++)
{
if (lake.hitTestPoint(octopus.x + octopus.hitTestBox1.x * octopus.scaleX,octopus.y + octopus.hitTestBox1.y * octopus.scaleY,true))
{
break;
}
else
{
octopus.x++;
}
}
for (i = 0; i < 100; i++)
{
if (lake.hitTestPoint(octopus.x + octopus.hitTestBox2.x * octopus.scaleX,octopus.y + octopus.hitTestBox2.y * octopus.scaleY,true))
{
break;
}
else
{
octopus.y--;
}
}
for (i = 0; i < 200; i++)
{
if (lake.hitTestPoint(octopus.x + octopus.hitTestBox3.x * octopus.scaleX,octopus.y + octopus.hitTestBox3.y * octopus.scaleY,true))
{
break;
}
else
{
trace(i);
octopus.x--;
}
}
for (i = 0; i < 100; i++)
{
if (lake.hitTestPoint(octopus.x + octopus.hitTestBox1.x * octopus.scaleX,octopus.y + octopus.hitTestBox1.y * octopus.scaleY,true))
{
break;
}
else
{
octopus.y--;
trace(i);
}
}
}
function randomNumber(min:Number, max:Number):Number
{
return Math.random() * (max - min) + min;
}
function reorderDisplayList():void
{
//the number of children in our component
var count:int = octopusContainer.numChildren;
var children:Array = [count];
//load all the children of the component into an Array
for (var i:int=0; i<count; i++)
{
children[i] = octopusContainer.getChildAt(i);
}
//sort the Array children based on their 'y' property
children.sortOn("y", Array.NUMERIC);
//re-add the children to the component ;
//in the order of the sorted Array we just created.
//When we add the children using 'addChild' it will
//be added at the top of the component's displaylist
//and will automatically be removed from its original position.
for (i=0; i<count/* - 1*/; i++)
{
if (children[i] is Octopus1 || children[i] is Octopus2)
{
// trace("child = " + children[i] + " at i: " + i);
octopusContainer.removeChild(children[i]);
octopusContainer.addChild(children[i]);
}
}
}
function removeOctopus(e:Event):void
{
var count:int = octopusContainer.numChildren;
var children:Array = [count];
//load all the children of the component into an Array
for (var i:int=0; i<count/* -1*/; i++)
{
children[i] = octopusContainer.getChildAt(i/* + 1*/);
}
for (i=0; i<count/* - 1*/; i++)
{
if (children[i] is Octopus1 || children[i] is Octopus2)
{
trace(i);
trace("Is an octopus");
if (children[i].currentFrame >= 202)
{
octopusContainer.removeChild(children[i]);
}
}
}
}
I would greatly appreciate any advice to help me overcome this hurdle and continue with my project.
Thank you in advance.
Chris Collins.

Your issue (or at least one of them) is that your code will only remove the most recent StoneSplash. So if you click a bunch of times in between the splash and end animation , only the last clicked one will get removed.
This is because you are using a global var (stoneSplash) to reference the splash, and it gets overwritten to the new one. You need to either add a splash reference on the stone itself, or create a dictionary so you know which splash goes with which stone.
Here would be one way:
if (children[i].currentFrameLabel == "Splash")
{
stoneSplash = new StoneSplash();
MovieClop(children[i]).stoneSplash = stoneSplash; //add a reference the splash on the stone itself
Then later, instead of octopusContainer.removeChild(stoneSplash); do:
octopusContainer.removeChild(MovieClop(children[i]).stoneSplash);
This way your removing the correct splash that goes with this stone.
Here would be a much cleaner way to architect this instead of using an enter frame handler:
On your Stone class timeline, put the following code on your Splash and End frames respectively:
Splash frame: this.dispatchEvent(new Event("Splash"));
End frame: this.dispatchEvent(new Event("End"));
Listen for those events when you create a new stone instance:
stone = new Stone();
stone.x = stonePositionX;
stone.y = stonePositionY;
stone.addEventListener("Splash", splashHandler,false,0,true);
stone.addEventListener("End",removeStone,false,0,true);
addChild(stone);
Respond to those events appropriately:
function splashHandler(e:Event):void {
var stone:Stone = e.currentTarget as Stone;
stoneSplash = new StoneSplash();
//you need a reference to the splash from the stone class - it would be best to create a class file and add a public property called splashObj and then just use stone.splashObj = new StoneSplash();
MovieClip(stone).stoneSplash = stoneSplash; //so on the end event we can read this var to remove stoneSplash
octopusContainer.addChild(stoneSplash);
stoneSplash.x = stone.x;
stoneSplash.y = stone.y;
}
function removeStone(e:Event):void {
var stone:Stone = e.currentTarget as Stone;
octopusContainer.removeChild(MovieClip(stone).stoneSplash);
addOctopus(stone.x, stone.y);
removeChild(stone);
}

Related

Random movide clip without loop?

I created some MovieClips and put all of them on the stage. I'm trying to move a random MovieClip, using it only once, but at the same time I need to know which one it is because it will conflict with other tween effects. Is there an easy way to do that?
Here is what I've tried:
var biryukseklev1, ikiyukseklev1, ucyukseklev1, dortyukseklev1, besyukseklev1:int;
var assignavalue1, assignavalue2:int;
stage.addEventListener(Event.ENTER_FRAME,survivordondurlev6);
function survivordondurlev6(e:Event) {
if (biryerlessurlev1 == 0) {
biryukseklev1 = 764;//36
} else if (biryerlessurlev1 == 1) {
biryukseklev1 = 680;//120
} else if (biryerlessurlev1 == 2) {
biryukseklev1 = 596;//204
} else if (biryerlessurlev1 == 3) {
biryukseklev1 = 512;//288
} else if (biryerlessurlev1 == 4) {
biryukseklev1 = 428;//372
}
if (assignavalue1 == 0 && rabbitstatus.text.length < 2) { //Make sure to not used before
var my:Tween = new Tween(rabbit, "y", Back.easeInOut, rabbit.y, rabbit.y -biryukseklev1, 3, true);
rabbitstatus.text = "okfull";
} else if((assignavalue1 == 1 && birdstatus.text.length < 2){ //Make sure to not used before
var mys:Tween = new Tween(bird, "y", Back.easeInOut, bird.y, bird.y -biryukseklev1, 3, true);
birdstatus.text = "okfull";
}
}
If you want to keep track of which clips you've animated, then you'll have to have some way of iterating through them. The easiest list would be an Array, though how you mark them as "animated" is up to you. If you never plan on animating them again, then simply removing the ones that you've animated, and leaving the rest for later will work.
Below is example code, which you should be able to run in a new scene. You should be able to apply it to your needs with some small edits.
import fl.transitions.Tween;
import fl.transitions.easing.*;
// We'll keep track of which blocks still need to be animated by including them in this list
// Once we animate a block, we remove it from the list.
var pool:Array = [];
// Populate the stage with blocks.
for (var i:int = 0; i < 30; i++) {
var block:Sprite = createBlock();
addChild(block);
block.y = 50;
if (i > 0) { block.x = block.width * i; }
pool.push(block);
}
// Create an interactive button.
var txt:TextField = new TextField();
txt.text = "Do something";
addChild(txt);
txt.addEventListener("click", animateNext);
function createBlock():Sprite {
// Creates a single, randomly colored block.
var block:Sprite = new Sprite();
block.graphics.beginFill(random(0x000000, 0xFFFFFF));
block.graphics.drawRect(0,0,16,16);
block.graphics.endFill();
return block;
}
function random(low:Number, high:Number):Number {
// Returns a random number between the low and high numbers.
return Math.floor(Math.random() * (1+high-low)) + low;
}
function animateNext(e:Event):void {
// Do this 5 times.
for (var i:int = 0; i < 5; i++) {
// As long as we have blocks in the pool...
if (pool.length > 0) {
// Pick a random block from the pool
var index:int = random(0, pool.length-1);
// Animate it
new Tween(pool[index], "y", Back.easeInOut, pool[index].y, pool[index].y + 50, 3, true);
// Remove it from the pool so it isn't picked again
pool.splice(index, 1);
} else {
// Otherwise, break the loop.
break;
}
}
}

AS3 How can I get back the object that being dragged to Item

I have board with 16 objects(MovieClip), every one of them have a unique instance name (slot1-16).
I'm trying to make a drag and drop code that return the instance name of the object dragged on it.
function fl_ReleaseToDrop(evt:MouseEvent):void {
var object = evt.currentTarget;
if(object is textBox || object is UILoader)
{
for(var i:int = 1; i < 16; i++){
//Checks the correct drop target
if (object.hitTestObject(getChildByName("slot" + i)))
{
trace("slot" + i);
slot(getChildByName("slot" + i)).gotoAndStop(3);
}else{
object.x = xPos; //If not, return the clip to its original position
object.y = yPos;
}
}
object.stopDrag();
}
}
What really happened is that the only place that i can drag in to is slot1, other slots are not working.
In your current code, if the 1st hit test with slot1 object fails, you return your dragged object to its original position and of course all the hit tests with other slots will fail.
So you should return your object to its original position just after doing the hit test with all objects. You can use, for example, a Boolean var to know if there is at least one succeeded hit test, in that case you don't need to return your dragged object to its original position :
function fl_ReleaseToDrop(evt:MouseEvent):void
{
var object = evt.currentTarget;
var hit_test:Boolean = false;
if(object is textBox || object is UILoader)
{
for(var i:int = 1; i < 16; i++)
{
if (object.hitTestObject(getChildByName("slot" + i)))
{
hit_test = true;
slot(getChildByName("slot" + i)).gotoAndStop(3);
}
}
if(!hit_test)
{
object.x = xPos;
object.y = yPos;
}
object.stopDrag();
}
}
Hope that can help.

as3 Quadtree being slow

This is my quadtree class, but i haven't added the collision detection yet, in all the examples online they can get 500 + at 60 fps with collision detection but my one only running at 20 fps without collision detection.
I'm following this tutorial http://gamedevelopment.tutsplus.com/tutorials/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space--gamedev-374 which is java but im using as3
public class Quadtree extends Entity
{
private var Max_objects:int = 1;
private var Max_levels:int = 5;
private var level:int;
private var objects:Vector.<Rectangle>;
public var rectangle:Rectangle;
public var Quadtree_list:Vector.<Quadtree>;
public function Quadtree(tmp_level:int , tmp_rec:Rectangle)
{
level = tmp_level;
objects = new Vector.<Rectangle>();
rectangle = tmp_rec;
Quadtree_list = new Vector.<Quadtree>();
Quadtree_list.length = 3;
}
public function clear():void
{
objects.length = 0;
for (var i:Number = 0; i < Quadtree_list.length ; i++)
{
if (Quadtree_list[i] != null)
{
Quadtree_list[i].clear();
world.remove(Quadtree_list[i]);
Quadtree_list[i] = null;
}
}
}
public function split():void
{
var subWidth:int = rectangle.width / 2;
var subHeight:int = rectangle.height / 2;
var xx:int = rectangle.x;
var yy:int = rectangle.y;
Base._world.add(Quadtree_list[0] = new Quadtree(level + 1, new Rectangle(xx + subWidth, yy, subWidth, subHeight)));
Base._world.add(Quadtree_list[1] = new Quadtree(level+1,new Rectangle(xx ,yy,subWidth,subHeight)));
Base._world.add(Quadtree_list[2] = new Quadtree(level+1,new Rectangle(xx,yy + subHeight,subWidth,subHeight)));
Base._world.add(Quadtree_list[3] = new Quadtree(level+1,new Rectangle(xx + subWidth,yy + subHeight,subWidth,subHeight)));
}
/*
* Determine which node the object belongs to. -1 means
* object cannot completely fit within a child node and is part
* of the parent node
*/
public function get_index(tmp_rect:Rectangle):Number
{
var index:int = -1;
var verticalMidpoint:Number = rectangle.x + (rectangle.width / 2);
var horizontalMidpoint:Number = rectangle.y + (rectangle.height / 2);
// Object can completely fit within the top quadrants
var topQuadrant:Boolean = (tmp_rect.y < horizontalMidpoint && tmp_rect.y + tmp_rect.height < horizontalMidpoint);
// Object can completely fit within the bottom quadrants
var bottomQuadrant:Boolean = (tmp_rect.y > horizontalMidpoint);
// Object can completely fit within the left quadrants
if (tmp_rect.x < verticalMidpoint && tmp_rect.x + tmp_rect.width < verticalMidpoint)
{
if (topQuadrant)
{
index = 1;
}
else if (bottomQuadrant)
{
index = 2;
}
}
else
// Object can completely fit within the right quadrants
if (tmp_rect.x > verticalMidpoint)
{
if (topQuadrant)
{
index = 0;
}
else if (bottomQuadrant)
{
index = 3;
}
}
return index;
}
/*
* Insert the object into the quadtree. If the node
* exceeds the capacity, it will split and add all
* objects to their corresponding nodes.
*/
public function insert(tmp_rect:Rectangle):void
{
if (Quadtree_list[0] != null)
{
var index:int = get_index(tmp_rect);
if (index != -1)
{
Quadtree_list[index].insert(tmp_rect)
return;
}
}
objects.push(tmp_rect);
if (objects.length > Max_objects && level < Max_levels)
{
if (Quadtree_list[0] == null)
{
split();
}
var i:int = 0;
while (i < objects.length)
{
var indexx:int = get_index(objects[i]);
if (indexx != -1)
{
Quadtree_list[indexx].insert(objects[i]);
objects.splice(i, 1);
}
else
{
i++;
}
}
}
}
Can you see why it's not performing very well?
Hard to say for certain without seeing exactly how you're using it. Plus it extends Entity which could be doing ...anything :)
I'm not an expert on Quadtrees either, but if you're calling split() a lot, it looks like it could end up being taxing - lots of instantiation calls to new Quadtree and new Rectangle. If this is indeed a bottleneck, you could look into instantiating one rectangle instance that you just pass around. Same with Quadtree. Or use object pooling so you're at least recycling instead of creating new things like crazy.
I hope that helps :)

Odd numbers of object extraction from an array

I'm trying to get 2 objects at a time form the array for now. but soon I will be using odd number of length and splicing items.
This works out perfectly so far with Even numbers in the Array, but I am not sure how to make it work with odd numbers. The way I think it may work is ask it to check the objects coming up next and if it is less than 2 than change the counters to 1. but I am not even sure how to put that in code specifically. I posted my code so far be
import flash.events.MouseEvent;
import flash.net.Socket;
var socket_Array_current_position = 0;
var socket_counter = 2;
var socket_Array: Array = new Array ();
socket_Array.push(socket_one, socket_two,socket_three, socket_four, socket_five, socket_six);
go_next_left.addEventListener(MouseEvent.CLICK, go_left);
go_next_right.addEventListener(MouseEvent.CLICK, go_right);
function go_left(going_left:MouseEvent)
{
if (socket_Array_current_position > 0)
{
socket_remove();
socket_Array_current_position -= socket_counter;
socket_x_position = 125;
socket_display();
}
}
function go_right(going_right:MouseEvent)
{
if (socket_Array_current_position < socket_Array.length-socket_counter)
{
socket_remove();
socket_Array_current_position += socket_counter;
socket_x_position = 125;
socket_display();
}
}
socket_display();
function socket_display()
{
var s = 0;
for (s; s < socket_counter; s++)
{
addChild(socket_Array[socket_Array_current_position + s]);
socket_Array[socket_Array_current_position + s].x = socket_x_position;
socket_Array[socket_Array_current_position + s].y = socket_y_position;
//socket_Array[s].addEventListener(MouseEvent.CLICK, picked);
socket_x_position = socket_x_position + 275;
}
}
function socket_remove()
{
var s = 0;
for (s; s < socket_counter; s++)
{
removeChild(socket_Array[socket_Array_current_position+s]);
}
}
I suppose that you want display X objects (in this case two) at a time from an array. Whatever length. I'm using Math lib. Consider that I didn't try the code below with sdk or Flash.
const X_START_POS:int = 125;
const COLUMN_WIDTH:int = 275;
const QTY_SCREEN:int = 2;
var socket_Array:Array = new Array();
var socket_Array_pos:int = 0;
var socket_Array_target:int = 0; // target is always right
var socket_Array_on_screen:Array = new Array();
// socket_Array.length must be >= QTY_SCREEN, always.
socket_Array.push(socket_one, socket_two, socket_three, socket_four, socket_five, socket_six);
go_next_left.addEventListener(MouseEvent.CLICK, go_left);
go_next_right.addEventListener(MouseEvent.CLICK, go_right);
socket_display();
function go_left(going_left:MouseEvent) {
socket_Array_target = Math.max(socket_Array_pos - QTY_SCREEN, 0);
socket_display();
}
function go_right(going_right:MouseEvent) {
socket_Array_target = Math.min(socket_Array_pos + QTY_SCREEN, socket_Array.length - QTY_SCREEN);
socket_display();
}
function socket_display() {
socket_remove();
socket_x_position = X_START_POS;
var limit:int = socket_Array_target + QTY_SCREEN;
for (var i = socket_Array_target; i < limit; i++) {
show_socket(socket_Array[i]);
socket_x_position += COLUMN_WIDTH;
}
socket_Array_pos = socket_Array_target;
}
function show_socket(asocket:DisplayObject) {
addChild(asocket);
asocket.x = socket_x_position;
asocket.y = socket_y_position;
socket_Array_on_screen.push(asocket); // remember me
}
function socket_remove() {
var qty:int = socket_Array_on_screen.length;
for (var s = 0; s < qty; s++) {
removeChild(socket_Array_on_screen.pop());
}
}

Flash AS3: position loaded images from loop based on image height

I'm trying to dynamically stack images that are being pulled in via an xml file. Below is what I'm doing, and it almost works. The problem is that it only seems to fire off the event complete function on the very last one, instead of going for all of them. Is there a way to make it run the even.complete function for each image?
function aboutfileLoaded(event:Event):void {
aboutXML = new XML(aboutTextLoader.data);
for(var l:int = 0; l < aboutXML.aboutimages.image.length(); l++)
{
imageLoader = new Loader();
imageSource = aboutXML.aboutimages.image[l];
if (imageSource != "") {
this.imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, aboutimageLoaded);
this.imageLoader.load(new URLRequest(imageSource));
//aboutBox.aboutContent.addChild(imageLoader);
//imageLoader.y = imageYpos;
//imageYpos = imageYpos + 50;
}
}
}
function aboutimageLoaded(event:Event):void {
aboutBox.aboutContent.addChild(imageLoader);
this.imageLoader.y = imageYpos;
imageYpos = imageYpos + this.imageLoader.height;
}
I use a simple Image class I wrote for loading all images.
public function loadImages(xml:XML):void {
for each(var img:XML in xml.images) {
var i:Image = new Image(img.src, img.width, img.height);
i.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, positionImage);
}
}
/**/
private function positionImage(e:Event):void {
var i:Image = e.target;
gallery.addChild(i);
// Do the positioning.
}
In your code above, you could always just change your aboutimageLoaded function:
// In aboutfileLoaded() change all "this.imageLoader" to just "imageLoader"
function aboutimageLoader(event:Event):void {
aboutBox.aboutContent.addChild(event.target);
event.target.y = imageYpos;
}
each Loader can only deal with one job at a time, so either you want to stack the jobs to load one after another:
//Global vars
loadIndex:int = 0;
imageLoader = new Loader();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, aboutimageLoaded);
loadImage();
private function loadimage(){
if(aboutXML.aboutimages.image[loadIndex] != null){
imageSource = aboutXML.aboutimages.image[loadIndex];
if (imageSource != "") {
imageLoader.load(new URLRequest(imageSource));
}
}
}
function aboutimageLoaded(event:Event):void {
var holder = (ev.content as Bitmap).clone();
aboutBox.aboutContent.addChild(holder);
holder .y = imageYpos;
imageYpos = imageYpos + holder.height;
loadIndex ++;
loadimage();
}
or make multiple instances of Loaders, one for each:
for(var l:int = 0; l < aboutXML.aboutimages.image.length(); l++)
{
var imageLoaderTemp = new Loader();
imageSource = aboutXML.aboutimages.image[l];
if (imageSource != "") {
imageLoaderTemp.contentLoaderInfo.addEventListener(Event.COMPLETE, aboutimageLoaded);
imageLoaderTemp.load(new URLRequest(imageSource));
}
}
}
function aboutimageLoaded(event:Event):void {
aboutBox.aboutContent.addChild(event.target);
event.target.y = imageYpos;
imageYpos = imageYpos + event.target.height;
}