Wednesday, 29 April 2009

splice returns an array!

This one caught me out for a long time today.

I have an array of Objects which I want to jumble up a bit. AS3 doesn't come with a randomise (or randomize in the US!) function so I Googled around a bit to find one. The following came from some site somewhere and is no doubt a very well intentioned piece of code.


function randomizeArray(array:Array):Array
{
var newArray:Array = new Array();
while(array.length > 0)
{
newArray.push(array.splice(Math.floor(Math.random()*array.length), 1));
}
return newArray;
}


So, you can kind of see what it's doing right. Take an array. While your way through it removing one item at a time by choosing a random item using the length of the array. Each time the length of the array shortens by one. You add each item to a new array and once your old array is empty return your newly random array. Sweet!

Only splice returns an array! Splice returns an array!! Do you hear?

So your newly randomised array isn't full of your objects it's full of lots of little arrays each containing one item which is your object. This is not fine. You try and do anything with those items (inside their arrays) and you can't. Cos they're inside arrays.

Which would be fine if you realised they were inside arrays... but I didn't.

Took me ages to work it out, because when you trace the blasted things out the way Flash traces single item arrays makes it look like it's the object itself. You don't see that it's inside an array.

*sigh*

Wednesday, 11 February 2009

localToGlobal

Right so you've got an hierarchy of nested movieclips (or sprites) and you're rotating each separately to get a kind chain effect like the different moving parts of an arm or a leg. At the end of your leg you've got a foot (and maybe even toes) and there you are with your leg flailing around like a dervish on heat! Perfect.

Now you need to workout the x and y coordinates of one of those toes, so you can... errr... make lasers fire out of it whenever the user sneezes, right? (am I right?).

Anyway, if you want those coordinates you can't just say toe.x and toe.y that's not going to work, because those coordinates are local to those toes, or at least local to the foot movieclip you've attached them to. So what do you do? Well, you might find yourself iterating back through the hierarchy of movieclips finding out the x and y and adding them up and stuff. Phew! It's hard work, but do-able. But wait, then you've got to degotiate all that rotation. Gah! What was that stuff I read about coordinate rotation? Oh god please don't send me back to the dark place!!!

Luckily you're saved all this by using the helpful function localToGlobal. It takes a Point which is local to your object and translates it to a Point which is relative the global object (let's call that the Stage).

You use it like this:


var p:Point = new Point(toe.x,toe.y);
var np:Point = toe.parent.localToGlobal(p);

What I'm doing there is finding out the actual global position of the toe. So I create a new Point and pass in the x and y coordinates of toe. Then I can find out where actually it is on the stage by passing that Point into the function localToGlobal.

Remember the key point here is that it's the toe's parent (the foot?) which has to call the function because of course the x and y coordinates of toe are relative to the foot aren't they.

Monday, 1 September 2008

The Stage is not the Document Class (or vice versa)

Just a quick one but this caught me out today. If you want to make the stage clickable from your Document Class you might include the following in your Main function.

public function Main()
{
addEventListener(MouseEvent.CLICK,handleClick);
}
To make any mouse click that happens anywhere on the stage trigger the handleClick function. However that's not going to work because although it's the top level class your Document Class isn't the same as your stage.

In order to get mouse clicks to register from an empty stage you'll need to add the event listener to the stage itself. Like so:
public function Main()
{
stage.addEventListener(MouseEvent.CLICK,handleClick);
}
I know this looks straightforward but it had me scratching my head for a while today. The stage is not the Document Class even though at that level you addChild to the Document Class to add it to the stage. I should find something technical regarding this to explain but I can't right now.

Thursday, 3 April 2008

Javascript, History, Resizing - Part 2

As mentioned. I wanted to create a Flash site which acted like a normal HTML website.

Two things I found are a problem when it comes to this. Resizing & History.

(I dealt with Resizing in Part 1)

History = the Back button. Flash tends to miss it as a rule, sending users back to the last HTML page they were viewing. Generally you'd get around this by making the interactions in your Flash site intuitive enough that the user is never stuck with that "I wanna hit Back!" instinct, but sometimes that's not the aim.

So we need to get the browser to record when a different "page" (i.e. set of content) is being viewed within the Flash site. Obviously it's important at this stage to define, at least to yourself, what a different "page" is but that's an interaction design issue and far too fuzzy to go into here.

All we need to care about it that this is a "new page", when the user clicks the Back button they'll want to go back to what they saw before. How do we achieve that?

Well it turns out that ExternalInterface is our friend here again although the solution this time is a little more complicated than before. Basically the HTML page hosting your swf needs to also include an iframe. Let's call that iframe "history" and make it so tiny that's it's invisible.

<iframe id="history" name="history" src="" border="0" height="0" width="0"></iframe>
What we do is load a new page in there each time we want to register a 'new page'in Flash. The browser thinks that it's loaded a new page (well it has really) so hitting the back button simply reloads the previous page in the iframe. If we can get the iframe page to trigger the previous content in Flash we're laughing!

So how do we do that? There are 3 things you must do.
  1. Write a JavaScript function to pass the info into Flash.
  2. Write a ActionScript function to accept that info a display the right content.
  3. Write a JavaScript function (on each page you'll load into your iframe) that will call the function in 1. and pass info into the function in 2. telling Flash what content to display.
1. JavaScript
<script>
function setPage(newPage)
{
var m = document.getElementById('something');
m.sendToActionScript(newPage);
}
</script>
OK things to note: 'something' is the id given to the object/embed tags used to put the flash content into your HTML. I covered the use of SWFObject for this before. sendToActionScript is an arbitary name for the function, you'll see below that it could have been anything, but that name seems to make sense. newPage is the info we're sending to ActionScript about what content to display.

2. ActionScript
if (ExternalInterface.available)
{
ExternalInterface.addCallback('sendToActionScript', fromJavascript);
}
This is where ActionScript picks up the fact that there's a function in JavaScript on your page called sendToActionScript and agrees to take some action when it is called. In fact it agrees to call another function called fromJavascript and pass whatever you've passed into the first, into the second.
function fromJavascript(contentName)
{
// do something with contentName
}
There's no point me telling you about fromJavascript. This will be an ActionScript function which will display the content. The only thing really to mention is that you're going to be passing something new into it each time the iframe is changed so you'll have to find some way to trigger the displaying of different content based on what's passed in. It could simply be a switch statement or it could be much much more complicated. The decision is yours!

3. JavaScript
<script>
parent.setPage('thispage');
</script>
Right here's the magic. In each of the HTML pages you're going to drag into your iframe you'll need to have the above JavaScript. So each time the iframe loads another bit of content something similar will be run.

The parent bit is talking about the HTML page containing the iframe. The HTML page which also contains your swf. The setPage bit is obviously the JavaScript function we've mentioned before, which is also sat in that page. Finally the 'thispage' is the info we want to pass to Flash about what should be displaying. The thing which will eventually fall into the lap of your fromJavascript function in your flash movie.

So how do we get all this up and running? Well the point with this is now that you have this system for dealing with user going 'Back' the way to make it work is to use it as the system for user going 'Forward'. So each time you load a new bit of content in your Flash (or at least a new bit you feel is worthy of being treated as a 'page') you should trigger it via the iframe.

In order to do this you'll actually need 2 more bits of code.
  1. A JavaScript function to load a new page into the iframe
  2. A bit of ActionScript to trigger this function and pass in the page to load.
4. JavaScript
<script>
function changeiFrame(url)
{
var h = document.getElementById('history');
h.src = url;
}
</script>
This simply takes a URL and loads it into the iframe. Simple as that.

5. ActionScript
ExternalInterface.call('changeiFrame',url);
This is what calls the above JavaScript from within Flash.

And there you have it. When you want to change your content you call JavaScript, which changes the iframe, which calls some JavaScript, which calls some ActionScript which triggers the Flash content to change in some way.

The problem with all this is obviously making the stuff which is passed around here unique enough to mean that the exact right bit of Flash is displayed each time. You'll need to take account of all the parameters which make your Flash content display a certain way and make sure that all that is able to be triggered through this mechanism. That's not always easy, but it's certainly far from impossible.

BTW much of the above is taken from or at least inspired by the following list of websites. I hope I've added enough (making it specifically AS3, and explaining it in my own inimitable style) to warrant rehashing it here. As usual this is mainly for my own records and on the off-chance that someone else might find it useful. "Shoulders of giants" I think is the phrase, 'twas always thus.

penner
stenhouse
hendershot

Javascript, History, Resizing - Part 1

OK here's a good one. I wanted to create a Flash site which acted like a normal HTML website. Why I would want to do this? You can only guess.

Two things I found are a problem when it comes to this. Resizing & History.

(I'll deal with History in Part 2)

Resizing means getting the browser to react to changes in the size of your Flash as it does to different HTML pages within a normal website (i.e. including and resizing scrollbars at the side of the page). Seeing as your swf is going to be the only thing on this page in order to achieve this we have to resize the swf within the HTML.

I'm happy to tell you that all this can be achieved via the wonder which is ExternalInterface. This is the AS3 class which can act as a bridge between Actionscript in your swf and JavaScript in the HTML page. It's also a cinch to use!

context: I use SWFObject.js as a way to embed my swf's into HTML pages. I don't know whether it's peculiar in this respect, but it means in the HTML the <embed> or <object> tags used always have a nice id="something" which identifies the swf within the page. That id="something" is going to be important.

I've embedded my swf in the HTML with the following code:

<script>
var so = new SWFObject("Something.swf", "something", "100%", "700", "8", "#FFFFFF");
so.addParam("scale", "noscale");
so.addParam("salign", "t");
so.write("flashcontent");
</script>
I won't explain how to use SWFObject here. There's lots of help out on the web. But the important bits to note are:
  • the width is 100% but the height is 700 - this height will be changing, so set it to something sensible to start with.
  • the id is going to be "something" that's the second parameter for SWFObject.
  • I've set scale to "noscale" this will keep the actual flash content looking normal.
  • I've set salign to "t" which will align the Flash movie top-centre.
(If you wanted to align your movie differently then top-left should be "tl" and top-right should be "tr". I'd have thought you'd want to keep it top-something.)

For resizing you'll need the following JavaScript in your HTML.
<script>
function resizePage(height)
{
var d = document.getElementById('something');
d.height = height;
}
</script>
This takes a height value and resizes your Flash movie to fit (using that id 'something' which we passed to SWFObject).

You'll then need the following ActionScript in your Flash.
function resizePage()
{
ExternalInterface.call('resizePage',this.height);
}
This must be placed at the root of your Flash movie, and called from there. I tend to use an Event or something to trigger it from elsewhere. The point here is that ExternalInterface is able to call the JavaScript function resizePage and pass in the value this.height. Because it's at the root of the swf this refers to the entire movie and therefore will expand and contract depending on the stuff you've added to it.

Again I won't go into how and why you might call this function because this post is long enough already but still, I've found the above works pretty well, so I'm happy.

Thursday, 14 February 2008

a possibly undefined method

It's undefined... possibly.

Maybe it's undefined or maybe it's actually defined. We don't know.

Given that calling a method is quite a common thing to do how is this better than "there's something wrong" in terms of helping you fix it.

1061: Call to a possibly undefined method X through
a reference with static type Y

Well, possibly a bit. Possibly.

Anyway here's where I went wrong.

Variable at the top of my (so I can access it everywhere) like so - it's using a class I wrote for a different project (do you hear that? reusable code. I like totally rool!).

var thing:ClassA;


I've not assigned anything to it yet I'll be doing that in the class somewhere. In fact here I am doing it now.

thing = new ClassA(var1, var2);


So all is well and good until I realise that despite the joys and karmic benefits of reusable code ClassA just isn't going to cut it with this new project. It's got the juice but I'm going to be needing biscuits with that juice if you know what I mean (tell me if I'm loosing you here).

So a quick new class.

class ClassB extends ClassA
{
/* new stuff */
}


and a change to the code

thing = new ClassB(var1, var2);


and I'm away using the new stuff and making good things happen. But no... I get that error above there. I'm scratching my head because as far as I know the whole thing with this class based OOP based programming shizzle is to do just that kind of thing.

And the method is there. It's fucking there. Look I extended ClassA and WROTE A NEW METHOD! Here it is! Aaaaarrgh!

Anyway turns out (as usual) I'd missed something. You see at the top of the class there when I defined the variable (but didn't assign anything to it) the variable was still being given a type of ClassA.

var thing:ClassA;


and it seems that because of that, even though I assigned an object of type ClassB to it later on. It never really got over being a ClassA. In fact it as so attached to it's status as a ClassA varaible it kind of ignored everything about ClassB that it didn't already know.

Hence when I come to call on one of my new methods, they "officially" didn't exist.

Possibly.

Tuesday, 12 February 2008

addChild and removeChild and references.

I've just found a tricky little thing which I didn't know before which might be of help.

I've declared a variable "img" which will be available from where ever I need it in my Document Class. It's declared as a Loader and when I come to load the image into it I use the following code.

img = new Loader(firstUrlRequest);
addChild(img);


Then later on I want to replace the image. My idea was that I'd simply re-use the "img" variable and load a new image into it like so:

img = new Loader(secondUrlRequest);
addChild(img);


This seems to work fine and new images keep on appearing as I re-use this code at various times within the interface.

Until I want there to be no image there. Then there is a problem. I use:

removeChild(img);


and sure enough the last image I added is gone, but wait, what's that? The image before that is still there. How the heck am I accidentally adding that back in?

The problem goes back to the fact that the img variable isn't the same as the Loader I've added to the stage. It's just a reference to that Loader. So what happens when I make the img variable reference a different Loader (i.e. the new one I've added)?

What happens is I loose my control over the original image. It's still sat there on the stage but now I have no way to connect to it (except perhaps by some iteration through the stage's children and possibly using removeChildAt).

So the moral of the story: if you're reusing references to load different objects onto the stage (to replace each other) make sure you removeChild the referenced object first, that way you'll have nothing floating around in the background to surprise you later.

Friday, 7 December 2007

TextField and buttonMode

Right so you want to create a click-able bit of text. So you create a TextField and stick your text in there and then you'll be wanting to addEventListener on that. Excellent, you've made your text click-able.

You might be thinking "that's great, but why doesn't my mouse pointer change when I hover over the text". Well, that's because you need to define the buttonMode to be true, to tell Flash that this thing you're creating is going to be a bit like a button.

However, TextFields can't have a buttonMode. Only descendants of Sprite can have buttonMode. So what you need to do is create a Sprite and add the TextField to that before adding the Sprite to the stage.

"But my mouse pointer still doesn't change when I hover over the text!?!"

Well that's because the Sprite you've created is empty apart from TextField and the TextField is still dealing with the MouseEvent.MOUSE_OVER by itself. We need to tell Flash that anything contained in the Sprite should be ignored in terms of what the mouse is doing.

That's where mouseChildren comes in.

sprite.mouseChildren = false;

... and your mouse pointer should now change when it's over the text.