TurbowarpでP5JSを動かす(OneBall自由落下)

『[前記事:TurbowarpでP5JSを動かす](https://qiita.com/harhid/items/66a472b5e6e0b58294f1)』の続きです

# 目標：TurbowarpでP5JS、クラスをインポート

Sketchファイルからさらに別のJSファイルをインポート、そのJSファイルからもクラス定義をインポートさせたいと思います。Sketchファイルには詳細の処理を書かずに 他のJSファイルにP5JSの詳細コードを書けるようにすることで、コード修正をしやすくします。Sketchファイルは入口みたいな個別に誘導するものになりますね。


# ファイルの関係/IMPORTの階層

カスタム拡張機能内で p5JSのSketchをimportします（絶対パスで）
p5JSのSketchからは 詳細処理を書いた sub.js をimportします（相対パスでOK)
sub.js 内でクラス定義を使うので、sub.js から ball.js を importします（相対パスでOK) 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294946/4098ecd6-caca-333b-3e5d-e07893a741ac.png)

※ 前提：sub.js ,  ball.js は、sketch.jsと同一フォルダーに格納しておくとします。

# 今回やりたいことを見せるためのサンプル動画

<iframe width="1280" height="720" src="https://www.youtube.com/embed/MiYv7AWLbWk" title="TurbowarpでP5JSを動かす(07 02 extension)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# ソース実装 ( Ball 一個 )

この記事では Ball 一個分を描画しています。複数Ballを描画するのは[次の記事](https://qiita.com/harhid/private/6228ecbe2666a8677621)をみてください。


## ball.js

[コードGITHUB](https://github.com/amami-harhid/turbowarpExtensions/blob/main/_07_01_extension/ball.js)

Ballクラスを書いてあるJSファイルです。

```javascript:ball.js
/*
 *  Ball クラス
 */
const Ball = class{
    constructor(x, y, r, power, c) {
        this._x = x;
        this._y = y;
        this._r = r;
        this._c = c;
        this._directionX = 1;
        this._power = power;
        this._speed = this._power;
        this._counter = 0;
    }
    get c() {
        return this._c;
    }
    get W(){
        return this._W;
    }
    set W(_W){
        this._W = _W;
    }
    get H() {
        return this._H;
    }
    set H(_H){
        this._H = _H;
    }
    // X方向の動き
    moveX () {
        this._x += this._directionX * 10;
        if( (this.x-this._r/2) < -this.W/2 || this.W/2 < (this.x +this._r/2)) {
            this._directionX *= -1;
        }
    }
    // Y方向の動き
    moveY () {
        this._y += this._speed;
        // 径の大きい円は減速が速いとする
        this._speed +=(this._r) / 25;
        if( (this.y+this._r/2) > this.H/2 ) {
            this._speed = this._power;
        }
    };
    // 動きをまとめる
    move () {
        this.moveX();
        this.moveY();
    }
    get x() {
        return this._x - this.W/2;
    }
    get y() {
        return this._y + this.H/2;
    }
    get r() {
        return this._r;
    }
}
export {Ball};
```

## sub.js
[コードGITHUB](https://github.com/amami-harhid/turbowarpExtensions/blob/main/_07_01_extension/sub.js)

```javascript:sub.js
/**
 * sub.js
 * 同じ階層にあるファイルなので、(./～)の書き方でOK。
 * await を忘れてはだめです。
 */
const {Ball} = await import(`./ball.js?t=${new Date().getTime()}`);

let ball;
const mySetup = (p) => {
    console.log('test setup');
    let c = p.color(
        p.random(0,255),
        p.random(0,255),
        p.random(0,255),
    ); 
    ball = new Ball(0, 0, 20, -20, c);
}
const myDraw = (p) => {

    ball.W = p.canvas.clientWidth;
    ball.H = p.canvas.clientHeight;
    const _c = ball.c;
    p.fill(_c);
    p.noStroke();    
    p.ellipse( ball.x, ball.y, ball.r );
    ball.move();

}

export {mySetup, myDraw};
```
## sketch.js
[コードGITHUB](https://github.com/amami-harhid/turbowarpExtensions/blob/main/_07_01_extension/sketch.js)

```javascript:sketch.js
/**
 * Ballクラスをインポート
 * 同じ階層にあるファイルなので、(./～)の書き方でOK。
 * await を忘れてはだめです。
 */
const {mySetup, myDraw} = await import(`./sub.js?_t=${new Date().getTime()}`);

/**
 * P5JS Sketch
 * @param {*} p 
 */
const sketch = (p) => {
    let ball;
    p.setup = () => {
        mySetup(p);
    }
    p.draw = () => {
        myDraw(p);
    }
}
```
## Extension.js

カスタム拡張機能の本体です。
[コードGITHUB](https://github.com/amami-harhid/turbowarpExtensions/blob/main/_07_01_extension/Extension.js)

```javascript:Extension.js
/**
 * Turbowarpの『カスタム拡張機能』を使おう【７】
 * TurbowarpでP5JSを動かす
 * SketchのURLを指定できるブロックを追加した
 * 
 */
((Scratch) => {
    /** 拡張機能ＩＤ */
    const ExtensionID = 'MyExtension0701P5JS';
    /** 拡張機能表示名 */
    const ExtensionName = 'P5JS練習';

    // 歯車画像URL
    const GEAR_IMAGE_SVG_URI 
        = 'https://amami-harhid.github.io/turbowarpExtensions/assets/gear.svg';

    // P5JS CDN URL
    const P5JSLIB 
        = "https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.js";

    // テスト用JSファイルの場所(HOST+DIRCTORY)
    const TEST_URL 
        = 'http://127.0.0.1:5500/_07_01_extension';
    
    // この拡張機能内(Module内)だけで使う前提のグローバル変数
    window._ExtentionGlobals = {};
    window._ExtentionGlobals.TEST_URL = TEST_URL;

    /**
     * 拡張機能定義
     */
    const ExtensionInfo = {
        id: ExtensionID,
        name: ExtensionName,
        blocks: [
            {
                opcode: "p5jsSketchUrl",
                blockType: Scratch.BlockType.COMMAND,
                text: "[IMG_GEAR]P5JS Sketch⇒[SKETCH_URL]",
                arguments: {
                    IMG_GEAR: {
                        type: Scratch.ArgumentType.IMAGE,       //タイプ
                        dataURI: GEAR_IMAGE_SVG_URI,            //歯車画像のURI
                    },
                    SKETCH_URL: {
                        type: Scratch.ArgumentType.STRING,
                        defaultValue: '',
                    }
                },
            },
            {
                opcode: "p5jsImport",
                blockType: Scratch.BlockType.COMMAND,
                text: "[IMG_GEAR]P5JS IMPORT",
                arguments: {
                    IMG_GEAR: {
                        type: Scratch.ArgumentType.IMAGE,       //タイプ
                        dataURI: GEAR_IMAGE_SVG_URI,            //歯車画像のURI
                    },
                },
            },
            {
                opcode: 'p5JsStart',
                blockType: Scratch.BlockType.COMMAND,
                text: "[IMG_GEAR]p5Jsを開始する",
                arguments: {
                    IMG_GEAR: {
                        type: Scratch.ArgumentType.IMAGE,       //タイプ
                        dataURI: GEAR_IMAGE_SVG_URI,            //歯車画像のURI
                    },
                },
            },
            {
                opcode: "p5JsDraw",
                blockType: Scratch.BlockType.COMMAND,
                text: "[IMG_GEAR]P5JS描画をする",
                arguments: {
                    IMG_GEAR: {
                        type: Scratch.ArgumentType.IMAGE,       //タイプ
                        dataURI: GEAR_IMAGE_SVG_URI,            //歯車画像のURI
                    },
                },
            },
        ],
    }
    /**
     * P5JS拡張用クラス（練習）
     */
    class MyExtension {
        getInfo(){
            return ExtensionInfo;
        }
        p5jsSketchUrl(args, util){
            const sketchUrl = args.SKETCH_URL;
            this.sketchUrl = sketchUrl;
            if(sketchUrl.length == 0) {
                this.sketchUrl = `${TEST_URL}/sketch.js`;
            }
        }
        /**
         * P5JSをインポートする
         * @param {*} args 
         * @param {*} util 
         */
        async p5jsImport( args, util ){
            try{
                // ここで P5JS CDN LIB を読み込む(キャッシュＯＫ)
                await import(P5JSLIB);
                // 【P5JS フックの登録】(beforeSetup)
                p5.prototype.registerMethod('beforeSetup', function(){
                    // フック実行時、thisは p5インスタンスである
                    const p = this; 
                    // Sketchにsetupが登録されているときSketchのsetupを上書きする                    
                    if(p.setup){
                        // 【StageのCanvasをP5jsのCanvasとして利用】
                        const _reuseCanvas = () => {
                            // StageのCanvasを取得する
                            const canvas = util.target.renderer.gl.canvas;
                            const w = canvas.clientWidth;
                            const h = canvas.clientHeight;
                            // StageのCanvasをp5jsのCanvasとして使う
                            p.createCanvas(w, h, p.WEBGL, canvas);
                        }
                        // 【Stageサイズ変化監視】
                        const _resizeCanvas = _reuseCanvas;
                        const _stageSizeObserver =()=>{
                            const canvas = util.target.renderer.gl.canvas;
                            // Stageサイズ変化時に resize処理をする
                            const observer = new MutationObserver(() => {
                                _resizeCanvas();
                            });
                            observer.observe(canvas, {
                                attriblutes: true,
                                attributeFilter: ["style"], 
                            });                    
                        };
                        // 【Sketchのsetupを置換】
                        const _sketchSetup = p.setup;
                        const _wraper = () => {
                            // drawの繰返しを抑止する
                            p.noLoop(); 
                            // StageのCanvasをP5jsのCanvasとして利用する
                            _reuseCanvas(); 
                            // Stageサイズ変化を監視する
                            _stageSizeObserver(); 
                            // 元のsetupを実行する
                            _sketchSetup();                            
                        }
                        p.setup = _wraper;
                    }

                });
            }catch(e){
                const mesagge = 'P5JSの読み込みに失敗したみたいです'
                console.error( mesagge, e );
                alert(mesagge);
            }
        }
        /**
         * p5JS setup を開始する
         * @param {*} args 
         * @param {*} util 
         */
        async p5JsStart( args, util ){
            if(this.sketchUrl == undefined) {
                this.sketchUrl = `${TEST_URL}/sketch.js`;
            }
            try{
                // Sketchを読み込む(キャッシュからの読み込みをしない)
                const _t = new Date().getTime();
                const sketchUrl = `${this.sketchUrl}?_t=${_t}`;
                const {sketch} = await import( sketchUrl );
                const p = new p5(sketch);
                this.p = p;

            }catch(e){
                const mesagge = 'Sketchの読み込みに失敗した、'
                    +'もしくはP5JSインスタンスモード開始に失敗した';
                console.error( mesagge, e );
                alert(mesagge);
            }
        }
        /**
         * Scratch3.x(=Turbowarp)のブロックから呼び出されるdraw処理
         * @param {*} args 
         * @param {*} util 
         */
        async p5JsDraw( args, util ) {
            this.p._draw();
        }
    }

    /** カスタム拡張機能クラスのインスタンスを登録する */
    Scratch.extensions.register(new MyExtension());

})(Scratch);
```

# 続き

『[次の記事:TurbowarpでP5JSを動かす(複数のBall:自由落下)](https://qiita.com/harhid/private/6228ecbe2666a8677621)』へ続きます