|
|
用Director 创建绘画程序
为儿童制作的常用教学软件是绘画软件。这些软件大都是基于早先为7 0 、8 0 年代的计算
机设计的简单的绘画软件。
最基本地,用户可以在屏幕上点击或拖动鼠标绘画。.在本例中,用户还可以选择不同颜
色和不同画刷。
用一个角色绘画实际上是非常简单的。所需要做的就是让角色跟随光标走,同时打开它
的t r a i l s 属性,这样当用户移动鼠标时,角色的印迹就一路留了下来。但是,这种方法有一个
缺陷。如果用户移动鼠标的速度过快,角色的印迹将不能连续,导致在各个印迹间出现空隙。
但我们用笔在纸上绘画时却不是这样的。因此,本程序必须很好地处理鼠标移动过快的情况,
在角色的两个印迹间放置平滑的线条。
图2 是这个影片实例。窗口的左侧有一些颜色块,当前选中色被用方框标示出来。它
们的下方有一些画刷,当前画刷也用方框标出来。主要绘画区域开始是空白的,用户可以在
其中绘画。
还有一个大型的行为驱动该影片,但它不是在角色上,而是在帧剧本通道内。这是有必
要的,因为用户并不是通过在角色上点击来绘画的,而是在舞台上的窗口区域点击而绘画的。
因此,帧剧本通道是放置该处理程序的合理位置。
不过绘画时仍需使用一个角色,因此绘画是否正在进行就是它的一个属性。有一个属性决
定当前是否正在绘画,另一个属性记录上一个印迹的位置和用来绘画的演员,即画刷( b r u s h )。

图2 在这个简单的绘图软件中,用户可以改变画刷及其颜色。
用大一点的画刷和白颜色,可以起到橡皮擦的作用
property pDrawSprite -- which sprite to use
property pDraw -- drawing in progress
property pLastDrawLoc -- last spot drawn on
property pBrush -- member to use to paint
on beginSprite me
pDrawSprite = 7 -- use sprite 7
pLastDrawLoc = 0 -- no last drawing loc
pBrush = sprite(pDrawSprite).member
end
当用户在舞台上点击时,绘画就开始了。所需要的只是把p D r a w 属性设为T R U E 。不过,
向用户表明当前所用的画刷也是有用的。.因为所有画刷都是小于1
6 ×1 6 的1 - b i t 位图,所以用
c u r s o r 命令可以把它们用作光标:
on mouseDown me
pDraw = TRUE
cursor([member pBrush, member pBrush])
end
当用户释放鼠标按钮时,绘画即告结束。不但p D r a w 属性应复位,而且画刷也应当从屏幕
上消失。由于用户再次绘画的位置可能与刚才完全不同,p L a s t D r a w L o c 也被复位,这样就不
会发生把本次绘画的最后一笔与下次的第一笔用线连结起来的现象。光标也要复位。
on mouseUp me
sprite(pDrawSprite).loc = point(-100,-100)
pDraw = FALSE
pLastDrawLoc = 0 -- forget last drawing loc
cursor(0)
end
实际上绘画是由on exitFrame 处理程序完成的。这意味着屏幕在每一帧都要被更新。由于
我们希望该绘画软件的画面运行得尽可能流畅,因此应把影片的节奏调节到最大值:9 9 9 f p s 。
该处理程序也要检查鼠标的位置是否在特定的边界内。在此之后,它调用on drawLine 处
理程序模块,把用户所画的上一点与当前点间的空隙填满:.
on exitFrame me
if pDraw then
-- get current location
curLoc = the mouseLoc
-- restrict draw area
if curLoc.locH < 60 then curLoc.locH = 60
if curLoc.locH > 480 then curLoc.locH = 480
if curLoc.locV < 35 then curLoc.locV = 35
if curLoc.locV > 320 then curLoc.locV = 320
-- if there is a last location
if (pLastDrawLoc <> 0) then
drawLine(me,pLastDrawLoc,curLoc)
-- if not, then just draw a point
else
drawLine(me,curLoc,curLoc)
end if
-- new last location
pLastDrawLoc = curLoc
end if
go to the frame
end
on drawLine 处理程序很简单,只是包含大量的运算。它使用d i s t a n c e 函数去确定这两点相
距有多少像素,然后按距离值除以2 得到的值进行循环,这样即每隔一点画一点。如果画刷只
有1 个像素大,这样画将会画出像虚线那样的线,但由于画刷都比1 个像素大,隔一点画一次
得到的线与原本应画的线相比就看不出有什么不同了,但提高了绘画的速度。
在循环中每走完一步,角色的位置离鼠标的最新位置就更近一步,离鼠标的前一个位置
就更远一步。u p d a t e S t a g e 命令将用来把角色的印迹放到舞台上:
-- this handle will draw a line of dots from one point to the next
on drawLine me, loc1, loc2
sprite(pDrawSprite).trails = TRUE
-- how many dots to draw
numSteps = float(distance(me,loc1,loc2))/2+1
-- repeat and place dots
repeat with i = 1 to numSteps
sprite(pDrawSprite).loc = loc1*(numSteps-float(i))/numStep ┐
+ loc2* (float(i)/numSteps)
updateStage
end repeat
end
这个简单的d i s t a n c e 函数根据两点的位置计算出其间的距离,以像素为单位。该值被用在
on drawLine 处理程序里,以确定在画刷的两个点之间有多少步:
-- calculate the distance between two pixels
on distance me, loc1, loc2
return sqrt(power(loc1.locH-loc2.locH,2)+power(loc1.locV-loc2.locV,2))
end
由于这个帧剧本行为似乎控制了这部影片的功能,我们不妨把一些辅助操作也放在其中。
下一个处理程序以一个颜色对象作为参数,并把绘画的角色的颜色设为那种颜色。由于画刷
都是1 - b i t 的演员,它们将采用角色的颜色,并从那一点起用这种颜色绘画。不过,需要由一
个外部处理程序来调用该处理程序,以执行颜色的改变。这是附属于图2 7 - 2 中的色块的剧
本。
-- accept a color and change to it
on changeColor me, color
sprite(pDrawSprite).color = color
end
使用同样的思路,下一个处理程序从左边那些画刷的剧本中接受一个新画刷演员。除了
改变该演员的编号,还需要设定p B r u s h 属性。
-- accept a brush member and change to it
on changeBrush me, brush
sprite(pDrawSprite).member = brush
pBrush = brush
end
影片的主要行为就这么多。现在万事俱备,只是当用户想要改变颜色或画刷时,却没有
任何反应。要实现这一点,需要给色块和画刷图标附带上很短的行为。下面是一个用于色块
的行为。它把指定给那个角色的颜色传送给帧剧本中的主要行为,即Sprite 0 。它还向所有角
色发出一个#change ColorSprit 消息。该消息用来重新设置表示当前颜色的那个方框。
on mouseDown me
color = sprite(me.spriteNum).color
sendSprite(sprite 0, #changeColor, color)
sendAllSprites(#changeColorSprite,me.spriteNum)
end
on changeColorSprite 处理程序应该位于一个小型的行为里,该行为附属于包围着当前颜
色的那个小方框。当s e n t A l l S p r i t e s 消息被发出后,所有的角色都能收到它,但只有这一个使
用它。这意味着该角色可以被放在任何地方,而不需要我们设置带有它的编号的全局变量。
该处理程序取得当前色块的矩形,再向四周各扩展3 个像素。色块外的方框随即被设置为
这个新的矩形,使它能够包围着当前色块:
on changeColorSprite me , colorSprite
sprite(me.spriteNum).rect = ┐
sprite(colorSprite).rect + rect(-3, -3, 3, 3)
end
下面是一个为画刷编写的行为。它告诉主行为画刷已经改变了,而且向其他所有行为发
出一个# c h a n g e B r u s h S p r i t e 消息,使得画刷外方框角色能识别它,并做出反应:
on mouseDown me
brush = sprite(me.spriteNum).member
sendSprite(sprite 0, #changeBrush, brush)
sendAllSprites(#changeBrushSprite,me.spriteNum)
end
画刷外方框的行为与色块外边框角色的行为几乎相同:
on changeBrushSprite me , colorSprite
sprite(me.spriteNum).rect = ┐
sprite(colorSprite).rect + rect(-3, -3, 3, 3)
end
C l e a r (清除)按钮的剧本也很好写。它的任务是清除所有由画笔的笔迹产生的图画。这是
用一种奇特的方法实现的:重设舞台的颜色。最好的方法是不做任何改动而设置一遍舞台的
颜色。结果是所有笔迹都消失了。
on mouseDown
the stageColor = the stageColor
end
最后,应该用一种方法阻止用户在绘画时点击标题条或工具条区域。由于帧剧本只在角
色接收了m o u s e D o w n 消息以后才能再接收该消息,我们所需要做的就是写一个简单的剧本,
让它吃掉所有m o u s e D o w n 消息,使得主体行为永远都接收不到这些消息:
on mouseDown
-- no action
end
把这个行为赋给所有应该处于非激活状态的角色,如屏幕顶端和左侧的灰色的矩形区域。
看一看C D - R O M 上的这个实例,以了解这些代码是如何结合在一起的。要做修改也是非
常容易的。我们可以通过编辑画刷的位图来改变它们。改变颜色也很容易,因为我们可以改
变舞台上的色块的颜色。也可以添加更多种颜色和画刷,方法是添加更多角色,并为它们附
带上正确的行为。
对该程序的一个重大改进应该是添加允许用户存储他们的作品的方法,可以使用( t h e
s t a g e ) . p i c t u r e 把画面放在一个演员内,并用c r o p 命令裁去标题部分和工具条部分。可以使用
s a v e M o v i e 命令把这些改动存储在影片文件里,或使用s a v e c a s t L i b 命令存储一个含有该图像的
演员表库,并随后把它带回该程序。
参见第1 8 章“控制位图”里的1 8 . 3 节“处理位图演员”,可以获得更多有关影响位图演员
的L i n g o 命令的信息。
|