一、引言

Scratch,作为世界流行的青少年编程语言,其对青少年智力的开发及计算思维的培养根本毋庸置疑。既然定位在青少年,那就不能复杂,但是,又要遵循“低门槛,高上限”两个基本特征。让青少年尽快入门的同时,又可以让部分能够深入钻研的同学深刻体会到编程的创造性、复杂性及内在乐趣。而后者,在全国青少年编程竞赛与等级考试中肯定要体现出来——自然也是体现参加者作品创意与难度的所在。

Scratch开发者恰当地把握了上述要求与特征:最基础最重要的计算机编程语言特征必须具备,同时又尽可能巧妙地“屏蔽”程序设计算法及内在原理的复杂性,在界面设计上追求“极简且稳定第一”的风格——就像小朋友手中的真实积木,任意摔打而毫无问题。

我们知道,Scratch程序运行原理上是基于事件驱动的,这一点学起来并不复杂。但另一方面,多线程编程及其同步技术这种“高上限”又无法回避——这是开发复杂应用程序最实用但又最复杂的技术之一。

还是那句话,Scratch绝不是玩具式语言!下面通过实例来说明问题。

二、问题需求

在本文中,我们想使用Scratch开发一个如图所示的小程序。

Scratch克隆技术、多线程编程及通讯技术初探

在三消游戏、卡牌游戏中经常出现本程序中的需求,即按指定矩阵排列规律在屏幕特定区域布局精灵。为了简化问题,我使用了大写的26个英文字母来做试验,如图所示。
为了朋友们看起来方便,有些基础性的准备工作我简单介绍一下。

为程序准备大小相同的26个英文字母角色

手段多样。我使用的办法是结合Scratch的导出功能与Photoshop联手搞定。


有兴趣的朋友高度推荐自学一下Photoshop,这个软件类似于日常办公中的Office软件,在日常图形图像处理中功能极其强大!一经学会,终生受益!


总体步骤如下:

【第一步】Scratch中内置的角色库的”字母“类型中已经提供26个字母的造型,如图所示:
Scratch克隆技术、多线程编程及通讯技术初探

【第二步】添加一个空白角色,切换到造型编辑器(或者称图形编辑器)状态下,从内置角色库中把上述字母造型逐个按序加入到本角色中,参考下图:

Scratch克隆技术、多线程编程及通讯技术初探

【第三步】仔细观察,这些字母的造型大小并不一致(我上图中给出的是经过我加工的,所以大小一样),而且大小差距不少。如果根据我们上面程序要求,非常有必要把它们调整得一样大小。怎么办?

考虑到系统内置造型编辑器的有限功能,想到:把它们导出,然后用Photoshop统一处理。请参考接下来的步骤。

【第四步】先在造型编辑器中把上述造型逐个转换成位图类型。注:默认添加的字母造型是矢量的,只能导出为.SVG格式。在转换成位图类型后即可把它们导出成.PNG格式,然后使用Photoshop处理。有兴趣的朋友,可以分析一下直接使用矢量编辑软件如CoreDraw或者Illustrator等直接处理.SVG文件。因为我的Photoshop是CS6,默认不能直接处理.SVG,所以我先转换成位图类型。
【第五步】在得到位于同一文件夹下的26个.PNG文件后,可以很轻松地使用Photoshop把它们批量修改成同一尺寸(有兴趣的朋友可以参考我本文稍后整理的短博文,在此省略介绍)。考虑到Scratch的创作屏幕为480X360像素,所以,我把每一个字母的大小修改成30X34像素大小。

为了度量方便性,这里还有一个小技巧是:我把背景图替换成了Scratch系统内置的背景图,名字为Xy-grid-30px。

克隆技术的需求

针对上面如图所示的字母排列,不少中学信息学教材中介绍逐个角色方式创建,并逐个排列到屏幕上。这种方法是最原始、最简单、最直接的,但往往也是最有问题的方法。不妨设想:在拥有几十甚至上百个关卡的卡牌或者消除类游戏中,如果使用这种排列方法,麻烦大了!
显然,最好的办法是利用Scratch中的克隆技术:创建一个角色,并让这个角色拥有上面26个英文字母的26种造型。那么问题来了:

【问题1】如何利用克隆技术?
【问题2】如何有效管理这些造型并适时地动态调整?


克隆对应的英文单词是“Clone”,意思是完全复制。在Scratch编程中,克隆不仅复制母体的所有静态属性,还复制包括执行代码在内的一切。在游戏软件开发中,常常要求相同的角色的大量不同的副本,而且要求这些副本表现出与母体完全相同的行为;例如,大量的僵尸与小鱼、无数的雨点与雪花……
因此,巧妙地使用克隆技术,能够生成大量相似或相同表现的角色,并极大地简化程序、特别是游戏软件的开发过程。
【注】在本文中,我们把作为克隆种子的角色统一称为“母体”,把每一个克隆生成的角色简称为“克隆体”,理解与搞清“母体”与“克隆体”之间的关系是克隆技术编程的关键。


克隆技术使用的情形

例一:生成天空中的雨点

下面的两段代码针对故事中的“雨点”角色编程:
第一段
Scratch克隆技术、多线程编程及通讯技术初探

第二段
Scratch克隆技术、多线程编程及通讯技术初探

【技术总结】此处代码的特点是:母体角色(只有一个)只负责生成克隆体角色,各种属性的设置统一由克隆体自身处理。这属于克隆技术编程的最简单的情形。这种情形下,第二段代码可以尽情使用程序中定义的全局变量,但要注意各克隆体之间使用上不应出现冲突。
【问题】这两段代码属于两个线程,这两个线程的执行看起来没有什么共享数据或者同步要求,所以,这种情形下的克隆编程是比较简单的。


多线程、多进程编程是几乎每一个中级程序员必须面对和要克服的难题,Scratch后台系统的特征与实际程序开发的需求要求它不得不引入多线程技术,尽管Scratch竭力巧妙地掩盖这种技术,但这些技术本身具有的复杂性在使用Scratch编写比较复杂的程序时必然(而且已经)彰显出来且需要克服。值得庆幸的是,Scratch开发者早已预料到这一点,并已经给出了圆满的解决方案。

例二:生成三只有规律排列的小猫
先看下图:
Scratch克隆技术、多线程编程及通讯技术初探

【问题一】哪一个是母体?答案见下图:
Scratch克隆技术、多线程编程及通讯技术初探

即最右边一个是母体!
截止到目的,我们对克隆技术使用要求是生成满天的雨滴或者是三只猫,并没有要求区别母体与克隆体!但是,有些,甚至是更多,应用情况下要求区别对待母体与克隆体(甚至是各克隆体之间也要区别)。
【问题二】要想使左边第一只猫成为母体,怎么办?

先看下面的图示:
Scratch克隆技术、多线程编程及通讯技术初探

结合上图,得出目前结论是:克隆体总是由母体生成的!

因此,要解决上面【问题二】要求在克隆体内修改坐标,而不能在母体代码中修改坐标(母体代码中修改总是修改母体角色的属性值)。自然,要实现“在克隆体内修改”这种目标,于是积木命令Scratch克隆技术、多线程编程及通讯技术初探登场!

那么,看接下来的这段代码及结果图示:

Scratch克隆技术、多线程编程及通讯技术初探

这段代码也好理解不是?【问题二】的答案初步有眉目了!但是,新的问题又出现了:

【问题三】第三只(最右边)小猫(克隆体)如何生成?

如果相当然修改,可能会有如图结果:

Scratch克隆技术、多线程编程及通讯技术初探

有关克隆体代码(积木【当作为克隆体启动时】所属代码)中继续克隆自己的问题暂时不讨论(涉及到克隆递归及最大递归深度的问题)。

继续修改,尝试如下代码(与结果图):
Scratch克隆技术、多线程编程及通讯技术初探

细心的朋友容易观察出,克隆母体两次,这两个克隆体都调用了积木【当作为克隆体启动时】所属代码,所以结果是:这两个克隆体小猫的位置是重合的。

再想办法(结果还是不行!):

Scratch克隆技术、多线程编程及通讯技术初探

不再纠缠下去了,其实,解决上面的【问题二】的办法是把两次克隆体代码区别开来,即区别哪是第一次克隆哪是第二次克隆,问题就好办了。于是,我们想到下面的解决办法:
Scratch克隆技术、多线程编程及通讯技术初探

运行结果如下:
Scratch克隆技术、多线程编程及通讯技术初探

注意到,这种方案具有代表性,即如果生成N个位置的X只小猫的话,只要区别这X次克隆问题就迎刃而解了。
上面提供的办法运用到了在克隆体执行代码中使用全局变量的功能,其实,大家顺着这个路子还会想出更复杂的与克隆有关的问题来。

有兴致的朋友可以考虑:解决上面【问题二】是不是还有其他更好的办法?

三、26个字母排列问题

其实,在前面的举例中,都涉及到了多线程编程的问题。此时,一旦涉及到数据的共享访问(或者称数据同步),则必须设法解决其中同步的问题。现在,让我们回到本文开始第一张图所展示的问题上。

篇幅所限,我们仅考虑如图所示的这一种情形,其他情形类似,有兴趣的朋友可以参考本文程序在51CTO上的源码(突然发现要联系博客主管才能上传,争取明天解决这个问题)。下面直接给出代码并辅助一定的解释。

角色按钮3X8+2的代码

Scratch克隆技术、多线程编程及通讯技术初探

含义一目了然。

角色字母A的代码

(1)第一段

Scratch克隆技术、多线程编程及通讯技术初探
这是消息接收到后的初始化工作,注意全局变量“gv克隆体计数器”是用来区别各个克隆体使用的(当然,本例子仅是一个入门,并没有深入区别对待每一个克隆体的操作)。

(2)第二段

Scratch克隆技术、多线程编程及通讯技术初探

注意:本例子中为了数据管理的方便,引入了两个分别记录屏幕上按规律排列的每一个角色横纵坐标的两个列表,如图所示:
Scratch克隆技术、多线程编程及通讯技术初探

其中,“const横坐标列表”用来存储垂直方向平均切割成16份(30X16=480)每一个角色的中心点的横坐标,“const纵坐标列表”用来存储水平方向平均切割成10份(10X34+20=360)每一个角色的中心点的纵坐标。

上面代码开始时,母体定位,并初始化几个辅助变量,例如row和column分别用来记录屏幕上角色所在的行与列(正好也与下面的二重条件循环次数大致相对应——并非严格对应)。

接下来,变量“____克隆结束2”的作用是非常重大的。我们引入这个变量的目的是让程序严格地接次序生成屏幕上的克隆体角色(如第1个、第2个、第3个......)。
接下来,两个循环的循环次数含义不用解释了,分别对应行数与列数。当生成了第26个角色(Z字母)后,循环结束。这里循环结束前的“隐藏”积木调用的作用是,用于隐藏母体!!!请各位根据前面克隆体与母体的举例仔细体会。

row和column在循环体内的修改用于控制屏幕上行数与列数。
接下来,看克隆体执行部分的代码。

(3)第三段

Scratch克隆技术、多线程编程及通讯技术初探

语句Scratch克隆技术、多线程编程及通讯技术初探的作用是,等待上一个角色操作结束(可能是很长时间,本例中时间是非常短暂的)。

接下来的语句Scratch克隆技术、多线程编程及通讯技术初探作用是,按字母顺序生成屏幕上的角色。

接下来,根据两个数组对应坐标值来修改当前角色的坐标值。
最后,克隆体计数加1,并设置克隆操作结束的标记。

总体来看,在本实例中,协调主程序与克隆体运行代码(其实这是典型的两个线程)的变量“____克隆结束2”的作用功不可没。

小结

在本文中,我们还只是初步探讨了Scratch多线程编程通讯技术与克隆体技术编程的部分内容,并未涉及到全部。但是,相信朋友们已经觉察出Scratch并非市面上谈论得那么“简单”。也正因如此,Scratch(及其各种“克隆体”)才成为风靡世界的青少年编程语言。在以后的文章中,我还会给出更多的有关使用Scratch开发创新性应用与游戏的实例,并进一步探讨Scratch编程的奥秘,敬请期待。不足之处,欢迎广大同仁与青少年朋友批评指正。