Articles Archive
Articles Search
Director Wiki

Dynamic Sprite Creation/Destruction in Windows For Shockwave

May 21, 2004
by Jim Andrews

A previous DOUG article outlines the functionality of WFS 3.0, so I won't restate it here, except to say that it outlines the windowing and menuing multi-sprite functionality of WFS 3. WFS 4 ( supports, among other things, optional dynamic sprite creation/destruction (DSCD), which is the ability to create/destroy sprites during run-time that were not placed in the Score during authoring. DSCD is not officially supported by Macromedia, despite the introduction in Director MX 2004 of the makeScriptedSprite and removeScriptedSprite commands.

WFS 4.8 works with Director 8.5, MX, and Director MX 2004; when doing DSCD, it checks which version of Director you're authoring with and uses makeScriptedSprite/removeScriptedSprite if you're using MX 2004 or later, and uses earlier methods involving the puppetSprite command otherwise. You can see this in the code of the wfsCreateSprite and wfsDestroyDynamicSprite handlers included in this article.

The fundamental reason why DSCD isn't yet supported by Macromedia is that, under the Director hood, the makeScriptedSprite and removeScriptedSprite commands rely on dynamic sprite creation/destruction techniques (involving the puppetSprite command) that Director developers have been using since Director 5, all the while officially unsupported by Macromedia.

To say that dynamic sprite creation/destruction is unsupported means that when the Director team tests a new version of Director, they do not test to see if DSCD works as it used to (although they do some testing of DSCD). Exhaustive testing would require a test suite that doubles the existing test suite. Lack of official support for DSCD means that in future versions of Director, DSCD, as it was done by developers in Director 5 through 8.5, could break. The puppetSprite command was never officially validated as supporting DSCD, but it works quite well with a few caveats we'll mention.

The introduction of the makeScriptedSprite and removeScriptedSprite commands suggests that the Director team intends to try to officially support DSCD at some point in the future, ie, they intend to migrate the implementation under the Director hood of makeScriptedSprite and removeScriptedSprite from the unsupported puppetSprite methods to a more solid implementation because, unlike the puppetSprite command, makeScriptedSprite and removeScriptedSprite are apparently meant to officially support DSCD eventually.

Partitioning the Score

When using DSCD, it is best to partition the Score into two vertical sections: the lower-numbered section consists of a contiguous block of sprite channels for use by static sprites (sprites not created by DSCD), and the higher-numbered contiguous block of sprite channels is for DSCD sprites.

The Dynamic Channel Manager (DCM)

We partition the Score in this way, via a Dynamic Channel Manager (DCM) because when you create a DSCD sprite, the channel in which it is created is occupied, whatever frame of the movie one is in, until the DSCD sprite is destroyed. You cannot specify the beginning frame or the end frame of a DSCD sprite. Conceptually, a DSCD sprite occupies frame 1 to the last frame of the movie while it exists.

It is not feasible, when creating DSCD sprites, to first check to see if a static sprite exists in that channel at any point in the movie (other than the current frame). It is a very bad idea to have a DSCD sprite in channel x and then move the playback head to a frame in which channel x also contains a static sprite. Hence the need to partition the Score into two vertical blocks.

So any project that involves DSCD should have a Dynamic Channel Manager (DCM).

The WFS DCM requests that you specify the (integer) last static sprite channel in the movie. This is the highest-numbered sprite channel that contains a static sprite anywhere in the Score, ie, all higher-numbered sprite channels must never contain a static sprite, and all higher-numbered sprite channels are used by the DCM for DSCD operations. The WFS documentation shows you how to specify the last static sprite channel. If you forget to specify this value, WFS will remind you.

You can change this value as you author as your Score becomes deeper with static sprites if you like. Normally what I do is set the total number of channels in the movie to 1000 and, until the project is finished, I set the last static sprite channel to something quite high, somewhere in 200-500 so I don't have to worry about it again until the project is complete and then I set it to its actual value so as to make use of as many channels as possible for DSCD.

The Dynamic Channel Manager API

Since DSCD sprites are invisible in the Score (but not on the Stage) during authoring run-time, the DCM must provide an API that lets you see what is going on in the block of dynamic sprite channels. WFS 4.5 provides the following public handlers into the DCM:

Fundamental DSCD Handlers

The three main handlers in the WFS DSCD system are wfsCreateSprite, wfsAttachBehavior, and wfsDestroyDynamicSprite. The rest of the WFS DSCD handlers build on these three handlers.

For instance, wfsCreateWindowManager, which creates a WFS window manager sprite, makes a call to wfsCreateSprite to create the sprite and makes a call to wfsAttachBehavior to attach the "3: Window Manager" behavior.

Script Writer API

Writing code using the three main handlers is not too hard. But to make your work easier, WFS comes with the Script Writer handlers. These are tools for use in authoring mode, and they write Lingo code for you. The generated code calls the three main handlers mentioned above to do things like create not just one sprite but a whole multi-sprite or a family of multi-sprites. Or an element of a multi-sprite.

The idea is that you create a multi-sprite or family of multi-sprites as you normally do via drag and drop of sprites into the Score/Stage and drag and drop of behaviors onto the sprites. Then you aim one of the Script Writer handlers at this static model (via a call from the Message Window), and the Script Writer outputs code to the Message Window which you copy and paste into your code. When it runs, the generated code creates a dynamic version of the static model you aimed it at.

The Script Writer API includes these handlers:

The Shockwave movie below shows code written by wfsWriteMultiSprite. You see the code written by wfsMultiSprite after you click "next" and then "create". Note that it consists mainly of calls to wfsCreateSprite and wfsAttachBehavior. Usually code is all under the hood, but this piece is all about showing code that generates a dynamic multi-sprite, a window in this case.

Dynamism API

So the Script Writer handlers can be great time-savers. But you have to know basically what sprites you want in the multi-sprite, ie, you have to be able to create a static model in author mode. Whereas, in some situations, you don't know until run-time what sprites are going to be in the window or menu or whatever.

Which is why WFS comes with not just the Script Writer handlers, but the Dynamism API so you can write your own code for just such situations. The API of the Dynamism script is quite extensive, so I won't list it all, but just the main handlers:


wfsCreateSprite is the fundamental sprite constructor. For instance, wfsCreateElement makes a call to wfsCreateSprite to create a sprite and also makes a call to wfsAttachBehavior to attach the Window/Menu Element behavior, which all elements of WFS multi-sprites must have attached to them. Handlers like wfsCreateElement reduce the amount of work for you rather than having to make the two calls.


Attaching behaviors to sprites at run-time is important to any DSCD system. wfsAttachBehavior is the main attacher. It attaches any old behavior to any old sprite. The others are for making attaching particular WFS behaviors less work.

So you can do things several ways. Instead of using any of the last eight of the attachers, you could instead use wfsAttachBehavior. Using wfsAttachBehavior is just a bit more work. Although using wfsWriteBehaviorCode, one of the Script Writers, makes using wfsAttachBehavior quite easy.


wfsDestroyDynamicSprite is the main destructor. The others (except wfsDestroyMemberCopy) make calls to wfsDestroyDynamicSprite in the same way that the constructors make calls to the fundamental constructor, wfsCreateSprite.

WFS does automatic garbage collection at the end of the movie of all dynamic sprites. So you don't have to use the destructors if you don't want, but if you're creating lots of dynamic sprites over time, you'll probably want to destroy some when they are no longer needed.

Now let's have a look at some coding with the Dynamism API. The piece below doesn't involve any multi-sprites. It's just about creating any old dynamic sprite, attaching any old behavior to any old sprite, and destroying any old dynamic sprite. I could have generated a lot of the code with the Script Writer routines, but there's some custom stuff in there that isn't generated by the Script Writer.

The next example shows how you can write your own code to create dynamic multi-sprites rather than having the Script Writer write it, if you need to. It also illustrates how to destroy multi-sprites.

Dynamic Sprite Creation Gotchas

The dirGames-L list contains an excellent thread (search on "dynamic sprite creation") on problems with dynamic sprites that I have gone through and summarized in the WFS documentation . These are not insurmountable difficulties if you are aware of them.

Under the Hood

I thought I'd share some of the fundamental code with you concerning DSCD. WfsCreateSprite is the code that creates an individual sprite; wfsDestroyDynamicSprite is the code used to destroy individual sprites, and wfsAttachBehavior is the code used to attach behaviors. These three handlers form a kind of basis for the other WFS constructors, attachers, and destructors. So that, if you look carefully at the below code, you have all you need to write your own DSCD system. I offer the code here because this code is a small but important part of the WFS DSCD system and a smaller part still of WFS as a whole. I should add that wfsCreateSprite, wfsDestroyDynamicSprite, and wfsAttachBehavior include code that I have picked up over the years from people experienced in DSCD; that is another reason to make the code public. Sébastien Portebois, Robert Tweed, Chuck Neal, Brennan Young, and others have helped me with this code.


Below is the wfsCreateSprite handler. It is well-commented, like all the WFS code. Code should, of course, be readable.

An example call to wfsCreateSprite is:

TheSpritenum = wfsCreateSprite(["oh","vah",36,100,50,50,10,20,1])


The return value (TheSpritenum) is the spritenum of the newly minted sprite. This lets you 'get your hands on it', ie, you can store this value in a list or whatever. It is wise to check this return value in your code to make sure it is non-zero before you manipulate the sprite. The operation can return 0 if there are no more dynamic channels available. Director supports an absolute maximum of 1000 simultaneously instantiates sprites.

on wfsCreateSprite (theMemberParams)
  --This handler creates a dynamic sprite. It doesn't attach any behaviors
  --to it. This handler is called in the code generated by wfsWriteMultiSprite
  --and wfsWriteElement in the 'Script Writer' script. These latter two
  --handlers are for generating code to generate copies of dynamic
  --RETURN VALUE:******************************
  --The handler returns the spritenum of the new sprite or returns 0 if the
  --member cannot be found in a cast or there are no available dynamic
  --channels to create the sprite in. The spritenum of the new sprite is
  --assigned by the Dynamic Channel Manager.
  --It takes theMemberParams as a parameter. This is a linear list like so:
  --[nameOrNum, castNameOrNum, ink, blend, width, height, locH,
  --locV, doNotCreateNewMember]
  --There are 9 list elements/parameters. doNotCreateNewMember is optional. If
  --you include any value at all for doNotCreateNewMember, then the created
  --sprite will use the same member specified by nameOrNum and castNameOrNum.
  --If you don't include a value for doNotCreateNewMember, a copy of the member
  --will be created and used. So, by default, then, a new member is created.
  nameOrNum= theMemberParams[1]
  castNameOrNum= theMemberParams[2]
  theInk= theMemberParams[3]
  theBlend = theMemberParams[4]
  theWidth= theMemberParams[5]
  theHeight= theMemberParams[6]
  theLocH= theMemberParams[7]
  theLocV= theMemberParams[8]
  --This line calls a handler associated with the Dynamic Channel Manager to
  --get an empty channel.
  if theChannel <> 0 then
    --Then the Dynamic Channel Manager has given us an available sprite
    if wfsMemberExists(member(nameOrNum, castNameOrNum)) then
      --Then the member we want to use is indeed in a cast.
      --Put the sprite in the control of Lingo dynamic creation/destruction
      if gWFSDirVersionIs10OrHigher then
        --Then we're dealing with Director version 10 (DMX 2004) or higher,
        --so we use makeScriptedSprite to create the sprite. makeScriptedSprite
        --was introduced in DMX 2004.
        if theMemberParams.Count =8 then
          --Then create a copy of the specified member and use it for the new
          memberNum=wfsCreateMemberCopy(member(nameOrNum, castNameOrNum))
          channel(theChannel).makeScriptedSprite(member(memberNum), point(theLocH,theLocV))
          --Else use the same member specified by
          --member(nameOrNum, castNameOrNum)
      channel(theChannel).makeScriptedSprite(member(nameOrNum, castNameOrNum), point(theLocH,theLocV))
        end if
        sprite(theChannel).ink = theInk
        sprite(theChannel).blend = theBlend
        return theChannel
        --Else we are dealing with a version of Director less than version 10
        --(DMX 2004) so we use the old way of creating dynamic sprites, not
        --makeScriptedSprite but puppetSprite.
        puppetSprite theChannel, true
        --Put the sprite in the control of Lingo dynamic creation/destruction
        if theMemberParams.Count =8 then
          --Then create a copy of the specified member and use it for the new
          memberNum=wfsCreateMemberCopy(member(nameOrNum, castNameOrNum))
          sprite(theChannel).member = member(memberNum)
          --Else use the same member specified by
          --member(nameOrNum, castNameOrNum)
          sprite(theChannel).member = member(nameOrNum, castNameOrNum)
        end if
        sprite(theChannel).ink = theInk
        sprite(theChannel).blend = theBlend
        sprite(theChannel).locH =theLocH
        sprite(theChannel).locV =theLocV
        return theChannel
      end if
      --Else the member is not in a Cast.
      --This informs the Dynamic Channel Manager that the channel is
      --available (we're not going to use it).
      return 0
    end if
    --Else we have no open channel to work with.
    return 0
  end if


Below we see the WFS code for wfsDestroyDynamicSprite . You specify the spritenum of the sprite you want destroyed and (optionally) whether you also want the member destroyed. Here is an example of a call to wfsDestroyDynamicSprite:

theReturnValue = wfsDestroyDynamicSprite(678, 1)

The above destroys sprite 678 and also destroys the member, if it is dynamic. If the member is not dynamic (ie, not created by WFS), it is not destroyed, regardless of the value of the second parameter. This is so that WFS does not destroy your cast members even if you slip up in configuring a destructor. WFS maintains a list of the dynamically created members and won't destroy a member not in that list.

on wfsDestroyDynamicSprite (elementSpriteNum, destroyDynamicMember)
  --This handler operates only on dynamic sprites. If the sprite isn't dynamic,
  --then all it does is make the sprite invisible. If the sprite is dynamic,
  --then the handler runs the endSprite handler, if elementSpriteNum
  --has one, and then destroys the sprite, wipes it out of existence.
  --If elementSpriteNum is not dynamic, this handler does nothing
  --except make the sprite invisible.
  --This handler is called in the stopMovie handler to do garbage
  --collection of dynamic sprites at the end of the movie. It is also
  --called in wfsDestroyMultiSprite. Use wfsDestroyDynamicSprite
  --to destroy individual dynamic sprites. They don't have to be
  --elements of multi-sprites for this handler to cleanly destroy them.
  --Also, the sprite channel is returned to the Dynamic Channel
  --Manager for later reuse.
  --RETURN VALUE******************************
  --Returns 1 if the sprite was successfully destroyed. Which it will
  --be if the sprite was created with wfsCreateSprite. Returns 0 if
  --the sprite is not dynamic (static sprites cannot be destroyed with
  --elementSpriteNum is the spriteNum of the element you want destroyed.
  --destryoyDynamicMember is a boolean that is optional. If you don't include
  --this parameter, then it is assumed to be FALSE, in which case the sprite's
  --member is not destroyed. If you set destroyDynamicMember to TRUE or 1, then
  --if the member was dynamically created, it is destroyed. If you set
  --destroyDynamicMember to TRUE or 1 and the member was not created
  --dynamically, it isn't destroyed.
  if wfsSpriteIsDynamic(elementSpriteNum) then
    --If the element is dynamic, then destroy it.
    --This gets rid of all entries in gWFSDynamicScriptList[elementSpriteNum]
    if wfsVersion10OrHigher() then
      --If the version of Director is 10 or higher, then use
      --removeScriptedSprite to destroy the sprite.
      --removeScriptedSprite was introduced in version 10, MX 2004.
      --Else run any and all endSprite handlers, unpuppet the channel,
      --and reset lots of properties. This is how a sprite is destroyed
      --in versions of Director less than DMX 2004.
      --Runs any and all endSprite handlers attached to the sprite.
      puppetSprite elementSpriteNum, 0
      sprite(elementSpriteNum).member = 0
      sprite(elementSpriteNum).rect = void
      sprite(elementSpriteNum).scriptInstanceList = []
      sprite(elementSpriteNum).stretch = FALSE
      sprite(elementSpriteNum).rotation = 0
      sprite(elementSpriteNum).color = paletteIndex( 255 )
      sprite(elementSpriteNum).forecolor = 255
      sprite(elementSpriteNum).backcolor = 0
      sprite(elementSpriteNum).visible = 1
    end if
    if destroyDynamicMember then
      --If we are supposed to destroy the dynamic member,
      --then do so.
      if isDynamicMember then
      end if
    end if
    return wfsReturnChannel(elementSpriteNum)
    --wfsReturnChannel returns the sprite channel to the
    --Dynamic Channel Manager.
    --Else the sprite is not dynamic, so just make it invisible.
    return 0
  end if


Below we see the code for wfsAttachBehavior . This attaches a behavior (named aScriptName) to a sprite and can override the behavior's getPropertyDescriptionList properties to configurable values. As noted in the code, I am indebted to Robert Tweed for much of this code. Here is an example call:

TheScript = wfsAttachBehavior("My Script", 78)

The above example attaches the "My Script" behavior to sprite 78. If there is an 'on new' handler, it runs. If there are getPropertyDescriptionList properties in "My Script", they are initialized to their default values. If there is a beginSprite handler in "My Script", it is run.

TheScript = wfsAttachBehavior("My Script", 78, [#pMyProperty1:45])

The above example does the same as the first example except that, supposing "My Script" contains a getPropertyDescriptionList property called pMyProperty1, it is initialized to 45 rather than whatever the pMyProperty1 default value is.

The return value (theScript), is a reference to the script so that if the behavior has a property pSomeProperty, then theScript.pSomeProperty will retrieve the propertyÕs value.

on wfsAttachBehavior (aScriptName, aSprite, aOverride)
  --This attaches a behavior to a sprite. Specifically, it attaches the
  --behavior named aScriptName to aSprite. Use this handler if you are
  --manually attaching scripts to sprites. You don't need to call this
  --handler if you are using the "Script Writer" handlers: the "Script Writer"
  --handlers do the attaching and initializing for you. wfsAttachBehavior
  --first calls the 'new' handler, if the script has one. wfsAttachBehavior
  --then initializes the PDL properties of the behavior, if any exist, to the
  --default values unless you specify the optional aOverride parameter. The
  --aOverride parameter allows you to give the PDL properties other values than
  --the default ones. You may also specify non-PDL properties in aOverride to
  --initialize them before the beginSprite runs (if there is a beginSprite
  --handler). wfsAttachBehavior then calls the beginSprite handler of the
  --script, if it has one. I am indebted to Robert Tweed for the initial
  --version of this code: thanks Robert!
  -- The string name of the behavior you want to attach to aSprite. You
  -- must have a copy of this behavior in a Cast for this handler to
  -- work properly.
  -- This is the sprite channel in which the sprite is located that you
  -- want to attach the behavior to. Alternatively, it can be a
  -- reference to the sprite as in sprite(4)
  -- This parameter is optional. Use it to override default values of
  -- PDL properties, if you want. It will also initialize properties
  -- that are in the behavior but not in the PDL. aOverride is a
  -- property list. Specify the property names you want to override and
  -- a value for each of them. Example:
  -- [#pSomeProp: "text1", #pSomeOtherProp: 5]
  --RETURN VALUE:******************************
  --Returns 0 if aSprite is not an integer or a reference to a sprite (as in
  --sprite(3). Returns the initialized script, otherwise (vScript in the below
  if( ilk( aSprite, #integer ) ) then
    aSprite = sprite( aSprite )
  else if( not ilk( aSprite, #sprite ) ) then
    --Then the sprite parameter is bad
    return 0
  end if
  -- The above makes sure we are dealing with a sprite.
  vScript = script( aScriptName ).rawNew()
  -- Create the script instance but if there is an 'on new' don't call it yet.
  vScript.setaProp( #spriteNum, aSprite.spriteNum )
  -- Add spriteNum and call new() (it may or may not have an 'on new' handler).
  --When you dynamically attach behaviors, you actually have to initialize the
  --spritenum property, ie, it does not auto-initialize as it does in static
  call( #new, [vScript] )
  --If the script has an 'on new' handler, execute it.
  if wfsSpriteIsDynamic(aSprite.spritenum) then
    --If the sprite is dynamic, then the sprite's scriptList is always [ ], so
    --WFS creates gWFSDynamicScriptList, which keeps track of the initial
    --property values so WFS can create dynamic copies of dynamic sprites, ie,
    --can initialize them properly.
    gWFSDynamicScriptList[aSprite.spritenum].add([aScriptName, aOverride])
  end if
  -- Check that aOverride is a list, (could be VOID) so we don't get errors.
  if( not ilk( aOverride, #propList ) )then
    aOverride = [:]
  end if
  if( vScript.handler( #getPropertyDescriptionList ) ) then
    --If the script has an getPropertyDescriptionList handler...
    vPDL = vScript.getPropertyDescriptionList()
    -- Get each default
    repeat with i = count( vPDL ) down to 1
      --Set each PDL property to its default value.
      --Your PDL properties must define a default value.
      vScript.setaProp(vProp, vDefaultValue)
    end repeat
  end if
  --Now we override the default values with the
  --values in aOverride.
  repeat with i=1 to aOverride.count
    vScript.setAProp(theProp, theValue)
  end repeat
  -- Add instance to scriptInstanceList
  aSprite.scriptInstanceList.add( vScript )
  -- Call beginSprite, if applicable
  call( #beginSprite, [vScript] )
  return vScript


Director has been around since 1987 and has survived while others have perished. Part of why it has survived is its combination of Score-based, drag and drop architecture combined with powerful multimedia scripting. But sometimes what makes a thing capable of surviving and being popular also sets its bounds of growth. The Score-based architecture under the hood of Director imposes a 1000 simultaneously-instantiated sprite limit and has resisted support for DSCD since Director 5, ie, the resistance of the architecture to DSCD is fundamental and abiding. The Score is an important part of Director that is not part of authoring environments like C++, Delphi, and Java. But DSCD can give larger projects a chance in Director. How much larger? Well, there is that 1000 simultaneously-instantiated sprite limit and we also note that when there are, say, 500 sprites of any type onstage at once performance slows to a crawl. So DSCD is not a silver bullet but it does save you from having to use too many (and never enough) dummy sprites, and it pushes the sprite-based Director envelope further than is possible without DSCD.

DSCD is a good and 'natural' addition to the feature-set of WFS because it makes sprite-based windowing more powerful and offers better performance. You don't have to have a Score continually full of sprites you rarely use; you can create and destroy them as needed.

There is an interesting thread on dirGames-L (search on "dynamic sprite creation") concerning DSCD and imaging Lingo. Imaging Lingo is not necessarily sprite-based and it offers power and performance that, somewhat oddly, the sprite-based model does not support. I say "oddly" because the vast majority of Director is sprite-based. The main drawback of imaging Lingo is that it is even more programming-centred than Director 3D, which has many library behaviors available to it.

I suppose the next step is to seek some synthesis of the best of the sprite and score-based ideas with the best of imaging Lingo in a system that offers RAD possibilities within Director. For instance, imagine a WFS/IL system which is as general as WFS in the types of constructions it allows (rather than the current IL systems which are specific to particular widgets like a menu system or a dialog box with specific looks). Imagine that you compose the 'multi-sprite' much as you do in WFS, ie, you drag and drop behaviors onto sprites on the stage. And then you aim an IL Script Writer handler at the multi-sprite and the WFS/IL Script Writer writes code that allows the entire multi-sprite to be dynamically reproduced using only one or just a few sprites, depending on the type of members in the multi-sprite (some member types are not so amenable to IL).

That would be nice. Fewer sprites, and use of the fast IL routines. And scrolling and resizable windows. Some users are already combining WFS and IL in their projects. But an integrated WFS/IL system is just a dream at the moment.

Jim Andrews has been using Director since 1999 when he started making interactive audio Shockwave pieces. He started development of WFS three years ago while making windows and menus for Arteroids. He is currently employed at the University of Victoria as a Director programmer of interactive audio with musicians and programmers across Canada.

Copyright 1997-2017, Director Online. Article content copyright by respective authors.