AS3 Creating an array of objects - actionscript-3

I would like to add a bunch of cars to the stage, and store them in an array as objects. The problem is I hate using external AS files and would like to keep it as simple as possible.
I tried doing :
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
var test:Array = new Array()
for (var i:Number=0; i<10; i++) {
test.push(car)
}
The problem is if I try to set a value of one object in the like
test[1].carscale = 5
Every object in the array gets their attribute carscale set to 5.
Is there any way I can do this without using external class files?

While you should use external AS files (its a good practice), here's the reason why you are having the issue, and I'm going to explain line-by-line
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
//This creates an object called car. Suppose it saves it in memory at "location" 0x12345
var test:Array = new Array();
//This creates an empty array
for (var i:Number=0; i<10; i++) {
test.push(car);
//This adds the object "car" to the array
//Since Object is a reference type, its memory location is actually added to the array
//This means you added 0x12345 to the array (10 times over the loop)
}
//The array now contains
[0x12345, 0x12345, 0x12345, .......];
//So now
test[1]; //returns the object at 0x12345
test[1].carscale=5; //sets the carscale property of the object at 0x12345
Since all objects in the array point to the same location, getting any of them will actually return the same object. This means that all of them will show carscale as 5
A solution to this would be:
var test:Array = new Array();
for (var i:Number=0; i<10; i++) {
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
test.push(car);
}
A better, REAL Object oriented solution would be to create a class called Car and then instead of doing
var car:Object = {carcolor:String,carscale:Number,carpower:Number};
you use
var car:Car = new Car();
The Car.as class would be like this:
public class Car {
public function Car() {
//this is the constructor, initialize the object here
//Suppose the default values of the car are as follows:
carcolor="red";
carscale=5;
carpower=1000;
}
public var carcolor:String;
public var carscale:Number, carpower:Number;
}
In fact, you could even use another constructor that automatically sets the properties based on arguments:
public function Car(_color:String, _scale:Number, _power:Number) {
carcolor=_color;
carscale=_scale;
carpower=_power;
}
and call it as
var car:Car=new Car("red", 5, 1000);
In fact, the car before carcolor, carscale and carpower is not even necessary because it is obvious when you put them in a class called Car.

Like TheDarkIn1978 said you're pushing a reference of your car instance into your array. When you change the value of one instance's property the same happens for each reference.
The simple answer is to create a new object upon each interation of your for loop like in the following:
var test:Array = [];
for (var i:Number = 0; i < 10; i++)
{
var car:Object = {carcolor:String, carscale:Number, carpower:Number};
test.push(car);
}// end for
[UPDATE]
I know you said that you didn't want to use "external classes" but there are advantages to using a custom class object to store values as opposed to a Object object. Here is an example:
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var cars:Vector.<Car> = new Vector.<Car>();
cars.push(new Car("red", 1, 1));
cars.push(new Car("blue", 2, 2));
cars.push(new Car("green", 3, 3));
trace(cars[2].color); // output: green
}// end function
}// class
}// end package
internal class Car
{
private var _color:String;
private var _scale:Number;
private var _power:Number;
public function get color():String { return color; }
public function get scale():String { return scale; }
public function get power():String { return power; }
public function Car(color:String, scale:Number, power:Number)
{
_color = color;
_scale = scale;
_power = power;
}// end function
}// end class
This is a good example of creating an object for the sole purpose of storing values that never change by only allowing the object's properties to be set upon initiation and using getter methods to make the values read only.

I feel dumb, I found the answer here :
http://board.flashkit.com/board/showthread.php?t=792345
You're pushing the Object reference to the array, not a unique Object each time. You have to do something like:
for(var temp=0;temp<100;temp++){
var roomData:Object=new Object;
roomData.first_time=true;
rooms.push(roomData);
}

you're adding the same object to the array multiple times. you need to create new instances of your car object.
EDIT:
although it would be a best practice to create your own "Car" class and create new instances of it, even if it's only a small object with 3 properties, here's a quick example that should get you started.
package
{
//Imports
import flash.display.Sprite;
//Class
public class Main extends Sprite
{
//Constants
private static const DEFAULT_CAR_COLOR:Number = 0x000000;
private static const DEFAULT_CAR_SCALE:Number = 1.0;
private static const DEFAULT_CAR_POWER:int = 50;
//Properties
private var carsArray:Array;
//Constructor
public function Main():void
{
init();
outputCarColors();
}
//Initialize
private function init():void
{
carsArray = new Array();
for (var i:int = 0; i < 10; i++)
{
carsArray.push(CreateCar(Math.random() * 0xFFFFFF));
}
}
//Output Car Colors
private function outputCarColors():void
{
for (var i:int = 0; i < carsArray.length; i++)
{
trace("Color of car " + i + " : " + carsArray[i].carColor);
}
}
//Create Car Object
private function CreateCar(carColor:Number = DEFAULT_CAR_COLOR, carScale:Number = DEFAULT_CAR_SCALE, carPower:int = DEFAULT_CAR_POWER):Object
{
var result:Object = new Object();
result.carColor = carColor;
result.carScale = carScale;
result.carPower = carPower;
return result;
}
}
}

Related

AS3: how to convert a string to a DisplayObject

This is driving me crazy.
In Main.as I have a button that, when pressed, trigger some stuff.
In arrayTambores I define an array with some Strings in it.
What I want to achieve is to create a big movieclip container with several mc inside, whose names are picked from that array.
Thanks you guys!
So this is what I have in ArrayTambores:
package
{
public class arrayTambores
{
public static var tempArray: Array = new Array();
public static var bigArrayTambor1: Array = new Array();
public static var randomPos:Number = 0;
public static var a:int = 0;
public static var z:int = 0;
tempArray[0] = "heladeraIcon";
tempArray[1] = "comodinIcon";
for (a = 0; a < 2; a++)
{
tempArray.unshift("microondasIcon");
}
for (a = 0; a < 5; a++)
{
tempArray.unshift("duchaIcon");
}
bigArrayTambor1.length = tempArray.length;
for (var z: int = 0; z < bigArrayTambor1.length; z++)
{
randomPos = int(Math.random() * tempArray.length);
bigArrayTambor1[z] = tempArray.splice(randomPos,1)[0];
}
public function arrayTambores()
{
}
}
}
And this is my Main.as:
package
{
import flash.display.*;
import flash.events.*;
import flash.utils.*;
public class Main extends MovieClip
{
public function Main():void
{
var Tambor1_mc:MovieClip = new MovieClip();//This is the container mc
/*I already convert some bitmaps to movieclips in my Library, and assign each of one a class name. I.e., for the var comodinIcon, the class to the comodin.jpg in the Library is comodin. And of course I set each one to Export for ActionScript and Export in frame 1.*/
var comodinIcon:comodin = new comodin();
var duchaIcon:ducha = new ducha();
var heladeraIcon:heladera = new heladera();
var microondasIcon:microondas = new microondas();
// BUTTON
makeButton(my_mc, my_mc_click);
function my_mc_click(evt: MouseEvent):void
{
Tambor1_mc.x = 132;
Tambor1_mc.y = 250;
var clipA1:String = new String();
clipA1 = arrayTambores.bigArrayTambor1[0];
// HERE is where it fails, because it canĀ“t convert clipA1, which is a String, //to a flash.display.DisplayObject
Tambor1_mc.addChild(DisplayObject(clipA1));
stage.addChild(Tambor1_mc);
}
function makeButton(which_mc: MovieClip, clickFunction: Function):void
{
which_mc.buttonMode = true;
which_mc.useHandCursor = true;
which_mc.mouseChildren = false;
which_mc.addEventListener(MouseEvent.CLICK, clickFunction);
}
}
}
}
This answer makes the assumption that you have two library objects with 'export for actionscript' enabled and class names of heladeraIcon & comodinIcon.
To instantiate those in your code, you can't use strings because AS3 doesn't reference classes via strings (Though you can obtain a Class reference from a string using flash.utils.getDefinitionByName() but that isn't necessary here).
You actually just reference them by whatever value you put in as the class, so in your case heladeraIcon (no quotes because it's not a string, it's a class). When you check Export for actionscript, that class name becomes available in your program just like built-in classes.
So your code should look something like this:
tempArray[0] = heladeraIcon; //match whatever you put in the library object's properties as the class name
tempArray[1] = comodinIcon;
//you have get a reference to the class
var clipClass:Class = arrayTambores.bigArrayTambor1[0] as Class;
//then instantiate that class
var clipA1:DisplayObject = (new clipClass()) as DisplayObject;
Tambor1_mc.addChild(clipA1);
stage.addChild(Tambor1_mc);
Here is an example how to properly setup your library object(s) with AS3 Class linkage:
In Flash/AnimateCC, right-click (or ctrl+click on Mac) your symbol and choose properties.
In the properties window, check the Export for ActionScript & Export in frame 1 checkboxes in the ActionScript Linkage section.
Also in the ActionScript Linkage section, enter in a unique class name (in this example I've put in MyCustomClass.
Close the properties window.
In the library, you should now see the Class name that was given under the Linkage column. Keep in mind, the object name and Class/Linkage name can be different.
Now with the above linkage setup, I can do the following:
var clipClass:Class = MyCustomClass;
var clipA1:DisplayObject = (new clipClass()) as DisplayObject;
addChild(clipA1);

Removing an Object when it hits another object AS3

Beginner here. I have a symbol on the timeline with an instance name of 'island', so basically I want to remove the cells that hits the 'island'
if (cell.hitTestObject (island)) {
if(stage.contains(cell))
removeChild (cell);
}
I tried this one under the moveCell function but it only removes one cell instead of every cell that hits the island. Thanks everyone!
Here's my code so far:
package {
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class Main extends MovieClip {
public var cell:Cell;
public var group:Array;
public var gameTimer:Timer;
public function Main() {
cell = new Cell (400, -15);
addChild (cell);
group = new Array();
var newCell = new Cell (100, -15);
group.push ( newCell);
addChild(newCell);
gameTimer = new Timer (25);
gameTimer.addEventListener(TimerEvent.TIMER,moveCell);
gameTimer.start();
}
public function moveCell (timerEvent:TimerEvent):void {
if (Math.random() < 0.01) {
var randomX:Number = Math.random() * 700;
var newCell:Cell = new Cell (randomX, -15);
group.push (newCell);
addChild(newCell);
}
for each(var i:MovieClip in group) {
if (i.hitTestObject(island)) {
i.visible = false;
//i.parent.removeChild(i);
var score:int = 0;
score ++;
scoreOutPut.text = score.toString();
}
}
}
}
}`
You got the "Cannot access a property or method of a null object reference" because you've removed the Cell object from the DisplayObjectContainer (its parent) but not from the group array, so in the next iteration of your for loop, that object didn't exist anymore and that error will be fired.
To avoid that you can do like this :
for(var i:int = 0; i < group.length; i++)
{
var cell:Cell = Cell(group[i]);
if (cell.hitTestObject(island))
{
cell.parent.removeChild(cell);
group.splice(i, 1);
score++;
}
}
For the score, it should be a global property for all your class to get updated every time.
Also, for your code to be more organised and clearer, it's better to put every task in a single method.
For example, for creating cells, you can use a createCell() method :
// 0 is the default value of __x and -15 is the default one of __y
private function createCell(__x:Number = 0, __y:Number = -15): void
{
var cell:Cell = new Cell(__x, __y);
group.push(cell);
addChild(cell);
}
Then you can use it in any place in your code, for example, for your two first cells that you create in the constructor :
public function Main()
{
// ..
createCell(400);
createCell(100);
// ...
}
Or inside the moveCell() method :
if (Math.random() < 0.01)
{
var randomX:Number = Math.random() * 700;
createCell(randomX);
}
Also, if you don't really need that a property or a method to be public, don't put it as public.
...
Hope that can help.

Actionscript 3 Call to a possibly undefined method

Here is the problem, the object is moved together with the clicked object. I want it to be moveable following the mouse pointer, but let the clicked object stays. so when an object is clicked, there will be 2 objects in the stage(the static and moving one).
I think I've figured it out by adding a new object to be moved. in function onClickHero I've tried movingHero = new heroes but it says "call to a possibly undefined method heroes". My question is there any other way how to make another clone of the clicked object since I made it in array? And why does movingHero = new heroes doesn't work?
I'm still amateur at classes. Sorry if it's messed up. Thanks for helping.
package {
import flash.display.MovieClip
import flash.events.MouseEvent
import flash.events.Event
import flash.display.Sprite
public class Hero {
private var heroesArray:Array;
private var heroContainer:Sprite = new Sprite;
private var hero1:MovieClip = new Hero1();
private var hero2:MovieClip = new Hero2();
private var moveHero:Boolean = false;
private var movingHero:MovieClip;
private var _money:Money = new Money();
private var _main:Main;
public function Hero(main:Main)
{ _main = main;
heroesArray = [hero1,hero2];
heroesArray.forEach(addHero);
}
public function addHero(heroes:MovieClip,index:int,array:Array):void
{
heroes.addEventListener(Event.ENTER_FRAME, playerMoving);
heroes.addEventListener(MouseEvent.CLICK, chooseHero);
}
public function playerMoving(e:Event):void
{
if (moveHero == true)
{
movingHero.x = _main.mouseX;
movingHero.y = _main.mouseY;
}
}
public function chooseHero(e:MouseEvent):void
{
var heroClicked:MovieClip = e.currentTarget as MovieClip;
var cost:int = _main._money.money ;
if(cost >= 10 && moveHero == false)
{
_main._money.money -= 10;
_main._money.addText(_main);
onClickHero(heroClicked);
moveHero = true;
}
}
public function onClickHero(heroes:MovieClip):void
{
movingHero = heroes;
heroContainer.addChild(movingHero);
}
public function displayHero(stage:Object):void
{
stage.addChild(heroContainer);
for (var i:int = 0; i<2;i++)
{
stage.addChild(heroesArray[i]);
heroesArray[i].x = 37;
heroesArray[i].y = 80+i*70;
heroesArray[i].width=60;
heroesArray[i].height=55;
heroesArray[i].buttonMode = true;
}
}
}
}
EDIT: I've tried to make movingHero = new Hero1(); but since I don't know which hero will be clicked so I can't just use Hero1 from library. and If I use movingHero = heroClicked I only get the value of hero1 which is a var from Hero1 movieclip. So, is there any way to call the movie clip from library the same as which hero was clicked in stage?
You seemingly want to clone an object while not knowing its type. If that object also containg game logic, it's not the best idea to say spawn new heroes of either type, this might make a mess of your code. But if not, you can get the exact class of the object given, and make an object of that class via the following code:
public function onClickHero(heroes:MovieClip):void
{
if (!heroes) {
trace('heroes is null!');
return;
}
var heroClass:Class = getDefinitionByName(getQualifiedClassName(heroes)) as Class;
movingHero = new heroClass(); // instantiate that class
heroContainer.addChild(movingHero);
// movingHero.startDrag(); if needed
}
Don't forget to clean up the movingHero once it's no longer needed.

Make Objects behave like Radio Buttons in AS3

I would like to implement a very simple way to store a variable containing the last specific "CustomObject" I clicked. I'd like clicks on other objects to be ignored. Take the following sample code for example, given CustomObject extends MovieClip:
//Code within the Document Class:
var square1:CustomObject = new CustomObject();
var square2:CustomObject = new CustomObject();
var square3:CustomObject = new CustomObject();
var triangle1:DifferentObject= new DifferentObject();
square1.x=100; square2.x=200; square3.x=300;
addChild(square1);
addChild(square2);
addChild(square3);
addChild(triangle1);
//Code within the CustomObject Class:
this.addEventListener(MouseEvent.CLICK,radioButtonGlow);
public function radioButtonGlow(e:MouseEvent):void
{
var myGlow:GlowFilter = new GlowFilter();
myGlow.color = 0xFF0000;
myGlow.blurX = 25;
myGlow.blurY = 25;
this.filters = [myGlow];
}
This works great for whenever I click on squares- they light up exactly as expected. However, I'd like to implement a functionality that:
1) Stores the last square I clicked into a variable in the document class
2) Removes the glow from all other squares when I click on another one
Any feedback is greatly appreciated!
I suggest creating a class that acts as a collection of CustomObject instances and manages them in that manner (i.e. ensuring only one of that collection can be selected, etc).
Sample:
public class CustomCollection
{
// Properties.
private var _selected:CustomObject;
private var _items:Array = [];
// Filters.
private const GLOW:GlowFilter = new GlowFilter(0xFF0000, 25, 25);
// Constructor.
// #param amt The amount of CustomObjects that should belong to this collection.
// #param container The container to add the CustomObjects to.
public function CustomCollection(amt:int, container:Sprite)
{
for(var i:int = 0; i < amt; i++)
{
var rb:CustomObject = new CustomObject();
rb.x = i * 100;
_items.push(rb);
container.addChild(rb);
}
}
// Selects a CustomObject at the specified index.
// #param index The index of the CustomObject to select.
public function select(index:int):void
{
for(var i:int = 0; i < _items.length; i++)
{
if(i == index)
{
_selected = _items[i];
_selected.filters = [GLOW];
continue;
}
_items[i].filters = [];
}
}
// The currently selected CustomObject.
public function get selected():CustomObject
{
return _selected;
}
// A copy of the array of CustomObjects associated with this collection.
public function get items():Array
{
return _items.slice();
}
}
Then you can revise your code in the document class to something like:
var collection:CustomCollection = new CustomCollection(3, this);
collection.select(1);
You will need to add your own logic for the click event that deals with selecting the buttons. I suggest adding an index property to each CustomObject as well as a reference to the collection it was added to. That way, you can simply add the click event into the CustomObject class and have the handler function something like:
private function _click(e:MouseEvent):void
{
_collection.select(index);
}

AS 3.0 Dynamic Instance Names

Hi i made a custom class where i would like to create x instances of a movieclip. But the following doesn't work:
package {
import flash.display.MovieClip;
public class CustomClass extends MovieClip {
public function CustomClass(amount:uint) {
var Collector:Array = new Array();
//Add and position Tiles to stage.
for (var i:uint = 1; i <= amount; i++){
var newMovieClip:MovieClip = new MovieClip;
newMovieClip.y = amount * 10;
Collector.push(newMovieClip);
}
addChild(Collector);
}
}
}
I would like to position them on the timeline with
var customClass_mc:CustomClass = new CustomClass(10);
addChild(customClass_mc);
//try to trace the x position of one of the instances.
trace(customClass_mc.Collector[5].x);
I keep getting the error: Scene 1, Layer 'Layer 1', Frame 1, Line 5 1119: Access of possibly undefined property Collector through a reference with static type CustomClass.
Firstly, you need to declare Collector as public:
public var Collector:Array = new Array();
Your Collector is an array, not a display object, and so it can't be added to the display tree. Instead you would push each newMovieClip onto the display of Custom class and position them inside your for loop. Then you don't need the collector at all, because you can target the movieclips using getChildAt():
trace(customClass_mc.getChildAt(5).x);
I found another answer myself which i think is even better!
You don't need the container at all.
when you use the following
package {
import flash.display.MovieClip;
public class CustomClass extends MovieClip {
public function CustomClass(amount:uint) {
//Add and position Tiles to stage.
for (var i:uint = 1; i <= amount; i++){
var newMovieClip:MovieClip = new MovieClip;
newMovieClip.y = amount * 10;
newMovieClip.name = "clip"+i;
addChild(newMovieClip);
}
}
}
}
No i can acces the movieclips by using:
var customClass_mc:CustomClass = new CustomClass(10);
addChild(customClass_mc);
//try to trace the x position of the fifth instance.
trace(customClass_mc.getChildByName("child5").y);
The variable 'Collector' is only available inside the constructor the way you have it. Collector has to be made public to be accessible from outside the timeline. The best thing to do would be to make a public getter method to access this. So something like:
import flash.display.MovieClip;
public class CustomClass extends MovieClip {
private var Collector:Array = new Array();
public function get Collector():Array
{
return Collector;
}
public function CustomClass(amount:uint) {
//Add and position Tiles to stage.
for (var i:uint = 1; i <= amount; i++){
var newMovieClip:MovieClip = new MovieClip;
newMovieClip.y = amount * 10;
Collector.push(newMovieClip);
}
addChild(Collector);
}
}