AS3 Psuedo 3D Z-Indexing - actionscript-3

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.

Related

Moving Object to another Objects position

Hey everyone so I am having some trouble trying to get this to work correctly. I have a MC Object called character and another called "points". I have a container object called planetContainer I add the character to the planetContainer the character is rotating around the planets that are also added to the container. The main issue I am having is when the points power up is activated I want the points to move off the other planets and to the charactercenter position. It was working perfect but had to update some code and remove the Points out of the planetContainer and attach them to the planets instead. I know I might have to use localToGlobal but not too sure.
Here is how I setup the character:
private function newCounterClockWise():void
{
planetContainer.addChild(character);
character.rotation = (Math.atan2(character.y - planetHit.y, character.x - planetHit.x) * 180 / Math.PI);
}
How the points are added to the Planets:
private function addPoints():void
{
points = new mcPoints();
var planetPosition:Point = planetContainer.parent.localToGlobal(new Point(0, 0));
points.x = planetPosition.x;
points.y = planetPosition.y;
outerPlanets.addChild(points);
aPointsArray.push(points);
}
Now this is the main function that handles the points to move to the character but it is not working correctly. The points move but they move off the screen or cause the game to kinda tweak out and do different things. Also the "magnetHandler(); is in my EnterFRame Event:
private function magnetHandler():void
{
for (var i:int = 0; i < aPointsArray.length; i++)
{
var currentPoints:mcPoints = aPointsArray[i];
var characterPosition:Point = planetContainer.parent.globalToLocal(new Point(character.x, character.y));
if (currentPoints.hitTestObject(playScreen.mcPointsHit))
{
trace("POINTS MID STAGE");
currentPoints.x -= (currentPoints.x - characterPosition.x);
currentPoints.y -= (currentPoints.y - characterPosition.y);
//currentPoints.x = character.x;
//currentPoints.y = character.y;
//TweenMax.to(currentPoints, 0.5, {x:characterGlobalPosition.x, y:characterGlobalPosition.y , ease:Power1.easeInOut } );
}
}
}
Can anyone see what I am doing wrong?
It's a hard to understand your question fully (or to understand why you're putting things that relate to each other in separate containers), but likely this line is where it's falling down:
var characterPosition:Point = planetContainer.parent.globalToLocal(new Point(character.x, character.y));
What you want to do, is get the characters x/y coordinates in the currentPoints parent space. To do that, you would do something like this:
//first, find the global position of character:
var globalCharacterPoint:Point = character.localToGlobal(new Point());
//then, convert that to the currentPoints parent local space:
var localCharacterPoint:Point = currentPoints.parent.globalToLocal(globalCharacterPoint);
Also, in this code of yours:
points = new mcPoints();
var planetPosition:Point = planetContainer.parent.localToGlobal(new Point(0, 0));
points.x = planetPosition.x;
points.y = planetPosition.y;
You are getting the global space of the planetContainer's parent, which is probably NOT what you want. You likely want:
planetContainer.localToGlobal(new Point()); //this gives you the global location of the planet container's top left corner
And, since you're adding the points object to outerPlanets, you probably want to convert to its local space (unless it's positioned at 0,0 globally - then it doesn't especially matter).
var outerPoint:Point = outerPlanets.globalToLocal(planetPosition);
points.x = outerPoint.x;
points.y = outerPoint.y;
Needless to say, for games it's best to have everything in the global coordinate space unless it's truly encapsulated assets (like smoke on a rocket etc.)

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/

ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.

Please, I need help trying to remove bullets and enemies from my stage. I am very new to programming. Thank you.
I receive the following error message:
ArgumentError: Error #2025: The supplied DisplayObject must be a child
of the caller. at flash.display::DisplayObjectContainer/removeChild()
at Main/fl_EnterFrameHandler() at
flash.utils::Timer/_timerDispatch() at flash.utils::Timer/tick()
The code where debug sent me.
package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.Timer;
public class Main extends MovieClip
{
var enemyShipTimer:Timer;
var coinTimer:Timer;
var playerscore:Number = 0;
var enemies:Array;
var bullets:Array;
public function Main()
{
enemyShipTimer = new Timer(1000);
enemyShipTimer.addEventListener("timer", fl_EnterFrameHandler);
enemyShipTimer.start();
coinTimer = new Timer(1000);
coinTimer.start();
enemies = new Array ();
bullets = new Array ();
}
function fl_EnterFrameHandler(event:Event):void
{
var enemyinstance = new enemy_ks();
stage.addChild(enemyinstance);
enemies.push(enemyinstance);
trace(enemies.length);
for (var count=0; count<enemies.length; count++)
{
for (var bcount=0; bcount<bullets.length; bcount++)
{
if (enemies[count].hitTestObject(bullets[bcount]))
{
removeChild(enemies[count]);
enemies.splice(count, 1);
removeChild(bullets[bcount]);
bullets.splice(bcount, 1);
}
}
score_ks.text = " " + playerscore;
}
}
}
}
EDIT: Reread your code and noticed the real error is actually that you are adding to the stage but removing from your Main sprite. You need to match those up. You cannot remove an object from a parent if it is not actually a child of that object.
The points below still need to be addressed as well, otherwise you may end up with other errors.
Your issue is with your loop. In your loop, you adjust the array length on each successful hit test but you never adjust the count of the loop.
So think of it like this.
You start with:
count = 0;
length = 10;
Now say you run a loop for count < length and you splice at count == 4 and count == 7. In your current scheme, you will only hit the following objects (using the original index)
0 1 2 3 4 6 7 9
Notice that you don't hit index 5 or 8. When you modify the array like that and don't modify the count, you end up skipping over certain items. After splicing index 4, the original index 5 moves to 4 and everything else moves back one as well. So when you move to index 5, you are actually reading the original index 6.
Very simple fix for this is to just adjust your counts as you splice.
if (enemies[count].hitTestObject(bullets[bcount]))
{
removeChild(enemies[count]);
enemies.splice(count, 1);
count--; //subtract 1 from the count
removeChild(bullets[bcount]);
bullets.splice(bcount, 1);
bcount--; //subtract 1 from the bcount
}
That will ensure your count actually hits every object. You could also use a for each loop to handle this, though that type of loop is slower than a standard for loop depending on application.
Additionally, a DisplayObject can only be removed from a DisplayObjectContainer if it is actually a child of that container. If it is not, it will error out. So I believe you may also be running into an issue where your array does not fully line up with what is on the stage and you are trying to remove an object that doesn't actually exist as a child of the parent
As a minor aside, you should avoid adding children directly to the stage unless you have a real reason to do so. Instead, add it directly to the application object's display list using the this keyword, or simply addChild().

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.

Supplied DisplayObject must be a child of the caller

I get this error once in a while for a specific object. For some reason, this issue seems to start when I spawn 2 of this object instead of one. I basically have enemies that drops coins and one enemy drops 2. When I pick them up at the same time I start getting this error.
public function removeCoin(){
removeEventListener(Event.ENTER_FRAME, moveCoin);
if(this.parent){
this.parent.removeChild(this);
}
parentMC.level.spawnedCoins.splice(this, 1);
}
This is the function called by the collision check when there is a collision between the player and the coin. The issue usually starts when I pick up two coins at once from this function.
var dropCoin:Number = Math.random() * 100;
if(dropCoin > 40){
var newCoin1:coin = new coin(parentMC);
var newCoin2:coin = new coin(parentMC);
newCoin1.x = x+7;
newCoin1.y = y;
parentMC.level.levelObjects.addChild(newCoin1);
parentMC.level.spawnedCoins.push(newCoin1);
newCoin2.x = x-7;
newCoin2.y = y;
parentMC.level.levelObjects.addChild(newCoin2);
parentMC.level.spawnedCoins.push(newCoin2);
}
Edited the code.
That error means that the item you're trying to remove from the display list (by calling removechild) either isn't on the display list, or isn't a child of the object your calling removeChild on.
Without analyzing all your code, a quick check can fix your problem likely.
Change you existing chunk of code:
if(this != null){
parentMC.lvl1.levelObjects.removeChild(this);
}
to this:
if(this.parent){
this.parent.removeChild(this);
}
This checks if 'this' has a parent, if so, it removes itself from it's parent.
I think your problem might be:
parentMC.level.spawnedCoins.splice(this, 1);
If spawnedCoins is just an array then splice should take 2 integer args startIndex and deleteCount relevant adobe help page
By passing an object what I think is happening is that it is casting the object to an int, value of '1' (i.e. not null).
What I think you want instead is parentMC.level.spawnedCoins.splice(parentMC.level.spawnedCoin.indexOf(this), 1);