Workshop Chapter 6. Working with Odd-Shaped Clickable Areas


Workshop Chapter 6. Working with Odd-Shaped Clickable Areas

    Flash buttons are the easiest way to trap mouse clicks. Buttons are simple and powerful. It's easy to include Over-and Down-states and even to define an odd shape that's clickable in the Hot-state. Placing a script on a button opens up a variety of mouse events to which you can respond.

    For all their greatness, buttons are fixed in their feature set: As soon as you want more, they become limited. In addition, the fact that Movie Clips can now trap mouse events (once reserved for buttons) means that you can avoid buttons completely. Movie Clips give you access to clip events such as mouseDown, mouseUp, and mouseMove. The only catch is that clips respond to all mouse events regardless of where the event occurs; for example, the mouseDown event will be received regardless of where you click. However, if we use clips in conjunction with the Movie Clip object method hitTest(), we can determine whether the user has clicked on a particular clip. You'll see that the alternative (to use a mouse event, such as onPress, on a clip) is not always ideal because it makes the clip start acting like a button including making the hand cursor appear.

    This workshop chapter involves a situation in which there are many individual shapes representing the outlines of various European countries that you want to become clickable (see Figure W6.1). Instead of making each shape a separate button, we'll use Movie Clips. And instead of naming each instance and assigning a unique callback function to each one, we'll just write a single for loop that checks whether the user is rolling over the shape.

    Figure W6.1. You might need odd-shaped clickable areas when letting a user select individual countries.


    In addition to exploring the hitTest() method, this workshop chapter exposes you to a technique which stores references to all the clips in the movie in an array.

    Here are the steps for creating and managing odd-shaped clickable areas:

    1. Draw a large, filled square. Set Stroke to at least 5 and use the Pencil tool with Pencil Mode set to Ink to draw squiggly lines (of a different color than the fill) to divide the square into sections (see Figure W6.2).

      Figure W6.2. We'll make odd shapes by drawing thick lines through a square.


    2. Select everything you've drawn and choose Modify, Shape, Convert Lines to Fills. Then carefully select and delete the lines you just drew. The result is that you are chopping up the original square into sections. Pretend that these are the borders of countries.

    3. Individually select each section and convert it to a Movie Clip (F8). This is the one time when you can use the default "Symbol 1" name. In this example, we won't even name the instances!


      Normally, you'd probably prefer to address clips by name when setting their _alpha or using the hitTest() method. We're not going to name the clips. Instead, we'll write a script that loops through all the "objects" on the Stage (that is, all the instances); for the objects that happen to be Movie Clips, we'll place a reference of that clip in an array. Consider if we did name the clips country1, country2, and so on. If we simply populated an array with a statement such as allClips=[_root.country1,_root.country2], we could later address any individual clip using an expression such as allClips[1]._alpha=50. Even though we're not going to name our clip instances, we can still populate the array with references to those clips. Specifically, we'll step through the built-in array for _root, which contains all objects on the Stage.

    4. We're about to write the script that checks whether the mouse is on top of any clips. Instead of writing a script for each instance, we'll just put the code in an onMouseMove callback function for the main timeline. This way we can monitor all the different clips in one place. First, we can store all the Movie Clip instances in an array in the main timeline. In the frame of the main timeline (where all the code will go), type the following code:

      allClips= new Array();    for (i in _root){     if (_root[i] instanceof MovieClip){       allClips.push(_root[i]);    }  }

      The variable allClips is an array. The for in loop checks each object in the _root array and, if it's an instanceof the MovieClip class, it pushes an object reference onto the end of the allClips array. (We discussed the instanceof operator in Chapter 13, "Homemade Objects.") Notice that not only is _root an address of a clip (the main movie's timeline), it also happens to contain an associative array with references to all the clips. Here, we're addressing each clip in _root not by an index in an array, but rather by the name of the sub-object in an object. That is, myArray[0] pulls something out of the array in index 0, but myObject[myProperty] pulls myProperty out of a generic object (even though it looks nearly the same). The fact that it's weird is not an issue because once the allClips array is populated, we step through it using the same for in technique used to create the array. You can learn about for loops in Chapter 5, "Programming Structures," and about how generic objects (AKA associative arrays) compare to arrays in Chapter 11, "Arrays."

    5. Now that the allClips array is populated, we can write script that runs every time the mouse moves. Below the initialization script you wrote in step 4, type the following code:

      _root.onMouseMove=function(){   for(i in allClips){     allClips[i]._alpha=30;    }  }

      If you test this, you should see all the clips dim down to 30 percent alpha as soon as you move the mouse. Incidentally, you could change the for in loop to a regular for using the following code in the second line above:

    6. Obviously, we want only one clip to dim out: The one with the mouse on it. The hitTest() method can help us figure out which clip that is. The form for hitTest() is targetClip.hitTest(x,y,flag), where targetClip is the clip you're checking, x and y are the coordinates of the point in question, and flag must be either 0 (when you only want to check whether the coordinates are within the square boundary of the clip) or 1 (when you want to check whether the coordinates are on top of the clip's outline just its shape, as we're doing in this case). Finally, this method returns either true or false depending on whether the coordinates are within the clip's shape or bounds. To apply this logic to our function, we can put an if statement inside the for in loop with the condition allClips[i].hitTest(_xmouse,_ymouse,1). If that condition is true,we can set _alpha to 30. Just for fun (and because we're so close to finishing), try the following replacement for the onMouseMove callback and see whether you can figure out the minor bug:

      _root.onMouseMove=function(){   for(i in allClips){     if(allClips[i].hitTest(_root._xmouse,_root._ymouse,1)){       allClips[i]._alpha=30;      }    }    updateAfterEvent();  }

      If you test this movie, you'll see that it works alright. However, once a clip's _alpha is set to 30, it never comes back up to 100. You can add an else clause to the if statement to solve the problem:

      _root.onMouseMove=function(){   for(i in allClips){     if(allClips[i].hitTest(_root._xmouse,_root._ymouse,1)){       allClips[i]._alpha=30;      }else{       allClips[i]._alpha=100;    }    updateAfterEvent();  }

      By the way, because allClips[i] addresses a clip, you can set any other property or use any other method with the form allClips[i].property or allClips[i].method(), respectively.

      This script works perfectly. However, it can be streamlined in several ways. Although it's not a problem, notice that we're addressing the current clip by using allClips[i] in three places. It's actually more efficient to first store the reference in a local variable and then refer to that local variable several times. We can just add the line var thisClip=allClips[i] as the first line inside the for loop, and then use thisClip in the three places where we currently use allClips[i]. Although this refinement is arguably useless, I wanted to point out that you can store references to clips in variables just as allClips already includes several references. Check out this finished alternative:

      _root.onMouseMove=function(){   for(i in allClips){     var thisClip=allClips[i]      if(thisClip.hitTest(_root._xmouse,_root._ymouse,1)){       thisClip._alpha=30;      }else{       thisClip._alpha=100;      }    }    updateAfterEvent();  }
    7. Finally, we'll add a feature that determines which clip, if any, the user has clicked when the mouse button is pressed. Add this script below the rest of your scripts:

      _root.onMouseDown=function(){   for(i in allClips){     var thisClip=allClips[i]      if(thisClip.hitTest(_root._xmouse,_root._ymouse,1)){       trace ("You clicked "+ thisClip._name);        thisClip._xScale-=10;        thisClip._yScale-=10;        return;      }    }  }

    Although this workshop (and this function in particular) might have little practical value the way it is, we can take away several lessons. In the onMouseDown callback, notice that because we're doing something only to the clip that matches (but nothing to all the other clips), we can use return to stop looping as soon as something is found. In the onMouseMove callback, we had to set every clip's _alpha to either 100 or 30. As it turns out, even when you have to loop through the entire array, the process goes quite quickly. After you learn more about getTimer() in Workshop Chapter 10, "Creating Timers," and Workshop Chapter 12, "Developing Time-Based Animations," you'll be able to test the impact that different script approaches have on performance. For example, I suggested that you could place the value of allClips[i] into the variable thisClip rather than constantly referring to allClips[i]. This change would certainly be worth the effort if performance noticeably increased because of the change. If you make 200 copies of the clips on the Stage, you'll start to see a slowdown. In my tests, for example, I found that when I had 200 instances on the Stage, the loop took as long as 100 milliseconds (1/10 of one second) to complete. By trying different scripts, I reduced the loop to half that time. The point is that looping through an array is fast, but trying different scripts can often make it faster.

    Remember that one of the main lessons in this workshop chapter was to store references to clip instances in an array variable so that you can use the variable when you want to address instances. We also got to use a for loop as well as hitTest().


    ActionScripting in MacromediaR FlashT MX
    ActionScripting in MacromediaR FlashT MX
    ISBN: N/A
    EAN: N/A
    Year: 2001
    Pages: 41 © 2008-2017.
    If you may any questions please contact us: