Adobe, MAKE SOME NOISE

Quick post today. If you're making the jump from timeline coding to class based development (and I definitely encourage you to!) you'll undoubtedly run into this problem. It goes something like this:

  1. Designate a Document Class for your fla - let's call it ProjectMain
  2. Create the ProjectMain class and save it alongside your fla as ProjectMain.as
  3. Set a property of something on stage, let's say myCoolLogo.x = stage.stageWidth / 2;
  4. Compile to receive your TypeError: Error #1009

    Your code is otherwise fine - commenting out that line seems to fix everything. Furthermore the object is right there on stage in your fla – your constructor just refuses to acknowledge it!

    The problem goes back to AS2 prehistory and the top-down/bottom-up problem. If you don't remember that (admittedly it's a little obscure even if you did work in AS2 a lot), here's the gist: Flash is single-threaded, meaning it can only do one discrete task at a time, which includes running your constructor, and parsing everything on stage. In AS2 you could specify whether you wanted to run your code "top down" - meaning, 'run all the code in the top layer, then the next below and so on' or vice versa for "bottom up."

    At this point you should be wondering what this has to do with anything - well let's say you create a variable on layer 10 (top layer) and give it a value - and in layer 1 (bottom layer) you wan't to do something with that value - it's only going to work if you're running top-down. If you run that fla bottom-up you'll execute the layer 1 code first and the variable will be undefined until frame 2. The exact same thing is happening with your constructor in AS3.

    The constructor will always precede any timeline code or bytecode (including positioning things on stage, tweens, &c.) - so you need to be careful with your initialization. It's alright to create new objects and work with them, but you can't query anything that you put down on stage (the same goes for library-linked Classes) until at least one render cycle has passed and allowed all those objects to be generated. Luckily, AS3 has an event for that so it takes some of the pain out.

    Here's how:

    1. // ...
    2.  
    3. //--------------------------------------
    4. // CONSTRUCTOR
    5. //--------------------------------------
    6.  
    7. public function ProjectMain(){
    8. super();
    9. addEventListener(Event.ADDED_TO_STAGE, function(e:Event):void{
    10. e.target.removeEventListener(e.type, arguments.callee);
    11. myCoolLogo.x = stage.stageWidth >> 1;
    12. });
    13. }

    If you're curious about line 39 in there, it's a generic way to remove event listeners - there's a little more detail over on the code page. Also as an added bonus, all the code has been moved into a second function - and since the ActionScript JIT compiler doesn't optimize constructors, there's a good chance that this code will run faster than before.

    I've been messing around with audio lately and it reminded me of a pet peeve so inane that I couldn't help but post about it. Volume controls!

    In AS3, the volume attribute ranges from from 0 to 1. Graphically it's a pretty obvious choice to just link your fader up on a one-to-one relation - the math is straightforward and you can quickly verify that, as you scale up the volume, it gets louder, and when you scale down you approach silence. Easy.

    The problem comes in when you listen to that audio with your fleshy human-ears, which detect sound logarithmically instead of linearly. That means that the perceptual difference in lower volume is much more noticeable than in higher volumes.

    Here's a linear slider:

    Get Adobe Flash player

    1. var v:Number = fader.width / totalWidth;
    2. SoundMixer.soundTransform = new SoundTransform(v);

    Notice that all the real action happens in the first half of the slider - the first 20% is a huge pickup in sound while the last 20% is almost imperceptible. Because your ear is countering the amplification, it's up to you to counter-counteract the faulty hardware (e.g. your user's inferior hearing) and still deliver a good experience. To do that, you need to remap the amplification from a linear input-equals-output model, onto a curve that's roughly opposite to the users perception.

    Here's that same slider remapped to a simple quadratic gain:

    Get Adobe Flash player

    1. var v:Number = fader.width / totalWidth;
    2. SoundMixer.soundTransform = new SoundTransform(Math.pow(v, 2);

    This has a lot more punch to it - the loud end definitely has teeth now, and the quiet end gives you a much finer control. Since we're already working between zero and one, we can employe a simple x² formula without dealing with offsetting or scaling in the equation. To really fine-tune you could use any exponent you want - say 1½ for a half-and-half approach, or 3 to move all the weight to the right side of the slider.


    To sum up, here's a rough graph that I generated drew in photoshop to illustrate the principle. And if you're wondering, the music is Ode to the Bridge Builder from Kyle Gabler's fantastic World of Goo Soundtrack – I'm using it here without permission because it's all I have on my laptop right now – please go buy the game so I get sued less (also because you're a Flash dev and it's an inspiring example of great gameplay)!