Scratch-guiのステージのサイズを変えるためのコード改造テクニック


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

scratch-guiをカスタマイズした自作のスクラッチゲームをwebサイトからiframeで埋め込んでページ内でプレーしてもらう場合、アプリのステージ画面サイズが固定値である仕様のために、スマホの小さなスクリーンでは画面の一部しか見えないなど、なかなか歯痒い思いをします。

今回は、scratch-guiのステージサイズをフレキシブル対応にして、どのようなブラウザでも使えるようにソースコードを調整する手順をご紹介します。


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

スクラッチのステージとは

あらためてスクラッチの操作画面について復習しておくと、主な画面を構成しているのは下の7箇所になります。

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

            
            ① メニューバー
② ブロックセレクター
③ ブロックコードのキャンバス
④ ステージ
⑤ スプライト操作画面
⑥ 背景操作画面
⑦ バックパック
        
この内、ステージのサイズに関しては、scratch-guiのプロジェクトにおいて、480px × 360px固定であり、cssなどのスタイルファイルで操作しようと思っても、自由には変えられなくなっております。

今回は、ブラウザーに表示する際に、より柔軟なスクラッチアプリの描画に対応させるために、ステージのサイズの調整方法をscratch-guiから行う手順を考察してみようと思います。


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

ステージのレンダラ(ソースコード)をいじる

当然ながらscratch-guiをそのまま立ち上げた際には、以下のようにステージは480px x 360pxの固定サイズ起動するようになっています。

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

次にステージ画面だけを表示させるにはに
http://localhost:8601/player.htmlにアクセスすると、以下のようになります。

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

当然ながら
480x360縛りがあるので、ブラウザの画面を最大化してもステージのサイズは変わることはありません。

ではこの固定の状態を可変にできるように以下で弄っていきましょう。

まずステージのレイアウト値を定義しているファイル・
scratch-gui/src/lib/layout-constants.jsを開き、以下のように該当のフィールド値のstandardStageWidthstandardStageHeightを探し出してコメントアウトしておきましょう。

            
            import keyMirror from 'keymirror';

//...中略

export default {
    // standardStageWidth: 480,
    // standardStageHeight: 360,
    fullSizeMinWidth: 1096,
    fullSizePaintMinWidth: 1250
};

//...中略
        
後述しますが、これをコメントアウトすることで、外部cssからのステージのcanvas要素へスタイリングができるようになります。

余談ではありますが、スクラッチアプリの本体である
scratch-vmの中身でsrc/engine/runtime.jsにもステージのサイズのデフォルト値が定義されています。

むしろこっちが大元のデフォルト値を与えているのですが、今回の記事の内容程度の変更では、こちらは特に変更する必要はありません。

さらにコードの改造を進みます。

次はReactjsのレンダリング後のステージ要素を決めている・
scratch-gui/src/components/stage/stage.jsxというファイルの内容を変更します。

ここを上手く弄ると、柔軟にサイズの変わるステージが作成できるようになります。

            
            import PropTypes from 'prop-types';

//...中略

const StageComponent = props => {

    //...中略

    //👇isFullScreen_という変数を定義し、trueを入れる
    const isFullScreen_ = true;

    //👇コメントアウト
    //const stageDimensions = getStageDimensions(stageSize, isFullScreen);
    //👇isFullScreen_に引数を書き換え
    const stageDimensions = getStageDimensions(stageSize, isFullScreen_);

    return (
        <React.Fragment>
            <Box
                className={classNames(
                    styles.stageWrapper,
                    //👇コメントアウト
                    //{[styles.withColorPicker]: !isFullScreen && isColorPicking})}
                    //👇isFullScreen_に引数を書き換え
                    {[styles.withColorPicker]: !isFullScreen_ && isColorPicking})}
                onDoubleClick={onDoubleClick}
            >
                <Box
                    className={classNames(
                        styles.stage,
                        //👇コメントアウト
                        // {[styles.fullScreen]: isFullScreen}
                        //👇isFullScreen_に引数を書き換え
                        {[styles.fullScreen]: isFullScreen_}
                    )}
                    //👇ステージのサイズの箇所をコメントアウト
                    // style={{
                    //     height: stageDimensions.height,
                    //     width: stageDimensions.width
                    // }}
                >
                    <DOMElementRenderer
                        domElement={canvas}
                        //👇外部cssファイルからステージサイズを注入(重要!)
                        className={styles.outerTargetedCanvas}
                        //👇ステージのサイズの箇所をコメントアウト
                        // style={{
                        //     height: stageDimensions.height,
                        //     width: stageDimensions.width
                        // }}
                        {...boxProps}
                    />
                    <Box className={styles.monitorWrapper}>
                        <MonitorList
                            draggable={useEditorDragStyle}
                            stageSize={stageDimensions}
                        />
                    </Box>
                    {/*👇ステージのサイズの箇所をコメントアウト*/}
                    {/* <Box className={styles.frameWrapper}>
                        <TargetHighlight
                            className={styles.frame}
                            stageHeight={stageDimensions.height}
                            stageWidth={stageDimensions.width}
                        />
                    </Box> */}
                    {isColorPicking && colorInfo ? (
                        <Loupe colorInfo={colorInfo} />
                    ) : null}
                </Box>

                {/* `stageOverlays` is for items that should *not* have their overflow contained within the stage */}
                <Box
                    className={classNames(
                        styles.stageOverlays,
                        //👇コメントアウト
                        // {[styles.fullScreen]: isFullScreen}
                        //👇isFullScreen_に引数を書き換え
                        {[styles.fullScreen]: isFullScreen_}
                    )}
                >
                    <div
                        className={styles.stageBottomWrapper}
                        //👇ステージのサイズの箇所をコメントアウト
                        // style={{
                        //     width: stageDimensions.width,
                        //     height: stageDimensions.height
                        // }}
                    >
                        {micIndicator ? (
                            <MicIndicator
                                className={styles.micIndicator}
                                stageSize={stageDimensions}
                            />
                        ) : null}
                        {question === null ? null : (
                            <div
                                className={styles.questionWrapper}
                                //👇ステージのサイズの箇所をコメントアウト
                                // style={{width: stageDimensions.width}}
                            >
                                <Question
                                    question={question}
                                    onQuestionAnswered={onQuestionAnswered}
                                />
                            </div>
                        )}
                    </div>
                    <canvas
                        className={styles.draggingSprite}
                        height={0}
                        ref={dragRef}
                        width={0}
                    />
                </Box>
                {isStarted ? null : (
                    <GreenFlagOverlay
                        className={styles.greenFlagOverlay}
                        wrapperClass={styles.greenFlagOverlayWrapper}
                    />
                )}
            </Box>
            {isColorPicking ? (
                <Box
                    className={styles.colorPickerBackground}
                    onClick={onDeactivateColorPicker}
                />
            ) : null}
        </React.Fragment>
    );
};
//...以下略
        
さて、上記のソースコードの書き換えのポイントは3つです。

            
            ① ステージをフルスクリーン表示に強制的に切り替えるために、
    isFullScreen_ = true を使って、各DOMの属性に割り当てる。

② ステージの中身であるDOMElementRendererコンポーネントに、
    外部cssファイルからouter-targeted-canvasクラスのスタイルを渡す。

③ ステージの画面サイズで固定値を渡している変数である
    stageDimensions.heightとstageDimensions.widthが属性値として仕込んである
    コンポーネントからは全て外しておく。
        
ここで、ステージのスタイルファイルscratch-gui/src/components/stage/stage.cssの中身にステージのcanvas要素に追加したクラスouter-targeted-canvasのスタイルを以下のように追記しておきます。

            
            /* ...中略 */

.outer-targeted-canvas {
    width: 90vw;
    max-width: 1056px;
}
        
ここでブラウザのビューサイズに併せて、ステージが伸縮するようにできるスタイルになるように90vw程度の幅をもたせています。

ちょうど
100vwにすると画面端が見切れてしまったりするので、90vw当たりに設定するのが無難です。

また、かなり大きなモニターだと今度はステージ画面が大きくなりすぎてしまうのを防ぐために、
max-width: 1056px;で上限を設けておくのも良いと思います。

続いて、こちらはついでですが、ステージの全体のラッパークラス
scratch-gui/src/components/stage-wrapper/stage-wrapper.jsxもフルスクリーンで始まるように調整しておきます。

            
            import PropTypes from 'prop-types';

//...中略

const StageWrapperComponent = function (props) {
    //...中略

    //👇isFullScreen_という変数を定義し、trueを入れる
    const isFullScreen_ = true;

    return (
        <Box
            className={classNames(
                styles.stageWrapper,
                //👇コメントアウト
                // {[styles.fullScreen]: isFullScreen}
                //👇isFullScreen_に引数を書き換え
                {[styles.fullScreen]: isFullScreen_}
            )}
            dir={isRtl ? 'rtl' : 'ltr'}
        >
            <Box className={styles.stageMenuWrapper}>
                <StageHeader
                    stageSize={stageSize}
                    vm={vm}
                />
            </Box>
            <Box className={styles.stageCanvasWrapper}>
                {
                    isRendererSupported ?
                        <Stage
                            stageSize={stageSize}
                            vm={vm}
                        /> :
                        null
                }
            </Box>
            {loading ? (
                //👇コメントアウト
                // <Loader isFullScreen={isFullScreen} />
                //👇isFullScreen_に引数を書き換え
                <Loader isFullScreen={isFullScreen_} />
            ) : null}
        </Box>
    );
};
//...以下略
        
このstage-wrapperのcssファイル・scratch-gui/src/components/stage-wrapper/stage-wrapper.cssを修正してステージ周りの無駄な空白を作らないようにしておきます。

            
            @import "../../css/units.css";

/* ...中略 */

.stage-wrapper.full-screen {

    /* top: $stage-menu-height; */
    /* 👇に書き換え */
    top: 0;

    /* 中略 */

    /* padding: $stage-full-screen-stage-padding; */
    /* 👇に書き換え */
    padding: 0;

    /* 以下略 */
}
        
早速これでビルドして、player.htmlを起動してみると、スクラッチアプリの画面サイズがブラウザの画面サイズ対応して、自動調整されるようになります。

ここまでの修正でステージ画面を
http://localhost:8601/player.htmlにアクセスすると、以下のようにドドンと大きくなっていたら成功です。

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

このテクニックを利用して、静的なWEBサイトから
<iframe>で埋め込んだスクラッチアプリなどを、スマートフォンからアクセスした時も画面が自動調整されるようになり、フレキシブル対応が可能となります。


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

まとめ

今回のテクニックでは、どうしてもscratch-guiで独自のエクステンション機能を追加したスクラッチゲームをwebサイトで公開したい人のための、ステージサイズ調整方法をご紹介しました。

通常スクラッチ公式サイトから埋め込みリンクでhtmlから使うと、ある程度上手くフレキシブル対応してくれるようです。

scratch-guiで自作する必要がなければ、スクラッチ公式からアプリを引っ張ってくる方法の方がスマートではあります。

参考サイト

Discuss Advanced Topics Scratch 3.0 mod