scratch3.0のディープな使い方 ~ scratch3.0のレンダラー(scratch-render)だけを動かしてみる


※ 当ページには【広告/PR】を含む場合があります。
2020/12/18

今回のお題は、
scratch-renderを使うためのビルドツールの構築と、Scracth3.0のレンダリング機能を取り出して簡単なプロジェクトを作成する流れを解説します。

※この記事はスクラッチの拡張機能を自作したいフロントエンドエンジニア等の技術者のための読み物です。


合同会社タコスキングダム|タコキンのPスクール【Pschool厳選】Scratchを学べるオンライン・駅前プログラミングスクール5選

スクラッチの拡張ペンみたいな機能を作成したい!

おそらくスクラッチを慣れ親しんでいる方は、ペン拡張機能を使うことも多いと思います。

合同会社タコスキングダム|タコキンのPスクール

もっとも身近とも思えるこのペン機能ですが、その主要な機能を
『scratch-render』のインスタンスメソッドから引っ張ってきて画像を表示させているので、scratch-renderを理解すると、拡張ペンのような画像を描画する機能が誰にでもカスタマイズできるようになります。

だた、スクラッチの重要な裏方なので、どのように使うのかを説明してくれるリファレンスやチュートリアルなどはなく、自分でソースコードやコメント書きを読み解きながら理解していくしか勉強方法がありません。

この記事では、とにかくscratch-render単体をテストできるように最小のnodeプロジェクトを作る手順を説明し、単純なsvg画像を描画するだけのプログラムを作成してみます。

なお、手順の説明中、プロジェクトをビルド・起動するために、node.js/npmやwebpackを使う必要があります。

そこらへんのメジャーな開発ツールは巷を見渡してもインストールなどの技術解説をしているサイトは数多くあるため、この記事での導入方法の解説は行いません。

既にお手元で、使えるようになっているという環境を前提として説明しますのでご了承ください。


合同会社タコスキングダム|タコキンのPスクール【Pschool厳選】Scratchをしっかり学ぶためのオススメ書籍まとめ

Scratch3.0の標準レンダラ・scratch-render

スクラッチ3.0のプログラム本体は、『scratch-gui』というプログラム統合型のプロジェクトとして継続して開発されています。このscratch-guiを裏で支えている主要な部品たちは以下のようになります。

            
            scratch-audio:
    音源を操作するライブラリ
scratch-blocks:
    プログラミングブロックを提供するライブラリ
scratch-l10n:
    多言語化対応機能を担当するライブラリ
scratch-storage:
    リソースファイルを操作するライブラリ
scratch-vm:
    プログラムのコアランタイム
scratch-paint:
    ペイントエディタ用のサブプログラム
scratch-render:
    WebGLベースのグラフィック用レンダラー
        
さて、sctatch-guiやsctatch-vmに関しては、例えば拡張機能の作成手順を本ブログでこれまで色々と解説してきました。

合同会社タコスキングダム|タコキンのPスクール
【Scratch拡張機能・自作】テキストデータを読み出し・保存するエクステンションを作ろう

Scratchの拡張機能の自作手順の基礎を、テキストを読み出し・書き出しできるような機能の実装を例に解説します。

合同会社タコスキングダム|タコキンのPスクール
【Scratch拡張機能・自作】文字からユニコード番号に変換するちょっとしたエクステンションを作る

Scratch-guiから、文字もしくは文字列からユニコード(16進数)に変換するための拡張機能を自作します。

スクラッチでちょっとした独自拡張機能の作成の際には、scratch-vmをソースコードを弄るだけで全然事足りるのですが、もっと高度な拡張機能やカスタムプログラムを作るためには、スクラッチのコアライブラリをある程度理解しておくことが必要です。

今回の内容でコアライブラリ単体を取り上げるのは
『scratch-render』です。

このライブラリはWebGL経由で画像をブラウザへ描画する機能を、スクラッチへ提供している重要なパーツの一つで、Scratch2.0からScratch3.0へ大きく飛躍できたのはこのライブラリの貢献がかなりありそうです。

当然、スクラッチプログラムの本体である
scratch-guiの実装の中もさることながら、コアランタイムのscratch-vmの中でも、scratch-renderは随所に利用されてます。

例えばscratch-vm内のペンの実装コード(
Scratch3PenBlocksクラス)でいうと、

            
            class Scratch3PenBlocks {
    constructor (runtime) {
        //👇コンストラクタでコアランタイムのインスタンスがthis.runtimeと設定されている
        this.runtime = runtime;

    ///...中略

    //👇一例として...ペンに割り当てるスキン(画像)を返すメソッド
    _getPenLayerID () {
        if (this._penSkinId < 0 && this.runtime.renderer) {
            //👇this.runtime.rendererで、scratch-renderのインスタンスが引き出され、
            // 色々とレンダリングを操作できるようになる
            this._penSkinId = this.runtime.renderer.createPenSkin();
            this._penDrawableId = this.runtime.renderer.createDrawable(StageLayering.PEN_LAYER);
            this.runtime.renderer.updateDrawableSkinId(this._penDrawableId, this._penSkinId);
        }
        return this._penSkinId;
    }

    ///...中略
        
のようにクラスのインストラクタからランタイムのインスタンスを引き込んで、this.runtime.rendererのようにすることでscratch-renderインスタンスを使うことができます。


合同会社タコスキングダム|タコキンのPスクール【Pschool厳選】Scratchを学べるオンライン・駅前プログラミングスクール5選

実践例 ~ scratch-renderで動かすサンプルプログラム

では、サンプルプログラムを作り方を説明していきます。

開発ツールの導入

簡単なnode.jsプロジェクトなのでさほど導入は難しくありません。

なお、パッケージマネージャはnpmでも良いのですが、著者の趣向でyarnで以降は統一しています。

            
            #👇webpack系ツール
$ yarn add webpack webpack-cli copy-webpack-plugin -D
#👇babel系ツール
$ yarn @babel/core @babel/preset-env babel-loader -D
#👇モックサーバー起動用
$ yarn http-server -D
        
そして、scratch-renderをgithub上からインストールします。

            
            $ yarn add https://github.com/LLK/scratch-render.git -S
        
最低限の導入としは以上です。

この記事を執筆時点で、scratch-renderをwebpack5系からビルド・バンドルは出来ても、ブラウザで起動するとエラーが発生してしまうようです。

そんな時にはwebpack4系にダウングレードして再度試してみると上手くいくと思います。

プロジェクトの構造

まず手始めにいかのようにプロジェクトのフォルダ&ファイルを配置しておきます。

            
            $ tree
.
├── package.json
├── src
│     ├── index.html #👈①メインhtmlファイル
│     └── main.js    #👈②メインjsファイル
├── webpack.config.js #👈③webpackバンドル設定
└── yarn.lock
        
上記の項目①~③についてですが、ルートにsrcフォルダを作って、空のindex.htmlとmain.jsを追加しておきます。

また、ルートにwebpack.config.jsも空にして追加しておきます。

以降ではこれらのリソースファイルの中身を実装していきます。

index.htmlとmain.js

まずindex.htmlを以下の内容で編集しておきます。

            
            <!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Scratch-renderer Simple Test</title>
        <style>
            #myStage {
                width: 560px;
                border: 3px solid black;
            }
        </style>
    </head>
    <body>
        <canvas id="myStage"></canvas>
        <script src="main.js"></script>
    </body>
</html>
        
続いてmain.jsの内容です。

            
            const ScratchRender = require('scratch-render');

class Taco {
    constructor(renderer, name, x=0, y=0) {
        this.x = x;
        this.y = y;
        this.name = name;
        this.drawableID = renderer.createDrawable('group1');
        this.updateImage(renderer, this.drawableID);
    }

    updateImage(renderer, drawableID) {
        const tacoSvgIcon = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="175.66603" height="176.4622" viewBox="0,0,175.66603,176.4622"><g transform="translate(-152.16699,-91.77312)"><g data-paper-data="{&quot;isPaintingLayer&quot;:true}" fill-rule="nonzero" stroke="#000000" stroke-miterlimit="4" stroke-dashoffset="0" style="mix-blend-mode: normal"><g fill="#ff9955" stroke-width="3" stroke-linecap="butt" stroke-linejoin="miter" stroke-dasharray=""><path d="M221.11856,195.01032c-0.17816,5.61244 -3.47457,13.00711 -9.35439,21.33692c-5.87982,8.32981 -14.34373,17.59558 -21.96068,24.2326c-7.61694,6.63703 -14.38762,10.64599 -11.35867,5.96877c3.02895,-4.67722 15.85751,-18.04032 23.65295,-30.29038c7.79544,-12.25006 10.55722,-23.38629 13.40803,-27.03883c2.85082,-3.65255 5.79091,0.17848 5.61275,5.79092z"/><path d="M213.67043,192.24645c-2.23534,5.15116 -8.02641,10.80898 -16.56365,16.38333c-8.53724,5.57435 -19.82161,11.06573 -29.34928,14.42598c-9.52767,3.36026 -17.29955,4.58978 -12.75923,1.35924c4.54032,-3.23054 21.3927,-10.92088 33.15625,-19.43272c11.76355,-8.51184 18.43747,-17.84467 22.43433,-20.18846c3.99686,-2.34378 5.31693,2.30147 3.08158,7.45263z"/><path d="M228.26088,198.32643c1.01103,5.5235 -0.64952,13.44752 -4.63774,22.83113c-3.98822,9.3836 -10.30463,20.22775 -16.34827,28.32357c-6.04364,8.09583 -11.81507,13.44415 -9.84211,8.23278c1.97296,-5.21137 11.69037,-20.98212 16.72318,-34.6021c5.03281,-13.61998 5.38069,-25.08829 7.39591,-29.26047c2.01522,-4.17219 5.698,-1.04841 6.70904,4.47509z"/><path d="M236.70781,199.07819c2.51886,5.01862 3.14616,13.09042 1.94828,23.21579c-1.19789,10.12537 -4.22117,22.30537 -7.75306,31.77077c-3.53188,9.46541 -7.57266,16.21716 -7.13967,10.66167c0.433,-5.55549 5.33988,-23.41795 6.35292,-37.90266c1.01304,-14.48471 -1.86785,-25.59072 -1.10298,-30.16053c0.76486,-4.56982 5.17566,-2.60366 7.69451,2.41496z"/></g><g fill="#ff9955" stroke-width="3" stroke-linecap="butt" stroke-linejoin="miter" stroke-dasharray=""><path d="M264.49409,189.2194c2.85082,3.65254 5.6126,14.78877 13.40805,27.03883c7.79545,12.25006 20.62403,25.61316 23.65299,30.29038c3.02896,4.67722 -3.74173,0.66826 -11.35868,-5.96877c-7.61696,-6.63702 -16.08089,-15.90279 -21.96071,-24.2326c-5.87982,-8.32981 -9.17624,-15.72448 -9.3544,-21.33692c-0.17816,-5.61244 2.76194,-9.44347 5.61276,-5.79092z"/><path d="M269.41106,184.79383c3.99687,2.34378 10.67079,11.67661 22.43436,20.18846c11.76356,8.51184 28.61598,16.20218 33.15631,19.43272c4.54033,3.23054 -3.23156,2.00103 -12.75925,-1.35924c-9.52769,-3.36025 -20.81207,-8.85163 -29.34933,-14.42598c-8.53726,-5.57435 -14.32833,-11.23217 -16.56367,-16.38333c-2.23535,-5.15116 -0.91528,-9.79641 3.08159,-7.45262z"/><path d="M258.44804,193.85137c2.01522,4.17218 2.3631,15.64048 7.39593,29.26046c5.03283,13.61998 14.75023,29.39073 16.7232,34.6021c1.97297,5.21137 -3.79846,-0.13695 -9.84212,-8.23278c-6.04366,-8.09582 -12.36007,-18.93997 -16.3483,-28.32357c-3.98824,-9.3836 -5.64879,-17.30762 -4.63776,-22.83112c1.01103,-5.5235 4.69383,-8.64728 6.70905,-4.47509z"/><path d="M250.98659,196.66321c0.76487,4.56982 -2.11603,15.67582 -1.10299,30.16053c1.01305,14.48471 5.91993,32.34717 6.35293,37.90266c0.433,5.55549 -3.60778,-1.19625 -7.13967,-10.66166c-3.53189,-9.4654 -6.55519,-21.6454 -7.75308,-31.77077c-1.19789,-10.12537 -0.57057,-18.19718 1.94828,-23.2158c2.51887,-5.01862 6.92966,-6.98478 7.69452,-2.41496z"/></g><path d="M182.25837,151.01469c0,-31.88979 25.85178,-57.74157 57.74157,-57.74157c31.88979,0 57.74156,25.85178 57.74156,57.74157c0,31.88978 -25.85178,57.74157 -57.74156,57.74157c-31.88978,0 -57.74157,-25.85179 -57.74157,-57.74157z" fill="#ff9955" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="0.75844,0.75844"/><g stroke-linecap="round" stroke-linejoin="round" stroke-dasharray=""><path d="M228.02859,156.08962c0,-6.83658 5.54214,-12.37872 12.37872,-12.37872c6.83658,0 12.37872,5.54214 12.37872,12.37872c0,6.83658 -5.54214,12.37872 -12.37872,12.37872c-6.83658,0 -12.37872,-5.54214 -12.37872,-12.37872z" fill="#ff9955" stroke-width="1.5"/><path d="M232.93275,156.38633c0,-4.12264 3.34206,-7.4647 7.46469,-7.4647c4.12264,0 7.4647,3.34206 7.4647,7.4647c0,4.12264 -3.34206,7.4647 -7.4647,7.4647c-4.12264,0 -7.46469,-3.34206 -7.46469,-7.4647z" fill="#ff9955" stroke-width="1.5"/><path d="M192.41198,131.03544c0,-3.25019 2.63479,-5.88498 5.88498,-5.88498c3.25018,0 5.88498,2.6348 5.88498,5.88498c0,3.25018 -2.6348,5.88498 -5.88498,5.88498c-3.25018,0 -5.88498,-2.63479 -5.88498,-5.88498z" fill="#241f1c" stroke-width="0.68809"/><path d="M274.063,131.03544c0,-3.25019 2.6348,-5.88498 5.88498,-5.88498c3.25018,0 5.88498,2.6348 5.88498,5.88498c0,3.25018 -2.63479,5.88498 -5.88498,5.88498c-3.25018,0 -5.88498,-2.63479 -5.88498,-5.88498z" fill="#241f1c" stroke-width="0.68809"/></g></g></g></svg>';
        const skinId = renderer.createSVGSkin(tacoSvgIcon);
        renderer.updateDrawableProperties(drawableID, {
            skinId: skinId
        });
    }

    update(renderer) {
        renderer.updateDrawableProperties(this.drawableID, {
            position: [this.x, this.y],
            scale: [100, 100],
            direction: 90
        });
    }
}

const canvas = document.getElementById('myStage');
const renderer = new ScratchRender(canvas);
renderer.setLayerGroupOrdering(['group1']);

const taco = new Taco(renderer, "taco.svg", 0, 0);

function drawStep() {
    taco.update(renderer);
    renderer.draw();
    requestAnimationFrame(drawStep);
}

drawStep();
        
上のソースコードで利用したscratch-renderのメソッドはほんの一部です。

今回はプログラムを動作させることにフォーカスしておりますので、各メソッドの説明は省きます。

メソッドの詳細が気になる方はソースコードの各コメントから機能の方を読み解いてください。

webpackの設定

webpack.config.jsは以下の内容で編集します。

            
            const path = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    target: 'web',
    entry: {
        'main': './src/main.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            {
                include: [
                    path.resolve(__dirname, 'src')
                ],
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            },
        ]
    },
    plugins: process.env.NODE_ENV === 'production' ? [
        new CopyWebpackPlugin({
            patterns: [
                { from: 'src' }
            ]
        }),
        new webpack.optimize.UglifyJsPlugin({
            include: /\.min\.js$/,
            minimize: true
        })
    ] : [
        new CopyWebpackPlugin({
            patterns: [
                { from: 'src' }
            ]
        })
    ]
};
        
webpackの設定はちょくちょく変更があるので、バージョンによって細かいエラーが発生する場合もあります。

もしよく分からないエラーに遭遇したら、エラーの内容ごとウェブ検索したら解決策を有志の何方かが説明して下さっていると思いますので、それでホットフィックスしてください。

もしくは、webpackに嫌気がさしたら、parcelなどの他の軽量バンドラを使うのも選択肢としてアリかと思います。

package.jsonのスクリプトの用意

package.jsonのscraptsタグに以下のようにセットしておきます。

            
            ///...省略
    "scripts": {
        "build": "rm -rf dist && webpack",
        "serve": "http-server ./dist -p 8080 -c-1"
    },
///...省略
        
これでyarn ***からスクリプトを実行できるようになりました。

ビルド&実行

さて、上記までで全ての材料が揃ったので、このプロジェクトをビルドしてみます。

            
            $ yarn build && yarn serve
        
コケずに上手く走ると、ブラウザでhttp://localhost:8080から以下のようにプログラムが動作していれば、もっとも簡単なscratch-renderのプログラムが走っています。

合同会社タコスキングダム|タコキンのPスクール


合同会社タコスキングダム|タコキンのPスクール【Pschool厳選】Scratchをしっかり学ぶためのオススメ書籍まとめ

まとめ

今回は、scratch-render単体を利用できる最小のnodeプロジェクトを作る方法を紹介して、サンプルとしてsvg画像をhtmlのcanvas要素に単純に描画するだけのプログラムを作成してみました。

スクラッチの主要な拡張機能である
ペンのように、scratch-renderを活用した高度な機能も応用として可能になります。

そこは面白いアイディア次第ですが、このscratch-renderを同じように利用して、ワンランク上の独自エクステンションを作成してみてはいかがかと思います。