How to check collision for multiple symbols - actionscript-3

I was wondering if it was possible to check for collision by the instance name and not separate mc names. I have about 150-200 objects (dots for pacman game) that I need to check collision for and want to do it efficently. Thanks!

If you have instance named dots, and a player, you could do something like this:
//a var to hold each loop iteration's dot for convenience
var tmpDot:DisplayObject;
//loop 200 times from 1 - 200
for(var i:int=1;i<= 200;i++){
//getChildByName gets an instance from a string, in this case dot plus i (i is the current iteration number)
tmpDot = getChildByName("dot" + i);
//check if the dot exists and is hitting the player
if(tmpDot && tmpDot.hitTestObject(player)){
//hit a dot, do something here like remove the dot
removeChild(tmpDot);
//increment points etc.
//if there's no possibility of the player hitting more than one dot at a time, then for efficiency you should break out of this loop
break;
}
}
Now, as mentioned in the comments on your question, giving 200 dots an instance name is tedious. An easier way, would be to take your dot MovieClip in your library, go to it's properties, and export it for actionscript (let's say you gave it a class name of Dot). Then what you could do, is at the start of a level find all the dot objects that you have on your timeline (no instance names needed) and add them to an array:
//DO THIS ONLY WHEN THE LEVEL STARTS
//create a vector/array to store all your dots for better speed
var allDots:Vector.<Dot> = new Vector.<Dot>();
//iterate over all the children of this timeline frame
for(var i:int=0;i<numChildren;i++){
//if the item is a Dot, add it to the array
if(getChildAt(i) is Dot){
allDots.push(getChildAt(i) as Dot);
}
}
Now, you can do the hit tests like this:
//YOU PROBABLY WANT TO DO THIS EITHER EVERY FRAME, OR WHENEVER THE PLAYER MOVES
//flag to see if all dots are eaten
var allEaten:Boolean = true;
var tmpDot:Dot;
for(var i:int=0;i<allDots.length;i++){
tmpDot = allDots[i];
//.... same as the top code example at this point
if(tmpDot && tmpDot.hitTestObject(player)){
removeChild(tmpDot);
//do anything else you need to do when a dot is eaten
//if we've already determined that we haven't eaten all the dots, then break the loop
if(!allEaten) break;
}
//if a dot has a parent, then they haven't been all eaten
if(tmpDot.parent){
allEaten = false;
}
}

Related

Actions with a list of instances - AS3

what is the best way to do an action with many instances at the same time?
Lets say I have 50 movieclip instances called A1 to A50, and I want to run an action with only A20 to A35.
For example:
(A20-A35).gotoAndStop(2)
You want an algorithm operation called loop. You are not able to abstractly address things in bunches at once, but you can loop and iterate the bunch one by one which produces basically the same result. Please read this: https://en.wikipedia.org/wiki/Control_flow#Loops When you need to do a quantity of similar operations it is always loop.
With regard to your problem:
// Loop iterator from 20 to 35 inclusive.
for (var i:int = 20; i <= 35; i++)
{
trace("");
// Compose the name of the MovieClip to retrieve.
var aName:String = "A" + i;
trace("Retrieving the MovieClip by name", aName);
// Retrieve the instance by its instance name.
var aChild:DisplayObject = getChildByName(aName);
// Sanity checks about what exactly did you find by that name.
if (aChild == null)
{
// Report the essence of the failure.
trace("Child", aName, "is not found.");
// Nothing to do here anymore, go for the next i.
continue;
}
else if (aChild is MovieClip)
{
// Everything is fine.
}
else
{
// Report the essence of the failure.
trace("Child", aName, "is not a MovieClip");
// Nothing to do here anymore, go for the next i.
continue;
}
// Type-casting: tell the compiler that the child is actually
// a MovieClip because DisplayObject doesn't have gotoAndStop(...)
// method so you will get a compile-time error even if you are
// sure the actual object is a valid MovieClip and definitely has
// the said method. Compile-time errors save us a lot of pain
// we would get from run-rime errors otherwise, so treasure it.
var aClip:MovieClip = aChild as MovieClip;
trace(aClip, "is a MovieClip and has", aClip.totalFrames, "frames.");
if (aClip.totalFrames < 2)
{
// Nothing to do here anymore, go for the next i.
continue;
}
// Now you can work with it.
aClip.gotoAndStop(2);
}
Now that you understand the while idea step by step, if you are sure all of them are present and all of them are MovieClips you can go for a shorter version:
for (var i:int = 20; i <= 35; i++)
{
(getChildByName("A" + i) as MovieClip).gotoAndStop(2);
}
UPD: You can as well address children with square bracket access operator.
for (var i:int = 20; i <= 35; i++)
{
// You can skip type-casting as this["A" + i] returns an untyped reference.
this["A" + i].gotoAndStop(2);
}
Yet there are differences and complications. Method getChildByName(...) always returns a DisplayObject with the given name (or null if none found). Square brackets operator returns an untyped OOP field of the current object.
It will not work with dynamically added children (unless you pass their references to the respective fields).
It will not work if "Automatically Declare Stage Instances" publish option is off.
Finally, this["A" + 1] and A1 are not exactly the same because the latter could refer to a local method variable rather than object member.
I'm not saying that square brackets are evil, they're as fine, yet, as always, programming is not a magick thus understanding what you are doing is the key.

as3 add multiple MovieClips into random MovieClips

I'm trying to to do some dynamic MovieClips placement here. I've been trying for a couple'o days and I can't find the right way to do the trick. It's the continuation for this. I didn't manage to properly make my MC's appear in the triangle, so I've made a pyramid of rectangles (suits my case better, beacuse I can change each of'em, to better fit my desired shape - a not-so-regular triangle).
Here's the code with some comments:
import flash.events.MouseEvent;
import flash.display.MovieClip;
btn_toys_2.confirm.addEventListener(MouseEvent.MOUSE_UP, confirmToys);
var toysPlc:Array = new Array(); //an array for a bunch of rectangles
var toAdd:int = 100 //this is supposed to be dynamic, user defined
var toy:MovieClip = new shar_001; //an MC from a library
for (var j:int=0; j<33; j++){
toysPlc.push("tPl_" + j); //here I add that bunch of rects into an array
}
function confirmToys(e:MouseEvent):void{
for (var k:int=0; k<toAdd; k++){ //supposed to add an "toAdd" amount of "toys"
var p:int = Math.random()*toysPlc.length; //^do so in a random rect
toysPlc[p].addChild(toy); //supposed to place a toy in a random rect
toy.x = Math.random()*toysPlc[p].width; //positioning
toy.y = Math.random()*toysPlc[p].height; //positioning
}
}
The error I get is: TypeError: Error #1006: value is not a function.
What I DID manage is to place a single toy in a random of these rects, tho I don't remember how :)
Thanks in advance!
EDIT: null asked me to clarify the case, so here's the whole picture:
I've got:
- triangle-like MC (since a triangle MC is a rectangle for flash anyway, I've solved this by creating a pyramid of 33 rectangles, layered on each other);
- toys (multiple frames to change whenever I need to);
- text field (to input the quantity of desired toys);
- confirm button (to make the magic happen once clicked);
Now I need a user to input a 3-digit number in the input field, press "confirm", and 0.4 of that quantity is supposed to appear in the "triangle-like MC".
For example: user inputs a number: 600, the end value is 600*0.4=240. Now 240 "toys" are randomly spreaded between my 33 rectangles, within their width (which is different for every single one).
Hope that explains a bit more.
A simplified sample of what I need here.
Is there a way to fill an array with MovieClips rather than String values? That would be THE answer to this question here.
There is in fact more than one way:
Place all the instance names into the Array when you create it:
var rectangles:Array = [instanceName1, instanceName2];
there are no quotation marks, which create string literals. Just the names.
This approach quickly becomes impractical for large numbers of objects.
Given that the instance names have a number component, iterate through the names in conjunction with getChildByName(). I assume that this is what you were trying with in your question:
var rectangles:Array = []; // initialise empty array
for (var j:int=0; j<33; j++){
rectangles.push(getChildByName("tPl_" + j));
}
original answer
toysPlc.push("tPl_" + j); //here I add that bunch of rects into an array
No you don't. You are filling the Array with String objects. It's totally unrelated to any rectangle whatsoever.
Now this next line, tries to call a function on each String, which fails.
toysPlc[p].addChild(toy);
The above is equivalent to
"tPl_0".addChild(toy);
"tPl_1".addChild(toy);
// etc.
A String doesn't have that method addChild.

How to Adjust Volume in an Audio loop?

How would someone change sound levels of a music playing in a loop? For example, I'm making a game and at a certain frame I want the music level (music.wav) to be decreased to half of its volume.
How could someone do this in AS3?
You are using the word "loop" in a confusing way. In programming, a loop usually refers to one of the "for" loops that looks like this:
for (var i:int = 0; i < 10; i++)
{
//do stuff 10 times
}
I surmise that this is not what you mean by loop, but rather that you would like a MovieClip or the main timeline to decrease the volume of a Sound object at the end of n frames. Or do you just mean the music itself is looping? Hopefully you see the value of asking well written questions. That being said..
Mind you, I haven't tried this, but according to my reference book (ActionScript 3.0 Cookbook by Lott, Schall & Peters) you need to use a SoundTransform object which specifies the volume at which you want the sound to be set. Try this:
var _sound:Sound = new Sound(music.wav); // creates a Sound object which has no internal volume control
var channel:SoundChannel = _sound.play(); // creates a SoundChannel which has a soundTransform property
var transform:SoundTransform = new SoundTransform(); // SoundTransform objects have a property called "volume". This is what you need to change volume.
Now in your loop (or on the frame event that you are using) do this:
transform.volume *= 0.9; // or whatever factor you want to have it decrease
//transform.volume /= 1.1; // or this if you prefer.
channel.soundTransform = transform; //
So anytime you want the volume to decrease by this incremental amount, run this bit of code. Of course, you need to make sure that any variables you set are accessible in the code that is referencing them. One way that comes to mind to do this is with a function.
private function soundDiminish(st:SoundTransform, c:SoundChannel, factor:Number = 0.9):void
{
st.volume *= factor;
c.soundTransform = st;
}
Now, whenever you want to diminish the volume just call the soundDiminish function.
Maybe your frame event looks like this:
function onLoadFrame(fe:Event):void
{
soundDiminish(transform, channel); // 3rd parameter optional
}
If you only want this function to be called every 20 frames then:
function onLoadFrame(fe:Event):void
{
// this is a counter that will count up each time this frame event happens
frameCount ++;
if (frameCount >= 20)
{
soundDiminish(transform, channel); // 3rd parameter optional
frameCount = 0; // reset the frame counter
}
}

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.

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