AS3 multiple textfields made easy - actionscript-3

I am working on a Results page for my game as well as upgrade page and looking for an easy way to do many textfields. I have a format for my text that takes care of font, colour, and size, but looking for an easy way to do the width and height of textfields to increase all at the same time.
I have been informed about a "with" keyword that may work but do not understand how to implement this within my program and essentially want to shorten my results class if possible.
Thank you,

The best way would be to create a custom function for generating textfield.
The example can be found in the livedocs itself.
So something like the following should suffice :
private function createCustomTextField(x:Number, y:Number, width:Number, height:Number):TextField {
var result:TextField = new TextField();
result.x = x;
result.y = y;
result.width = width;
result.height = height;
return result;
}
You may also set a default value to each attribute in the function.
private function createCustomTextField ( x:Number= <Default Value>, ...
Use it to add a textfield inside the container form.
var container:Sprite = new Sprite(); // New form container
container.addChild(createCustomTextField (20,20,50,50)); // Text Filed 1
container.addChild(createCustomTextField (20,50,50,50)); // Text Filed 2
addChild(container); // Add to current class
You may want to modify the function to accept a name so that each variable can be accessed later.

As far as I am aware, you can't use a "with" keyword to target multiple objects. Here's the documentation for it: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/statements.html#with
What I've done in the past is just make an array of all the targets, and then write a loop to apply properties to each:
var textFormat:TextFormat = generateMyCustomTextFormat();
var textField1:TextField = new TextField();
var textField2:TextField = new TextField();
//...
var textField3:TextField = new TextField();
var targets:Array = [textField1, textField2, textField3];
for(var i:int=0; i<targets.length; i++)
{
targets[i].defaultTextFormat = textFormat;
targets[i].width = 250;
//...
}

Related

Update text field into movie clip from main timeline code

I'm working in a project for basketball. I have an issue, all my code works great if all my components are in the main timeline.
But as soon as I convert the text fields into a movie clip so I can animate and apply alpha value, all stops working.
what am I doing wrong ? The only solution that I could think of is writing the result of my countdown into the text field in the movie clip, but it didn't work as well.
this is my code.
function onTimer ( ev:TimerEvent ) : void {
timeRemaining--;
if (timeRemaining < 0) {
timeRemaining = 0;
loseGame();
}
else
showTime.text = formatTimeRemaining ();
var miReloj:MovieClip;
var titulo_txt:TextField = new TextField();
titulo_txt.text = formatTimeRemaining ();
addChild(miReloj);
miReloj.addChild(titulo_txt);
// miReloj.addChild(showTime1.text);
//miReloj.showTime1.text = formatTimeRemaining ();
}
There's never a value being assigned to miReloj, that's why it's null
var miReloj:MovieClip; // no assignment here
var titulo_txt:TextField = new TextField();
titulo_txt.text = formatTimeRemaining ();
addChild(miReloj); // miReloj is still null here
miReloj.addChild(titulo_txt); // cannot call method on null
But as soon as I convert the text fields into a movie clip
That's impossible. From your code it looks like what you actually want to do is to add the TextField object to a container. There's no need to use a MovieClip for that, simply create a Sprite object:
var container:Sprite = new Sprite(); // create container
var title:TextField = new TextField();
title.text = formatTimeRemaining();
addChild(container); // add container
container.addChild(title); // add title to container
Always use English for all your programming. Don't mix other languages into it. It becomes a pain to read your code and decreases the number of people that can help you if you have problems with your code.

Loading images using the URLRequest

recently started to learn ActionScript 3 and already have questions.
question remains the same: I'm uploading a picture using the object Loader.load (URLRequest). Loaded and displayed a picture normally. But it is impossible to read the values of attributes of the image height and width, instead issued zero. That is, do this:
var loader:Loader=new Loader();
var urlR:URLRequest=new URLRequest("Image.jpg");
public function main()
{
loader.load(urlR);
var h:Number = loader.height;// here instead of the width of the image of h is set to 0
// And if you do like this:
DrawText(loader.height.toString(10), 50, 50); // Function which draws the text as defined below
// 256 is displayed, as necessary
}
private function DrawText(text:String, x:int, y:int):void
{
var txt:TextField = new TextField();
txt.autoSize = TextFieldAutoSize.LEFT;
txt.background = true;
txt.border = true;
txt.backgroundColor = 0xff000000;
var tFor:TextFormat = new TextFormat();
tFor.font = "Charlemagne Std";
tFor.color = 0xff00ff00;
tFor.size = 20;
txt.x = x;
txt.y = y;
txt.text = text;
txt.setTextFormat(tFor);
addChild(txt);
}
Maybe attribute values must be obtained through the special features, but in the book K.Muka "ActionScript 3 for fash" says that it is necessary to do so. Please help me to solve this. Thanks in advance.
Well it's simple.
Flash is focused on the Internet, hence such problems.
If you wrote loader.load (urlR); it does not mean loaded. Accordingly, prior to the event confirming the end of loading, in loadare Null
if, instead of certain functions would be more code that would perhaps tripped your approach.
Yeah plus still highly dependent on the size of the file that you read.
Well, in general it all lyrics. Listen event on loader.contentLoaderInfo.addEventListener (Event.INIT, _onEvent), onEvent and read properties.
You need to wait for your image to load to be able to get values out of it.
Attach an eventListener to your URLLoader.
var urlR:URLRequest = new URLRequest("Image.jpg");
loader.load(urlR);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loader_complete);
function loader_complete(e:Event): void {
// Here you can get the height and width values etc.
var target_mc:Loader = evt.currentTarget.loader as Loader;
// target_mc.height , target_mc.width
}
Loader

Remove randomly generated sprites with Mouse.Event

I am making a game for my class where different chemical elements are generated as sprites at the top of the screen and then fall down. Different types are made and I want students to mouse over specific types depending on where they are in the game.
My question is how to write the function to remove them when they are correctly selected? I've tried a lot of different ways but am having a lot of trouble. An example of the code that I wrote to make each element is below and then I have a separate function to move down all of the sprites created.
var spriteArray:Array = new Array();
var halogenArray:Array = new Array("F", "Cl", "Br", "I");
var rndnum:Number = Math.random();
//Halogens
if (rndnum < 0.05)
{
var halo:Sprite = new Sprite();
halo.graphics.beginFill(0x00FF00, 1);
halo.graphics.drawCircle(7.5, 7.5, 15);
halo.graphics.endFill();
halo.addEventListener(MouseEvent.MOUSE_OVER, removeElement);
halo.x = Math.random()*500 + 50;
halo.y = -18;
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
textField.height = 30;
textField.x = (15 - textField.textWidth)/2; // center it horizontally
textField.y = (15 - textField.textHeight)/2; // center it vertically
halo.addChild(textField);
spriteArray.push(halo);
addChild(halo);
}
At what point are you struggling?
I am assuming it is in determining the types of the halogens.
In your remove function I assume you have the desired type already figured out, you would then compare it to
element.getChildAt(0).text
and you would get the element by either looping across every element in the spriteArray, or using the mouseEvent's target
My suggestion is to use a halogen Class to contain the grapics & textfield, and a vector to hold the objects. It would then be easier to get the type rather than searching the anonymous children of the sprite.
I believe you are looking for something like this:
//give your textfields a name, it isn't totally necessary as we can do getChildAt(0)
//but it's more readable, and if you decide to add more children before you
//add the text field, then this will still work
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
...
textField.name = "haloTx"; //for tracking later
//assuming you have some variable set to the correct answer
var correctAnswer:String = "F";
function removeElement( e:MouseEvent ):void {
var element:TextField = ( e.target as Sprite ).getChildByName( "haloTx" );
//if we have the correct element, remove from parent and list
if ( element && element.text == correctAnswer ) {
var index:int = spriteArray.indexOf( e.target as Sprite );
removeChild( spriteArray.splice( index, 1 )[0] );
}
}
Although #VBCPP is right, doing that in a separate class is definitely the best way organizationally. Which might look something like:
class ElementSprite extends Sprite {
public var textField:TextField;
//pass shapeArgs as string, so say a circle at x=7.5, y=7.5, and radius=15 -- shapeArgs = "7.5, 7.5, 15"
public function ElementSprite( element:String, drawShape:String="Circle", shapeArgs:String="7.5, 7.5, 15", fillColor:uint=0x00FF00 ) {
//set textfield properties etc. or textFormat
textField = new TextField();
textField.text = element;
addChild( textField );
//if you passed some arguments to draw our shape
if ( shapeArgs != "" ) {
graphics.beginFill( fillColor );
graphics[ "draw" + drawShape ].apply( this, shapeArgs.split( "," ) );
}
}
public function get currentElement():String { return textField.text }
}
Then you would use it like so in your if statement if (rndnum < 0.05):
var elementSprite:ElementSprite = new ElementSprite( "A" );
//elementSprite.x = set your x;
//elementSprite.y = set your y;
addChild(elementSprite);
That would be replacing all your current code in that if statement. This is all a working example, if you have an questions feel free to comment.

Setting default values for object properties in AS3

I'm an actionscript newbie so please bear with me. Below is a function, and I am curious how to set default property values for objects that are being created in a loop.
In the example below, these propeties are the same for each object created in the loop: titleTextField.selectable, titleTextField.wordWrap, titleTextField.x
If you pull these properties out of the loop, they are null because the TextField objects have not been created, but it seems silly to have to set them each time. What is the correct way to do this. Thanks!
var titleTextFormat:TextFormat = new TextFormat();
titleTextFormat.size = 10;
titleTextFormat.font = "Arial";
titleTextFormat.color = 0xfff200;
for (var i=0; i<arrThumbPicList.length; i++) {
var yPos = 55 * i
var titleTextField:TextField = new TextField();
titleTextField.selectable = false;
titleTextField.wordWrap = true;
titleTextField.text = arrThumbTitles[i];
titleTextField.x = 106;
titleTextField.y = 331 + yPos;
container.addChild(titleTextField);
titleTextField.setTextFormat(titleTextFormat);
}
There are basically three options here.
You could create a custom class that
acts as a proxy to a TextFormat.
TextFormatProxy, for instance, which
could create a TextFormat in it's
constructor and set each of your
default values. See this link
or google "AS3 proxy pattern."
You could write a custom class that
extends TextFormat and likewise, set
these default values in the
constructor, or in a function of
your choosing.
You could use a little helper
function like this
package {
import flash.display.Sprite;
public class DefaultProperties extends Sprite{
public var s:Sprite;
public function DefaultProperties() {
s = new Sprite();
s.graphics.beginFill(0x00ff00, 1);
s.graphics.drawRect(0, 0, 100, 100);
s.graphics.endFill();
this.setDefaults(s, {x:100, y:200, scaleX:.5});
this.addChild(s);
}
function setDefaults($obj:*, $properties:Object):void {
for (var i in $properties) {
$obj[i] = $properties[i];
}
}
}
}
Without knowing the surrounding code, I'd suggest putting any construction logic in a factory object. If that's overkill, then I'd say your code is fine. Pulling it out of the loop and into a helper method is really just adding another layer of indirection which is not only harder to read, but it's also less efficient since you're introducing another method call on the stack.
Using a factory object is good if your class is really not interested in how things are created, but rather just needs one or more instance of something. That way, the factory is responsible for the creation of the object and you get a nice separation of concerns.
I wouldn't recommend creating a proxy structure that only has the purpose of setting the properties of your instance. I think it's an anti pattern because it favors inheritance over composition and it does so solely for the purpose of code re-use. But doing this means you're locking yourself to that specific implementation, which is likely not what you want. Just because your textfield has certain value, doesn't mean that it is a different type.
This is how your code could look when using the factory pattern, first out, the format factory:
public interface FormatFactory
{
function getInstance():TextFormat;
}
public class TitleFormatFactory implements FormatFactory
{
public function getInstance():TextFormat
{
var format:TextFormat = new TextFormat();
format.size = 10;
format.font = "Arial";
format.color = 0xfff200;
return format;
}
}
Factories may or may not be parameterized:
public interface TextFieldFactory
{
function getInstance(text:String, position:Point, format:TextFormat):TextField;
}
public class TitleFactory implements TextFieldFactory
{
public function getInstance(text:String, position:Point, format:TextFormat):TextField
{
var title:TextField = new TextField();
title.selectable = false;
title.wordWrap = true;
title.text = text;
title.x = position.x;
title.y = position.y;
title.setTextFormat(format);
return title;
}
}
Lastly, this is how you'd use the code:
var formatFactory:FormatFactory = new TitleFormatFactory();
var titleFactory:TextFieldFactory = new TitleFactory();
var format:TextFormat = formatFactory.getInstance();
for (var i = 0; i < arrThumbPicList.length; i++)
{
var position:Point = new Point(106, 331 + 55 * i);
var title:TextField = titleFactory.getInstance(text, position, format);
container.addChild(title);
}
Besides being readable, a huge benefit is that you can now swap the implementations of the factories and thus changing what kind of components you're using, without having to change your actual logic. All you have to do is change the references to the factories.
Also, by separating the concerns you make it easier to focus on one aspect of your code and thus run less of a risk of introducing errors and if you still do, those are often easier to spot and fix. More-over, it's much easier to unit test code when you separate concerns and more importantly, creation logic from business logic.
If I were to approach the same problem I'd use the same as you posted, just because making the new data structure would be unnecessary. An alternative would be to create a function that accepts the textbox as a parameter:
for (var i=0; i<arrThumbPicList.length; i++) {
var titleTextField:TextField = new TextField();
setProperties(titleTextField,i);
}
function setProperties(txt:TextField,offset:int){
var yPos = 55 * offset;
txt.selectable = false;
txt.wordWrap = true;
txt.text = arrThumbTitles[offset];
txt.x = 106;
txt.y = 331 + yPos;
container.addChild(titleTextField);
titleTextField.setTextFormat(titleTextFormat);
i++;
}
Although I'd probably go with the way you posted unless you're adding things from multiple for loops.
Your code is ok for me. Simple and effective. You only have 3 fixed parameters, for me it's not necessary to do more :
titleTextField.selectable = false;
titleTextField.wordWrap = true;
titleTextField.x = 106;
Unlike the other answer, I would do a helper function to get the textfield with all fixed parameters (I would do that ONLY if there was a LOT of stuff to do).
function createMyContextTextField():TextField{
textfield:TextField = new textField();
textfield.selectable = false;
textfield.wordWrap = true;
textfield.x = 106;
return textfield;
}

Add multiple movieclips, not replacing the old ones

So, in short, my problem is this. I am using a variable which is a movieclip loaded from an external swf. I want to "spawn" multiple instances of the movieclip that all react to the same code, so for example if I say var1.x = 100, they all are at 100x. But my problem is when I run addChild(var1) multiple times(I'm not actually typing in addChild(var1) over and over, I just have it set to add them at random times), the new child just replaces the old one, instead of making multiple movieclips. Should I do something like
var var1:MovieClip
var var2:MovieClip = new var1 ?(which doesnt work for me btw, gives me errors)
Oh, heres the code, and also, I am pretty new to as3 fyi, still don't even know how arrays work, which was my second guess to the problem.
var zombieExt:MovieClip;
var ldr2:Loader = new Loader();
ldr2.contentLoaderInfo.addEventListener(Event.COMPLETE, swfLoaded2);
ldr2.load(new URLRequest("ZombieSource.swf"));
function swfLoaded2(event:Event):void
{
zombieExt = MovieClip(ldr2.contentLoaderInfo.content);
ldr2.contentLoaderInfo.removeEventListener(Event.COMPLETE, swfLoaded2);
//zombieExt.addEventListener(Event.ENTER_FRAME, moveZombie)
zombieExt.addEventListener(Event.ENTER_FRAME,rotate2);
function rotate2 (event:Event)
{
var the2X:int = playerExt.x - zombieExt.x;
var the2Y:int = (playerExt.y - zombieExt.y) * 1;
var angle = Math.atan(the2Y/the2X)/(Math.PI/180);
if (the2X<0) {
angle += 180;
}
if (the2X>=0 && the2Y<0) {
angle += 360;
}
//angletext.text = angle;
zombieExt.rotation = (angle*1) + 90;
}
playerExt.addEventListener(Event.ENTER_FRAME,spawn1);
function spawn1 (event:Event)
{
if(playerExt.y < 417)
{
var someNum:Number = Math.round(Math.random()*20);
if(someNum == 20)
{
addChild(zombieExt)
zombieExt.x = Math.round(Math.random()*100)
zombieExt.y = Math.round(Math.random()*100)
}
}
}
}
addChild() does not create new instances. It is used to add an already created instance to the display list. If you call addChild() multiple times on the same instance then you are just readding itself.
Also each instance is unique, you can not globally change the x position of an instance by changing another one of them. What you would do is as Henry suggests and add each new instance of a MovieClip into an array, then whenever you change something you can loop through the array and apply the changes to each instance.
You can not go var2:MovieClip = new var1 either since var1 is an instance and not a class.
Here's a different method of receiving loaded MovieClips, which i use when i need many copies of the item.
in the swf you are loading, give the target movieclip a linkage name in the library, for this example i will use "foo"
private var loadedSwfClass:Class
private var newZombie:MovieClip;
private var zombieArray:Array = new Array();
function swfLoaded2(event:Event):void
{
loadedSwfClass = event.target.applicationDomain.getDefinition("foo");
for(var n:int = 0; n<100; n++){
newZombie = new loadedSwfClass()
zombieArray.push(newZombie);
addChild(newZombie);
}
}
as per this tutorial
http://darylteo.com/blog/2007/11/16/abstracting-assets-from-actionscript-in-as30-asset-libraries/
although the comments say that
var dClip:MovieClip = this;
var new_mc = new dClip.constructor();
this.addChild(new_mc);
will also work.
It sounds like you might be accessing the same instance some how in your code. It would be helpful to see your code to figure this one out.
If I wanted to load in one swf files and add a MovieClip multiple times I would place it in the library of that SWF file. And then instantiate it and store it into an object pool or a hash or some list.
// after the library were finished loading
var list:Array = [];
for(var i:int=0; i<10; i++) {
var myCreation:MySpecialThing = new MySpecialThing();
addChild(myCreation);
list.push(myCreation);
}
where my library would contain a linkage to the class MySpecialThing.
Calling addChild(var1) multiple times on the same parent doesn't have any effect (unless you have added another child to the same parent in between, in which case it will change the child index and bring var1 to the top). If you call it on different parents, it will just change the parent of var1, doesn't duplicate. Call addChild(new MovieClassName()) at random times instead to add new copies of it. Use an array as suggested here to access them later.
Wow, thanks there henry, just using an array did exactly what I needed, and made things alot simpler.
when you load in using a loader you only get 1 instance, however you can do some funky reflection to determine what class type the given loader.content is, and then instantiate them using that. For Example:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loader_completeHandler);
loader.load(new URLRequest("ZombieSource.swf"));
var classType:Class;
function loader_completeHandler(evt:Event):void
{
var loadInfo:LoaderInfo = (evt.target as LoaderInfo);
var loadedInstance:DisplayObject = loadInfo.content;
// getQualifiedClassName() is a top-level function, like trace()
var nameStr:String = getQualifiedClassName(loadedInstance);
if( loadInfo.applicationDomain.hasDefinition(nameStr) )
{
classType = loadInfo.applicationDomain.getDefinition(nameStr) as Class;
init();
}
else
{
//could not extract the class
}
}
function init():void
{
// to make a new instance of the ZombieMovie object, you create it
// directly from the classType variable
var i:int = 0;
while(i < 10)
{
var newZombie:DisplayObject = new classType();
// your code here
newZombie.x = stage.stageWidth * Math.random();
newZombie.x = stage.stageHeight * Math.random();
i++;
}
}
Any problems let me know, hope this helps.