
许式伟:Scratch vs. Go+ Spx STEM 引擎详细对比
一. Go+ Spx 引擎概要
1. Go+ 与 Spx 的关系
介绍概要前,需要先说明一下 Go+ 与 Spx 的关系。总结下来是两句话:
技术上,Spx 是 Go+ 的某种 ClassFile
业务上,Spx 是 Go+ 面向 STEM 教育领域的解决方案
ClassFile 这个概念大家比较陌生,但实际上 ClassFile 是 Go+ 中非常核心的一个概念。 Go 语言之父 Rob Pike 曾经发表过一个观点,如果只能把 Go 的一个特性带到其他语言,他不会选择 goroutine,而是会选择 interface。很多人对这个选择会感觉诧异,毕竟 goroutine 是 Go 语言中最有特色的一个功能。但我个人非常认可他的选择,如果是我也会做同样的决定。 原因在于,goroutine 在其他语言中是有机会自己完成实现,但实现 interface 会比较难。interface 会涉及到社区共识的问题,不完全是一个纯粹的技术问题。 社区共识的概念比较「虚」,完全「技术思维」的人可能不太能完全理解这一点。但了解 Go 社区核心理念的小伙伴一定知道,Go 是最在意社区共识问题的一门编程语言,包括代码的强制格式化等都是源于社区共识。 之所以分享 Rob Pike 的这个故事,是因为 ClassFile 对于 Go+ 来说,和 interface 对 Go 的地位类似。我个人认为 ClassFile 是 Go+ 最重要的能力,没有之一!如果要我将 Go+ 的一个能力带到其他语言中,我一定会选择 ClassFile。对于 ClassFile,后续我们会设置单独的一期公开课详细介绍。 Rob Pike 此前说过 Go 是一门面向连接的语言 ,我想套用一下他的这个说法,Go+ 是一门面向连接的语言。之所以 Go+ 关注面向连接,背后是我经常讲到的一个趋势——软件会吞噬一切(行业)。未来所有的工种、职业都需要软件来解决自己的领域问题。但 Go+ 和 Go 面向的「连接」是不同的:
Go:连接代码/组件,关注大型软件工程
Go+:连接工种/职业,关注低代码与跨工种协同
最近 Go 官网进行了一次改变,最新的 slogen 是「Build fast, reliable, and efficient software at scale」,最后的「scale」其实代表着 Go 的关注点是如何编写大型软件工程。 Go+ 选择面向连接的概念有所不同,是连接工种和职业。我们关注的实际上是低代码和跨工种的协同,赋予 Go+ 的使命也是连接所有的职业,包括医生、老师、美工、策划等等。 对于这一点,可能大家会觉得有些奇怪。作为一门编程语言为什么需要连接所有的职业?正如刚才所说,所有工种都需要借助软件来解决各自领域的问题,如果每个领域间没有连接,便会形成一个个「孤岛」,这种情况是比较可怕的。未来软件在连接层面一定会面临更加巨大的问题。 从这个视角来看,Go+ 虽然基于 Go,但要解决的问题领域是完全不同的。今天很多和软件开发看似无关的工种,当未来需要拥抱软件去解决领域问题时,便会体现出连接的重要性,而这正是 Go+ 希望解决的问题。 下面是几个样例,包含最基础的「Hello world」工程。如果大家希望制作 Spx 的作品,可以结合样例展开。
Hello world 工程:https://github.com/xushiwei/HelloSpx飞机大战:https://github.com/goplus/AircraftWar ##al/09-AircraftWar围棋:https://github.com/xushiwei/BetaGo五子棋:https://github.com/xushiwei/Gobang
2. Go+ Spx 程序的框架
接下来是第一部分的重点,Go+ Spx 程序框架的介绍。 大家都知道,大部分的桌面编程框架都是基于 View 或者 Window 这样的对象,配合事件驱动。Go+ Spx 也类似,同样是基于对象的事件驱动模型。 Go+ Spx 和 Scratch 都是基于对象实现事件驱动的模式,它们的核心都是对象和事件。只是它们基于的对象不是 View 或者 Window,而是舞台(单个)、精灵(Sprite)和挂件(Widget)这三类对象。 其中舞台和每个精灵都有对应的 ClassFile,可进行相应的代码编程。
舞台的 ClassFile 实际上就是工程的入口文件:XXX.gmx(可任意设置,一般为 index.gmx)
精灵的 ClassFile 通常是:精灵名.spx
挂件可以在配置文件中声明,但没有对应的 ClassFile
理解这几个概念后,我们来看程序如何串联以及界面具体的绘制过程。 界面的呈现中,舞台位于最底层,可以认为是游戏的背景画面。舞台可以有多个场景(Scene),也可以设置当前场景。切换场景,就相当于切换背景图。 舞台之上便是挂件和精灵。每个精灵有多个帧,这和舞台有多个场景的概念很像,通过切换不同的帧的便可以变成帧动画,实现如猴子的行走与拍手等动作。 挂件可以理解为特殊的精灵,但如前文所说,挂件没有对应的 ClassFile。精灵和挂件都是由 zorder 来定义层次关系,整个的绘制过程也非常简单,按照 zorder 从下往上画就好了。 绘制界面的过程比较容易理解,但挂件的概念大家可能有些陌生,我举一个例子来说明。 Spx 未来可能会包含很多种类的挂件,但目前只实现了三类典型的挂件:
stageMonitor
measure
精灵对话框
其中 stageMonitor 这个名字的来由,在于其本质是通过舞台来监控一个变量,显示一个变量的值。比如定时器(timer)就是一个可被监控的变量。 measure 是一段标记了长度的标尺,这在数学中比较常见。 第三类是精灵对话。大家写完「Hello world」后,通常都会试着调用精灵来 say「Hello world」,say 出来后会出现一个对话气泡,比如上图中的气泡「I come from England.」。这类气泡也是一类特殊的挂件,不能单独配置在配置文件中,只有在调用精灵 say 时才会产生。 前面部分的内容,我们将「对象」的概念讲清楚了。第二部分我们来聊一下「事件」。 事件大体分三类:
程序开始事件:onStart
用户交互事件:onKey、onClick
对象交互事件:
onScene、onMsg、onCloned
onMoving、onTurning、onTouched
每个精灵在程序开始时,都会收到一个 onStart 的事件,在该事件中可以设置想要做的操作。用户交互事件主要指按下键盘以及点击鼠标后的事件。事件最多的是对象交互事件,也就是精灵与精灵间的交互,包括消息互动、对象的克隆、碰撞等等。 前 6 个事件都是 Scratch 中也包含的,而 onMoving、onTurning 以及 onTouched 是 Go+ Spx 相比 Scratch 额外增加的事件类型。后面我们会展开分析这三个事件的意义与用途。 下面我们来看程序工作的几个步骤:
1. 从配置文件(res/index.json)中读取整个世界的样子- 舞台有多少个场景,有哪些精灵与挂件- 根据配置构建出整个世界
2. 给舞台及所有精灵发送程序开始事件- 执行各个 ClassFile 里面定义的 onStart 代码
3. 如果用户发生交互(比如按了键盘,用鼠标点击了舞台或者精灵)- 执行对应的 onKey、onClick 事件的代码我们举个相对复杂的例子: 工程地址:https://github.com/xushiwei/FireBullets
大家都知道,飞机大战中的飞机需要发射子弹。那么,子弹是如何发射的呢? 上图中包含两个精灵,MyAircraft 是我的飞机,它一上来是显示的(这个由配置文件 index.json 定义),Bullet 是子弹,它一上来是隐藏的。发子弹整个过程对应的代码比较简单。我们先看左侧我的飞机部分,首先它执行 onStart 事件,进入一个 for 的死循环语句,每隔 0.1s 后克隆(发射)一颗子弹,同时让飞机跟随鼠标来运动。 子弹在 Bullet.clone 后会生成子弹被克隆的信息,对应上图中右侧的代码。这个例子中子弹的精灵只需要关心一个 onCloned 事件。在事件中它首先设置了自身的坐标为跟随飞机的坐标(其中 y 坐标偏离5个单元),并显示出来(发射出来)。接下来同样进入 for 的死循环语句,每隔 0.06s 前进 20 个单元,如果碰到边界便会销毁,这颗子弹的生命周期就结束了。 整个事件中代码量非常少,配合游戏的逻辑非常容易理解。
二. Go+ Spx vs. Scratch 详细对比
开始这部分之前,我还是先分享下为什么会做 Go+ Spx 和 Scratch 的对比,以及这个对比的价值所在。 首先是使用者的角度,将 Go+ 与 Scratch 的功能模块一一对照,方便使用者查询、对比。也建议 Go+ Spx 的开发者可以将这个对比表格打印出来放到电脑前(总共也就 8 页,不多),写程序时会比较方便; 从实现角度,贡献者可以清晰的看到哪些功能尚未实现,方便大家一起来完善和优化。 我个人非常重视这个表格,但不希望在 Go+ 的工程项目中涉及到任何与其他项目的对比,因此每次 release Spx 的版本后,我都会在自己的 GitHub repo 中(https://github.com/xushiwei/goplus-Spx-vs-scratch/)中更新对比表格,并保留此前每一个版本的对比表,进行长期的维护。 Go+ Spx 与 Scratch 的对比目前大致分为 8 类:
· Motion(运动)
· Looks(外观)
· Sound(声音)
· Events(事件)
· Control(控制)
· Sensing(感应)
· Variables(变量)
· Pen(画笔)
其实 Scratch 的功能模块共分为九大类,还包含运算(Operators)。但这一模块 Scratch 和其他工程类的语言相比属于「被秒杀」的状态,因此我们去掉这一模块的对比。
1. Motion(运动)
(对比表中标黄色部分内容,指现阶段存在缺陷或尚未实现) Motion 中涉及的方法较多,Motion 模块的功能 Go+ Spx 完成度相对较高,基本都已经实现了。其中未完成部分:
setRotationStyle 优先级最高
glide 只需要补充一个动画,相对容易
在 Motion 部分,Go+ Spx 整体要强于 Scratch。在 Scratch 中,step 和 turn/turnTo 默认是瞬移的,而 Go+ Spx 为这两项动作指令增加了动画配置,小朋友不需要通过复杂的代码实现,便可为这两类动作增加动画效果。
2. Looks(外观)
Looks 中有几个比较重要的类型。第一类是「切换帧」。因为一个角色会包含很多帧,通过切换不同的帧实现如走路、挠头、拍手等动作。 第二类是 say 或者 think。这两类除了气泡的外观外,基本是一样的。 第三类是特效。比如「植物大战僵尸」中子弹打到了僵尸,僵尸的模型会高亮一下,这就是特效的一种。 此外还有隐藏和显示,以及切换 zorder 等。目前 Looks 中大部分模块都已经完成,在未完成中:
为了方便 API 实现,think 目前的气泡和 say 一致,是对话,而不是 think 类型的气泡;
特效是未完成中的大头。待实现优先级最高的是颜色(color)特效和亮度(brightness)特效。
在 Looks 部分,Go+ Spx 也做了部分功能增强。做过游戏的朋友都知道,触发事件时执行一段帧动画是较为常见的。Scratch 中需要用自己用 for 循环语句来实现;在 Go+ Spx 中可以借助 animate 方法自动实现帧动画。比如「飞机大战」中飞机爆炸的声音与飞机爆炸的过程都可以通过 animate 方法结合预配置的动画便可以实现,而不用额外播放声音、编写爆炸的代码。
3. Sound(声音)
Sound 部分的类型主要包含声音的播放、声音的特效、音量以及一些乐器类功能。这部分 Go+ Spx 的完成度相对较低,主要就实现了用得比较多的声音播放相关的功能。 声音特效,乐器类、音量调节都相对较少会被使用。音量调节更不用说,可以靠系统自带的音量控制来实现。
4. Events(事件)
Events 是非常重要的模块之一。该部分 Go+ Spx 的完成度非常高,在未完成中:
未支持麦克风的声音大小(loudness)感知事件
未支持定时器事件(相对不重要,很多方法可实现类似功能)
总体来看,Go+ Spx 在 Events 模块比 Scratch 功能要强很多,增加了 onMoving、onTurning 事件,可以实现多个精灵间的协同。比如想要实现 Code Monkey 中猴子骑着乌龟一起走的小游戏,让猴子转身时乌龟也转身、猴子移动时乌龟也同步移动,便可借助 Go+ Spx 的 onMoving、onTurning 事件来完成。
5. Control(控制)
Control 部分主要是各类语法,比如 if 语句、for 循环等,都是比较常用的事件。Go+ Spx 除了完成了 Scratch 的所有功能外,还额外增加了几类 Scratch 中没有的事件功能。 其中最重要的是新增加了 die 方法。Scratch 中的 destroy 功能只能删除克隆体,不能删除本体,而 die 方法对本体和克隆体是都有效的(对于克隆体我们调用 destroy,本体调用 hide 隐藏)。另外,在 die 方法中我们还支持配置死亡动画,比如飞机爆炸的动画等都可以通过配置死亡动画来完成,而不用自己手工去做爆炸的帧动画。 像类似 die、step、turn 这些常用的行为,Go+ spx 都支持配置它们对应的行为动画。这是 Go+ Spx 对 Scratch 的一项较大的改进,也是特色功能,它让代码变得简洁。 另外 die 方法内部引入了「死亡模式」,让精灵进入一种特殊的状态。这时精灵在界面中仍然可见,可能正在播放死亡动画时,但它已经不会再与其他精灵发生碰撞,就像幽灵一样。
6. Sensing(感应)
Sensing 的概念有些「玄乎」,主要包含鼠标和键盘按击操作类的 API,但基本都是交互类或感知类的操作,比如调取精灵的 x,y 坐标、对象的属性、系统的当前时间、碰撞判断等等都在这部分当中。 在该类别中虽然包含很多标黄的部分,但其实完成度是挺高的,没有实现的基本都是非关键事件。在未完成中:
· ask/answer 优先级最高
· touchingColor 较为重要,但不太常用
· 定时器可由其他方式替代,完成只是为了可完整兼容 Scratch
在这部分中,Go+ Spx 增加了一项非常重要的事件功能 onTouched。Scratch 中有个比较复杂的问题是,两个精灵碰撞时会出现时序问题:精灵 A 认为自己碰到了 B,但精灵 B 却不认为自己碰到了 A。 我们仍以「植物大战僵尸」为例。当豌豆射手发射豌豆后,豌豆前进击中僵尸。这时豌豆判定自己碰到了僵尸然后消失了,但僵尸没有监测到自己碰到豌豆,因此没有掉血。这种情况下豌豆虽然不断击中僵尸并且消失,但僵尸因为没有掉血会处于无敌的状态。 这个问题在小朋友编写「植物大战僵尸」游戏时经常会碰到。而 Go+ Spx 中的 onTouched 事件,可以非常简单的解决这一问题。onTouched 的逻辑是主动碰撞方在判定碰撞发生后,会告知被动碰撞方,从而实现事件的一致性,解决时序问题,并且提升了碰撞检测的效率。 在 Scratch 中也自己解决这个时序不一致的方式。通常来说,豌豆碰到僵尸后不立即执行消失操作,而是借助 wait 方法等待一段时间,比如 0.1s,等待僵尸能够检测到碰撞后再消失,从而解决时序问题。具体应该等多久,这个是比较靠经验的,等多等少都会有问题。等少了,僵尸检测不到自己碰到了豌豆,等太久了则会让僵尸以为自己被豌豆打中了多次。 我认为这是一种另类的、不好的编程习惯,所以在 Go+ Spx 中不会推荐这种方式。时序问题还有很多,我们都希望可以通过类似 onTouched 的方式来解决,从而让小朋友们在学习过程中获得正确的思考方式。
7. Variables(变量)
Variables 对于 Go+ 来说是比较基础的功能,基本不需要额外进行支持,因此整体完成度非常高。在未完成的 API 中,showList、hideList 是尚未完成的,但因为 List 对小朋友来说本身就较难理解,把 List 对象显示在屏幕中就更少了,所以优先级不高。
8. Pen(画笔)
Pen(画笔)在 STEM 教育中是较早出现的东西,它类似 Logo 语言。大家都知道,有案可考的最早的 STEM 教育类作品就是 Logo 海龟作图了。 这部分 Go+ 的完成度也较高,除了画笔部分的属性配置未完成外,基本都已经实现。