AS3 Child Index in relation to Stage - actionscript-3

I m currently moving my CPU image post processing frame from AS2 to AS3. I used an array to handle all the objects that need to be rendered each frame.
I sort the array by the object's depth on _root, and then render them all onto one bitmapdata in order to draw them in the correct order.
In my AS3 game project I used displayobjectcontainers to handle "depths". for example I have multiple movieclips acting as containers on MovieClip(root), and then add the child to those movieclips to sort them. I also have other child containers inside those containers, forming an hierarchy.
But now I find it hard to sort the render list array because I cannot simply input an absolute "depth" value for the object. I use parent.getChildIndex, but this just gives me the index of the child in one of those containers.
Ofcourse you can make a big function and finally sort the array, but is there anyway to give the object's absolute index in relation to the root/stage?
Thanks a lot.

You could try something like this, which will start at a given DisplayObjectContainer and generate a list of all descendants in order of depth:
function collateChildren(container:DisplayObjectContainer):Vector.<DisplayObject>
{
var list:Vector.<DisplayObject> = new <DisplayObject>[];
for(var i:int = 0; i < container.numChildren; i++)
{
var child:DisplayObject = container.getChildAt(i);
if(child is DisplayObjectContainer && (child as DisplayObjectContainer).numChildren > 0)
{
// Target contains additional children.
list = list.concat(collateChildren(child as DisplayObjectContainer));
}
else
{
// Target is a child.
list.push(child);
}
}
return list;
}
This will not include children who contain other children, and can be used like:
var children:Vector.<DisplayObject> = collateChildren(stage);
trace(children);

A game world would consist of some layers,like role layer, map layer, and each layer has it's depth in the game world. When you try to add some displayobjects to the world, add the displayobjects to the targe layer.
At most time, the layer count won't be large, like 5 or 6. So you can draw the objects on each layer by layer depth.

Related

as3 - How to sort display objects that constantly switch depths?

I have a 2.5D game (2D game that acts like a 3D game) where you constantly switch depths, where the player displays on top of an object when it walks in front of it and when it walks behind it, the object displays on top of the player. Like when the player's y is less than the object's y, the player would be going behind the object and vice versa.
I tried to use a code like this:
if (player.y < block.y)
{
setChildIndex(block, numChildren - 5);
}
else if (player.y > block.y)
{
setChildIndex(block, numChildren - 10);
}
However, I see if I do it this way with multiple times, I would need tons of codes and the display list would get mixed up and sort the wrong depths in the wrong orders. Would anyone please show an organized depth changer with minimal code?
Use a z-index stack sorting, (also refered as z-buffer in the 3D graphics literature) the same used to create the 3D depth effect using just 2D techniques.
In other words assign to each object a zIndex and at regular intervals (e.g onEnterFrame event) run a zsort routine which sorts (the order of) display objects based on their zIndex value. Or, alternatively, you can run zsort routine manualy each time a change of zIndex happens on objects.
Then in your code you simply assign zIndex values to display objects to simulate an object passing in-front or behind another object and zsort takes care of the rest.
A trick here is to have appropriate gaps (i.e not necessarily next zIndex+1) in the values of zIndex assigned on objects, so that objects can be placed between these gaps to simulate passing in front or behind other objects, without having to adjust more than one zIndex value each time, i.e you adjust only one zIndex value of the object passing in-front or behind another object, and not the zIndex of that other object.
The amount of gap between successive zIndexes can be estimated from the maximum number of (other) objects which at any given time might be between these objects (so for example, if, at maximum, 3 objects might at some time move between, in-front or behind any given object, then a gap value for successive zIndexes would be 3 so that all the objects can be accomodated)
here is a very simple zsorter routine which runs periodicaly onEnterFrame event and does the necessary depth sorting for you (from reference 1. below)
package {
import flash.display.*;
import flash.events.*;
public class DepthSortSpace extends MovieClip {
public function DepthSortSpace() {
super();
this.addEventListener( Event.ADDED_TO_STAGE, this.addedToStage, false, 0, true );
}
private function addedToStage( e:Event ) {
this.stage.addEventListener( Event.ENTER_FRAME, this.enterFrame, false, 0, true );
}
private function sortDisplayList():void {
var len:uint = numChildren;
var i,j;
for( i=0; i < len-1; i++ )
for (j=i+1; j < len; j++)
if ( getChildAt(i).y > getChildAt(j).y ) this.swapChildrenAt( i, j );
}
private function enterFrame(e:Event) {
this.sortDisplayList();
}
}
}
The zsorter above is in-fact a movieClip which acts as a scene container, in that you add your display objects to the zsorter movieClip and this takes care to sort them accordingly, but one can just take the zsort routine and apply it to any DisplayObjectContainer or Scene object instance.
Note, the zsorter above uses a bubbleSort sorting algorithm, which has a O(n^2) complexity, but one can use another sorting algorithm (e.g mergeSort with O(n lgn) complexity)
examples and references
http://nephilim.blogspot.gr/2010/06/easy-depth-sorting-in-actionscript-3.html
http://www.actionscript.org/forums/actionscript-3-0-a/169035-sorting-technique.html
http://www.simppa.fi/blog/the-fastest-way-to-z-sort-and-handle-objects-in-as3/

AS3 Isometric game Sorting Object Index

I am creating an isometric game, and I would like to know how to correctly position the (Z-index) of objects when the player goes behind them and infront of them.
I have been using
if(y>stage.stageHeight/2){
parent.setChildIndex(this,parent.getChildIndex(this)+1);
gotoAndStop(2);
} else if (y<stage.stageHeight/2){
parent.setChildIndex(this,parent.getChildIndex(this)+1);
gotoAndStop(1);
}
So far yet I have been receiving this error
RangeError: Error #2006: The supplied index is out of bounds.
My logic for this is; "If the object is UNDER the player then move its index up so that it is over the player, but if the object is ABOVE the player, then decrease it's index so that is under the player."
Any ideas on how I could improve this code so that it works without giving me errors?
Firstly if you're going to be swapping many objects it's best to work from outside the object. For example a Level class that contains an array of all your swappable objects. It will also be responsible for looping through each object and swapping it according to its y value.
public var objectArray:Array = [];
for (var ob1:Object in objectArray){
for (var ob2:Object in objectArray){
if(ob1 == ob2) continue;
swap(ob1, ob2);
}
}
//...
public function swap(a:Object, b:Object):void {
if (a.y > b.y != a.parent.getChildIndex(a) > b.parent.getChildIndex(b)) {
a.parent.swapChildren(a, b);
}
}
Keep in mind this is not the most efficient way to do it, because you have to check each object against each other object O(n^2).
you have to keep in mind that the index you provide is the index of the object in the container's children array. it cannot be less than zero and it cannot be more or equal to the actual childCount of the container.
so parent.getChildIndex(this)+1 is very unsafe
var nextIndex:int = parent.getChildIndex(this)+1;
if(nextIndex < parent.numChildren && nextIndex >= 0)
parent.setChildIndex(this, nextIndex);

issue with addChild's X and Y value

I have an Enemy class that deals with my monster moving and attacking. Within that class, I have another class called enemyMagic, which is a blank movieclip that serves as a masterclass to different movieclips that I will make.
So in the enemyMagic class, I add a movieclip called attack1
public var attack1:Attack1 = new Attack1;
public function EnemyMagic() {
////////////CREATE THE TIMER//////////
masterEnemyAttackTimer.addEventListener(TimerEvent.TIMER, mastertimer);
////////////ATTACKS/////////
//TIER 1//
addChild(attack1);
}
And in the enemy class, I add the enemyMagic when the enemy is attacking a certain position.
for (var k:int = 0; k < Main.tileset.length; k++)
{
if (! Main.tileset[k].tileMiddle.hitTestObject(this.enemyVisionPoint))
{
if (Main.tileset[k].tileHP !== 0)
{
attackoptions.push(Main.tileset[k]);
}
if (Main.tileset[k].tileMiddle.hitTestObject(Main.player.visionPoint))
{
addChild(enemymagic);
Main.tileset[k].outline.gotoAndStop("attack");
this.enemymagic.x = (Main.tileset[k].x);
this.enemymagic.y = (Main.tileset[k].y);
trace(enemymagic.x, enemymagic.y, Main.tileset[k].x, Main.tileset[k].y);
For some reason, the enemymagic is tracing the exact same number as the tile's x and y, but it isn't adding it on the tile. It adds it way off the screen. I think it might be because it starts on the enemy's x and y and then calculates?
So my question is how can I get the enemymagic movie clip to get exactly on the position of the tile?
You can do two things. First, when you do a plain addChild() the base coordinate system of the child is the one of its parent, which is your Enemy instance, which is of course at somewhere nonzero. And then you assign it the coordinates of Main.tileset[k] which has a different parent (most likely instance of Main). This creates the distance you speak of. So, in order to locate your magic over the exact tile, either use this.globalToLocal(Main.tileset[k].localToGlobal(PZERO)) where PZERO is a new Point() constant (or write new Point() instead of PZERO, but this will create another empty Point object and will quickly escalate), or do an addChild() directly to the tile you are attacking with unaltered coordinates.

How to determine whether a given object is a mask

Apparently, in Adobe's wisdom, both the object being mask, and the masking object contain a "mask" property. This leads to a cyclical reference that prevents determining which is the actual mask and which is the masked.
For example...
var clip:MovieClip = new MovieClip();
clip.name = "clip";
addChild(clip);
var boundary:Shape = new Shape();
boundary.name = "boundary";
clip.addChild(boundary);
clip.mask = boundary;
trace(clip.mask.name); // outputs "boundary"
trace(clip.mask.mask.name); // outputs "clip"
I've iterated through the properties of both clip and boundary, and there doesn't seem to be anything unique that sets them apart. My first thought was to force a removal of the superfluous "mask" reference in boundary, however, that also sets the mask property in clip to null, thereby removing the mask.
My second thought was to check the parent relationship of a mask. If the parent is the same as the object's mask, then the object in question is itself the mask.
var a:Array = [clip, boundary];
for each (var item in a) {
if (item.mask == item.parent) {
trace(item.name + " is a mask");
}
}
// outputs "boundary is a mask"
Seems to work, and after checking the API reference on masks, it's clear that when caching, a mask will need to be a child of the masked, however... it's also valid to have a mask at the same depth as the masked (I do this from time to time when a mask needs to not travel with the masked content).
For example...
MainTimeline ¬
0: clip ¬
0: boundary
... can also be laid out as ...
MainTimeline ¬
0: clip ¬
1: boundary
So, there's the conundrum. Any ideas on how to resolve this?
The "best" hack I've found so far is to run hitTestPoint on the objects (after making sure they have something to hit under the target). Masks do not appear to ever return true for a full pixel hit test. This seems to work in most basic situations that I've tested:
public function isMask(displayObject:DisplayObject):Boolean {
// Make sure the display object is a Class which has Graphics available,
// and is part of a mask / maskee pair.
if ((displayObject is Shape || displayObject is Sprite) && displayObject.mask) {
// Add a circle at the target object's origin.
displayObject['graphics'].beginFill(0);
displayObject['graphics'].drawCircle(0, 0, 10);
var origin:Point = displayObject.localToGlobal(new Point());
var maskLocal:Point = displayObject.mask.globalToLocal(origin);
// Add a circle at the same relative position on the "mask".
displayObject.mask['graphics'].beginFill(0);
displayObject.mask['graphics'].drawCircle(maskLocal.x, maskLocal.y, 10);
// No matter which is the actual mask, one circle will reveal the other,
// so hit testing the origin point should return true.
// However, it seems to return false if the object is actually a mask.
var hit:Boolean = displayObject.hitTestPoint(origin.x, origin.y, true);
displayObject['graphics'].clear();
displayObject.mask['graphics'].clear();
// Return true if the hit test failed.
return !hit;
} else {
return false;
}
}
Obviously you'd want to cache the graphics in case the objects already have some, and it could do with something more elegant than casting as Sprite so that it can handle Shapes, but it's a start.
Edit: Accessing ['graphics'] lets this accept Shapes, but obviously isn't super efficient. I'm not sure what the best method would be, short of adding an interface.
Great question, haven't run into this before. I wasn't aware of the cyclical reference.
If your masks are exclusively masks, I would suggest just incorporating that into your naming convention. For example calling it clipMask as opposed to boundary.
As noted in the comments, in the situation where the mask is on the same display list, you could use getChildIndex() to compare their position on the display list of the parent.
Typically in that situation I'll have the mask layered over the other display object. This is not enforced obviously, and I don't believe that it has any effect on the result of the mask visually. But it's easier to maintain for a large group than a naming convention.
Still not ideal obviously.

AS3: How to refer an object by his properties

Well, I'm doing a checkers game and I need to refer a piece by its position (x and y, both) and remove it from the screen (no problem with this).
I've been traying combinations with "this." but nothing.
How would you do that?
this.x and this.y are functional from the scope of your checkers pieces object; however, if you're accessing a piece outside of their scope, you must use a piece's instance name. Although not optimal, you could loop through children DisplayObjects.
// create a collection of your checker pieces
var checkers:Array = [];
// create a checker piece, whatever your DisplayObject class is.
var checker:Checker;
checkers.push(checker);
// add it to the stage, probably your game board
addChild(checker);
checker.x = 100;
checker.y = 100;
// loop through the children (from your game board)
for (var i:uint = 0; i < numChildren; i++)
{
var checker:DisplayObject = getChildAt(i);
trace(checker.x);
trace(checker.y);
}
Using coordinates to reference a piece may not be optimal for game play. You might want to consider a row / column or approach it from how your game board works.
If this is not clear, you should specify some code or expand your question with more detail.