Using RGB colors in behaviors
August 15, 2000
by Zac Belado
Director 7 introduced many new features, and one of the more useful was the RGB color object. It finally allowed Director developers to specify exact colors in their projects. And while it is very useful in that respect, there are two major problems with it. First, it's almost impossible to relate an RGB color to an actual color. For instance, what color is rgb (32,56,117)? Not many people would guess that it's a medium blue. So, while it's very handy to be able to specify exact red, green and blue values, its utility is limited because it's difficult to relate those values to descriptive names, like blood red, olive green or navy blue. The other, more serious, problem is that Director doesn't handle RGB colors in behaviors very well.
For example, let's assume you are writing a behavior for a Text member. The behavior will change the color of each line of text as the user rolls over it, and also will change the color of a line to a separate color when the user clicks on it. You want to be able to specify an RGB color for both of these effects in a GPDL dialog. You could always try this by defining a list of colors and then saving them as string properties.
on getPropertyDescriptionList
pList = [:]
colorNames = [rgb(0,0,0), rgb(255,255,255), rgb(255,0,0), rgb(0,255,0), rgb(0,0,255)]
addProp pList, #pRolloverColor, [#default:#black, #format:#string, #comment:"Rollover color?", #range:colorNames]
addProp pList, #pSelectedColor, [#default:#black, #format:#string, #comment:"Selected color?", #range:colorNames]
return pList
end
Then, in the beginSprite handler for the behavior you could use the value() function to convert the string to a color object. This would certainly work, but it has a few problems. First, the color list is hard-coded, so if you need to be able to specify new colors you will have to edit the behavior. Secondly, the GPDL dialogs are almost unreadable.
So, while you can specify a color, the pop-up menus contain text that might as well be in Latin or hexadecimal (which, to a degree, they actually are).
An alternative approach
Instead, let's examine a system that will allow you to:
- Quickly and easily add new colors
- Assign colors in a behavior using meaningful names
- Use consistent colors and names in all your behaviors and movies
The system requires two components: first, two lists, one containing the names of the colors as symbols, and the second (property) list containing the name/color pairs; and second, a set of scripts that will:
- build a list of color names and a property list of name/color pairs
- check to see if the lists exist
- a function that returns the list of color names
- a function that takes a name as an input parameter and returns an RGB color object
The process is as follows:
The lists that the system needs are initialised by making a call to a handler called buildColorLists. This handler is called in one of two ways. The first way it can be called is directly, in a prepareMovie handler. This code needs to be added to ensure that the data is available at runtime. The second would be in authoring, where you need to ensure that the lists (the color name list and the color property list) exist when you apply a behavior to a sprite. In the GPDL handler there is a call to a method, checkColorLists, that tests to see that both lists have been created; if not, it calls buildColorLists to create them.
Let's look at these two handlers. The buildColorLists handler reads the color data from a Field member called colorNames. The data in the Field member looks like this:
black, 0,0,0 red, 255,0,0 blue, 0,0,255 olive,153, 204, 51 purple,102, 0, 153 light_green, 0, 238, 0 light_brown,102, 102, 0 pale_blue,0, 153, 255 blood_red,170, 0, 0 suede,204, 204, 153 lavender,204, 153, 204
and is in the format:
<colorname>, <red color value>, <green color value>, <blue color value>
Note: the author is red/green colorblind, so his idea of light green might be your idea of tan...
You aren't limited to the colors that I've included. You can add your own colors to this field (as many as you'd like), but you need to make sure that you call buildColorLists before you try to access them.
The buildColorLists handler simply reads through each of these lines and creates a symbol and an RGB color object from the data.
on buildColorLists
-- repeat though all the values in the colorNames member
-- and make a property list of the values
thisText = member("colorNames").text
limit = thisText.lines.count
gColorNames = []
gColorList = [:]
repeat with index = 1 to limit
thisLine = thisText.line[index]
if thisLine <> "" then
-- make a symbol of the colour name
thisName = symbol(thisLine.item[1])
-- get the red, green and blue values
aRed = value(thisLine.item[2])
aGreen = value(thisLine.item[3])
aBlue = value(thisLine.item[4])
-- make a colour
thisColor = rgb(aRed,aGreen,aBlue)
-- append the values to the appropriate lists
append gColorNames, thisName
addProp gColorList, thisName, thisColor
end if
end repeat
end
The checkColorLists handler just tests to see if either of the two global lists is VOID (i.e. it hasn't been declared); if either is, it calls buildColorLists to create them.
on checkColorLists
if voidP(gColorNames) OR voidP(gColorList) then
buildColorLists
end if
end
The core of the system is two functions: returnColorList() and returnColor(). The returnColorList() function returns the global list gColorNames.
on returnColorList
-- return the list of parsed colors
return gColorNames
end
This might seem rather pointless, but its value is that it encapsulates the task of returning the color names so that behaviors aren't relying on direct knowledge of any of the global variables the system requires. This allows you to safely change the names of the global variables, or change the way the data is stored, without ever having to modify your behaviors.
The second function, returnColor(), takes a color name, a symbol, and returns the RGB color object that is associated with that name. The gColorList property list looks like this:
put gColorList -- [#black: rgb( 0, 0, 0 ), #red: rgb( 255, 0, 0 ), #blue: rgb( 0, 0, 255 ), #olive: rgb( 153, 204, 51 ), #purple: rgb( 102, 0, 153 ), #light_green: rgb( 0, 238, 0 ), #light_brown: rgb( 102, 102, 0 ), #pale_blue: rgb( 0, 153, 255 ), #blood_red: rgb( 170, 0, 0 ), #suede: rgb( 204, 204, 153 ), #lavender: rgb( 204, 153, 204 )]
So, if you wanted to get the color value for the suede color you would write:
thisColor = returnColor (#suede)
And you would get back the appropriate color:
put thisColor -- rgb( 204, 204, 153 )
Putting it into practice
Let's use this system in a small behavior. The behavior will use three colors, which are defined in the GPDL: a default color for the text; a color used for a rollover effect; and a final color, used to indicate which line the user has selected. These are all defined in the behavior's getPropertyDescriptionList handler.
on getPropertyDescriptionList
pList = [:]
checkColorLists
colorNames = returnColorList()
addProp pList, #pRolloverColor, [#default:#black, #format:#string, #comment:"Rollover color?", #range:colorNames]
addProp pList, #pSelectedColor, [#default:#black, #format:#string, #comment:"Selected color?", #range:colorNames]
addProp pList, #pDefaultColor, [#default:#black, #format:#string, #comment:"Default color?", #range:colorNames]
return pList
end
The handler first calls checkColorLists to make sure that the color system has been initialised. It then gets a list of the color names by calling returnColorList(). This list is then used in the #range parameter of each of the properties to make a pop-up menu of the colors that are available
At this point, the colors are stored as symbols in the behavior's properties. Once the sprite "begins", the behavior will change those symbols into actual RGB color objects by passing the symbol to the returnColor() function.
on beginSprite me
pMySprite = sprite(me.spriteNum)
pMyMember = pMySprite.member
pLastRollover = 0
pLastSelect = 0
-- get the color value as an rgb colour
pRolloverColor = returnColor (pRolloverColor)
pSelectedColor = returnColor (pSelectedColor)
pDefaultColor = returnColor(pDefaultColor)
-- set the color of the Text member
pMyMember.color = pDefaultColor
end
The code has safely converted a symbol into a color object that it can use through the rest of the behavior.
on turnOffRollover me
if pLastRollover <> 0 then
-- make sure that we set any selected line back to the selected colour instead
if pLastRollover <> pLastSelect then
pMyMember.line[pLastRollover].color = pDefaultColor
else
pMyMember.line[pLastRollover].color = pSelectedColor
end if
end if
end
on setLine me, thisLine
if pLastSelect<> 0 then
pMyMember.line[pLastSelect].color = pDefaultColor
end if
pLastSelect = thisLine
pMyMember.line[pLastSelect].color = pSelectedColor
end
Check out the Director 7 sample movie (Mac or PC format) to see the full behavior.
Wrapping up
This system can also be used in other handlers and objects in your movies. The only concern in using this color system in anything other than a behavior is that you will need to check that a color exists before you try to use it. Since the behaviors are only displaying color names that are in the system, there isn't any need for error checking. This wouldn't be the case if you were just passing a symbol that was hand-coded. The current code will return VOID if the color doesn't actually exist in gColorList.
There are some ways that you can expand this system. One example that comes to mind immediately is that the color names could be stored in an external text file that is read in when the movie starts. This would make authoring use problematic, but you could always write code that checks for the existence of an internal Field or Text member and, if it doesn't exist, would read in the color names from an external file. This would make sharing colors among a team of developers very easy.
Have fun with the code, and don't forget that there are always methods that you can use to get around some of the inconveniences in Director.
Copyright 1997-2025, Director Online. Article content copyright by respective authors.