TweetFollow Us on Twitter

July 01 QT Toolkit

Volume Number: 17 (2001)
Issue Number: 07
Column Tag: QuickTime Toolkit

Back In Action

by Tim Monroe

Working with Wired Actions

Introduction

The previous four QuickTime Toolkit articles have focused largely on two topics: how to create and manipulate sprites, and how to attach dynamic, interactive behaviors to sprites. Interactivity is one of QuickTime's greatest assets and perhaps its most significant advantage over competing media architectures. And, no doubt, wired sprites are the cornerstone of QuickTime interactivity. As we've seen, we can do some pretty nifty things with just a few event and action atoms placed in the right locations in a sprite track.

In this article, we're going to take one more look at wired actions. I want to consider a few of the event types and actions recently introduced in QuickTime 5, and I especially want to consider how to use wired actions with other kinds of tracks. So far, all the actions we've encoountered have been triggered by some event involving a sprite (for example, clicking on the sprite) or a sprite track (for example, loading a sprite track key frame). Beginning in QuickTime 4, however, it's also possible to attach wired actions to QuickTime VR nodes and hot spots, to text in text tracks, and to Flash content. Let's take a moment to see what we can do here.

QuickTime VR supports two general kinds of wired actions: (1) actions that apply to a particular node, and (2) actions that apply to a particular hot spot in a node. The node-specific actions can be attached to idle events (so that an action can be triggered periodically) or to frame-loaded events (so that an action can be triggered when the node is first entered). We could, for instance, attach an action to a frame-loaded event to set the initial pan and tilt angles that are displayed when the user enters the node. Similarly, we could attach an action to a frame-loaded event to start a sound track playing when the user enters the node. In addition, we could use idle event actions to dynamically adjust the balance and volume of that sound as the user changes the pan angle of the movie; this provides an easy way to make a sound appear to emanate from a specific location in a node (which is sometimes called directional sound). Or, we could adjust the pan and tilt angles of some other QuickTime VR movie, so that panning and tilting one panorama caused similar panning and tilting in a second.

We can also attach wired actions to hot spots inside of QuickTime VR nodes. We can configure these actions to be triggered by a variety of mouse events, including moving the mouse over a hot spot, moving the mouse out of a hot spot, and clicking the mouse within a hot spot. Once again, the actions that are triggered by these events can be any actions supported by the QuickTime wired actions architecture, such as starting and stopping sounds, enabling and disabling tracks, changing pan and tilt angles, and so forth.

QuickTime allows us to attach wired actions to a text track, giving us wired text. Consider, for instance, the movie shown in Figure 1. This movie contains a text track that's wired to displays the amount of memory currently available in the application's heap. (The text track is updated every two seconds by an idle event action.) We can use this movie to track the memory usage of any QuickTime-savvy application.


Figure 1: A text track displaying the application's free memory

Or consider the movie shown in Figure 2, which also contains a single text track. This time the text track is wired so that clicking on the word "Apple" launches the user's default web browser and loads the URL ; similarly, clicking on the word "CNN" loads the URL .


Figure 2: A text track with hypertext links

Finally, we can attach wired actions to items in a Flash track. Flash is a file format developed by Macromedia, Inc. for displaying vector-based graphics and animations. It's especially popular for web-based content delivery, since it combines compact data size (and hence speedier downloads) with quick rendering on the client, advanced graphics manipulations, and support for mouse-based interactivity with the graphics objects. In QuickTime 4, Apple added a Flash media handler that provides support for Flash graphics, animations, and interactivity inside of QuickTime movies. In addition, QuickTime 4 added the ability to attach wired actions to elements in a Flash track, thereby supplementing the native Flash interactivity. So, for instance, we could use Flash graphic elements (instead of sprites) to provide a user-interface for a QuickTime movie.

Unfortunately, we don't yet know enough about the structure of QuickTime VR movies or Flash tracks to know where to insert event atoms. As a result, we'll have to postpone our hands-on work with wiring VR or Flash content. But we do know how text tracks are put together (see "Word Is Out" in MacTech, November 2000), so here we'll learn how to add some interactivity to our text tracks.

Our sample application this month is called QTWiredActions. The Test menu of QTWiredActions is shown in Figure 3. The first menu item creates the wired text movie shown in Figure 2. The second menu item creates the memory display movie shown in Figure 1.


Figure 3: The Test menu of QTWiredActions

The next two menu items create two more sprite movies, which use wired actions and operands to make sprites react "physically" with the movie rectangle and with one another.

Text Actions

Let's begin by investigating a few of the ways in which we can use wired actions in text tracks. In general, we attach an event atom to a text media sample by concatenating the atom container that holds the event atom onto the media sample data. (We'll see exactly how this works in a moment.) The event atom can specify any of the kinds of events that we've considered so far, including mouse-click events, mouse-over events, frame-loaded events, and idle events. These wired text samples work exactly as you'd expect; for instance, if a text sample contains a mouse-click event atom, then the associated actions are triggered when the user clicks the mouse button while the cursor is within the bounds of the text track.

Sometimes, however, we'd like to handle user events that involve only part of the text displayed in a text sample. Consider once again the text movie shown in Figure 2. In this case, we want to trigger actions only when the user clicks on specific portions of the text data (namely, the strings "Apple" and "CNN"). To handle this kind of wiring, QuickTime supports a class of atoms called hypertext atoms. The goal here is to provide the sort of hyperlinks that you typically find in web browsers or other HTML-based applications. By clicking on a hypertext link in a text track, the user can launch the default web browser and navigate to a specific URL, by triggering the kActionGoToURL action. But in fact hypertext atoms can contain any kinds of wired action, not just kActionGoToURL actions. So the user's actions can just as easily (for instance) trigger a jump forward or backward in the movie, or cause some changes in an external movie.

As you can see in Figure 2, QuickTime automatically sets the color of hypertext links to the familiar browser-default blue and underlines the hypertext. Both of these provide visual cues that the user can interact with that segment of text. QuickTime also provides wired actions that allow us to change the color of a segment of hypertext, for example when the user rolls the cursor over that segment or after the user has clicked on the link. Figure 4 shows the same hypertext movie (this time on Windows) with the cursor resting over the first hyperlink.


Figure 4: A selected hypertext link

Adding Actions to a Text Sample

A text media sample consists of a 16-bit length word followed by the text of that sample. Optionally, one or more atoms of additional data (called text atom extensions) may follow the text in the sample. The length word specifies the total number of bytes in the text (not including the 2 bytes occupied by the length field itself or the length of any of the optional text atom extensions). The text atom extensions are organized as "classic" atom structures: a 32-bit length field, followed by a 32-bit type field, followed by the data in the atom. Here, the length field specifies the total length of the atom (that is, 8 plus the number of bytes in the data). All the data in a text extension atom must be in big-endian format.

Whether we use a hypertext atom or any of the standard types of event atoms, we add the event atom to a text media sample in the same way, by attaching the atom container that contains the event and action atoms to the text media sample as a text atom extension. Figure 5 shows the general structure of a text media sample that contains a text action.


Figure 5: The structure of a wired text media sample

If we're given a text media sample and an event atom container, it's quite easy to wire that atom container to the text. We simply need to determine the lengths of the media sample and the atom container, enlarge the text media sample to hold all of its existing data and the atom container, plus the 8-byte atom header shown in Figure 5. We set up the atom header as appropriate and then copy it and the atom container into the enlarged media sample.

The size of a text atom extension for wired actions, of course, is the size of the atom container plus the size of the atom header (8 bytes). The type of the text atom extension can be one of three values. If the atom container holds a frame-loaded event atom, then the type of the text atom extension should be set to kQTEventFrameLoaded. If the atom container holds any other standard event atom (including an idle event atom), then the type of the text atom extension should be set to kQTEventType. Finally, if the atom container holds a hypertext event atom, then the type of the text atom extension should be set to ‘htxt'. Currently there is no symbolic constant for this value defined in any of the public QuickTime header files, so I've included this definition in the file QTWiredActions.h:

>
#define kHyperTextTextAtomType      FOUR_CHAR_CODE(‘htxt')

Listing 1 shows our definition of the function QTWired_AddActionsToSample, which adds an event atom container to an existing text media sample. Notice that the function parameters include the text media sample, the event atom container, and the type of atom extension. This type should be one of the three recognized types for text atom extensions that specify wired actions.

Listing 1: Adding wired actions to a text media sample

static OSErr QTWired_AddActionsToSample (Handle theSample, 
               QTAtomContainer theActions, SInt32 theAtomExtType)
{
   Ptr          myPtr = NULL;
   long         myHandleLength;
   long         myContainerLength;
   long         myNewLength;
   OSErr        myErr = noErr;

   if ((theSample == NULL) || (theActions == NULL))
      return(paramErr);

   myHandleLength    = GetHandleSize(theSample);
   myContainerLength = GetHandleSize((Handle)theActions);

   myNewLength = (long)(sizeof(long) + sizeof(OSType) + 
                           myContainerLength);

   SetHandleSize(theSample, (myHandleLength + myNewLength));
   myErr = MemError();
   if (myErr != noErr)
      goto bail;

   HLock(theSample);

   // get a pointer to the beginning of the new block of space added to the sample
   // by the previous call to SetHandleSize; we need to format that space as a text
   // atom extension
   myPtr = *theSample + myHandleLength;

   // set the length of the text atom extension
   *(long *)myPtr = EndianS32_NtoB((long)(sizeof(long) + 
                              sizeof(OSType) + myContainerLength));
   myPtr += (sizeof(long));

   // set the type of the text atom extension
   *(OSType *)myPtr = EndianS32_NtoB(theAtomExtType);
   myPtr += (sizeof(OSType));
   
   // set the data of the text atom extension;
   // we assume that this data is already in big-endian format
   HLock((Handle)theActions);
   BlockMove(*theActions, myPtr, myContainerLength);

   HUnlock((Handle)theActions);
   HUnlock(theSample);

bail:
   return(myErr);
}

For more information on working with atoms, see "The Atomic Café" in MacTech, September 2000.

Creating Text Actions

As we've just seen, we add a wired action event handler to a text sample by adding a text atom extension of type kQTEventFrameLoaded or kQTEventType to the end of the sample; the data in the text atom extension is the atom container that holds the information about the wired actions triggered by some event. So, our task boils down to this: find the data in a text sample, create an atom container holding information about the desired actions, and then append a text extension atom whose data is that atom container to the end of the text sample data. Then we replace the previous text sample with the new one in the text track.

Listing 2 shows the code that handles the "Make Memory Display Movie..." menu item.

Listing 2: Handling the "Make Memory Display Movie..." menu item

case IDM_MAKE_MEM_DISPLAY_MOVIE:
   myPrompt = QTUtils_ConvertCToPascalString(kMDSavePrompt);
   myName = QTUtils_ConvertCToPascalString(kMDSaveFileName);

   // elicit a file from the user to save the new movie into
   QTFrame_PutFile(myPrompt, myName, &myFile, &myIsSelected, 
                              &myIsReplacing);

   // create a text movie that displays the amount of memory free in the application heap
   if (myIsSelected) {
      myErr = QTWired_CreateMemoryDisplayMovie(&myFile);
      if (myErr == noErr)
         QTWired_AddActionsToTextMovie(&myFile, theMenuItem);
   }

   myIsHandled = true;
   break;

We don't need to consider the function QTWired_CreateMemoryDisplayMovie in detail, since it's really just a variant of the QTText_AddTextTrack function we considered in an earlier article. In the present case, we set the initial text in the text track to the single character "0", and we set the font to 48-point Times-Roman (using the constant kFontIDTimes).

The interesting work in Listing 2 is done by the QTWired_AddActionsToTextMovie function, which creates the appropriate wired action atom container and then calls QTWired_AddActionsToSample (defined in Listing 1) to append that container to the first (and only) text media sample in the file created by QTWired_CreateMemoryDisplayMovie. Listing 3 shows our definition of QTWired_AddActionsToTextMovie.

Listing 3: Adding wired actions to a text movie

OSErr QTWired_AddActionsToTextMovie 
                           (FSSpec *theFSSpec, UInt16 theMenuItem)
{
   short                              myResID = 0;
   short                              myResRefNum = -1;
   Movie                              myMovie = NULL;
   Track                              myTrack = NULL;
   Media                              myMedia = NULL;
   TimeValue                          myTrackOffset;
   TimeValue                          myMediaTime;
   TimeValue                          mySampleDuration;
   TimeValue                          mySelectionDuration;
   TimeValue                          myNewMediaTime;
   TextDescriptionHandle              myTextDesc = NULL;
   Handle                             mySample = NULL;
   short                              mySampleFlags;
   Fixed                              myTrackEditRate;
   QTAtomContainer                    myActions = NULL;
   OSErr                              myErr = noErr;

   // open the movie file for reading and writing
   myErr = OpenMovieFile(theFSSpec, &myResRefNum, fsRdWrPerm);
   if (myErr != noErr)
      goto bail;

   myErr = NewMovieFromFile(&myMovie, myResRefNum, &myResID, 
                        NULL, newMovieActive, NULL);
   if (myErr != noErr)
      goto bail;
      
   // find first text track in the movie
   myTrack = GetMovieIndTrackType(myMovie, kIndexOne, 
                        TextMediaType, movieTrackMediaType);
   if (myTrack == NULL)
      goto bail;
   
   // get first media sample in the text track
   myMedia = GetTrackMedia(myTrack);
   if (myMedia == NULL)
      goto bail;
   
   myTrackOffset = GetTrackOffset(myTrack);
   myMediaTime = TrackTimeToMediaTime(myTrackOffset, myTrack);

   // allocate some storage to hold the sample description for the text track
   myTextDesc = (TextDescriptionHandle)NewHandle(4);
   if (myTextDesc == NULL)
      goto bail;

   mySample = NewHandle(0);
   if (mySample == NULL)
      goto bail;

   myErr = GetMediaSample(myMedia, mySample, 0, NULL, 
                        myMediaTime, NULL, &mySampleDuration, 
                        (SampleDescriptionHandle)myTextDesc, NULL, 
                        1, NULL, &mySampleFlags);
   if (myErr != noErr)
      goto bail;

   // add actions to the first media sample
   switch (theMenuItem) {
      case IDM_MAKE_HYPERTEXT_MOVIE:
         // create an action container for hypertext actions
         myErr = 
            QTWired_CreateHyperTextActionContainer(&myActions);
         if (myErr != noErr)
            goto bail;

         // add hypertext actions to sample
         myErr = QTWired_AddActionsToSample(mySample, myActions, 
                        kHyperTextTextAtomType);
         if (myErr != noErr)
            goto bail;
         break;

      case IDM_MAKE_MEM_DISPLAY_MOVIE:
         // create an action container for wired actions
         myErr = 
         QTWired_CreateMemoryDisplayActionContainer(&myActions);
         if (myErr != noErr)
            goto bail;

         // add actions to sample
         myErr = QTWired_AddActionsToSample(mySample, myActions, 
                        kQTEventType);
         if (myErr != noErr)
            goto bail;
         break;

      default:
         myErr = paramErr;
         goto bail;
   }

   // replace sample in media
   myTrackEditRate = GetTrackEditRate(myTrack, myTrackOffset);
   if (GetMoviesError() != noErr)
      goto bail;

   GetTrackNextInterestingTime(myTrack, nextTimeMediaSample | 
                        nextTimeEdgeOK, myTrackOffset, fixed1, NULL, 
                        &mySelectionDuration);
   if (GetMoviesError() != noErr)
      goto bail;

   myErr = DeleteTrackSegment(myTrack, myTrackOffset, 
                        mySelectionDuration);
   if (myErr != noErr)
      goto bail;

   myErr = BeginMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;

   myErr = AddMediaSample(   myMedia,
                     mySample,
                     0,
                     GetHandleSize(mySample),
                     mySampleDuration,
                     (SampleDescriptionHandle)myTextDesc, 
                     1,
                     mySampleFlags,
                     &myNewMediaTime);
   if (myErr != noErr)
      goto bail;
   
   myErr = EndMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;
   
   // add the media to the track
   myErr = InsertMediaIntoTrack(myTrack, myTrackOffset, 
                        myNewMediaTime, mySelectionDuration, 
                        myTrackEditRate);
   if (myErr != noErr)
      goto bail;

   // update the movie resource
   myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, 
                        NULL);
   if (myErr != noErr)
      goto bail;
   
   // close the movie file
   myErr = CloseMovieFile(myResRefNum);

bail:
   if (myActions != NULL)
      (void)QTDisposeAtomContainer(myActions);

   if (mySample != NULL)
      DisposeHandle(mySample);

   if (myTextDesc != NULL)
      DisposeHandle((Handle)myTextDesc);

   if (myMovie != NULL)
      DisposeMovie(myMovie);

   return(myErr);
}

Everything in Listing 3 is standard Movie Toolbox stuff that we've seen before, except of course for the application functions QTWired_CreateHyperTextActionContainer (which we'll discuss in the next section) and QTWired_CreateMemoryDisplayActionContainer. This latter function is in fact relatively simple. It builds an atom container that uses the kOperandFreeMemory operand to retrieve the amount of memory that is currently free in the application heap, as well as the kActionTextTrackPasteText wired action to paste the value returned by that operand into the text track. The kActionTextTrackPasteText action, which is new in QuickTime 5, takes three parameters: the text to paste and the beginning and ending locations in the text track to paste the text. Each time we paste a new value into the text track of the memory-display movie, we want to replace all of the existing text, so we'll set the second and third parameters to 0 and 0xffff respectively.

There's one undocumented "gotcha" here: in order for kActionTextTrackPasteText to have any effect, we need to explicitly enable text editing on the target text track. We can do this by executing the kActionTextTrackSetEditable wired action (also new to QuickTime 5). This action takes one parameter, which specifies the kind of editing we want to enable. QuickTime currently supports three settings for the editing state of a text track, which the documentation lists like this:

#define kKeyEntryDisabled                0
#define kKeyEntryDirect                  1
#define kKeyEntryScript                  2

(Once again, however, these values are not defined in any public header file, so I've added those lines to the file QTWiredActions.h.) The default value kKeyEntryDisabled indicates that no editing is allowed on the text track; key events are passed to the movie controller (which will probably ignore most of them) and any editing actions sent to the track are ignored. The value kKeyEntryDirect indicates that direct editing is to be enabled; this means that key events are passed directly to the text track (for instance, key-down events result in the characters being inserted into the text track). The value kKeyEntryScript indicates that script editing is to be enabled; this means that editing actions sent to the track are interpreted by the text media handler and executed.

For present purposes, we want to enable script editing on the text track. So we'll execute the code in Listing 4 inside of QTWired_CreateMemoryDisplayActionContainer.

Listing 4: Enabling editing on a text track

myErr = QTNewAtomContainer(theActions);
if (myErr != noErr)
   goto bail;

// add an event atom that enables text editing
myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
               kParentAtomIsContainer, kQTEventIdle, 
               kActionTextTrackSetEditable, &myActionAtom);
if (myErr != noErr)
   goto bail;

myEditState = EndianS16_NtoB(kKeyEntryScript);
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, 1, sizeof(myEditState), 
               &myEditState, NULL);

On every idle event sent to the text track, we set the track to be editable (just before we execute the kActionTextTrackPasteText action). In theory, we could enable editing just once in a frame-loaded event. But the idle event action is simpler to construct and also ensures that we can always paste the current memory value into the text track (since some other action may have disabled editing on that track).

The remainder of QTWired_CreateMemoryDisplayActionContainer is straightforward. It adds an idle event call to the kActionTextTrackPasteText action, using the result of the kOperandFreeMemory operand as the text to be pasted. QuickTime is smart enough to convert the floating-point value returned by kOperandFreeMemory into a string, as expected by kActionTextTrackPasteText. Listing 5 shows the complete definition.

Listing 5: Pasting the amount of free memory into a text track

static OSErr QTWired_CreateMemoryDisplayActionContainer 
                        (QTAtomContainer *theActions)
{
   QTAtom         myEventAtom = 0;
   QTAtom         myActionAtom = 0;
   QTAtom         myParamAtom = 0;
   short          myEditState;
   UInt32         myPos;
   QTAtom         myParameterAtom = 0;
   QTAtom         myExpressionAtom = 0;
   QTAtom         myOperandAtom = 0;
   OSErr          myErr = noErr;

   myErr = QTNewAtomContainer(theActions);
   if (myErr != noErr)
      goto bail;

   // add an event atom that enables text editing
   myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
                  kParentAtomIsContainer, kQTEventIdle, 
                  kActionTextTrackSetEditable, &myActionAtom);
   if (myErr != noErr)
      goto bail;

   myEditState = EndianS16_NtoB(kKeyEntryScript);
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
                  myActionAtom, 1, sizeof(myEditState), 
                  &myEditState, NULL);
   if (myErr != noErr)
      goto bail;

   // add an event atom that displays the amount of application memory currently free
   myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
                  kParentAtomIsContainer, kQTEventIdle, 
                  kActionTextTrackPasteText, &myActionAtom);
   if (myErr != noErr)
      goto bail;

   // first parameter: the text to be pasted
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
                  myActionAtom, 1, 0, NULL, &myParameterAtom);
   if (myErr != noErr)
      goto bail;

   myErr = WiredUtils_AddExpressionContainerAtomType(
                  *theActions, myParameterAtom, 
                  &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(*theActions, myExpressionAtom, 
               kOperandAtomType, 1, 1, 0, NULL, &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(*theActions, myOperandAtom, 
               kOperandFreeMemory, 1, 1, 0, NULL, NULL);
   if (myErr != noErr)
      goto bail;

   // second parameter: selection range begin: 0
   myPos = EndianU32_NtoB(0);
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, 2, sizeof(myPos), &myPos, NULL);
   if (myErr != noErr)
      goto bail;

   // third parameter: selection range end: 0xffff
   myPos = EndianU32_NtoB(0xffff);
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, 3, sizeof(myPos), &myPos, NULL);

bail:
   return(myErr);
}

On classic Macintosh systems (that is, Mac OS 8 and 9), the movie created by all this code will display the amount of free memory in the application heap (or essentially what you could determine by executing the Memory Manager function FreeMem). On Windows systems and on Mac OS X, where there are no application heaps in the classic-Mac sense, the movie will display less useful numbers. In fact, I'm not exactly sure what the numbers displayed on Windows and Mac OS X represent.

Creating Hypertext Actions

Let's consider now how to construct text movies that contain hypertext actions, like the one shown in Figures 2 and 4. The basic ideas are the same as with constructing wired text movies, except that now we add a text atom extension of type kHyperTextTextAtomType to the end of the text sample. Also, the atom container that's inside of the text atom extension should have the structure shown in Figure 6.


Figure 6: The structure of a hypertext text atom

As you can see, the root atom in a hypertext text atom is of type ‘wtxt'. This atom contains one child of type ‘htxt' for each hypertext link in the text sample. Let's call this child atom a hypertext item atom. A hypertext item atom specifies the information about a single hypertext link in a text sample. We need to specify the starting point and ending point in the sample text for the hypertext link, and we need to specify the event and action atoms associated with that segment of text. So a hypertext item atom needs to contain at least three children, of types ‘strt', ‘endĘ', and kQTEventType. In our source code, we'll use these constants:

#define kHyperTextTextAtomType            FOUR_CHAR_CODE(‘htxt')
#define kTextWiredObjectsAtomType         FOUR_CHAR_CODE(‘wtxt')
#define kHyperTextItemAtomType            FOUR_CHAR_CODE(‘htxt')
#define kRangeStart                       FOUR_CHAR_CODE(‘strt')
#define kRangeEnd                         FOUR_CHAR_CODE(‘end ‘)

There's no need to consider the function QTWired_CreateHyperTextActionContainer in detail. It simply builds the atom container shown in Figure 6, using hard-coded values for the parameter data of the kRangeStart and kRangeEnd atoms. Each of our hypertext item atoms contains three event atoms. The first event atom looks for mouse-clicks on the hypertext link and executes a kActionGoToURL action in response. The second and third event atoms watch for the cursor to enter and exit the hypertext link, changing the color of the text appropriately. Listing 6 shows the code for changing the hypertext color to purple when the mouse enters the first hyperlink. The kActionTextTrackSetHyperTextColor action takes two parameters, which are the index of the hypertext link whose color is to change and the desired new color.

Listing 6: Changing the color of a hypertext link

ModifierTrackGraphicsModeRecord      myGraphicsModeRecord;

myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
               myHyperTextAtom, kQTEventMouseEnter, 
               kActionTextTrackSetHyperTextColor, &myActionAtom);
if (myErr != noErr)
   goto bail;

myIndex = EndianS16_NtoB(1);
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, kIndexOne, sizeof(myIndex), 
               &myIndex, NULL);
if (myErr != noErr)
   goto bail;
   
myGraphicsModeRecord.graphicsMode = 
               EndianS32_NtoB(ditherCopy);
myGraphicsModeRecord.opColor.red = EndianS16_NtoB(0x9999);
myGraphicsModeRecord.opColor.green = EndianS16_NtoB(0x0000);
myGraphicsModeRecord.opColor.blue = EndianS16_NtoB(0xcccc);
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, kIndexTwo, 
               sizeof(myGraphicsModeRecord), 
               &myGraphicsModeRecord, NULL);

Take a look at the source file QTWiredActions.c for the complete definition of QTWired_CreateHyperTextActionContainer.

Key Events

QuickTime 5 introduced a new type of event, the key event (of type kQTEventKey), which is issued when keys on the keyboard are pressed. Consider, for instance, the movie shown in Figure 7, which displays the ASCII character code of whatever key the user presses. In the case shown here, the user has just pressed the "T" key.


Figure 7: A movie that displays ASCII character codes of pressed keys.

A key event can be used in event atoms just like any of the other event types we've encountered so far. What's interesting about key events is that we can determine not only that the user has pressed a key, but also which particular key was pressed. We do this by inspecting the event parameters of the key event, using the new operand kOperandEventParameter. This operand itself takes one parameter, which specifies the index of the event parameter we want to inspect. In the case of key events, we can inspect any one of 5 event parameters:

  • Parameter index 1 is the horizontal position of the cursor at the time the key event occurred, in coordinates relative to the text track rectangle.
  • Parameter index 2 is the vertical position of the cursor at the time the key event occurred, in coordinates relative to the text track rectangle.
  • Parameter index 3 encodes any modifier keys that are down when the key event occurs, using these constants from Events.h:
   enum {
      cmdKey                  = 1 << cmdKeyBit,      // 0x0100
      shiftKey                = 1 << shiftKeyBit,    // 0x0200
      alphaLock               = 1 << alphaLockBit,   // 0x0400
      optionKey               = 1 << optionKeyBit,   // 0x0800
      controlKey              = 1 << controlKeyBit   // 0x1000
   };

For instance, if the Control and Shift keys are both down when the key event occurs, then the third parameter would be 0x00001200, or 4608.

  • Parameter index 4 is the ASCII character code of the key pressed.
  • Parameter index 5 is the virtual key code (or scan code) of the key pressed. A virtual key code is a value that represents a specific physical key on a specific model of keyboard. As a result, these values are generally less useful than the ASCII character codes.

We can construct the ASCII-display movie in Figure 7 in exactly the same way we previously constructed the memory-display movie. Instead of issuing the kActionTextTrackPasteText action in response to idle events, we now do so in response to key events. And we get the text to be pasted not from the kOperandFreeMemory operand, but from the kOperandEventParameter operand. Listing 7 shows some of the code we could use to construct the ASCII-display movie. (This code could be substituted for part of Listing 5, for instance.)

Listing 7: Adding a key event

QTAtom     myEventParamAtom = 0;
short      myIndex;

// add an event atom that displays the ASCII code of the pressed key
myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
                  kParentAtomIsContainer, kQTEventKey, 
                  kActionTextTrackPasteText, &myActionAtom);
if (myErr != noErr)
   goto bail;

// first parameter: the text to be pasted
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
                  myActionAtom, 1, 0, NULL, &myParameterAtom);
if (myErr != noErr)
   goto bail;

myErr = WiredUtils_AddExpressionContainerAtomType(
                  *theActions, myParameterAtom, 
                  &myExpressionAtom);
if (myErr != noErr)
   goto bail;

myErr = QTInsertChild(*theActions, myExpressionAtom, 
                  kOperandAtomType, 1, 1, 0, NULL, 
                  &myOperandAtom);
if (myErr != noErr)
   goto bail;

myErr = QTInsertChild(*theActions, myOperandAtom, 
                  kOperandEventParameter, 1, 1, 0, NULL, 
                  &myEventParamAtom);
if (myErr != noErr)
   goto bail;

myIndex = EndianS16_NtoB(4);
myErr = QTInsertChild(*theActions, myEventParamAtom, 
                  kActionParameter, 1, 1, sizeof(myIndex), 
                  &myIndex, NULL);

Notice that we add a parameter atom (myEventParamAtom) to the operand atom, to specify the desired index for the event parameter we want to retrieve. In this case, we specify the index 4, to obtain the ASCII character code of the key pressed.

It's also possible to use the kOperandEventParameter operand to retrieve event parameters for mouse-related events, such as mouse-enter and mouse-click events. With mouse events, there are only three available parameters, which are the same as the first three parameters of key events: the horizontal and vertical mouse positions, and the modifier keys currently down. In an earlier article, you may recall, we used the kOperandMouseLocalHLoc and kOperandMouseLocalVLoc operands to get these mouse positions. In QuickTime 5 and later, we can instead use kOperandEventParameter, if we so desire.

Bouncing Sprites

In several of the previous articles, we've seen that we can move a sprite around inside a sprite track by altering its matrix, either directly (by setting the sprite's matrix property) or indirectly (by performing wired actions such as kActionSpriteTranslate). In this section and the next, we'll consider a couple of ways to build on this capability. First, we'll see how to make a sprite bounce around inside of the sprite track's enclosing rectangle; later we'll see how to figure out when two sprites collide with one another. Both of these are ways to give a sprite "physical" properties, so that it appears to react with things around it.

Figure 8 shows (about as well as any static image can, I guess) a sprite bouncing off the track's enclosing rectangle. The sprite is first moving down and to the right; when its bottom edge touches the bottom of the track rectangle, the sprite bounces up and continues to the right. When any other edge of the sprite touches a side of the track rectangle, the sprite does the appropriate thing by moving back away from the side it touched but otherwise continuing in the same direction. (In the event that two of its edges touch two sides of the track rectangle at the same time, the sprite would reverse both its horizontal and vertical directions; this would happen when the sprite moves cleanly into a corner of the track rectangle.)


Figure 8: A sprite bouncing off the movie edge

Moving the Sprite

The first thing we need to do is get the sprite moving. To do this, we can simply change the horizontal and vertical positions of the sprite during idle events. Since we're going to be changing the direction of movement when the sprite collides with the track rectangle, we'll maintain two sprite track variables whose values are the number of pixels in the horizontal and vertical direction that the sprite is to be offset during the next idle event. We'll use these variables IDs:

#define kXMoveVarID                     2000
#define kYMoveVarID                     2100

We can set the initial horizontal and vertical offsets by adding a couple of frame-loaded event actions to the sprite, using our utility WiredUtils_AddSpriteTrackSetVariableAction (defined in "Wired" in MacTech, May 2001):

#define kIdleOffset                     2

myErr = WiredUtils_AddSpriteTrackSetVariableAction(
               mySample, kParentAtomIsContainer, 
               kQTEventFrameLoaded, kXMoveVarID, kIdleOffset, 
               0, NULL, 0);

myErr = WiredUtils_AddSpriteTrackSetVariableAction(
               mySample, kParentAtomIsContainer, 
               kQTEventFrameLoaded, kYMoveVarID, kIdleOffset, 
               0, NULL, 0);

During idle events, we'll move the sprite horizontally by the current value of the kXMoveVarID variable and vertically by the current value of the kYMoveVarID variable. The speed of the moving sprite is determined both by the values of these variables and by the frequency with which we receive idle events (which, you'll recall, is determined by the sprite track's kSpriteTrackPropertyQTIdleEventsFrequency property). For the bouncing sprite movie, we'll tell the sprite media handler to send us an idle event every tick, and (as you can see) we'll offset the sprite in each direction by two pixels during every idle event. We add actions to move the sprite during an idle event like this:

myErr = WiredUtils_AddQTEventAtom(mySpriteData, 
            kParentAtomIsContainer, kQTEventIdle, &myEventAtom);

myErr = QTWired_AddTranslateByVariablesAction(mySpriteData, 
            myEventAtom, kXMoveVarID, kYMoveVarID, NULL);

The QTWired_AddTranslateByVariablesAction function adds to the specified event atom (here, myEventAtom) an action atom that translates the sprite relatively, taking the horizontal and vertical offsets from the sprite track variables whose IDs are specified by the third and fourth parameters (here, kXMoveVarID and kYMoveVarID). Listing 8 shows our definition of the QTWired_AddTranslateByVariablesAction function.

Listing 8: Translating a sprite using variable values

static OSErr QTWired_AddTranslateByVariablesAction (
               QTAtomContainer theSprite, QTAtom theParentAtom, 
               QTAtomID theXVariableID, QTAtomID theYVariableID, 
               QTAtom *theActionAtom)
{
   QTAtom            myActionAtom = 0;
   QTAtom            myExpressionAtom = 0;
   QTAtom            myParamAtom = 0;
   QTAtom            myOperatorAtom = 0;
   QTAtom            myOperandAtom = 0;
   QTAtom            myOperandTypeAtom = 0;
   QTAtomID          myVariableID;
   Boolean           myBoolean;
   OSErr             myErr = paramErr;

   if (theSprite == NULL)
      goto bail;

   // add a translate action atom to the specified parent atom
   myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, 
            kActionSpriteTranslate, &myActionAtom);
   if (myErr != noErr)
      goto bail;

   // first parameter: get value of variable theXVariableID
   myErr = WiredUtils_AddActionParameterAtom(theSprite, 
            myActionAtom, kFirstParam, 0, NULL, &myParamAtom);
   if (myErr != noErr)
      goto bail;

   myErr = WiredUtils_AddExpressionContainerAtomType
            (theSprite, myParamAtom, &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myExpressionAtom, 
            kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperandAtom, 
            kOperandSpriteTrackVariable, 1, 1, 0, NULL, 
            &myOperandTypeAtom);
   if (myErr != noErr)
      goto bail;

   myVariableID = EndianU32_NtoB(theXVariableID);
   myErr = QTInsertChild(theSprite, myOperandTypeAtom, 
            kActionParameter, 1, 1, sizeof(myVariableID), 
            &myVariableID, NULL);
   if (myErr != noErr)
      goto bail;

   // second parameter: get value of variable theYVariableID
   myErr = WiredUtils_AddActionParameterAtom(theSprite, 
            myActionAtom, kSecondParam, 0, NULL, &myParamAtom);
   if (myErr != noErr)
      goto bail;

   myErr = WiredUtils_AddExpressionContainerAtomType
            (theSprite, myParamAtom, &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myExpressionAtom, 
            kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperandAtom, 
            kOperandSpriteTrackVariable, 1, 1, 0, NULL, 
            &myOperandTypeAtom);
   if (myErr != noErr)
      goto bail;

   myVariableID = EndianU32_NtoB(theYVariableID);
   myErr = QTInsertChild(theSprite, myOperandTypeAtom, 
            kActionParameter, 1, 1, sizeof(myVariableID), 
            &myVariableID, NULL);
   if (myErr != noErr)
      goto bail;

   // third parameter: false (for relative translation)
   myBoolean = false;
   myErr = WiredUtils_AddActionParameterAtom(theSprite, 
            myActionAtom, kThirdParam, sizeof(myBoolean), 
            &myBoolean, NULL);

bail:
   if (theActionAtom != NULL)
      *theActionAtom = myActionAtom;

   return(myErr);
}

Here we add an action of type kActionSpriteTranslate, which requires three parameters: the desired horizontal translation, the desired vertical translation, and a Boolean that indicates whether the translation is absolute or relative. For the first and second parameters, we add expression container atoms that retrieve the value of the variable with the specified ID. (For more information about expression container atoms, see "Wired" in MacTech, May 2001.)

Detecting Track Rectangle Collisions

So far, so good. But if this were all the wiring we attached to the sprite, we wouldn't get quite the behavior we're looking for. When the movie was first opened, the sprite would begin moving down to the right and would continue moving in that direction forever. We need to add some wiring to figure out when an edge of the sprite hits an edge of the track rectangle and then adjust the direction of movement accordingly. In a nutshell, we want to add some logic that says:

  • If the left edge of the sprite is less than the left edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the horizontal direction of travel.
  • If the right edge of the sprite is greater than the right edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the horizontal direction of travel.
  • If the top edge of the sprite is less than the top edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the vertical direction of travel.
  • If the bottom edge of the sprite is greater than the bottom edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the vertical direction of travel.

It's worth pointing out that a more elegant design would use two "if-else" statements here instead of four "if" statements, since the opposite edges of our sprite cannot both be outside the track bounds at the same time. This refinement would, however, require a more complicated wiring. I'll leave that as an exercise for the persnickety reader.

We know the dimensions of the sprite track (since we created it using the constants kIconSpriteTrackHeight and kIconSpriteTrackWidth). So all we really have to learn now is how to figure out the position of the sprite's edges. The sprite media handler supports these four operands, which give us the information we need:

enum {
   kOperandSpriteBoundsLeft         = 3072,
   kOperandSpriteBoundsTop          = 3073,
   kOperandSpriteBoundsRight        = 3074,
   kOperandSpriteBoundsBottom       = 3075
};

The operand kOperandSpriteBoundsLeft, for instance, returns the left side of the sprite's bounding box (the rectangle that encloses the sprite), in the local coordinate system of the sprite track.

In our code, we'll add the side-bounce logic to the sprite by making four calls to a QTWiredActions function QTWired_AddSideBounceToSprite, like this:

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsLeft, 1, kOperatorLessThan, 
         kXMoveVarID);

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsRight, kIconSpriteTrackWidth, 
         kOperatorGreaterThan, kXMoveVarID);

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsTop, 1, kOperatorLessThan, 
         kYMoveVarID);

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsBottom, kIconSpriteTrackHeight, 
         kOperatorGreaterThan, kYMoveVarID);

The QTWired_AddSideBounceToSprite function is really quite simple, but (as we've grown to expect when building wired actions) a tad lengthy. Since we've built a few wired event handlers already, let's just survey the main points. First of all, we want to install an idle event atom by using our utility function WiredUtils_AddQTEventAndActionAtoms. The action should be a kActionCase action, since we want to ask whether a side of the sprite lies outside the track rectangle. As we saw in the previous article, a kActionCase action has a single parameter, whose data is an atom of type kConditionalAtomType. This conditional atom, in turn, has two children, an expression container atom and an action list atom.

The expression container atom needs to test whether the specified side of the sprite lies outside the sprite track rectangle. As you can see, when we call QTWired_AddSideBounceToSprite, we pass in the operand that we need to use to select the side, along with the track limit and the test to perform (for instance, kOperatorLessThan). We build the expression container atom data like this:

WiredUtils_AddOperatorAtom(theSprite, myExpressionAtom, 
                  theTest, &myOperatorAtom);

// first operand: the specified side of the sprite
QTInsertChild(theSprite, myOperatorAtom, kOperandAtomType, 1, 
                  1, 0, NULL, &myOperandAtom);

QTInsertChild(theSprite, myOperandAtom, theSide, 1, 1, 0, 
                  NULL, NULL);

// second operand: the specified limit
WiredUtils_AddOperandAtom(theSprite, myOperatorAtom, 
                  kOperandConstant, 2, NULL, theLimit);

The contents of the action list atom are pretty straightforward. Remember that we need to perform two actions: (1) translate the sprite back to the edge of the track rectangle and (2) reverse the direction of travel in the horizontal or vertical dimension. We've already seen how to construct a translate action, so we don't need to repeat that here. We can change the direction of travel simply by negating the value of the variable whose ID is passed to QTWired_AddSideBounceToSprite. We'll do that using another function, QTWired_AddNegateVariableAction, defined in Listing 9.

Listing 9: Negating a sprite track variable

static OSErr QTWired_AddNegateVariableAction (
            QTAtomContainer theSprite, QTAtom theParentAtom, 
            QTAtomID theVariableID)
{
   QTAtom            myActionAtom = 0;
   QTAtom            myExpressionAtom = 0;
   QTAtom            myParamAtom = 0;
   QTAtom            myOperatorAtom = 0;
   QTAtom            myOperandAtom = 0;
   QTAtom            myOperandTypeAtom = 0;
   QTAtomID          myVariableID;
   OSErr             myErr = paramErr;

   if ((theSprite == NULL) || (theParentAtom == 0))
      goto bail;

   myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, 
                  kActionSpriteTrackSetVariable, &myActionAtom);
   if (myErr != noErr)
      goto bail;
   
   // add parameters to the set variable action: variable ID (QTAtomID) and value (float)
   myVariableID = EndianU32_NtoB(theVariableID);
   myErr = QTInsertChild(theSprite, myActionAtom, 
                  kActionParameter, 0, (short)kFirstParam, 
                  sizeof(myVariableID), &myVariableID, NULL);
   if (myErr != noErr)
      goto bail;
   
   myErr = QTInsertChild(theSprite, myActionAtom, 
                  kActionParameter, 0, (short)kSecondParam, 0, 
                  NULL, &myParamAtom);
   if (myErr != noErr)
      goto bail;
   
   myErr = WiredUtils_AddExpressionContainerAtomType(
                  theSprite, myParamAtom, &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myExpressionAtom, 
                  kOperatorAtomType, kOperatorNegate, 1, 0, NULL, 
                  &myOperatorAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperatorAtom, 
                  kOperandAtomType, 0, 1, 0, NULL, 
                  &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperandAtom, 
                  kOperandSpriteTrackVariable, 1, 1, 0, NULL, 
                  &myOperandTypeAtom);
   if (myErr != noErr)
      goto bail;

   myVariableID = EndianU32_NtoB(theVariableID);
   myErr = QTInsertChild(theSprite, myOperandTypeAtom, 
                  kActionParameter, 1, 1, sizeof(myVariableID), 
                  &myVariableID, NULL);

bail:
   return(myErr);
}

There's nothing too exciting here; this just says: set the value of the variable whose ID is theVariableID to the result of negating the value of the variable whose ID is theVariableID.

Colliding Sprites

So now we've got sprites bouncing off the walls. Let's get them to bounce off one another as well. In specific, let's create a movie with two sprites, both of which have the bouncing logic that we developed in the previous section. Then, let's add some wiring to make them recoil from one another when part of one sprite touches part of the other. Figure 9 shows two sprites about to collide: the sprite with the new QuickTime logo has just bounced off the bottom and is moving up to the right; the other sprite is moving down to the left. When the sprites collide, we'll reverse the horizontal direction of travel of each sprite unless they are traveling in the same horizontal direction when they collide. And ditto for the vertical directions of travel. This logic gives the sprites a nice feel as they bounce around.


Figure 9: Two sprites colliding

But how do we know when two sprites collide? Well, we know the coordinates of each of the corners of a sprite (using the operands we encountered in the previous section). So a reasonable strategy might be to ask, for each corner of the sprite, whether it lies on top of the other sprite. QuickTime 5 introduced a very useful operand for this, the kOperandSpriteTrackSpriteIDAtPoint operand. This operand takes two parameters, which are the horizontal and vertical coordinates of a point; it returns the ID of the topmost sprite at that point, if any.

In the case where we have just two sprites that can collide, we need to attach the collision logic to only one of the sprites. That sprite (let's call it the collider) can check, on every idle event, whether any of its four corners has come into contact with any part of the other sprite. That is, we'll call kOperandSpriteTrackSpriteIDAtPoint four times, each time passing one of the four corners of the collider. QTWiredActions uses the QTWired_AddCollisionLogicToSprite function (defined in Listing 10) to attach the collision logic to the collider.

Listing 10: Wiring a sprite for collisions

OSErr QTWired_AddCollisionLogicToSprite 
                                    (QTAtomContainer theSprite)
{
   OSErr            myErr = noErr;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsLeft, kOperandSpriteBoundsTop);
   if (myErr != noErr)
      goto bail;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsRight, kOperandSpriteBoundsTop);
   if (myErr != noErr)
      goto bail;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsLeft, kOperandSpriteBoundsBottom);
   if (myErr != noErr)
      goto bail;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsRight, kOperandSpriteBoundsBottom);

bail:
   return(myErr);
}

We won't bother to dissect QTWired_AddCornerCollisionLogicToSprite, as it mostly covers routine ground while setting a new record for length (almost 300 lines of code and comments). The key step is using kOperandSpriteTrackSpriteIDAtPoint, as described above. You should know that kOperandSpriteTrackSpriteIDAtPoint does its work by hit-testing for sprites in the sprite track (probably using SpriteMediaHitTestAllSprites, which we used in an earlier article). So a sprite ID will be returned as the operand's value only if some non-transparent part of a sprite is situated at the specified point.

Our collision logic as developed so far is pretty good, but alas not perfect. Recall that we are testing each of the four corners of the collider sprite's bounding rectangle, to see if it lies on top of some sprite. If the collider's sprite image at that corner is non-transparent, then the call to kOperandSpriteTrackSpriteIDAtPoint will always return a non-zero value (since the sprite hit test will find that corner, even if the other sprite is nowhere near the collider). We can solve this problem by ensuring that the other sprite has a lower layer property than the collider (so that we get its ID if the other sprite and the collider overlap at the tested point) and by ignoring the collider's ID if kOperandSpriteTrackSpriteIDAtPoint returns it to us.

Another problem arises if the collider's sprite image is transparent at a corner whose coordinates are passed to kOperandSpriteTrackSpriteIDAtPoint. In this case, it's possible for that corner to lie on top of a non-transparent part of the other sprite and hence trigger a hit, even though the sprites do not appear to be touching. Figure 10 illustrates this possibility. In this case, the collider is the top-right sprite, and its lower-left corner is transparent. kOperandSpriteTrackSpriteIDAtPoint will return the ID of the other sprite, because the collider's lower left corner does in fact lie on top of a non-transparent pixel of the other sprite.


Figure 10: Testing a transparent corner of the collider sprite

I don't see an easy way to solve this problem, but all in all it's a fairly minor one. For most purposes, I suspect, it's good enough to have the sprites recoil from one another using the simple wiring we've developed here. More complex collision algorithms are of course left as exercises for the reader.

Conclusion

In this article, we've seen how to add wiring to text tracks and we've investigated a few of the text-related actions and operands added in QuickTime 5. We've also touched briefly on the new key event (of type kQTEventKey) and seen for the first time the technique for retrieving event parameters. Finally, we've added some wiring to allow sprites to collide with the track rectangle and with one another.

With all of this, we've reached the end of our "mini-series" on sprites and wired actions. We'll revisit them in the future, however, especially when we learn how to work with QuickTime VR movies and Flash tracks. In the next article, though, we'll shift gears and move on to consider some new and exciting QuickTime capabilities.

Credits

Thanks are due to Bill Wright for clarifying some issues with text wiring.


Tim Monroe works in the QuickTime engineering team at Apple Computer, Inc. You can contact him at monroe@apple.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! In this role, Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Apr 20, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.