Working in Director
In this section, you'll learn how to work in Director. You'll learn the importance of the size of your .W3D files, how to set up the score, how to handle visibility and color issues, and finally, how to add Lingo.
File Size Considerations
In this example, the .W3D file consumes a majority of the Director movie's size. This means that the .W3D file size is what you should be concerned with, even before you import it into Director. If it's too large, your users will have an overall bad experience viewing your movie. The movie will take a long time to download, and it might not play smoothly, especially if your viewers don't have hardware-accelerated OpenGL or DirectX type video cards.
The first thing to remember is that when you "get information" (in Macintoshes) or view properties (in Windows), ensure that you are viewing the network file size, not the file size on disk. Although it's usually not a considerable difference, it's best to use accurate information for posting next to the movie, so that users know what they will have to endure in terms of download times [3.29].
Figure 3.29. The download size for this file is 890K, not 872K. Try to make every K count.
Another disadvantage of having a large file is that the majority of Internet users are not on a broadband connection; thus, a 1 megabyte file's download can take a long time. It can become an obsession to see just how small you can get a .W3D or Shockwave 3D file. I tell you about more file-saving tips later in this chapter. First, we'll set up the movie.
Score Setup
After you have imported your .W3D file and any additional assets into Director, you can lay out the score. If you prefer, you can use the house.dir file on the CD-ROM accompanying this book. This file represents a simple movie that has three necessary frames to run the entire movie [3.30].
Figure 3.30. The three frames that this movie uses are for preloading, waiting until the user is ready, and displaying the .W3D movie and interface.
I recommend that you never use the first frame of the score. This isn't a Shockwave 3D tip, but a general rule when working in Director. Director performs a lot of pre-tasks before loading the movie, such as general script initiations for sounds, windows, and video. When Director or runtime versions of your movie, such as DCRs or projectors, have assets in the first frame, you can sometimes run into inconsistencies and erratic behavior when you start the movie. I recommend that you always put sprites in frame 2, and treat frame 2 as if it is frame 1. However, if you need to run initialization scripts, putting those scripts in frame 1 is fine.
Color and Visibility
When you place your .W3D castmember into the score, you will notice a lag as Director pauses for a few seconds to process the Shockwave 3D castmember and load it into memory. The lag time occurs during any time that your playback head is moved off the Shockwave 3D castmember frame and then back on to it again. This delay can become considerably annoying at times because the program appears to hang briefly. You can also get this lag when you are trying to add behaviors to Shockwave 3D castmembers.
To avoid the lag time as you navigate the score, place your Shockwave 3D castmember in the score, and then position it on the stage where you want it to go. Now, go back to the score and turn off the visibility of the channel that contains your Shockwave 3D castmember. You don't need it to be visible to add behaviors and scripts. Now, you can scrub the playback head around the score to navigate without it pausing and hanging over the Shockwave 3D member [3.31].
Figure 3.31. Turn off the visibility of the channel that contains your Shockwave 3D sprite for better score performance.
Another tip is to color your Shockwave 3D member in the score, using the color chips in the lower-left corner of the score. This helps you quickly identify where in the score your castmember is.
Redrawing Issues
When visibility is turned on for a castmember, and you move that member to the stage, redraw issues occur. I have seen these same redraw issues occur in projectors and web browsers, where you have no control over it, because it is occurring outside of Director. This is a Shockwave 3D bug that Macromedia has to resolve in the future.
Another good reason to turn off the visibility of your Shockwave 3D member is because leaving it on can result in some "redrawing funkiness" that is present in Director after an image is redrawn in Director.
To simulate this problem, add your Shockwave 3D castmember to the stage. Then, move the Stage window to another location. The Shockwave 3D sprite does not want to update the image when the Stage window moves [3.32].
Figure 3.32. Moving your stage with a Shockwave 3D sprite can cause redraw issues.
In my opinion, there is currently no ideal work-around for this bug. You can make the redrawing problem go away by turning off the Direct to stage option in the Property Inspector window; however, when you do this, your Shockwave 3D movie is drawn in screen buffer layers, and other castmembers in the score can pass on top of the Shockwave 3D sprite. This causes such a substantial slowdown in the redrawing of the Shockwave 3D sprite that it becomes useless. It's reminiscent of the days when Director's QuickTime functionality was first added to the application. You had the ability to turn off the Direct to Stage function for the QuickTime sprite, but the number of frames per second playback would drop from 24 to approximately 4, making the QuickTime movie useless. However, today you can do this with QuickTime sprites, and the performance isn't as bad as it used to be. Hopefully, redrawing sprites will get better with future improvements.
The real issue is that these redrawing artifacts occur in projectors and browsers. This is bad, because you can't turn off the Direct to Stage option for performance reasons, and you can't let your user move the window around or scroll without causing this redrawing problem. I recommend that you simply stay away from the projector option that lets your movies be in their own window (show title bar). As for working in Director, your only options are to turn off the Shockwave 3D visibility or open and close the stage window and use other windows as "giant erasers" to wipe the screen clean.
Adding Lingo
Yes, it's Lingo time. This movie contains Lingo examples that are either simple or difficult. There is no "middle ground" with these scripts. If you open the house.dir movie from the CD, you will see in the cast that there are four scriptsmembers 19, 20, 21, and 23. Number 23 is the most difficult if you are new to Lingo.
Each piece of Lingo preloads the Shockwave castmember into memory, waits for the user to continue, pauses the movie in the frame that the Shockwave castmember is in, and finally, performs a form of collision detection known as ray casting.
The first script that handles the preloading is a frame script. The script should be added to one of the Score window's frame script channels. It looks like this (remember, these scripts are on the CD accompanying this book as well):
property p3dMember on beginSprite (me) p3dMember = member("3D") p3dMember.preload() end beginSprite on exitFrame (me) if (p3dMember.state <> 4) then go to the frame else p3dMember.resetWorld() go to the frame + 1 end if end exitFrame
Member number 19 is a script that preloads the Shockwave 3D castmember into memory (RAM) before it starts playing. The script looks for your Shockwave 3D castmember by name, not by sprite number. Because it looks for the name, be careful. If you copy the script word-for-word, don't forget to use the name of your Shockwave 3D castmember instead of the one in the script, or change the name to 3D.
Before talking about the details of this script, I want to mention Thomas Higgins. Thomas is the author of the scripts in this movie, and he is the author of many movies located at http://www.directordev.com/. Thomas is an employee of Macromedia, and I want to extend my appreciation for his help with this demonstration and his contribution to the book.
---- Preload Shockwave 3D member into memory script ---- property p3dMember -- reference to the 3D member on beginSprite (me) -- stores the 3D members name for reference p3dMember = member("3D") -- starts the preloading of the castmember p3dMember.preload() end beginSprite on exitFrame (me) -- check the member's state; is it loaded or not? if (p3dMember.state <> 4) then -- if it's not loaded, then hold on current frame go to the frame else -- reset member's world p3dMember.resetWorld() -- if it is loaded then step to next frame go to the frame + 1 end if end exitFrame ----
The next script that is used in the movie simply pauses it until the user is ready. After the movie has been preloaded, you don't want it to instantly start playing, because the user might not be ready. This script waits until the user clicks on the screen before advancing to the next frame to play the Shockwave 3D movie you just preloaded.
-- This script holds the playback head on a given -- frame until the user clicks anywhere on stage. -- on exitFrame -- hold on current frame go to the frame end exitFrame on mouseDown -- step to the next frame go to the frame + 1 end mouseDown
There is one additional frame script that is used to pause the Director movie on the frame that the Shockwave 3D castmember is playing on:
on exitFrame -- hold on current frame go to the frame end exitFrame
The next Lingo script is attached to the Shockwave 3D sprite and controls the navigation and collision of your camera. It's a very intuitive use of ray casting. (This script is also on the CD.)
What the ray casting script does is project a ray (or think of it as a vector) from the camera in the direction that the camera is moving. The camera has a sphere surrounding it that is invisible. If the camera is moving forward, a ray is being projected forward to determine the distance the camera is from an object in front of it [3.33]. If the ray being projected or cast from the camera is shorter than the distance the camera is to the sphere, the camera is close to the object, if it isn't colliding with it. The script then sets the camera's position back to the path the ray was being cast. This gives the impression that your camera has collided with an object and bounced back off of it.
Figure 3.33. Ray casting is used to determine the camera's distance from an object. A result of ray casting is shown here.
Following is the ray casting Lingo and an explanation of what it is doing.
These are the properties that get initialized for the script. Properties are similar to variables or global variables depending on the programming language you are familiar with:
property pMember -- 3D member used by this sprite property pSprite -- reference to this sprite property pMouseDown -- user is pressing the left mouse button property pRightMouseDown -- user is pressing the right mouse button property pCamera -- the sprite's camera property pCameraSphere -- the sphere used to surround the camera property pCamAnimFlag -- introduction animation is running? property pCamAnimInfo -- perform an introduction animation
The beginSprite handler fills the properties with their functions and any necessary data. It also creates the sphere around the camera and a light that the camera carries with it:
on beginSprite (me) -- initialize properties pMember = sprite(me.spriteNum).member pSprite = sprite(me.spriteNum) pMouseDown = FALSE pRightMouseDown = FALSE pCamera = sprite(me.spriteNum).camera pCamAnimFlag = TRUE -pCamAnimInfo = [#initT: pCamera.transform. _duplicate(), \ -#f inalT: pMember.camera _("f irst_person").transform. duplicate(), \ #count: 0] -- create the camera's bounding sphere mr = pMember.newModelResource("camera_sphere",#sphere) mr.radius = 7.5 pCameraSphere = pMember.newModel("camera_sphere",mr) -- create a light to carry with the camera camLight = pMember.newLight("camera_light",#point) camLight.color = rgb(170,170,170) -- make the sphere and light children of the camera pCamera.addChild(pCameraSphere,#preserveParent) pCamera.addChild(camLight,#preserveParent) -- register the camera for the timeMS event in order to perform the intro animation if any -pCamera.registerScript(#timeMS,#animateCamera,me, 0,50,51) -- register the member for regular timeMS events in order to respond to user input and resolve camera
"collisions" -pMember.registerForEvent(#timeMS,#controlCamera, me,2500,50,0) end beginSprite
These mouse handlers switch the Boolean state for the property. If the mouse is down the pMouseDown property is TRUE, for example:
on mouseDown (me) -- update the mouse down property pMouseDown = TRUE end mouseDown on mouseUp (me) -- update the mouse down property pMouseDown = FALSE end mouseUp on rightMouseDown (me) -- update the right mouse down property pRightMouseDown = TRUE end rightMouseDown on rightMouseUp (me) -- update the right mouse down property pRightMouseDown = FALSE end rightMouseUp
The animateCamera handler is used to animate the camera automatically over time:
on animateCamera (me) -- increment the internal counter for the animation pCamAnimInfo.count = pCamAnimInfo.count + 1 -- determine the interpolation percentage pctg = 100.0 _ (pCamAnimInfo.count / 50.0) -- if pctg is greater than 100 end animation sequence if (pctg > 100) then pctg = 100 pCamAnimFlag = FALSE end if -- determine the new interpolated camera transform -t = pCamAnimInfo.initT.interpolate _(pCamAnimInfo.finalT,pctg) -- apply that transform to the camera pCamera.transform = t end animateCamera
This controlCamera handler is what controls the movement of the camera for each of the four directions. It also controls the collisions with objects:
on controlCamera (me)
-- respond only if the intro camera animation is completed if not(pCamAnimFlag) then -- CONTROL THE LEFT/RIGHT ROTATION OF THE CAMERA -- if the shift key is *not* down then follow the mouse -- to adjust left right looking if not(the shiftDown) then -- check for the mouse locH wrt the sprite loc deltaH = the mouseH - pSprite.locH -- calculate rotation value to apply rotn = -(deltaH/165.0) * 4.0 -- apply that rotation -pCamera.rotate(pCamera.worldPosition, vector(0,0,1),rotn,#world) end if -- CONTROL THE FORWARD/BACKWARD MOVEMENT OF THE CAMERA -- if the left mouse is down then move the character forward if pMouseDown then pCamera.translate(0,0,-2.5) end if -- if the right mouse is down then move the character backward if pRightMouseDown then pCamera.translate(0,0,2.5) end if
The following part of the script controls the collisions of the camera with the walls or other objects. This casts rays forward or backward depending upon movement, and it will also cast rays to the left and to the right. For each ray that is cast, the script will verify that the distance to the nearest model exceeds the camera's bounding sphere radius; if the distance is less than the bounding sphere's radius, move the camera out of the collision state in a direction perpendicular to the intersected model's surface.
--- cast one ray fwd/bckwrd depending upon which mouse button is down case (TRUE) of -- left mouse down, cast ray forward (pMouseDown): -- cast ray -tList = pMember.modelsUnderRay _(pCamera.worldPosition,-pCamera.transform. zAxis,#detailed) -- if there are models in front of the camera, check for collisions if (tList.count) then me.checkForCollision(tList[1]) end if -- right (control+) mouse down, cast ray backward (pRightMouseDown): -- cast ray -tList = pMember.modelsUnderRay _(pCamera.worldPosition,pCamera.transform. zAxis,#detailed) -- if there are models in front of the camera, check for collisions if (tList.count) then me.checkForCollision(tList[1]) end if end case -- cast a ray to the left -tList = pMember.modelsUnderRay _(pCamera.worldPosition,-pCamera.transform. xAxis,#detailed) -- if there are models in front of the camera, check _for collisions if (tList.count) then me.checkForCollision(tList[1]) end if -- cast a ray to the right -tList = pMember.modelsUnderRay _(pCamera.worldPosition,pCamera.transform.xAxis, #detailed) -- if there are models in front of the camera, check for collisions if (tList.count) then me.checkForCollision(tList[1]) end if end if end controlCamera on checkForCollision (me, thisData) -- grab the distance value dist = thisData.distance -- if distance is smaller than bounding radius, resolve collision if (dist < pCameraSphere.resource.radius) then -- get distance of penetration diff = pCameraSphere.resource.radius - dist -- calculate vector perpendicular to the wall's surface to move the camera tVector = thisData.isectNormal * diff -- move the camera in order to resolve the collision pCamera.translate(tVector,#world) end if end checkForCollision
This script controls the navigation for the Shockwave 3D sprite. After the script is attached to your sprite and the movie is started, the cursor position relative to the Shockwave 3D sprite determines the rotation of the camera, even if your cursor isn't rolled over the Shockwave 3D sprite. So, for example, if your cursor is off the screen to the right, your camera will be turning to the right inside your Shockwave movie. Be aware of the functionality, because at some point, your movie will need to explain to your users how to navigate.
With the script controls in order, it's time to test what you have completed.