Extend the Array class in AS3 - arrays

I've been learning OOP recently and have decided to take that paradigm shift into it (its not easy...at ALL) and in trying some of the concepts of it I'm having a bit of a problem inheriting from the Array class in ActionScript 3.0 (not that i have tried in AS 2.0)...sigh. Anyway I am trying to call the parent constructor to instantiate the ...rest arguments from within the child class like so
public class CustomArray extends Array{
public function CustomArray(...rest):void {
super(rest);
}
}
And I keep getting this Error from the output...
ReferenceError: Error #1056: Cannot create property 0 on classes.CustomArray.
...to my utter dismay :(.
I'm obviously doing something wrong but for the love of me can't seem to find out what it is. Really in need of help. Thanks.

Unfortunately in AS3 you can't call super constructor and pass parameters to it in Function::apply style, so in your Array implementation array with length=1 and one element (the passed rest parameter with the type of Array) will always be created.
If you want to implement the default AS3 Array constructor behavior:
Array Constructor
public function Array(... values)
Parameters
... values — A comma-separated list of one or more arbitrary values.
Note: If only a single numeric parameter is passed to the Array constructor,
it is assumed to specify the array's length property.
You have to add some code to the constructor of your CustomArray:
public dynamic class CustomArray extends Array
{
public function CustomArray(...rest)
{
super();
var i:int, len:int;
if(rest.length == 1)
{
len = rest[0];
for (i = 0; i < len; i++)
this[i] = "";
}
else
{
len = rest.length;
for (i = 0; i < len; i++)
this[i] = rest[i];
}
}
}
Also don't forget to make this class dynamic.

Declare this class as dynamic. Also constructor is a method which doesn't specify return type, remove :void from its declaration.

Sorry to revive this "old" thread. I felt compelled to improve the constructor proposed by fsbmain.
When one calls new Array("foo"), an Array object is created containing the String "foo" - ["foo"]. So I think the custom constructor must take that possibility into account ( only one parameter that is not a number).
Here's the code I propose:
package {
public dynamic class MyArray extends Array {
public function MyArray(...rest) {
// super();
// I think super() is not mandatory here since
// we are basically replacing the constructor...
var i: int, len: int;
if (rest.length == 1) {
if(rest[0] is Number){
len = rest[0];
for (i = 0; i < len; i++) {
this[i] = null; // empty values must be null, not ""
}
}else{
this[0]=rest[0];
}
} else {
len = rest.length;
for (i = 0; i < len; i++) {
this[i] = rest[i];
}
}
}
}
}

Related

What does the error unexpected token 'public' mean?

I am a beginner in Apex and I need your help. What I am trying to do is creating a class that returns an array of formatted strings. The class has as a parameter the number of strings and it returns the array of strings formatted as:
Test 0, Test 1, ...Test n
The error I get is:
unexpected token 'public (line 1).
There might be more than one error in my code, if yes please feel free to let me know.
Thank you in advance!
public class StringArrayTest {
public static void generateStringArray(Integer n){
//List<String> stringArray = new List<String>();
for(Integer i=0; i<n; i++){
List<String>stringArray = new List<String>{'Test '+i};
}
return stringArray[];
}
}
There are a few things wrong with the code you have posted. When I tried this out I didn't get the error you mentioned, but here are the things to fix and get your code working:
The method should declare a return value of List<String>
Uncomment the line to initialize your list before the loop.
Return just the variable itself, no need to have the brackets. It's already a list.
Here is working code:
public class StringArrayTest {
public static List<String> generateStringArray(Integer n){
List<String> stringArray = new List<String>();
for(Integer i = 0; i < n; i++){
stringArray.add('Test ' + i);
}
return stringArray;
}
}

AS3 remove all instances of an object?

I have a game with in AS3 with a document class and a custom class which is attached to a movieclip in my .fla. Instances of that object are made multiple times/second. I want to delete those instances when, let's say, there are 100 of them. (because of performance problems after a while) The instances are stored in an Array after they are made.
You can remove them by using this.removeChild(obj); and obj is your object from array. So what you need is to loop through array and remove them.
That will remove all objects when objects are over 100.
if(array.length > 100)
{
for(var i:int = array.length - 1; i > -1; i--)
{
stage.removeChild(array[i]);// or any other parent containing instances
array.pop();
//array[i] = null; // if you want to make them null instead of deleting from array
}
}
Tip: Negative Loop (i--) is faster in performance than Positive Loop (i++).
Tip: pop() is faster in performance than unshift().
Update:
That will remove objects only if they are over 100, resulting in only 100 last objects remain on stage.
if(array.length > 100)
{
for(var i:int = array.length - 1; i > -1; i--)
{
if(array.length > 100)
{
stage.removeChild(array[i]);// or any other parent containing instances
array.unshift();// if you want to delete oldest objects, you must use unshift(), instead of pop(), which deletes newer objects
//array[i] = null; // if you want to make them null instead of deleting from array
}
}
/****** MyClass.as *********/
public class MyClass extends Sprite{
private var myVar:int=8567;
private function myClass():void{
//blablabla
}
public class destroy():void{
myVar = null;
this.removeFromParent(true); //method of Starling framework
}
}
/******** Main.as ****************/
public var myInstance:MyClass = new Myclass();
//Oh!! i need remove this instance D:
myInstance.destroy();

Creating a new array for eache iteration of the function call. AS3

so I have writing a function that returns objects on the stage and puts them into an array. and the function works fine until i call the function on more than one object name, meaning if im in the root class, and I call this function on object1 lets say it will add all the object one's from the stage, but if i call it on object2 it will throw an error, which makes some sense, i guess it means that it is not adding it to a unique array, but im not sure how to do that.
would it be a good idea to maybe make a multidimensional array? if that is the case would it be too slow?
here is the function code:
public function findObjects(objectName, objLocation, bVisible = false):Array{
for (var i = 0; i < objLocation.numChildren; i++){
var nObj=objLocation.getChildAt(i);
if (nObj is objectName){
// add to array and make invisible
obj.push(nObj);
nObj.visible=bVisible;
}
}
return obj;
}
any help with this would be greatly appreciated.
Try this:
public function findObjects(type:Class, target:DisplayObjectContainer, bVisible:Boolean=false):Array
{
var out:Array = [];
for(var i:int = 0; i<target.numChildren; i++)
{
var obj:DisplayObject = target.getChildAt(i);
if(obj is type)
{
out.push(obj);
obj.visible = bVisible;
}
}
return out;
}
And then based on your code, the implementation would probably be:
obj = findObjects(MovieClip, container);

Moving objects in array

I have an array which is filled with platforms that are supposed to move.
var MovingPlatformArray:Array = new Array();
for (var c:int = numChildren - 1; c >= 0; c--){
var child3:DisplayObject = getChildAt(c);
if (child3.name == "movingplatform"){
MovingPlatformArray.push(child3);
}
}
this.addEventListener(Event.ENTER_FRAME,ctrl_birdie);
function ctrl_birdie(e:Event):void{
for(var c in MovingPlatformArray){
MovingPlatform[c].y += speed;
if(MovingPlatformArray[c].hitTestPoint(birdie.x,birdie.y,true)){
birdtelleryvertrager=0;
birdtellery = 0;
birdie.y-=14;
}
if(movingplatform.y <= 25){
speed = 2;
}
if(movingplatform.y >= 350){
speed = -2;
}
}
Right now I have 2 moving platforms in this array. But only one moves up and down. But they both register a touch with the birdie. Am I doing something wrong?
In your listener, you're only setting the position of one platform, which ever one "movingplatform" is a reference to. As all your stage instances of moving platforms are named "movingplatform", one lucky platform is getting referenced by name (the rest ignored), instead of what you intended, which is to use the references in your array and adjust each platform.
You probably meant for movingplatform to be a local variable in your event handler, declared something like this:
var movingplatform:DisplayObject = MovingPlatformArray[c] as DisplayObject;
I'd recommend using a for each loop in place of the for in, because I think it's a little cleaner, but this is a minor style thing:
for each (var platform:DisplayObject in MovingPlatformArray)
{
platform.y += speed;
... rest of your code ...
}
For the sake of clarity, I edited the loop variable to be platform instead of movingplatform, to avoid confusion of having a local variable shadow a stage instance (i.e. this.movingplatform). I wanted it to be clear that the stage instance name is not being used here, because the unintentional instance name reference in your code is the source of your problem in the first place.
As far as i'm concerned, you have two options. use a for each, as adam smith suggested or use a for-loop as it was intended to be used :)
for(var c:uint = 0; c < MovingPlatformArray.length; c++){...
and btw: should "MovingPlatform[c].y += speed;" not be "MovingPlatformArray[c].y += speed;"?
edit: looking at your code, i would also suggest you use MovingPlatformArray[c].hitTestObject(birdie) instead of MovingPlatformArray[c].hitTestPoint(birdie.x,birdie.y,true)
If I were you, I would bring the logic for the platform out, and store it in a class. (Ideally you would do this for the birdie object as well). I have created an example below. The movieclips on the stage should extend Platform rather than MovieClip so they invoke the methods at the bottom.
// Use vectors if you know all the items are going to be the same type
var platforms:Vector.<Platform> = new <Platform>[];
for (var c:int = numChildren - 1; c >= 0; c--){
var child:DisplayObject = getChildAt(c);
// You shouldn't check against names (as per the original post). Because
// names should be unique
if (child is Platform){
platforms.push(child);
// This could be random so each platform has a different range
// This means platform 1 could go from y 30 to y 400, platform 2
// could go from y 60 to y 200, etc
child.setRange(25, 400);
}
}
this.addEventListener(Event.ENTER_FRAME, gameLoop);
// Have an overall game loop
function gameLoop(e:Event):void {
// Loop over the platforms
platforms.forEach(function(item:Platform, i:int, a:Vector.<Platform>):void {
// Hit test function in the class means you only have to pass in one mc
// rather than the points and a boolean
if(item.hitTest(birdie)) {
birdtelleryvertrager=0;
birdtellery = 0;
birdie.y-=14;
}
// Removed the movement logic, this should be kept out of the game loop
// plus how much better does this read?
item.move();
});
}
Then in a class location somewhere, like in a folder game/activeObjects
// A class for the platform stored else where
package game.activeObjects
{
import flash.display.MovieClip;
/**
*
*/
public class Platform extends MovieClip {
private const SPEED:Number = 2;
private var _direction:int = 1;
private var _minimumHeight:Number = 25;
private var _maximumHeight:Number = 350;
public function Platform() {
}
public function setRange(minimumHeight:Number, maximumHeight:Number) {
_minimumHeight = minimumHeight;
_maximumHeight = maximumHeight;
}
public function move():void {
this.y += SPEED * _direction;
if(this.y <= _minimumHeight) {
_direction = 1;
} else if(this.y >= _maximumHeight) {
_direction = -1;
}
}
public function hitTest(mc:MovieClip):Boolean {
return hitTestPoint(mc.x,mc.y,true);
}
}
}

AS3: Only allow a certain number of a certain type of object into an Array

I want to find a way to only allow certain objects into an array that have a certain word in thier class name. Or at least find the optimal way of doing something like this. Heres the details. I have an Array that stores all the objects dropped into a cart.
function addProductToArray (e:MouseEvent):void{
currMC = (e.target as MovieClip);
myCart.itemsInCart.push(currMC);
trace(myCart.itemsInCart);}
If, for example, I drop an [object BreadGrain] and a [object PastaGrain].
trace(myCart.itemsInCart);// would trace [object BreadGrain],[object PastaGrain].
Easy, no problems there. But what do I do if I only want to allow 2 objects with "Grain" in their Classname into the array? I want to do this so that the user can only drop 2 of each type of food into the 'cart'. The types of food are Grain, Fruit, Vegetable, Meats etc and I've appended the type of food to the end of the Classname, hopefully so that I can use it to detect what type of food it is and stop it from being added over the limit as well as displaying an error. i.e "You already have 2 Grain products".
I hope that makes sense. Anyway, i've found that works well to a degree:
if (currMC is BreadGrain) {
myCart.itemsInCart.push(currMC);
} else {
// error message code here
}
BUT I have several products and I don't want to have to write a if/else or switch statement for them all. I was hoping to do this dynamically with something similar to:
//this is an example of the logic
if (currMC classNameContainsTheWord "Grain" AND myCart.itemsInCart DoesNotContainMoreThan 2 Grain Objects) {
myCart.itemsInCart.push(currMC);
} else {
// error message code here
}
I'm stumped. Even just a "Dude, you are doing this all wrong" would help. Thanks.
You can get the class name of any object with the getQualifiedClassName function. Then you could try to match strings agains a certain pattern, with a RegExp or you could also just check if the class name contains some substring.
That said, I think a better approach could be using either a common base class or an interface.
// assuming your objects extend MovieClip
public class Grain extends MovieClip{
public function Grain() {
super();
}
public function someCommonMethodToAllGrains():void {
}
}
or
// It's customary to prefix interfaces name with an "I" in AS;
// I'm not doing it here so the code works for both a base class and an interface
public interface Grain {
function someCommonMethodToAllGrains():void;
}
Then, if you went with the base class:
public class BreadGrain extends Grain {
public function BreadGrain() {
super();
}
override public function someCommonMethodToAllGrains():void {
// if this makes sense for your object...
super.someCommonMethodToAllGrains();
}
}
public class PastaGrain extends Grain {
public function PastaGrain() {
super();
}
override public function someCommonMethodToAllGrains():void {
// if this makes sense for your object...
super.someCommonMethodToAllGrains();
}
}
Or, with the interface
public class BreadGrain extends MovieClip implements Grain {
public function BreadGrain() {
super();
}
public function someCommonMethodToAllGrains():void {
}
}
public class PastaGrain extends MovieClip implements Grain {
public function PastaGrain() {
super();
}
public function someCommonMethodToAllGrains():void {
}
}
If these objects are MovieClips, perhaps it's less tedious to use a base class, because otherwise you'd have to cast your objects back to MovieClip (or DisplayObject) any time you want to add them to the display list (or remove them). By the way, that's because someone at Adobe forgot to include an IDisplayObject interface and have the display list API accept objects that implemented this interface instead of a half-assed abstract class that you can't derive directly anyway (a.k.a. DisplayObject); this would have make it easier to treat display objects as interfaces, but I digress).
Anyway, either with an interface or a common base class you could do your validation with the is operator, but you'd just have to check for one type: Grain.
if(theObject is Graing && theArray.lenght <= 2) {
theArray.push(theObject);
}
You could also take this further and use a Vector instead of an Array. A Vector works almost the same as an Array, but it's strictly typed, so you could do:
var items:Vector.<Grain> = new Vector.<Grain>();
items.push(grainObject);
You'll get a compile time error if you try to add an object that does not extend/implement Grain.
Vectors are available for Flash Player 10 and you'd need Flash CS4, though (if you're using the Flash IDE; otherwise, I think you'd need at least the 3.2 SDK to compile).
Hm. I think you're going to need something a bit more complex to make this work properly. You're actually asking a two-part question: how to keep track of stuff, and how to identify stuff. I'll start with the easy bit, keeping track.
DISCLAIMER: My AS3 is pretty rusty, but at least the theory should be sound, even if the implementation might be a bit off.
First, you'd want to define the limits for each type of food, thus:
var dctLimits = new Object(); // not really a Dictionary, but we'll use it like one
dctLimits[ "grain" ] = 3;
dctLimits[ "meat" ] = 5;
...
Then, you want to keep count of objects you're adding to your cart
var dctCount = new Object();
dctCount[ "grain" ] = 0;
dctCount[ "meat" ] = 0;
...
Then, when you add a new object, first check its type against the relevant count. If the counter is less than the limit, let it in and increment the counter:
var type:String = getFoodTypeForObject( currMc );
if( dctCount[ type ] < dctLimit[ type ] ){
items.push( currMc );
dctCount[ type ]++;
} else {
// throw error
}
You'll notice that I've created an imaginary function, getFoodTypeForObject(). This is the trickier bit: identification.
You could implement your example logic like so:
function getFoodTypeForObject( currMc ){
if( getQualifiedClassName( currMc ).indexOf( "Grain" ) > -1 ){
return( "grain" );
} else if( getQualifiedClassName( currMc ).indexOf( "Meat" ) > -1 ){
return( "meat" );
}
...
}
Where classNameContainsTheWord is achieved with a combination of getQualifiedClassName and indexOf, but better would be to use a common base class, as suggested by Juan Pablo Califano. I'd suggest a slightly different style though:
public class CartItem extends MovieClip{
public var isGrain:Boolean;
public var isMeat:Boolean;
public function CartItem() {
super();
}
}
use that as the base Class for your cart item MCs, then set those boolean properties on the instances of MCs on your stage. Then, you can detect the type of something like this:
function getFoodTypeForObject( object ){
if( object.isGrain ){
return( "grain" );
} else if( object.isMeat ){
return( "meat" );
}
...
}
Cleaner than all that classname business, and has the added benefit that you can set something's properties independent of its class name.
It's not perfect; for instance, you'd need something more advanced if you needed a lot of properties, but it should be enough for you to keep going.
Uni had me doing other stuff for a while but finally I can get back into my game project.
I've got it working. I used Juan Pablo Califano's method. I did initially use Henry Cooke's because I wanted to get away with making a .AS file for each food (i.e. apple.as, cereal.as, bread.as, oranges.as). With Henry Cooke's method I created a
`var foodTypeLimit:Object = new Object();
foodTypeLimit["grain"]=2;
foodTypeLimit["fruit"]=2;
And var foodTypeCount:Object = new Object();
etc etc
`
For each food type. Then used the:
var type:String = getFoodTypeForObject( currMc );
if( foodTypeCount[ type ] < foodTypeLimit[ type ] ){
items.push( currMc );
foodTypeCount[ type ]++;
} else {
// throw error
}
As suggested. The function returned the string and viola it worked fine. However because my foodTypeCount variables (for example foodTypeCount["grain"]=0;) was inside the function, every time the function called these were set to 0 so the increment never got above with each call. So I thought, ok, i'll put these foodTypeCount variables outside of the function along with the instantiation of the var foodTypeCount:Object = new Object(); BUT NO, I kept getting the:
Error #1120: Access of undefined property foodTypeObject.
Even though it was right under the freakin declaration. I get i'm just too noob to understand why this is so. Anyway, for this reason (the lack of incrementation, which was essential to this function) I bit the bullet and used Juan Pablo Califano's way.
First I wrote out the classes like so:
public class Bread extends MovieClip implements Grain {
public function Bread() {
super();
}
public function someCommonMethodToAllGrains():void {
}
}
And then added the interface
`public interface Grain {
function someCommonMethodToAllGrains():void;
}
`
And now my function looks something like this:
if(currMC is Grain){
if(grainCount<2){
addProductToBasket();
grainCount++;
} else {
notAllowed("Grain");
}
}
function addProductToBasket(){
removeChild(currMC);
basketArray.push(currMC);
//remove listeners set mc to null etc
}
function notAllowed(foodType:String){
error.text="Sorry but you already have 2"+foodType+"products";
}
I tried to put all this into a switch. For example:
switch(currMC){
case is Grain:
//do this
break;
}
The above implementation didn't work. Perhaps switch statements probably aren't meant to be used that way. idk. :S
Anyway, thanks for the really great answers guys, this is my favorite site to come to for answers to life the universe and everything.

Resources