jd's z-sort behavior


[ Zettels Traum ] [ search / suche ]

von dp am 23.August 98 um 17:59:07:

I don't know "the best" Z-sorting approach... other DIRECT-L listmembers have
done some great work on this, and I suspect different situations would require
different solutions.

But if you wish to use Lingo's internal "sort" command on a list, then the
following shows the skeleton of one approach. The core is a sorted propList of
behavior instances in [zPos: object] format, similar to the tactic Jim Collins
described.

At exitFrame each sprite calculates its new XYZ position, removes its old
zPos:obj entry from the zList, and then adds its new zPos:obj entry. On
prepareFrame each instance then matches its position in the zList to a
corresponding spriteList to find in which channel to draw its member.

There are two unusual techniques here. First is the automatic registration of
all similar behaviors and their sharing of common list objects. There are more
examples of such behavior registration in the technotes. The second unusual
technique is how each behavior can control the member and loc of a different
sprite channel in the group. This latter point introduces a complication,
described after the snippet.

  --  Z-Sort Example (behavior skeleton)
  --  Sprites with similar behavior find each other and 
  --  sort their depth in the screen.
  --  Needs your own transforms and stresstesting.      Aug 2 98  jd

  property spriteNum, myX, myY, myZ, myZdelta
  property myMember, ourZlist, ourSpriteList

  on beginSprite me
    set myMember to the member of sprite spriteNum
    sendAllSprites(#ZSortExample_RegisterBehaviors, [:], [])
    sort ourZlist
    --  Simple static values for illustration:
    set myX to the locH of sprite spriteNum
    set myY to the locV of sprite spriteNum
  end

  --  Called for all sprites from each sprite's beginSprite.
  --  All sprites will share the same z-order and sprite lists in memory.
  on ZSortExample_RegisterBehaviors me, zList, spriteList
    set ourZlist to zList
    set ourSpriteList to spriteList
    addProp ourZlist, myZ, me
    add ourSpriteList, spriteNum
  end

  --  Calculate new positions at the end of each frame:
  on exitFrame me
    --  Your XYZ transform operations here:
    set myZ to myZ + myZdelta
    --  Insert object reference at new position in zList:
    deleteAt(ourZlist, getPos(ourZlist, me))
    addProp ourZlist, myZ, me
  end

  --  And then before the next frame, display new positions:
  on prepareFrame me
    set whichChannel to getAt(ourSpriteList, getPos(ourZlist, me))
    set the member of sprite (whichChannel) to myMember
    set the loc of sprite (whichChannel) to point(myX, myY)
  end

  --  A sprite can be removed without affecting the group:
  on endSprite me
    deleteAt(ourZlist, getPos(ourZlist, me))
    deleteOne(ourSpriteList, spriteNum)
  end

  --  Simple properties for illustration:
  on getPropertyDescriptionList me
    set theProps to [:]
    set c to "Initial Z position:"
    set r to [#min: -10.0, #max: 10.0]
    addProp theProps, #myZ, [#comment:c, #format:#float, #range:r, #default:0]
    set c to "Z movement:"
    set r to [#min: -1.0, #max: 1.0]
    addProp theProps, #myZdelta, [#comment:c, #format:#float, #range:r,#default:0]
    return theProps
  end


(Warning: I reserve the right to have overlooked something here... it passed
my quick tests, but it's hot out and I may have missed a test. Use your own
judgment and experience, thanks.)

Each behavior instance always controls the same graphic, but can display it in
different channels depending on the graphic's current Z-depth. Here's the
sequence:

--  Each sprite in turn has its behaviors instantiated. They receive spriteNum
and getPDL values from the Score in the usual way, and then broadcast a
message via sendAllSprites, sending along a propList and a linearList as
parameters. Each existing behavior instance then stores a reference to each of
these list objects and adds its own data to these shared lists.
    (During initialization there are many extra messages and lists which are
never used, but the flexibility of this approach means that sprites can start
and end on different frames and still maintain current references to each
other.)

--  Before leaving each frame the 3D transforms are performed by each behavior
instance. Each then removes its old zPos:obj reference from the shared sorted
zList, and adds a new reference with its new zPos. Because the proplist is
sorted this new behavior reference automatically goes into the correct
position in the list.

--  At prepareFrame, each behavior instance finds its position in the zList,
and takes the sprite channel in the corresponding position of the list of
available sprite channels. There's no need to hardwire a range of sprite
channels, or even of using contiguous channels -- the behavior skeleton
handles all of this automatically.


The above suffices for non-interactive pieces. But if these sprites can react
to user events, then there's a problem with the "this behavior controls that
other sprite channel" approach: mouse events will appear to go to the wrong
sprite. This is because the user associates their clicks with the thing they
can see, while Director catches behaviors based upon their channels, or depth
in the screen.

It's possible to catch events in the current display channel and relay them to
the behaviors in the sprite channel controlling the clicked graphic. Two
things to watch for are infinite recursion, and preventing additional
behaviors in the clicked channel from executing. You could add something like
this to the above behavior skeleton:

  on mouseUp me, isPassedMessage
    if voidP(isPassedMessage) then  -- it's a real mouseUp
      set whichChannel to getAt(ourSpriteList, getPos(ourZlist, me))
      sendSprite(whichChannel, #mouseUp, TRUE)
      stopEvent  -- prevents other behaviors on the clicked channel from firing
    end if
  end

Here's a variant with info dumped to the Message Window:

  on mouseUp me, isPassedMessage
    if voidP(isPassedMessage) then
      put RETURN & "Caught by: " & spriteNum, the member of sprite spriteNum
      set whichChannel to getAt(ourSpriteList, getPos(ourZlist, me))
      sendSprite(whichChannel, #mouseUp, TRUE)
      stopEvent
    else put "Relayed to: " & spriteNum, the member of sprite spriteNum
  end

If using something like this with multiple behaviors on a sprite, then it's
important to have this behavior at the top of the sprite's list of behaviors,
so that it can "stopEvent" before other behaviors in the clicked channel
evaluate the mouse event.


Summary: Although it's not possible to sort a propList by an arbitrary
property name, it's possible to create a second list for sorting and then
cross-reference. A skeleton 3D behavior was shown, which handles behavior
registration, sorting, display, and event-routing. The above should be
doublechecked against your own experience and tests.

Whew... hope I got that right, and that it's of interest.   People have
been doing great things with 3D displays in Director, and I'm personally
looking forward to what you accomplish here in the future.

jd





Dazu:























D. Plänitz