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

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/

Related

AS3 Child Index in relation to Stage

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.

AS3 Psuedo 3D Z-Indexing

I am attempting to make a really basic Pseudo 3D game in AS3. When I press certain keys my character moves up and down but what I want to happen is when the characters y position is above an objects y position then the character should appear behind the object.
Here is my code for an objects class at the moment:
package {
import flash.display.MovieClip;
import flash.utils.getTimer
import flash.events.Event;
public class bushMC extends MovieClip {
private var lastFrame:int = new int(0);
private var dt:Number = new Number();
private var main:Main;
public function bushMC(){
main = this.parent as Main;
stage.addEventListener(Event.ENTER_FRAME, update);
trace(main.getChildIndex(this));
}
private function update(e:Event):void{
dt = (getTimer() - lastFrame)/30;
lastFrame = getTimer();
if(main.char.y + 200 < this.y + 55 && main.getChildIndex(main.char) > main.getChildIndex(this)){
main.setChildIndex(this, main.getChildIndex(main.char)+1);
}
else if(main.getChildIndex(main.char) < main.getChildIndex(this)){
main.setChildIndex(this, main.getChildIndex(main.char));
}
}
}
}
I have tried editing loads of the values(+1, -1, equal to) for each calculation but I can't seem to find the right ones. One I tried almost works but instead when the char is supposed to be behind the object it simply flickers in-front and then behind continuously.
Thanks in advance, Kyle.
I just tried a little quick mock script based off your code. I got it working how I assume you are attempting to get it to work:
import flash.events.Event;
import flash.display.MovieClip;
var char:MovieClip = new MovieClip();
var bush:MovieClip = new MovieClip();
char.graphics.beginFill(0xFF0000);
char.graphics.drawCircle(0, 0, 30);
bush.graphics.beginFill(0x00FF00);
bush.graphics.drawEllipse(0, 0, 40, 80);
this.addChild(char);
this.addChild(bush);
bush.x = 100+(Math.random()*350);
bush.y = 100+(Math.random()*200);
this.addEventListener(Event.ENTER_FRAME, updateYPos);
function updateYPos(e:Event):void {
char.x = mouseX;
char.y = mouseY;
if(char.y < bush.y + 30 && this.getChildIndex(char) >= this.getChildIndex(bush)){
this.setChildIndex(bush, this.getChildIndex(char));
}
else if(char.y > bush.y + 30 && this.getChildIndex(char) < this.getChildIndex(bush)){
this.setChildIndex(bush, this.getChildIndex(char));
}
}
I hope this sample is enough to help you. All it needed was an extra condition on the else if and it works. :)
You should have a sorted list of bushes somewhere, which is then added via addChild() in the right order - uppermost bush has lowermost Z-position (child index 0 or the least of bushes, there could be other objects). Then, as your player moves, you track its position relative to list of bushes, so you don't run the full list check for z-ordering of player, but only check "nearest" bushes for possible change, then you set child index of player to found value. Note that if you're setting child index of player to bush's index, if you are moving player forwards (greater indexes), set to -1, as the bush will actually be one position lower because of player occupying a position in the display list, and if you are setting child index to lower values, set to equal. There is a more elegant version of this, using the fact that your bushes are continuous within display list, with only interruption being player, although it will run out of steam once more moving objects will appear.
And yes, you run update on the player or any other moving objects, not on the bush.
function updateZPos(e:Event):void {
// process coordinates change
var p:DisplayObjectContainer=this.parent;
// a bit faster to use function local var to get parent
var ci:int=p.getChildIndex(this); // changeable, get current index
var prev:DisplayObject=null;
if(ci>0) prev=p.getChildAt(ci-1);
var next:DisplayObject=null;
if(ci<p.numChildren-1) next=p.getChildAt(ci+1);
while(prev) {
if (this.y<prev.y) {
ci--;
p.setChildIndex(this,ci);
if (ci>0) prev=p.getChildAt(ci-1); else prev=null;
} else break;
while(next) {
if (this.y>next.y) {
ci++;
p.setChildIndex(this,ci);
if(ci<p.numChildren-1) next=p.getChildAt(ci+1); else next=null;
} else break;
}
}
This function was written with implication of display list of p being pre-sorted, and will maintain sorted state of it after moving of this, and is suitable for any moving object, not just the player. For this to work without messing up your other objects, separate everything moving into one container which will then get referenced as base for sorting display list. Otherwise your player might eventually get above all as the last element to check will be say score textfield with Y of 0. Also you will need to maintain coherent position of register point all over your set of moving objects' classes, so that say the base of a bush will be at Y=0 instead of being at Y=30, as implied in your code. The legs of a player should then also be at Y=0.

need AS3 movieclip parent child related functions

hi i've taken on a new coding technique and its leaving me a little stranded, alot of concepts ive previously applied now take new syntax and sometimes create unforseen problems.
OK, so i make multiplayer flash games. In order to cut down on clutter i no longer use multiple class.as files, instead, i have my stage, and one library object called triggers, which i place just out of sight in the upper left of the stage. i then make a class.as file for this one movieclip object, and from there i instantiate everything else in my program - so far a login splash-screen movieclip, a game-window movieclip, a lobby movieclip, and finally the game-instance movieclip. these come in and out of .visible appropriately, and when not in use they are stored at off screen x and y values, they progress sequentially based on userinput. additionally i have public arrays which store importantMessages[], myplayerarray[], myArrowsarray[], myenemyarray[]
now my biggest issue at the moment is i'll recieve in from the server the variables i need to build a new arrow and monster unit -- so ill do like movieclip orc, with orc.speed, orc.xstartlocation orc.hp and so on, and ill have a similar arrow movieclip with arrow.speed, arrow.gravity, and so on. both of these movieclips, with added properties, are then pushed onto the appropriate public arrays, and not added to the stage, but instead, are added to the stage.add(gamewindow:Movieclip) (the reasoning behind this is so that later if i want to move everything on the stage at once, they are already oriented on a single cohesive movieclip, then i can just move this movieclip)
ok now onto the problem stuff, when i have two gamewindow.movieclips collide, like an arrow versus an orc (lets say arrow13 hittest orc42 == true) i remove the arrow movieclip object from the gamewindow:movieclip and splice it from its myarrowarray, however, even though the graphic dissapears, it continues to move its current trajectory and hit everything else on its way. I believe the reasoning behind this is because during the creation of the movieclip with its variables, i include an eventlistener on enterframe, i think its removing the clip but not the event listener (see very bottom for instantiated arrow Movieclip class)
so this brings me to my concise question:
QUESTION ONE:
is it possible to not only gamewindow.removeChild(arrow13) but also gamewindow.removeChild(arrow13[and all variables and eventlisteners at once])
QUESTIONTWO:
my second question is a bit easier: since switching to movieclip() instead of object() ive been using brute force, what would be a 1 line piece of code to do all of the following:
var newarrow:MovieClip = new playerarrow();
newarrow.theowner = username
newarrow.thespeed = speed
newarrow.thegravity = gravity
newarrow.thepower = power
newarrow.arrownumber = arrowid
and my third question goes back to my splashscreen movieclips idea, im having trouble playing around with thier z-values
basically when i call the importantmessage() its creates a new movieclip in the lower left, which alpha fades to 0 and it removes itself, however i have a problem where my new movieclip windows will overwrite these messages since they were added a split second after, the example in my program is i will have 2 messages spit out stage.add "attempting to connect to server" "connected" then the next major function is invoked and it instantiates the loginsplash:movieclip = new loginwindow -- i've tried taking this new stage.addchild(loginsplash) and do setChildIndex(loginsplash, 0) as well as -1 and 1. both 1's are out of bounds and 0 produces : The supplied DisplayObject must be a child of the caller.
QUESTION THREE:
so if i have gamemsg z = 0 gamemsg2 z = 1 and loginsplash z = (0?), how can i get the game messages to always lay on top ( i think its more of a referenceing problem then anything else
========================================
connection.addMessageHandler("newarrow",
function(m:Message, username, speed, gravity, power, arrowid)
{
var newarrow:MovieClip = new playerarrow();
newarrow.theowner = username
newarrow.thespeed = speed
newarrow.thegravity = gravity
newarrow.thepower = power
newarrow.arrownumber = arrowid
for each(var p in myplayerarray)
if (p.mpname == username){
newarrow.x = p.theanimation.x + 100
newarrow.y = p.theanimation.y + 100
}
myarrowarray.push(newarrow)
gw.addChild(newarrow)
newarrow.addEventListener(Event.ENTER_FRAME, arrowenterframe)
function arrowenterframe(e:Event){
newarrow.thegravity = 0 //+=.6
speed = 5
newarrow.x = newarrow.x+speed
newarrow.y = newarrow.y + newarrow.thegravity
//ROTATE FUNCTION
newarrow.rotation = Math.atan(newarrow.thegravity / speed ) /(Math.PI/180)
if (speed < 0) {newarrow.rotation +=180}
for each(var d in myenemyarray){
if (newarrow.hitTestObject(d.orcicon)){
connection.send("arrowhitmonster", newarrow.arrownumber, d.monsternumber)
trace("hitting monster")
}
}
if(newarrow.hitTestObject(gw.theground)){
}
}
})
Q1 ... is possible, but not with a single command. I would recommend you use the casalib (which I tend to recommend often) If you use CasaMovieClip instead of MovieClip, it extends it by adding some additional functions that deal with these issues like removeEventListeners() and removeAllChildrenAndDestroy()(which removes listeners). With the event listeners, just be aware that it destroys only events that this object is listening to, and not the listeners that other objects have to this mc. Instead of trying to convert assets to use CasaMovieClip, you could also just look at the code and implement it over top of your classes/MCs
Another alternative to dealing with event listeners is to switch to using signals by Robert Penner. It's a much more elegant way of working with event notifications, and by the sounds of your setup (relying on few classes with big reach), it might work better when all communication between objects is happening through a single channel rather than being handled by every object individually.
Q2 - you could create a factory function.
public function createMC($mc:MovieClip,$owner:String,$speed:int,...etc):MovieClip{
$mc.theowner = $owner;
// etc.
return $mc;
}
or
public function createMC($mc:MovieClip,$properties:Object):MovieClip{
$mc.theowner = $owner;
for (var $property:String in $properties)
if ($mc.hasOwnProperty($property))
$mc[$property] = $properties[$property];
return $mc;
}
where you call the function like this var newarrow:MovieClip = createMC(new playerarrow(), { theowner:username});
but I'm not sure why you would want to really
Q3 - the way I deal with these is set up movie clip holders. The critical messages will always be on top, the game menu bellow, the game background always on bottom. In the main view I would have a gameholder MC and above the menu and above that the criticalMessage holder, any objects that are added and removed are only within the appropriate holder.

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.

Faster way to tell if a sprite is near another sprite?

When one of my sprites is being dragged (moved around), I'm cycling through other sprites on the canvas, checking whether they are in range, and if they are, I set a background glow on them. Here is how I'm doing it now:
//Sprite is made somewhere else
public var circle:Sprite;
//Array of 25 sprites
public var sprites:Array;
public function init():void {
circle.addEventListener(MouseEvent.MOUSE_DOWN, startDrag);
}
private function startDrag(event:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE, glowNearbySprites);
stage.addEventListener(MouseEvent.MOUSE_UP, stopDrag);
circle.startDrag();
}
private function stopDrag(event:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, glowNearbySprites);
stage.removeEventListener(MouseEvent.MOUSE_UP, stopDrag);
circle.stopDrag();
}
private function glowNearbySprites(event:MouseEvent):void {
for (var i = 0; i < sprites.length; i++) {
var tSprite = sprites.getItemAt(i) as Sprite;
if (Math.abs(tSprite.x - circle.x) < 30 &&
Math.abs(tSprite.y - circle.y) < 30) {
tSprite.filters = [new GlowFilter(0xFFFFFF)];
}
else {
tSprite.filters = null;
}
}
}
Basically I'm cycling through each sprite every time a MOUSE_MOVE event is triggered. This works fine, but the lag when dragging the sprite around is pretty noticeable. Is there a way to do this that is more efficient, with no or less lag?
Well, depending on the size of the amount of sprites you have, it may be trivial. However, if you're dealing with over 1k sprites -- use a data structure to help you reduce the amount of checks. Look at this QuadTree Demo
Basically you have to create indexes for all the sprites, so that you're not checking against ALL of them. Since your threshold is 30, when a sprite moves, you could place it into a row/column index of int(x / 30), int(y / 30). Then you can check just the sprites that exist in 9 columns around the row/column index of the mouse position.
While this would seem more cumbersome, it actually it way more efficient if you have more items -- the number of checks stays consistent even as you add more sprites. With this method I'm assuming you could run 10k sprites without any hiccup.
Other performance optimizations would be:
use an vector/array of sprites rather than getChildAt
preincrement i (++i)
store a static single instance glowfilter, so it's only one array, rather creating a separate filter for all the sprites.
GlowFilter is pretty CPU intensive. Might make sense to draw all the sprites together in one shot, and then apply GlowFilter once to it -- (this of course depends on how you have things set up -- might even be more cumbersome to blit your own bitmap).
Make your variable declaration var sprite:Sprite = .... If you're not hard typing it, it has to do the "filters" variable lookup by string, and not by the much faster getlex opcode.
I'd incorporate all the improvements that The_asMan suggested. Additionally, this line:
tSprite.filters = [new GlowFilter(0xFFFFFF)];
is probably really bad, since you're just creating the same GlowFilter over and over again, and creating new objects is always expensive (and you're doing this in a for loop every time a mouse_move fires!). Instead create it once when you create this class and assign it to a variable:
var whiteGlow:GlowFilter = new GlowFilter(0xFFFFFF);
...
tSprite.filters = [whiteGlow];
If you're still having performance issues after this, consider only checking half (or even less) of the objects every time you call glowNearbySprites (set some type of flag that will let it know where to continue on the next call (first half of array or second half). You probably won't notice any difference visually, and you should be able to almost double performance.
Attempting to compile the suggestions by others into a solution based on your original code, so far I've created the GlowFilter only once and re-used, secondly I've changed the loop to use a for each instead of the iterant based loop, third I've updated to use ENTER_FRAME event instead of MOUSE_MOVE. The only thing I've left out that's been suggested so far that I see is using a Vector, my knowledge there is pretty much nil so I'm not going to suggest it or attempt until I do some self education. Another Edit
Just changed the declaration of sprites to type Vector no code here for how it's populated but article below says you can basically treat like an Array as it has all the same method implemented but has a couple of caveats you should be aware of, namely that you cannot have empty spots in a Vector and so if that is a possibility you have to declare it with a size. Given it knows the type of the object this probably gets a performance gain from being able to compute the exact position of any element in the array in constant time (sizeOfObject*index + baseOffset = offset of item). The exact performance implications aren't entirely clear but it would seem this will always result in at least as good as Array times if not better.
http://www.mikechambers.com/blog/2008/08/19/using-vectors-in-actionscript-3-and-flash-player-10/
//Array of 25 sprites
public var sprites:Vector.<Sprite>;
private var theGlowFilterArray:Array;
public function init():void
{
theGlowFilterArray = [new GlowFilter(0xFFFFFF)];
circle.addEventListener(MouseEvent.MOUSE_DOWN, startDrag);
}
private function startDrag(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, stopDrag);
addEventListener(Event.ENTER_FRAME, glowNearbySprites);
circle.startDrag();
}
private function stopDrag(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, stopDrag);
removeEventListener(Event.ENTER_FRAME, glowNearbySprites);
circle.stopDrag();
}
private function glowNearbySprites(event:Event):void
{
var circleX:Number = circle.x;
var circleY:Number = circle.y;
for each(var tSprite:Sprite in sprites) {
if (Math.abs(tSprite.x - circleX) < 30 && Math.abs(tSprite.y - circleY) < 30)
tSprite.filters = theGlowFilterArray;
else
tSprite.filters = null;
}
}
You problem is that making calculations that are at least linear O(n) on every mouse change event is terribly inefficient.
One simple heuristic to bring down the amount of times that you make your calculations is to save the distance to the closest sprite and only after mouse moved that distance would you recalculate the potential crash. This can be calculated in constant time O(1).
Notice that this works only when one sprite moves at a time.