最新一直在做少兒編程方向的創(chuàng)業(yè)寻定,用到了scratch 3.0,在這里簡(jiǎn)單分享一下其原理精耐。
什么是 scratch 3.0狼速?
Scratch是美國(guó)麻省理工學(xué)院的“終身幼兒園團(tuán)隊(duì)”開(kāi)發(fā)的一款圖形化編程工具,通過(guò)點(diǎn)擊并拖拽的方式就能完成編程卦停,可以幫助兒童或成人初學(xué)者更好地學(xué)習(xí)編程的基礎(chǔ)概念等向胡。
Scratch1.0在2007年第一次公開(kāi)發(fā)布,隨后在2012年又推出了Scratch2.0版本惊完,而Scratch3.0則是2019年的1月初正式推出的僵芹。
Scratch 3.0 采用了HTML5和JavaScript技術(shù)來(lái)編寫,支持所有的現(xiàn)代瀏覽器和WebGL小槐,能夠跨平臺(tái)使用拇派。
Scratch 在github上有一系列的開(kāi)源項(xiàng)目,其官方的開(kāi)源scratch 3.0的編程網(wǎng)站的源碼地址為:https://github.com/LLK/scratch-gui.git
系統(tǒng)構(gòu)成
官方網(wǎng)站的主要界面和我們的很類似凿跳,因?yàn)槲覀兙褪腔谒伍_(kāi)發(fā)的:可以看到件豌,從左到右,依次為代碼塊區(qū)控嗜,編輯區(qū)和展示區(qū)茧彤,對(duì)于其使用,本文就不展開(kāi)了疆栏,重點(diǎn)放在其原理上曾掂。
技術(shù)架構(gòu)
主項(xiàng)目的目錄結(jié)構(gòu)如下:
├── build # 默認(rèn)編譯后的文件夾
│ ├── static # 靜態(tài)資源
│ ├── index.html
│ ├── gui.js
│ ├── lib.js # 編譯后主要的js文件
├── src
│ ├── components # UI組件,負(fù)責(zé)頁(yè)面呈現(xiàn)
│ ├── containers # 容器組件承边,承載容器組件業(yè)務(wù)邏輯
│ ├── css # 全局通用css
│ ├── examples # 集成測(cè)試用例
│ ├── extensions # 拓展案例
│ ├── lib # 插件及高階組件
│ ├── audio # 聲音插件
│ ├── backpack # 背包插件
│ ├── default-project # 默認(rèn)項(xiàng)目
│ ├── libraries # 素材庫(kù)相關(guān)
│ ├── video # 視頻模塊
│ ├── playground # 編譯后頁(yè)面的模版
│ ├── reducers # 全局狀態(tài)控制
├── test # 測(cè)試用例
├── translations # 翻譯庫(kù)
├── README.md
└── package.json
└── webpack.consig.js
通過(guò)查看其源碼的package.json遭殉,我們可以看到,它是基于react 技術(shù)棧開(kāi)發(fā)博助,核心依賴包有:
- scratch-vm:虛擬機(jī)险污,管理狀態(tài)并執(zhí)行業(yè)務(wù)邏輯
- scratch-blocks:代碼積木塊
- scratch-l10n:國(guó)際化
- scratch-paint:繪圖拓展
- scratch-render:舞臺(tái)渲染,在舞臺(tái)區(qū)域出現(xiàn)的基于WebGL的處理器。
- scratch-storage:作品存儲(chǔ)加載
- scratch-svg-renderer:svg處理
- scratch-audio:聲音拓展
其核心原理就是用scratch-blocks生成語(yǔ)句塊后蛔糯,用scratch-vm 虛擬機(jī)抽象成底層語(yǔ)法拯腮,最后再調(diào)用scratch-render 和scratch-paint渲染到界面,而scratch-audio主要用于音頻的剪輯處理蚁飒。
關(guān)于 scratch-vm
Scratch-VM提供了一套Scratch-Blocks運(yùn)行環(huán)境动壤,因此VM定義很多豐富的外部庫(kù),在初始化時(shí)淮逻,需要對(duì)VM進(jìn)行初始化琼懊,然后再定義Scratch-Blocks,最后對(duì)Blockly和VM進(jìn)行事件綁定和監(jiān)聽(tīng):
- 定義VirtualMachine爬早,VirtualMachine是Scratch-VM的核心入口類哼丈;
- 為VirtualMachine設(shè)置ScratchStorage、ScratchSVGRenderer筛严、S- cratchRender醉旦、AudioEngine等輔助工具;
- 定義工作空間,設(shè)置Blockly桨啃,使用inject方法车胡,加載Scratch-Blocks;
- 為工作空間設(shè)置相關(guān)事件;
- 為VirtualMachine設(shè)置相關(guān)事件;
- 開(kāi)始運(yùn)行VirtualMachine照瘾。
scratch-vm 的一些概念
- sb2/sb3文件: Scratch VM能解析的文件類型匈棘,sb2為Scratch2.0項(xiàng)目文件,sb3為Scratch3.0項(xiàng)目文件析命;
- Sprite角色:需要執(zhí)行動(dòng)畫效果的對(duì)象羹饰,對(duì)象可以是svg、圖像碳却;
- Clone克隆: 有時(shí)候角色動(dòng)畫需要復(fù)制自己本身队秩,會(huì)產(chǎn)生許多虛擬的角色,稱為克轮缙帧馍资;
- Costume造型:角色會(huì)有多個(gè)外觀,就像人有很多衣服一樣关噪,一個(gè)角色有多個(gè)造型鸟蟹,造型會(huì)讓角色更加生動(dòng);
- Target目標(biāo):角色屬性信息(基礎(chǔ)信息使兔、腳本信息)建钥,譬如角色的位置、方向虐沥、放大縮小熊经、特效等等泽艘。每個(gè)Clone體對(duì)應(yīng)一個(gè)Target;
- Stage舞臺(tái):角色在舞臺(tái)上完成動(dòng)畫镐依,舞臺(tái)是一個(gè)特殊的角色匹涮;
- backdrop舞臺(tái)背景:舞臺(tái)會(huì)有多個(gè)外觀,稱為舞臺(tái)背景
- Thread線程:角色產(chǎn)生動(dòng)畫槐壳,需要運(yùn)行環(huán)境然低,Thread線程就是角色的運(yùn)行時(shí)環(huán)境;
- Drawable圖形:每個(gè)角色的Clone體务唐,會(huì)對(duì)應(yīng)一個(gè)Target雳攘,Target目標(biāo)真正的繪圖對(duì)象實(shí)際上是Drawable;
- Sound聲音:聲音會(huì)讓動(dòng)畫更加生動(dòng)枫笛,Sound角色的屬性来农,由所有Clone體共用;
- effects特效:角色有很多造型崇堰,使用特效,可以在造型的基礎(chǔ)上涩咖,修改造型外觀海诲,特效有color,fisheye,whirl,pixelate, mosaic,brightness, ghost;
- IO輸入輸出:Scratch-VM將鍵盤檩互、鼠標(biāo)左右鍵特幔、鼠標(biāo)滾輪、設(shè)備連接等定義為IO闸昨,鍵盤蚯斯、鼠標(biāo)左右鍵、鼠標(biāo)滾輪等提供統(tǒng)一的IO輸入饵较,設(shè)備提供藍(lán)牙和BT連接拍嵌,使用socket與Scratch-link進(jìn)行通信;
- 刷新率:每秒鐘動(dòng)畫繪制的次數(shù);
- 時(shí)間片:一次繪制分配給scratch運(yùn)行的時(shí)間循诉,時(shí)間片到期自動(dòng)放棄運(yùn)行等待下一次運(yùn)行横辆,正常情況下,刷新率為每秒60次茄猫,時(shí)間片為1000/60ms狈蚤;
- StackFrame棧楨:線程運(yùn)行時(shí)棧空間划纽,通常情況下棿辔辏空間只有一個(gè)棧元素,當(dāng)遇到循環(huán)勇劣、事件靖避、函數(shù)時(shí),會(huì)保留當(dāng)前棧楨,新增一個(gè)棧楨筋蓖;
- ExtensionManager擴(kuò)展積木管理器:擴(kuò)展積木的管理卸耘,Scratch自定義了一種積木的定義方法,將自定義的積木轉(zhuǎn)為json粘咖,供blockly解析蚣抗,所以我們定義積木時(shí),可以在擴(kuò)展中定義瓮下。
vm調(diào)用過(guò)程
- 加載時(shí)先全局加載虛擬機(jī),初始化虛擬IO翰铡,然后查看網(wǎng)絡(luò)請(qǐng)求中l(wèi)ocation對(duì)象的hash,如果不能識(shí)別,直接在本地新建工程,并為工程賦予唯一ID值;
- 如果能識(shí)別,從網(wǎng)絡(luò)或從本地加載工程,將舞臺(tái)推入render生成渲染器,再把渲染器推入vm讽坏;
- 然后調(diào)用Blockly.inject函數(shù)在一個(gè)dom(類似于div#id)上面,初始化workspace和flyoutWorkspace樣式
- 接著為workspace建立blockListener(在vm/engine/block中定義,為block建立的通用(非特定)動(dòng)作函數(shù),如move,delete,create,click什么的,特定的block動(dòng)作函數(shù)的加載)锭魔;
- 然后為flyoutWorkspace建立flyoutBlockListener(所以如果需要給所有的block加統(tǒng)一的事件(僅限field和mutation)可以在這里添加 engine/blocks.js)
深入 scratch-vm
- vm是在containers/gui.jsx中啟動(dòng)的,scratch中components是純函數(shù)組件,而在containers文件夾中會(huì)把同名components與redux和vm連接,同時(shí)進(jìn)行國(guó)際化,組件節(jié)流,版本控制,虛擬IO監(jiān)聽(tīng)等操作,邏輯非常清晰;
- 所有ui狀態(tài)在reducer/gui.js中進(jìn)行組裝然后統(tǒng)一導(dǎo)出,但是要注意scratch根目錄下的index.js是個(gè)假的入口文件,reducer真正是在lib/app-state-hoc中的AppStateHOC類組裝的,這是一個(gè)中間件路呜;
- 在入口函數(shù)render-gui中GUI組件使用compose函數(shù)進(jìn)行柯里化(將f(a)(b)(c)(d)變成f(a,b,c)(d)叫做柯里化)封裝了AppStateHOC, HashParserHOC,TitledHOC三個(gè)中間件迷捧;
- AppStateHOC通過(guò)判斷是否需要加載paint和gui來(lái)加載不同的store,因此<Provider>也在這個(gè)組件當(dāng)中,guiMiddleware是一個(gè)封裝了throttle的柯里化函數(shù),按照中間件模式調(diào)用,用于為組件節(jié)流(如果一秒內(nèi)點(diǎn)了很多次,只會(huì)執(zhí)行兩次),封裝之后返回了經(jīng)過(guò)國(guó)際化(多語(yǔ)言模塊)和節(jié)流處理的高級(jí)組件;
- 當(dāng)vm啟動(dòng)時(shí),在runtime入口定義了defaultBlockPackages類,這里面聲明了每個(gè)block塊的功能函數(shù)(比如moveTo)胀葱;
- 在vm/engine/runtime 715行,有一個(gè)_registerBlockPackages函數(shù),會(huì)加載所有block塊動(dòng)作漠秋;
- 然后,通過(guò)聲明基類函數(shù)getPrimitives取得各個(gè)模塊中的block預(yù)定義動(dòng)作,之后通過(guò)訂閱分發(fā)(路由模式)的方式生成packagePrimitives類,這樣block塊的特定功能就都可以在vm中使用了;
- 當(dāng)有點(diǎn)擊事件發(fā)生時(shí),vm-listener首先捕獲事件,然后把消息推送至虛擬機(jī),虛擬機(jī)會(huì)定位到對(duì)應(yīng)的sprite或者帶有hats的sprite,執(zhí)行注冊(cè)的函數(shù);
小結(jié)
整個(gè)scratch 項(xiàng)目的源碼非常龐大硼婿,有很多我還沒(méi)有時(shí)間和精力進(jìn)行總結(jié),期待項(xiàng)目能夠順利推進(jìn)搂抒,然后投入更多的研發(fā),然后才能進(jìn)一步深入尿扯。
我曾打算一個(gè)人使用vue 3 重寫整個(gè)gui求晶,但是沒(méi)有經(jīng)費(fèi)支持,業(yè)余時(shí)間衷笋,個(gè)人的話真的有點(diǎn)力不從心誉帅。