最新一直在做少儿编程方向的创业,用到了scratch 3.0,在这里简单分享一下其原理。
什么是 scratch 3.0?
Scratch是美国麻省理工学院的“终身幼儿园团队”开发的一款图形化编程工具,通过点击并拖拽的方式就能完成编程,可以帮助儿童或成人初学者更好地学习编程的基础概念等。
Scratch1.0在2007年第一次公开发布,随后在2012年又推出了Scratch2.0版本,而Scratch3.0则是2019年的1月初正式推出的。
Scratch 3.0 采用了HTML5和JavaScript技术来编写,支持所有的现代浏览器和WebGL,能够跨平台使用。
Scratch 在github上有一系列的开源项目,其官方的开源scratch 3.0的编程网站的源码地址为:https://github.com/LLK/scratch-gui.git
系统构成
官方网站的主要界面和我们的很类似,因为我们就是基于它二次开发的: scratch.png可以看到,从左到右,依次为代码块区,编辑区和展示区,对于其使用,本文就不展开了,重点放在其原理上。
技术架构
主项目的目录结构如下:
├── build # 默认编译后的文件夹
│ ├── static # 静态资源
│ ├── index.html
│ ├── gui.js
│ ├── lib.js # 编译后主要的js文件
├── src
│ ├── components # UI组件,负责页面呈现
│ ├── containers # 容器组件,承载容器组件业务逻辑
│ ├── css # 全局通用css
│ ├── examples # 集成测试用例
│ ├── extensions # 拓展案例
│ ├── lib # 插件及高阶组件
│ ├── audio # 声音插件
│ ├── backpack # 背包插件
│ ├── default-project # 默认项目
│ ├── libraries # 素材库相关
│ ├── video # 视频模块
│ ├── playground # 编译后页面的模版
│ ├── reducers # 全局状态控制
├── test # 测试用例
├── translations # 翻译库
├── README.md
└── package.json
└── webpack.consig.js
通过查看其源码的package.json,我们可以看到,它是基于react 技术栈开发,核心依赖包有:
- scratch-vm:虚拟机,管理状态并执行业务逻辑
- scratch-blocks:代码积木块
- scratch-l10n:国际化
- scratch-paint:绘图拓展
- scratch-render:舞台渲染,在舞台区域出现的基于WebGL的处理器。
- scratch-storage:作品存储加载
- scratch-svg-renderer:svg处理
- scratch-audio:声音拓展
其核心原理就是用scratch-blocks生成语句块后,用scratch-vm 虚拟机抽象成底层语法,最后再调用scratch-render 和scratch-paint渲染到界面,而scratch-audio主要用于音频的剪辑处理。
关于 scratch-vm
Scratch-VM提供了一套Scratch-Blocks运行环境,因此VM定义很多丰富的外部库,在初始化时,需要对VM进行初始化,然后再定义Scratch-Blocks,最后对Blockly和VM进行事件绑定和监听:
- 定义VirtualMachine,VirtualMachine是Scratch-VM的核心入口类;
- 为VirtualMachine设置ScratchStorage、ScratchSVGRenderer、S- cratchRender、AudioEngine等辅助工具;
- 定义工作空间,设置Blockly,使用inject方法,加载Scratch-Blocks;
- 为工作空间设置相关事件;
- 为VirtualMachine设置相关事件;
- 开始运行VirtualMachine。
scratch-vm 的一些概念
- sb2/sb3文件: Scratch VM能解析的文件类型,sb2为Scratch2.0项目文件,sb3为Scratch3.0项目文件;
- Sprite角色:需要执行动画效果的对象,对象可以是svg、图像;
- Clone克隆: 有时候角色动画需要复制自己本身,会产生许多虚拟的角色,称为克隆;
- Costume造型:角色会有多个外观,就像人有很多衣服一样,一个角色有多个造型,造型会让角色更加生动;
- Target目标:角色属性信息(基础信息、脚本信息),譬如角色的位置、方向、放大缩小、特效等等。每个Clone体对应一个Target;
- Stage舞台:角色在舞台上完成动画,舞台是一个特殊的角色;
- backdrop舞台背景:舞台会有多个外观,称为舞台背景
- Thread线程:角色产生动画,需要运行环境,Thread线程就是角色的运行时环境;
- Drawable图形:每个角色的Clone体,会对应一个Target,Target目标真正的绘图对象实际上是Drawable;
- Sound声音:声音会让动画更加生动,Sound角色的属性,由所有Clone体共用;
- effects特效:角色有很多造型,使用特效,可以在造型的基础上,修改造型外观,特效有color,fisheye,whirl,pixelate, mosaic,brightness, ghost;
- IO输入输出:Scratch-VM将键盘、鼠标左右键、鼠标滚轮、设备连接等定义为IO,键盘、鼠标左右键、鼠标滚轮等提供统一的IO输入,设备提供蓝牙和BT连接,使用socket与Scratch-link进行通信;
- 刷新率:每秒钟动画绘制的次数;
- 时间片:一次绘制分配给scratch运行的时间,时间片到期自动放弃运行等待下一次运行,正常情况下,刷新率为每秒60次,时间片为1000/60ms;
- StackFrame栈桢:线程运行时栈空间,通常情况下栈空间只有一个栈元素,当遇到循环、事件、函数时,会保留当前栈桢,新增一个栈桢;
- ExtensionManager扩展积木管理器:扩展积木的管理,Scratch自定义了一种积木的定义方法,将自定义的积木转为json,供blockly解析,所以我们定义积木时,可以在扩展中定义。
vm调用过程
- 加载时先全局加载虚拟机,初始化虚拟IO,然后查看网络请求中location对象的hash,如果不能识别,直接在本地新建工程,并为工程赋予唯一ID值;
- 如果能识别,从网络或从本地加载工程,将舞台推入render生成渲染器,再把渲染器推入vm;
- 然后调用Blockly.inject函数在一个dom(类似于div#id)上面,初始化workspace和flyoutWorkspace样式
- 接着为workspace建立blockListener(在vm/engine/block中定义,为block建立的通用(非特定)动作函数,如move,delete,create,click什么的,特定的block动作函数的加载);
- 然后为flyoutWorkspace建立flyoutBlockListener(所以如果需要给所有的block加统一的事件(仅限field和mutation)可以在这里添加 engine/blocks.js)
深入 scratch-vm
- vm是在containers/gui.jsx中启动的,scratch中components是纯函数组件,而在containers文件夹中会把同名components与redux和vm连接,同时进行国际化,组件节流,版本控制,虚拟IO监听等操作,逻辑非常清晰;
- 所有ui状态在reducer/gui.js中进行组装然后统一导出,但是要注意scratch根目录下的index.js是个假的入口文件,reducer真正是在lib/app-state-hoc中的AppStateHOC类组装的,这是一个中间件;
- 在入口函数render-gui中GUI组件使用compose函数进行柯里化(将f(a)(b)(c)(d)变成f(a,b,c)(d)叫做柯里化)封装了AppStateHOC, HashParserHOC,TitledHOC三个中间件;
- AppStateHOC通过判断是否需要加载paint和gui来加载不同的store,因此<Provider>也在这个组件当中,guiMiddleware是一个封装了throttle的柯里化函数,按照中间件模式调用,用于为组件节流(如果一秒内点了很多次,只会执行两次),封装之后返回了经过国际化(多语言模块)和节流处理的高级组件;
- 当vm启动时,在runtime入口定义了defaultBlockPackages类,这里面声明了每个block块的功能函数(比如moveTo);
- 在vm/engine/runtime 715行,有一个_registerBlockPackages函数,会加载所有block块动作;
- 然后,通过声明基类函数getPrimitives取得各个模块中的block预定义动作,之后通过订阅分发(路由模式)的方式生成packagePrimitives类,这样block块的特定功能就都可以在vm中使用了;
- 当有点击事件发生时,vm-listener首先捕获事件,然后把消息推送至虚拟机,虚拟机会定位到对应的sprite或者带有hats的sprite,执行注册的函数;
小结
整个scratch 项目的源码非常庞大,有很多我还没有时间和精力进行总结,期待项目能够顺利推进,然后投入更多的研发,然后才能进一步深入。
我曾打算一个人使用vue 3 重写整个gui,但是没有经费支持,业余时间,个人的话真的有点力不从心。
网友评论