有趣的Morphic图形系统

教程: 有趣的Morphic图形系统

作者:John Maloney
翻译: @ NirvanaStudio
原文地址:http://static.squeak.org/tutorials/morphic-tutorial-1.html

这篇教程要用在Morphic工程中。要建立一个Morphic工程,只要在Squeak窗口的背景上点击鼠标,不要再任何其他窗口上,出现了屏幕菜单。选择“open….”并选择“project(mophic)”。一个小的桔色的“Unnamed”Morphic工程窗口就出现了。点击窗口中间可以放大到全屏幕。(如果你不确定你在什么地方,只要探出屏幕菜单——如果标题是“World”,那你已经在Morphic工程中了)

首先我们需要建立一个浏览器“Browser”和一个工作区“Workspace”。先复习一下,在Squeak屏幕的空白处按下鼠标左键。在出现的屏幕菜单上选择 ”open…”。点击“browser”。一个绿色的浏览器就出现了。再次利用这个屏幕菜单,选择 ”open…” ,并选择“workspace”。

如果你是一个新手,刚刚看完前面的教程,你应该已经有一个叫“My Stuff”的分类。把浏览器最左边的列表拉到最底端,应该就放在那里。

如果没有,我们需要新建一个地方来添加你的东西。在浏览器的左上面板中,点击右键呼出菜单[Option-click] (在苹果机上 – 教程以下都将Mac苹果的指令以这种方式显示)选择“add item…”并输入“My Stuff”并按回车。把列表拖到最下面,你会发现它已经被选中了。

我们再次白手起家,建立一个程序。我们先从一个空的类开始,这里我们要让他成为Morph的一个子类。在Morphic对象系统中,Morph是一个通用的图形类。我们现在看一下建立新类的一个模版。修改成下面这样:

Morph subclass: #TestMorph
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'My Stuff'

首先选择“Object”并输入单词“Morph”(M一定要大写)。选择“NameOfClass”并输入“TestMorph”。新建立的类就会成为Morph的一个子类。删除实例变量名和类变量名的文字内容。按Alt+s接受。[Command-s]

现在我们来到工作区并新建一个我们刚刚建立的类的实例。输入:

TestMorph new openInWorld.

选择这行并按下Alt-d [Command-d]或者从右键菜单中选择“do It” [Option-click]

一个新的图形对象——一个蓝色的方块,现在出现在左上角。这样你就搞定了一个图形对象,虽然几乎没写什么代码。你可以拾起这个对象并移动它。现在先用鼠标把它抓起来。注意了,当你拾起这个Morphic对象的时候,他下面投射出了一个阴影。这个可以帮助你确定你已经确切的把他抓起来了,而且在其他任何物体之上。如果有对象覆盖在上面,抓起这个对象会自动地把他拉到最前端,这时投影就会显示它现在在其他任何物体之上了。

你可能注意到奇怪的大写方式,但是在Smalltalk中,一个选择器只能是中间没有任何空格的一个字符串。所以我们约定在每个英文单词的第一个字母大写,然后去掉中间的空格连起来。其他语言也常在单词之间使用下划线或者破折号。

halo labels.gif
现在按住Alt并单击 [Command-click]这个蓝色的东西,这时你会看见一组不同颜色的小圈环绕在周围,我们称之为“光环”。这些光环每一个都是向这个Morph对象发送指令的一种快捷的途径。如果我的鼠标停在任意一个圆点上,一个小小的帮助气球会弹出来告诉我这个圆点能做什么。这个黑色的点上的气球说:“Pick up”。也就是,如果我拖动这个黑色的把手,他可以像我直接拖动这个蓝色方框一样。这样可能看起来是一个多余的功能,但是因为可能有时候Morph对象会有交互的行为,比如按钮,这个把手让你可以直接操作一些直接和鼠标动作相关的对象。

这里一个基本的想法是,大多数系统,如HyperCard,依靠模式来改变对象的大小等等。也就是编辑一个对象针对使用它是分离的。在Mophics系统中,你可以在任何东西上面使用光环不用考虑他是如何活动的。光环是在它运行时的一种安全的处理方法。你不需要中止他的按钮功能或者是其他的东西。如果这个东西是一个按钮,你可以无需激活就可以编辑它,也不需要关闭它。

让我们来看看光环到底能做什么。比如这个绿色的,它会产生一个副本。粉红的光环中的X会将它删除(移动到垃圾桶中)。另一个常用的功能是改变它的尺寸,由这个黄色的实现。现在暂时不要对它做其他事情了,尤其不要使用蓝色的按钮(在对象的左下角)。

现在让我们开始制作我们自己的对象吧。我们先写一些方法来自定义它。

我要做的第一件事情是让我们的对象在你用鼠标点击他的时候做点什么。这里我们要实现两个方法。点击浏览器,选中第三个面板中的 “no message”。把下面的代码粘贴到底部的面板并接受,Alt-s[Command-s]

handlesMouseDown: evt
^ true

如果这是我第一次在这个映像中接受一个方法,我必须先输入初始值,这个值将会跟着我修改方法一直记录。这个方法,handlesMouseDown,告诉Morphic对象有一个对鼠标敏感的东西。

下一个方法我们要添加的是当鼠标按下的时候实际要做的事情。继续,把下面的代码粘贴进去成为另一个方法。

mouseDown: evt
self position: self position + (10 @ 0).

将在你点击它的时候,这个代码会让对象向右边移动10个像素。试着点击这个对象看看会发生什么。

在这时我们已经有了两个方法。一个返回“true”说我们需要响应mouseDown:事件。第二个是对鼠标按下事件做出的响应,移动了对象的位置。

现在我想给这个Morph对象一些行为来重新绘制它自己。把这个方法粘贴到浏览器中并用Alt-s接受 [Command-s].

drawOn: aCanvas
| colors |
colors := Color wheel: 10.
colors withIndexDo: [:c :i |
aCanvas fillOval: (self bounds insetBy: self width // 25 * i + 1)
color: c].

现在,当你点击对象的时候,你会看到对象的完全不同的外观。让我们回头看看这个方法究竟做了什么。

这个方法的第三行(代码的第一行)是给临时变量‘colors’赋值。如果你看到两个竖线和一列变量名,那这些变量就是这个方法的局部变量,通常可以叫做临时变量。在Smalltalk和Squeak中,你可以不用自己声明变量——如果确实有系统会自动询问你。而且我们也没有任何“类型”所以你不用声明任何类型。任何变量都可以存放任何对象。所以我们把表达式(Color wheel: 10)的值赋给‘colors’。要显示这个方法作了什么,选择‘wheel:’并按下Alt-m查看实现器。你会看到有两个实现——选择Color class的那个。

我们在Squeak中经常做的一件事情是在方法之前放上一小段注释。第一行说了‘wheel:’是干什么的。他返回一个thisMany colors的一个集合,thisMany是一个参数。这些颜色会均匀分散在色环上。这是颜色的光谱。

如果很方便可以给出这个方法的一个例子,这也是我们经常做的。这里第二个注释是一个表达式你可以实际运行的。选中引号中的内容并按下Alt-d。在屏幕的最上方出现了一条展开的色环。这时一个在光谱上间隔的一些颜色的集合。(但不是最大饱和也是最大亮度,所以他们不会太刺眼。

在这个世界里面我们可以直接在屏幕上涂鸦,但他之后不会自动清除。所以我使用了屏幕菜单的“restore diplay”菜单项来去掉那些画在屏幕顶部的东西。

了解“wheel:”的含义在这里有点偏离主题了。我们知道现在这是一些颜色的集合。点击标题栏左边的 “X”关闭窗口。

这个方法的下一部分遍历这个颜色集合。

"下面是drawOn方法的一部分:"
colors withIndexDo: [:c :i |
aCanvas fillOval: (selfbounds insetBy: self width // 25 * i + 1)
color: c].

withIndexDo: 消息同时提供了一个颜色元素“c”和一个索引“i”,让我们能跟踪我们在列表中的位置。第一次循环索引会变成1,第二次是2……如此继续,直到10。我们只需要知道我们在列表中的哪个位置并增加缩进,让椭圆每次更小。我们发出消息”fillOval:color:“并在带上加上了括号的参数——一个矩形,比外部的矩形尺寸上缩小了 (((self width)// 25) * i + 1)像素,由(self bounds)返回——第二个参数是一个颜色。作为索引,i增加,缩进也增加,每次返回一个更小的嵌入矩形,每个矩形里面我们都会画一个椭圆并用颜色c填充。

当我们再看这个对象时,我们看见了10条颜色。这个看起来很不错啊,同时由于使用了发送给自身的消息“width”,作为他的一个参数,它就可以知道它实际上有多大。用Alt-点击[Command-click] 呼出光环并拖动黄颜色的点。你就可以改变它的大小了,不管变成什么尺寸,他都会一直重新绘制自身。

我下一个想做的事情是点击他并调用一些动画。既然这样那么第一件要做的事情是建立一系列的点。当我点击这个对象的时候,我希望他会沿着每个点来回移动直到所有的点都走过了。为了这个想法,我需要做些工作

Morph subclass: #TestMorph
instanceVariableNames: 'path'
classVariableNames: ''
poolDictionaries: ''
category: 'My Stuff'

回到浏览器,并点击“instance”按钮。然后会出现TestMorph的类定义。然后点击实例变量名的位置,在单引号中间,并输入“path”。这个会成为一个新的实例变量来保存一个显示我们对象位置的点的列表。现在我要点击名称为 “as yet unclassified”的消息分类。

当我添加了一个实例变量,我要知道它应该初始化为什么内容。当我们给TestMorph定义这个消息:

initialize
super initialize.
path := OrderedCollection new.

这一行代码“super initialize”为什么出现在这里?虽然类TestMorph的定义看起来十分简单,但path并不是他唯一的实例变量。在浏览器的第二个面板中,右键点击出现一个菜单,选择“inst var refs”,你会看见一个列表,里边列出了这个对象从超类继承的其他变量。我这里不想选择其中任何一个。如果我做了,他会把所有用到这个实例变量的方法列出来,不过,你可以看到这里有自类Morph继承的6个实例变量。

“super initialize”给我的超类,Morph类发送了初始化消息。当你给接收器“super”发送消息“initialize”的时候,他其实是发送给“self”,但是要保证是被继承的initialize的版本被调用。

我们的初始化的中心是:“ path := OrderedCollection new”。它将一个空的集合赋值给path。现在当我的TestMorph接收了发送的intialize消息,他首先完成Morph要做的初始化,然后,他再完成它自己的初始化。结果是所有从Morph继承的实例变量包括我新添加的都被初始化了。

我们实际上在这里又有一个约定,一般不会自动给一个新的对象发送“initialize”消息。但是,Morph对象总是给每一个新的Morph实例发送初始化消息。

这个TestMorph对象,现在就在屏幕上,并没有对我们新的代码获得初始化。我们在定义了新的 initialize 之前就创建了他。让我们先把它删除并做一个新的。用Alt-点击 [Command-click]呼出对象的光环。点击里面带个 X 的粉红色圆圈来删除这个对象。他现在被扔进了垃圾筒了。现在我们执行下面的代码来搞一个新的TestMorph(也许这次就可以初始化好了):

TestMorph new openInWorld.

我们现在要当你在我们的对象上点击鼠标的时候,让他做点什么事儿。我们现在已经有了一个“path”集合。把下面的方法复制进去并且接受:

startAnimation
path := OrderedCollection new.
0 to: 9 do: [:i | path add: self position + (0@(10* i))].
path := path, path reversed.
self startStepping.

我们在第一行创建了一个新的 OrderedCollection。在下一行,我们作了10次计算并把表达式的结果加到变量path上。我们保存了相对于目前位置的一些点,”self position”,加上一个我建立的点。建立的点的X坐标是0,“@”(可以建立一个点)后面的,Y左边是i的10倍,“i”是循环的变量,从0到9每次增加1。所以,最终的效果是建立的10个点,Y坐标是从当前坐标加上(0的10倍)到(9的10倍)并把这些新的点添加到path集合中。

接下来一行,我们把path扩展为(path, path reverse)。“reverse”会将任何集合用相反的顺序放置。逗号(“,”)操作符是另一个消息选择器用来连接两个集合。

我们所做的是把一个10个点的列表,从0到90,并且附加上一个列表,从90返回到0。这样我们现在就有20个点,出发并最终回到原来的位置。

该方法的最后一行把我们的Morph对象登记在一个可以持续发送“step”消息的一个引擎中。这是动画引擎的秒针,是动画的心跳。现在我们必须让我们的Morph对象理解step方法:

step
path size > 0 ifTrue: [self position: path removeFirst].

这个方法很容易理解。它说只要在点的列表中还有东西,也就是只要path集合的长度大于零,就移动自己。给自己发送消息“position”,并将从点集合中删除的第一个点作为参数传送进去。他在做每一步的之前检查并确保表中确实有东西可以删除。

从图象上看,它会立刻跳到下一个点。

现在这些东西都已经设置好要准备运行了,但是,他并没有开始动画。好吧,那是因为我们并没有真正通过向他发送开始动画的消息,所以我们才没有把这个球踢出去。我非得要做的一件事就是要试着找出发送消息给这个对象的途径,其实有一个最方便的方法就是让他自己给自己发消息,当鼠标按下的时候。

mouseDown: evt
self startAnimation.

这里有两种途径可以定义这个方法。你可以在任何现有的方法上粘贴(在浏览器下面的面板中),或者你可以点击“mouseDown”在右边的面板中,并修改我们现有的mouseDown。其实当你接受的时候都是一样的。

当你完成了上面的,再去点击你的Morph对象,你应该能看到发生了一些东西,它动了,太好了~,但是,他动得十分十分十分得慢。哎~,我失望了因为我以为Sqeak是很快的。其实呢,是默认仅仅每秒钟才给对象发送一次“step”消息.但是对象可以决定它自己需要多久被通知要执行“step”,我们只要实现一个消息“stepTime”:

stepTime
^ 50

它返回对象要求的 step 消息的时间间隔(以毫秒计)。现在,明显你可以要求两次 step 消息之间是 0 秒,但是你不会成功。这里存在一个基本的判断。

现在如果我们再次点击我们的对象,我们就能看到一个快得多的动画。我现在把它改成 10 毫秒,看看能不能达到每秒 100 帧。让我们看看能不能成功。好的,我不知道我是不是真的达到了 100 帧每秒,不过他看上去确实很快。

你知道,我是给“动画公司”工作的。这个动画起始并立刻停止,同时每个人都知道还有“渐进”和“慢出”来让他看上去更优雅。这里有一个小小的改进,使用了“i”的平方来让动画开始的时候比较慢。

startAnimation
path := OrderedCollection new.
0 to: 29 do: [:i | path add: self position + (0@(isquared / 5.0))].
path := path, path reversed.
self startStepping.

我只更改了这个方法里面的一行。我们现在准备在path中建立30个点,然后附加上反转的,一共60个点,另一个需要更改的是替代10次。最后,表达式将会是“841 / 5”。

现在动画就是开始下落时比较慢并且加速然后在他回来时又慢下来。事实上,他看上去像某种球在弹跳。

现在是一个很好机会让你自己和预先制作的Morphic对象玩玩乐。我们先拿一个装满对象的容器。如果你的系统中启用了标签,点击屏幕底部的 “Supplies”标签。如果底部没有标签页,使用屏幕菜单并选择“authoring tools…”。选择“standard parts bin”。无论什么情况你都能获得一大帮对象你可以拖到屏幕上,在你拖出来一个之后,Alt-点击[Command-click] 呼出光环,并按住红色圆圈呼出对象的内容菜单(contents menu)。这里是一些Morph对象:

  • A RectangleMorph (内容菜单中, 试试 “raised bevel” 和 “insetbevel”)

  • An EllipseMorph

  • A StarMorph

  • A CurveMorph (内容菜单中, 试试 “show handles”)

  • A PolygonMorph (f内容菜单中 , 试试”show handles”)

  • A TextMorph (可以用黄色的把手把文字块加宽)

  • [ignore the gradient filled Sketch Morph for now]

  • [ignore the green PasteUpMorph]

  • 画家的调色板可以在你丢下的位置开始新绘制一幅图。画完后,点击“Keep”按钮把图案变成Morph对象

  • [ignore the StringMorph]

发表评论

电子邮件地址不会被公开。 必填项已用*标注