Articles Archive
Articles Search
Director Wiki
 

Look, no sprites!

June 20, 2000
by James Newton

© 2000 by James Newton

Before we get into the specifics of the Lingo techniques that are going to be discussed in this article take a second to play with the demo to see what it is we are hoping to accomplish. Use the movie below to have fun painting in the style of Piet Mondriaan. Simply click on the black canvas and drag the mouse while holding the mouse button down. Click on the palette chips to change the color.

Download this Director 8 movie in Mac or PC format

When you click on a paint chip, notice how it passes in front of the others. Notice how the palette always appears in the foreground: you can't paint over it. Notice that the rectangles you paint leave no trails: when you shrink the rectangle in any direction, the area behind the rectangle reappears as before.

Click on the last rectangle you painted. You can now move it around with the mouse, as long as you hold the mouse button down. If you change colors before moving on the rectangle, the rectangle changes color to match.

Nothing up my sleeve

Download the sample movie and open it in Director 8. Once you have opened the movie in Director 8, take a look at the Score window. What do you see?

Nothing.

The movie uses no sprites. The sprite channels are all empty. Oops, sorry, there is a little behavior in the Script channel of frame 1. Now open the Cast window.

You may be surprised to discover that this movie contains a single script and nothing else. Yet you have been manipulating colored rectangles; you have been changing their shape, size, position and locZ. Where do these shapes come from?

Imaging Lingo

Director 8 proposes a whole new set of commands for dealing with images. These commands are fast and powerful. You can create images on the fly, stock them in RAM, and manipulate their pixels in many ways. You can use ink effects and quad distortions. You can save the result in a bitmap member, and even send it over the Internet via the Multiuser Xtra.

In this article, I will show you how to:

This is only an introduction to the possibilities of Imaging Lingo.

Despite appearances, my purpose is not to encourage you to stop using sprites in your projects. In a complex movie, controlling what appears on the screen without using sprites would be somewhat like reinventing the wheel. On the contrary, I hope that my demonstration will help you to understand better how sprites work "under the hood". This knowledge could help you to use Imaging Lingo as a complement to the existing sprite features.

Drawing on the Stage

Here are two simple handlers that draw shapes on the Stage:

on redSquare
  
  square = rect(60, 20, 260, 220)
  red = rgb(255, 0, 0)
  (the stage).image.fill(square, red)
  
  updateStage
  
end

on blueCircle
  
  square = rect(50, 10, 270, 230)
  blue = rgb(0, 0, 255)
  options = [#color: blue, #shapeType: #oval]
  (the stage).image.fill(square, options)
  
  updateStage
  
end

As you can see, the fill() command accepts several types of syntax. To insert the basic syntax automatically into your handlers, remember than you can use the "L" (for Lingo) button in the tool bar of the Message and Script windows.

Note that in order to draw an ellipse, you first have to define a rectangle, then use a list of options to indicate that Director should fill the rectangle with an oval shape. Test the different properties that can be used in this options list: #shapeType, #lineSize, #color and #bgColor. Check the Lingo Dictionary for more details.

Director 8 has a new image property, which gives you direct access to the pixels. Bitmap, Flash and Vector Shape members also have an "image" property. You will shortly be using the similarly-named image() function to create a "raw" image and stock it in a variable.

Create a new movie, open the Script window, and paste the two handlers quoted above into it. For the moment, it is useless to run your movie, since it contains no sprites and will stop as soon as you start it. You can, however, type the following commands into the Message window while the movie is halted.

redSquare
blueCircle

Other shapes with quads

You can create even more interesting shapes by using a quad as the destination area, rather than a rectangle. A quad is a list of four points: each point defines the corner of an arbitrary quadrilateral shape. In the example below, two of these points coincide, and the quad thus forms a triangle.

on yellowTriangle
  
  left = point( 50, 35)
  right = point(270, 35)
  bottom = point(160, 235)
  triangle = [left, right, bottom, bottom] -- triangular quad
  
  yellow = rgb(255, 255, 0)
  square = image(200, 200, 8) -- square image is blank
  square.fill(square.rect, yellow) -- square image is now yellow
  
  (the stage).image.copyPixels(square, triangle, square.rect)
  updateStage
  
end

Add this handler to your script, then type the following into the Message window:

yellowTriangle

Note the image() function, which allows you to create a new image out of nothing, directly in the computer's memory. The pixels of this new image are all white by default. (To test this, comment out the line that fills the square image with yellow, by placing two hyphens "--" at the beginning of this line).

Note also the copyPixels() command. You will find a simpler use of this command further on. Here, it is used to copy a square image onto the Stage, and to deform it by limiting it to an area defined by the quad "rectangle". CopyPixels is a very powerful command. You can, for instance, use a list of parameters to modify the way the copy is carried out. You can thus manipulate:

Check the Lingo Dictionary for more details. Or read the two part "Introduction to Imaging Lingo" published here on DOUG (check out part 1 and part 2).

Sketching a behavior

Now, let's try to automate this task. Convert your Movie script to a behavior. You can do this by clicking on the button with a white "i" (for Information) in a blue circle. This opens the Property Inspector, new to Director 8. You can now choose "Behavior" in the pop-up menu.

Add these handlers to your behavior:

on beginSprite
  redSquare
  blueCircle
  yellowTriangle
end


on exitFrame
  go the frame
end exitFrame

Drag your behavior into the Script channel of the first frame of your movie, then run the movie.

The on beginSprite handler is executed before the contents of the first frame are drawn onto the Stage. But the Stage remains empty. No shapes appear. This is not due to a mistake in your Imaging Lingo handlers. It's a question of timing. The handlers on redSquare, on blueCircle and on yellowTriangle have been called too soon. You can make the on beginSprite handler execute again, in order to call these three handlers, by typing the following command in the Message window:

sendAllSprites(#beginSprite) 

Abracadabra, the three shapes appear. But why did they not appear automatically before?

Too quick on the draw

When displaying the image of a given frame, Director recognizes several stages. It punctuates these stages with messages that can be fielded by your handlers. As far as we are concerned here, this is the order:

If you use Imaging Lingo in an on beginSprite or on prepareFrame handler, Director is likely to draw any sprites over the top of your images. In the current case, Director flatly refuses to execute the updateStage command, since this command forces it to redraw the Stage and thus to send out new #beginSprite and #prepareFrame messages. If this were allowed, it would lead to a vicious circle: your computer would freeze.

To make your shapes appear, you must therefore wait for the first #enterFrame message. Replace the current on beginSprite handler with the following:

property startFlag

on beginSprite
  startFlag = TRUE
end

on enterFrame
  
  if startFlag then
    
    -- These lines will only be executed once
    redSquare
    blueCircle
    yellowTriangle
    startFlag = FALSE
    
  end if
  
end

Run your movie again. This time the shapes appear as expected. Timing is important when you are using Imaging Lingo.

Changing the locZ

A sprite's locZ determines the order in which its image will be copied on to the stage, compared with those of the other sprites. You can easily simulate this effect by redrawing one shape in front of the others.

Since we are using areas of a single flat color, we can determine which shape is clicked by testing the color under the mouse at the moment it is clicked. (In the sample movie, I use a list of rects to achieve the same result).

Add the following lines to your behavior (the properties must be declared at the very top of the script, before the handlers on redSquare, on blueCircle and on yellowTriangle):

property red
property blue
property yellow

on mouseUp
  
  colorUnderMouse = (the stage).image.getPixel(the mouseLoc)
  
  case colorUnderMouse of
    red: redSquare
    blue: blueCircle
    yellow: yellowTriangle
  end case
  
end

This competed behavior is demonstrated in the shapes.dir movie.

Run the movie again. Now, click on any part of a shape that is not covered by the foremost shape. The shape you click on is redrawn in front of the others.

getPixel() gets a facelift

In Director 7, the getPixel() function already existed, but it was not officially supported by Macromedia. It therefore did not appear in the Lingo Dictionary, and Macromedia reserved the right to change its syntax. As a result, in Director 8, getPixel() does not work the same way as in Director 7. If you are updating a movie where you used the function unofficially, you will have to modify your scripts. In the same way, setPixel() has also taken on a new form. Check the Lingo Dictionary for details.

Screen buffer

When you move a sprite across the Stage, the background reappears, unchanged, in the wake of the sprite. Director must hold in its memory any part of the background that is hidden by a sprite, or it would not know what to show once the sprite has moved on. To do this, Director reserves a block of memory know as the "Screen buffer", where it stocks the entire image of the Stage before it displays it on the screen.

In order to imitate the sprite's independence with respect to the background, you are going to create your own screen buffer. This is a very memory-hungry step. The dimensions of the stage need to by multiplied by the screen's color resolution. Try this in the Message window:

put the stage.rect.width * the stage.rect.height * the colorDepth / 8 / 1024 && "Ko"

If the Stage is 800 x 600 pixels and the screen has a resolution of 32 bits, this represents almost 2MB. Since Director already monopolizes a memory block of this size, it is not usually good practice to grab the same amount of memory for your scripts. I only do this here in order to show you how sprites work... and I use a small Stage.

A two-tiered image

Here is a stripped-down version of the "Mondriaan" behavior. It allows you to draw a series of red rectangles on the screen. (In this simplified version, you have to drag from the top left to the bottom right). Create a new behavior and paste this script into it. Drag the new behavior into the Script channel of the first frame, to replace the behavior that is currently there. Now run your movie.

property myStartH -- the mouseH on mouseDown
property myStartV -- the mouseV on mouseDown
property myOblong -- rect of the current oblong
property myStageImage -- copy of (the stage).image before painting


on mouseDown(me)
  
  -- Start to draw a new oblong
  myStartH = the mouseH
  myStartV = the mouseV
  myOblong = rect(0, 0, 0, 0) -- dummy value
  myStageImage = duplicate((the stage)Image) -- current state
  
end mouseDown


on exitFrame(me)
  
  if the mouseDown then
    -- Restore the area that was modified the last time round
    (the stage).image.copyPixels(myStageImage, myOblong, myOblong)
    
    -- Remember which area is about to be changed
    myOblong = rect(myStartH, myStartV, the mouseH, the mouseV)
    
    -- Draw the oblong in its new position
    (the stage).image.fill(myOblong, rgb(255, 0, 0))
  end if
  
  go the frame
  
end exitFrame

Note that the zone previously hidden by the rectangle myOblong is drawn first, and the colored rectangle is then drawn on top. The screen is not refreshed between these two actions: there is no updateStage command, and Director waits for the next frame before it redraws the Stage. As a result, the user only sees the result of the two actions: the Stage image only changes once.

Also, note the duplicate() function, which is used on the image of the Stage. For Lingo, an image is a pointer. That means that it contains the address in the computer's memory where the pixel information is stored. It doesn't contain the pixel information itself. If two variables contain the same memory address, they can both be used to manipulate the same pixels. A change made using one of the variables is immediately detected by the other. It is as if two doors open into the same room.

If you set the variable myStageImage directly to the (the stage)Image, then the pixels addressed by myStageImage will change continually so as to reflect the changes on the Stage. This is why we had to make a separate copy of the current state of the Stage, by using the duplicate() function; this is where that massive extra chunk of memory is commandeered. To use the same metaphor, the duplicate() function here creates a whole new room, then tells us where to find the door.

The copyPixels() command is used here in a straightforward fashion. In this example, it is used to copy the pixels from an area of the screen buffer to the corresponding area on the Stage. The source and destination image are the same size and the areas in question have the same coordinates in both. In the on yellowTriangle handler above, the area that the pixels were copied to was more complex. In this case, the image being copied is not a simple area of flat color. You create a custom painting on the Stage: copyPixels() deals with copying a part of the unique arrangement of different colored rectangles, just as if you were using a cookie cutter.

This behavior is completed and demonstrated in the simplified.dir movie.

Way to go

The sample movie offers a richer set of features. The "Mondriaan" behavior is constructed in a more rigorous fashion and is well commented. You could try to complete the two simplified behaviors that you have studied here, using the "Mondriaan" behavior for inspiration. You could even add your own features: a choice of shapes (line, oval, quad ...); an "Undo" button"; or a button to "Replay the creation", ...

If you have any questions on the issues dealt with here, you can contact me at newton / at / planetb.fr.

James Newton started working with Director 5 in 1997. He wrote many of the behaviors that ship with Director since version 7. James lives in Dunoon, near Glasgow, Scotland. His company, OpenSpark Interactive, is responsible for marketing PimZ OSControl Xtra. When not coding he can be found racing his classic Flying Fifteen around the Holy Loch, making things with his four children, or catching escaped hamsters.

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