how to trace "depth" or stacking order of a display object? - actionscript-3

How can you trace the "depth" or stacking order of a display object with AS3?
I'm trying to figure out if my sprite is behind another sprite...

container.getChildIndex(displayObject);
but that will only tell you how deep it is, not necessarily if anything is in front of it.

Function comparing two DisplayObject instances to determine which one is at a higher "depth" on the display list:
private function higher(a:DisplayObject, b:DisplayObject):DisplayObject
{
// Parent chains
var ac:Array = [a];
var bc:Array = [b];
// Pointers to individual nodes
var an:DisplayObject = a.parent;
var bn:DisplayObject = b.parent;
while (an != null) {
ac.push(an);
an = an.parent;
}
while (bn != null) {
bc.push(bn);
bn = bn.parent;
}
var acl:int = ac.length;
var bcl:int = bc.length;
var n:int = Math.min(acl, bcl);
var i:int = 0;
for (; i < n; i++) {
an = ac[acl - i - 1];
bn = bc[bcl - i - 1];
// First uncommon ancestor
if (an != bn)
break;
}
var ca:DisplayObjectContainer = an.parent;
if (!ca)
return null;
if (ca.getChildIndex(an) > ca.getChildIndex(bn))
return a;
else
return b;
}
Note: If one of the objects is not on the display list, the function returns null. You can change it to return the other object instead.
You can almost certainly optimize this, but this is a first cut.

Just a refactored version of Manish answer using vectors and which won't return weird result if you ever call higher(a,a.parent).
parents() may be used for other purpose too :
public function higher(a:DisplayObject, b:DisplayObject):DisplayObject
{
var aParents:Vector.<DisplayObject> = parents(a);
var bParents:Vector.<DisplayObject> = parents(b);
var commonDepth:int = Math.min(aParents.length, bParents.length);
for (var depth:int = 0; depth < commonDepth; depth++)
if (aParents[depth] != bParents[depth])
break;
if (depth == 0 || depth == commonDepth)
return null;
var commonAncestor:DisplayObjectContainer = aParents[depth].parent;
if (commonAncestor.getChildIndex(aParents[depth]) > commonAncestor.getChildIndex(bParents[depth]))
return a;
else
return b;
}
private function parents(d:DisplayObject):Vector.<DisplayObject>
{
var result:Vector.<DisplayObject> = new Vector.<DisplayObject>;
while (d != null)
{
result.unshift(d);
d = d.parent;
}
return result;
}

private function getDepth(clip:DisplayObject):uint
{
var depth:uint = 0;
var currentClip:DisplayObject = clip;
while (currentClip.parent && currentClip.parent != this)
{
depth++;
currentClip = currentClip.parent;
}
return depth;
}

container.getChildIndex(child) should do it, it returns the index of the child

This is a revised version of what jauboux did from a version Manish did.
Namely, adding a null return value from highestOf() when depths match.
/**
* #param ifDepthsMatchReturnObjectA
* #return Whichever DisplayObject is higher on the display list.
* Optionally returns `null` if they're at the same depth.
*/
public function highestOf(a:DisplayObject, b:DisplayObject, ifDepthsMatchReturnObjectA:Boolean = false):DisplayObject
{
var aParents:Vector.<DisplayObject> = ancestorsOf(a);
var bParents:Vector.<DisplayObject> = ancestorsOf(b);
var commonDepth:int = Math.min(aParents.length, bParents.length);
for (var depth:int = 0; depth < commonDepth; depth++)
if (aParents[depth] != bParents[depth])
break;
if (depth == 0 || depth == commonDepth)
return null;
var commonAncestor:DisplayObjectContainer = aParents[depth].parent;
var aDepthOnCommonAncestor:int = commonAncestor.getChildIndex(aParents[depth]);
var bDepthOnCommonAncestor:int = commonAncestor.getChildIndex(bParents[depth]);
if (aDepthOnCommonAncestor > bDepthOnCommonAncestor)
return a;
else if (aDepthOnCommonAncestor < bDepthOnCommonAncestor)
return b;
else
return ifDepthsMatchReturnObjectA ? a : null;
}
/**
* #return Whether a is higher than b.
*/
public function isHigher(a:DisplayObject, b:DisplayObject):Boolean
{
return highestOf(a, b) === a;
}
/**
* #return All ancestors of given display.
*/
private function ancestorsOf(display:DisplayObject):Vector.<DisplayObject>
{
var result:Vector.<DisplayObject> = new Vector.<DisplayObject>;
while (display != null)
{
result.unshift(display);
display = display.parent;
}
return result;
}

Related

AS3 printing the lowest value to a dynamic textfield

var a:Object =({label:"2008",n:8560,i:15909});
var b:Object ={(label:"2009",n:8146,i:14197});
Lets say I have five objects similar to this in a list component. Is there possible to have a function that prints out the label of the item which has the lowest value n ? What would I have to do to accomplish this?
Sort on the field you want first (n in this case) using Array.sortOn(), then access it and print:
var arrayToSort:Array = [{label:"2008",n:8560,i:15909},{label:"2009",n:8146,i:14197}];
var sortedArray = arrayToSort.sortOn ("n" , Array.NUMERIC);
trace(sortedArray[0].label);
public function findMax( o:Object ):String {
var lowest:Number = Number.MAX_VALUE;
var lowestLabel:String;
for (var label:String in o) {
var val:Number = o[label];
if ( val != null && val < lowest ) {
lowest = val;
lowestLabel = label;
}
}
return lowestLabel;
}

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 :)

Filling open spaces in a grid top down

I am writing a match three engine and I succeed in creating the matching with using huge loops to find the matching items. Any ideas on how to fill the empty spaces with the items ( dropping down into the empty spaces ) and creating new items without excessive looping and if statements?
Here is my relavant code so far.
public var rows:uint = 8;
public var cols:uint = 7;
public var cell:Array = new Array();
public var plot:Array = new Array();
public var height:int;
public var width:int;
public var relativePositions:Array = [{name:'top', position:-1}, {name:'bottom', position:1}, {name:'left', position:rows*-1}, {name:'right', position:rows*1}];
public var dictionary:Dictionary = new Dictionary();
public var matches:Array = new Array();
public function createGrid(target:*, displayObject:*, spacer:int) : void {
var iterator:uint = 0;
for(var c:uint = 0;c<cols;c++){
for(var r:uint = 0;r<rows;r++){
cell[iterator] = createGamePiece();
Sprite(cell[iterator]).name = String(iterator);
Sprite(cell[iterator]).addEventListener(MouseEvent.CLICK, _handleGamePiece_CLICK);
Sprite(cell[iterator]).addEventListener(MouseEvent.MOUSE_OVER, _handleGamePiece_MOUSE_OVER);
Sprite(cell[iterator]).addEventListener(MouseEvent.MOUSE_OUT, _handleGamePiece_MOUSE_OUT);
cell[iterator].y = cell[iterator].height * r + (spacer*r);
cell[iterator].x = cell[iterator].width * c + (spacer*c);
GamePiece(cell[iterator]).positionX = cell[iterator].x;
GamePiece(cell[iterator]).positionY = cell[iterator].y;
GamePiece(cell[iterator]).positionRow = r;
GamePiece(cell[iterator]).positionCol = c;
target.addChild(cell[iterator]);
dictionary[String(iterator)] = cell[iterator]
iterator++
}
}
}
public function findRelativeMatches(targetSprite:Sprite) : void {
targetSprite.alpha = .5;
var rootPosition:Number = Number(targetSprite.name);
for ( var i:int = 0; i < relativePositions.length; i ++ ) {
var key:String = String(rootPosition + relativePositions[i].position);
// to do >> Not hardcoded to 'Pig'
if (findSprite(key) != null && GamePiece(targetSprite).color == GamePiece(findSprite(key)).color && GamePiece(findSprite(key)).found == false) {
var sprite:Sprite = findSprite(key);
sprite.alpha = .5;
GamePiece(sprite).found = true;
matches.push(sprite);
findRelativeMatches(sprite);
};
};
targetSprite.addEventListener(MouseEvent.MOUSE_OUT, function() : void {
if ( matches.length != 0 ) {
for ( var j:int = 0 ; j < matches.length ; j++ ) {
Sprite(matches[j]).alpha = 1;
GamePiece(matches[j]).found = false;
}
matches.splice(0);
}
});
}
public function findSprite(key:String) : Sprite {
var sprite:Sprite;
dictionary[key] != undefined ? sprite = dictionary[key] : null;
return sprite;
}
protected function _handleGamePiece_CLICK(event:MouseEvent):void
{
for ( var j:int = 0 ; j < matches.length ; j++ ) {
var sprite:Sprite = matches[j];
view.removeChild(matches[j]);
}
matches.splice(0);
}
public function createGamePiece() : Sprite {
var gamePiece:GamePiece = new GamePiece();
return gamePiece;
}
You want to collapse your grid downwards, right? The common algorithm is going from the bottom of every row upwards, having one index of the first empty space found, and the other for the first occupied space above empty space, then exchange those values once found, iterating to the top. But you don't store the grid in any accessible form! You should create a grid object, say a vector of vectors of sprites, and assign values in it as you move pieces around. Like this:
var GRID:Vector.<Vector.<Sprite>>; // this should be allocated at createGrid
// populate GRID with your sprites once generated:
// put the following into your inner loop in CreateGrid:
GRID[r][c]=cell[iterator];
// and the following into your removal of matches[] sprites:
GRID[GamePiece(sprite).positionRow][GamePiece(sprite).positionCol]=null; // release link from GRID
// now to move grid objects:
function DropAll():void {
var i:int;
var j:int;
for (i=GRID.length-1;i>=0;i--) {
var lastEmpty:int=-1;
for (j=GRID[i].length-1;j>=0;j--) {
if (GRID[i][j]) {
if (lastEmpty>0) {
GRID[i][lastEmpty--]=GRID[i][j];
// relocate your sprite properly here
GRID[i][j]=null;
} // else we're still at full part of grid, continue
} else if (lastEmpty<0) lastEmpty=j;
}
}
}
To properly instantiate GRID you need to allocate vectors of desired length that are filled with "null" values. Also, "GRID" itself is a Vector, and needs to be instantiated too.
GRID=new Vector.<Vector.<Sprite>>();
for (i=0;i<rows;i++) {
var a:Vector.<Sprite>=new Vector.<Sprite>(cols);
GRID.push(a);
}
After you do this, you fill the GRID by directly assigning links in it, like GRID[r][c]=gameObject;
This is actually what I wanted. A way to collapse WITHOUT iterating over the entire board. This way I JUST loop through the items that have been removed.
protected function _handleGamePiece_CLICK(event:MouseEvent):void
{
for ( var j:int = 0 ; j < matches.length ; j++ ) {
var oldSprite:Sprite = matches[j];
moveAllPiecesDown(oldSprite);
view.removeChild(oldSprite);
oldSprite = null;
}
matches.splice(0);
}
private function moveAllPiecesDown(oldSprite:Sprite):void
{
var piecesAbove:int = GamePiece(oldSprite).positionRow;
var index:int = int(oldSprite.name);
for( var i:int = 0; i < piecesAbove; i ++ ) {
var spriteAbove:Sprite = Sprite(view.getChildByName(String(index-(1+i))));
if(spriteAbove) {
spriteAbove.y = spriteAbove.y + spriteAbove.height + 1;
spriteAbove.name = String(Number(spriteAbove.name)+1);
GamePiece(spriteAbove).textField.text = spriteAbove.name;
delete dictionary[spriteAbove.name];
dictionary[spriteAbove.name] = spriteAbove;
}
}
}

setting the chances of occurrence of elements in an array

For example I have 3 elements in an array:
public function randomTile():Number
{
var tiles:Array = new Array(fire,ice,water);
var index:Number=Math.floor(Math.random()*tiles.length);
return tiles[index];
}
How to set the chances of occurrence of fire(70%), ice(10%), and water(20%)?
This should work for any number of elements and you can specify any chance value.
var tiles:Array = [
{"item":"fire", "chance":70 },
{"item":"ice", "chance":10 },
{"item":"water","chance":20}
];
var picked:Object = pickRandomByChance(tiles);
trace(picked.item);
public function pickRandomByChance(options:Array):Object
{
var copy:Array = [];
var range:Number = 0;
for (var i:int = 0; i < options.length; i++)
{
copy.push( { "item":options[i].item, "chance":options[i].chance } );
range += copy[i].chance;
if (i > 0)
copy[i].chance += copy[i - 1].chance;
}
var pick:Number = Math.floor(Math.random() * range);
for (i = 0; i < copy.length; i++)
{
if (pick <= copy[i].chance)
return copy[i];
}
return null;
}
There quite a few ways you could do this, and it largely depends of the scope of your project. If you just have the three elements, using a switch statement would be easy:
var rand:Number = Math.random();
switch(true){
case rand >= .3:
//use fire
break;
case rand >= .1
//use water
break;
default:
//use ice
}
Someone else may have a better way though

What is the fastest way to compare two dictionaries?

I'm trying to workout the fastest way to match two dictionaries in Actionscript-3. This is what I've got so far.
function compareDictionaries(p0:Dictionary, p1:Dictionary):Boolean
{
if(p0 == p1) {
return true;
} else {
const matched:Dictionary = new Dictionary();
for(var k0:Object in p0) {
matched[k0] = k0;
if(p0[k0] != p1[k0]) {
return false;
}
}
for(var k1:Object in p1) {
if(matched[k1]) {
continue;
} else {
if(p1[k1] != p0[k1]) {
return false;
}
}
}
return true;
}
}
Obviously it's not ideal to create a new dictionary with the key for the item, but I really don't want to retest the matched item (not that I know if this is any slower or not!). This of course would be circumvented by having a length on the dictionary class, which would make the second for loop redundant.
Any better ideas over this?
EDIT
I created a gist of the benchmark to show the results for a successful match (use the release player)
(results output in (ms))
true
43
true
497
true
22
true
16
EDIT 2
I created another gist showing a miss-hit of equality.
(results output in (ms))
false
1
false
472
false
0
false
0
Something simple like this should work - no new arrays/dictionaries etc:
public function compareDictionaries( d1:Dictionary, d2:Dictionary ):Boolean
{
// quick check for the same object
if( d1 == d2 )
return true;
// check for null
if( d1 == null || d2 == null )
return false;
// go through the keys in d1 and check if they're in d2 - also keep a count
var count:int = 0;
for( var key:* in d1 )
{
// check if the key exists
if( !( key in d2 ) )
return false;
// check that the values are the same
if( d1[key] != d2[key] )
return false;
count++;
}
// now just make sure d2 has the same number of keys
var count2:int = 0;
for( key in d2 )
count2++;
// return if they're the same size
return ( count == count2 );
}
Technically you could just make a direct comparison with the values (as in don't check for the existence of the key in d2) as if you search for a key that's not there, it should resolve to null (or undefined, I'm not 100% sure), but I left it in there in the case where a value in d1 is null and it doesn't exist in d2
Start by comparing strict equality. Then, test equality of the keys in both dictionaries:
Iterate over the properties and return all keys in arrays
Test for equal array length
Sort the arrays
Iterate once more for comparison
Only if all of those tests are passed, compare the actual values:
public function areEqualDictionaries( dict1:Dictionary, dict2:Dictionary ):Boolean {
if(dict1 === dict2) return true;
var keys:Array = equalKeysArrayOrNull( dict1, dict2 );
if(keys)
return haveEqualValues( keys, dict1, dict2 );
else
return false;
}
private function equalKeysArrayOrNull( dict1:Dictionary, dict2:Dictionary ):Array {
var keys1:Array = enumerateKeys( dict1 ).sort();
var keys2:Array = enumerateKeys( dict2 ).sort();
if( keys1.length != keys2.length ) return null;
var i:int = -1;
while(++i < keys1.length)
if(keys1[i] !== keys2[i]) return null;
return keys1;
}
private function haveEqualValues ( keys:Array, dict1 : Dictionary, dict2:Dictionary) :Boolean {
for each (var key:* in keys)
if (dict1[key] != dict2[key]) return false;
return true;
}
private function enumerateKeys( dict:Dictionary ):Array {
var keys:Array = [];
for(var key:* in dict)
keys.push( key );
return keys;
}
Note that dictionaries use identity (strict equality) for matching keys, which makes it necessary to use !== for comparing them. I suppose it's okay to use != for comparing the values, though.
EDIT: Comparison of for vs. for each
Here's my own benchmark for vs. for each:
var dict : Dictionary = new Dictionary();
var keys : Array = [];
for (var i : int = 0; i < 1000000; i++) {
var n : Object = { index:i };
dict[n] = n;
keys.push (n);
}
trace ("benchmark:");
trace ("----------");
var start : int = getTimer();
for each (var key:* in keys) {
var m:* = dict[key];
}
var elapsed : int = getTimer() - start;
trace ("for each:" + elapsed);
trace ("----------");
start = getTimer();
for (var j:int = 0; j < keys.length; j++) {
var o:* = dict[keys[j]];
}
elapsed = getTimer() - start;
trace ("for:" + elapsed);
returns on my machine:
for each:213
----------
for:274