プログラミング雑記

プログラミングが趣味のだらだら学生。自作したプログラムの紹介やいまハマってることなんかを適当に書いていったりいかなったり

Processingでビジュアルプログラミング言語を自作するpart1

 

はじめに

本記事ではProcessingで簡単なビジュアルプログラミング言語を作るのを目的に、ソースコードをあげながら作り方の解説を行っています。今回は第1回目ということで一番基本となるもっとも単純なブロックの作り方について取り上げています。

ビジュアルプログラミング言語(VPL)

小中学生向けであったり、プログラミングを初めて学習する人でも簡単にプログラミングができるように、プログラムの命令を視覚的に表現したプログラミング言語のこと。

多くはプログラムの命令をブロックで表すものが多い。

主流なビジュアルプログラミング言語としてはScratchが挙げられる。またビジュアルプログラミング言語の作成を支援するBlocklyというGoogleの作ったライブラリも広く知られている。

 

Processing

Download \ Processing.org

もしダウンロードされてない方は上記のサイトからダウンロードをお願いします。

今回はこのProcessingを用いてビジュアルプログラミング言語に用いるブロックを作っていきます。

 

実装

①ブロックの見た目をつくる

まずはブロックを描画するところまで書いていきます。

以下のソースコードを打ち込むなり、コピペするなりしてください。

細かい点についてはコメントで説明しているので、随時読んでみてください。

 

 

 

Block block;

//プログラム開始時に一度だけ実行される関数
void setup(){
    size(800,450);  //画面の幅を800px, 高さを450pxに設定
    block = new Block("print", 100, 100);
}

//setup()の後、毎秒60回呼ばれる関数
void draw(){
    background(255);    //背景を真っ白に塗りつぶす
    block.display();    //display()をdraw()の中に記述しないと描画されないので注意
}

class Block {

    String name;
    int x, y, w, h;

    Block(String name, int x, int y){
        this.name = name;
        this.x = x;
        this.y = y;
        w = 120; h = 30;    //現状は決め打ちで
    }

    //描画部をここに記述
    void display(){
        fill(241,196,15);   //塗りつぶしの色を黄色に
        rect(x, y, w, h, 15);    //最後の引数は矩形の角の曲がり具合

        fill(0);    //文字色を黒色に
        textAlign(CENTER, CENTER);  //テキストの描画位置をx,yともに真ん中に
        strokeWeight(3);//枠線を少し太めに
        stroke(243,156,18);//枠線の色をオレンジっぽく
        textSize(20);
        text(name, x + w / 2, y + h / 3);
    }

}

 

これを実行すると以下のような結果が描写されると思います。

 

f:id:kirari0831:20180515110520p:plain

 

print命令を表すブロックを表示することができました!

 

②ブロックを移動できるようにする

マウスのドラッグでブロックを移動できるようにしましょう。

Processingで用意されているmousePressed()やmouseReleased()を使います。

また、あとでブロックを複数にする時のためにすべてのBlockをリストで保持するようにします。

 

詳しい解説は再びコメントを参照してください。

 

ArrayList<Block> blockList;
Block selectedBlock;    //マウスでドラッグしているブロック

//プログラム開始時に一度だけ実行される関数
void setup(){
    size(800,450);  //画面の幅を800px, 高さを450pxに設定
    blockList = new ArrayList<Block>();
    blockList.add(new Block("print", 100, 100));
}

//setup()の後、毎秒60回呼ばれる関数
void draw(){
    background(255);    //背景を真っ白に塗りつぶす
    for(Block block : blockList){   //リストの中身をすべて取得し
        block.display();    //表示させる
    }
}

//マウスが押されたときに呼ばれる関数
void mousePressed(){
    //ここで逆順に走査しているのはリストの後ろ側にあるブロックの方が
    //画面上では手前にあるブロックであるため
    for(int i = blockList.size()-1; i >= 0; i--){
        Block block = blockList.get(i);
        if(block.isPressed()) { //マウスがそのブロック内にあれば
            selectedBlock = block;
            break;
        }
    }
}

//マウスをドラッグしている間呼ばれる関数
void mouseDragged(){
    if(selectedBlock != null){
        selectedBlock.move(mouseX - pmouseX, mouseY - pmouseY); //移動量はカーソルが前フレームから移動した量
    }
}

//マウスが離されたとき呼ばれる関数
void mouseReleased(){
    selectedBlock = null;   //ドラッグ終了のため選択されたブロックは必ずnullに
}

class Block {

    String name;
    int x, y, w, h;

    Block(String name, int x, int y){
        this.name = name;
        this.x = x;
        this.y = y;
        w = 120; h = 30;    //現状は決め打ちで
    }

    //描画部をここに記述
    void display(){
        fill(241,196,15);   //塗りつぶしの色を黄色に
        rect(x, y, w, h, 15);    //最後の引数は矩形の角の曲がり具合

        fill(0);    //文字色を黒色に
        textAlign(CENTER, CENTER);  //テキストの描画位置をx,yともに真ん中に
        strokeWeight(3);
        stroke(243,156,18);
        textSize(20);
        text(name, x + w / 2, y + h / 3);
    }

    void move(int addX, int addY){
        x += addX;
        y += addY;
    }

    //マウスがブロック内にあるかどうか
    boolean isPressed(){
        return x <= mouseX && mouseX <= x + w &&
               y <= mouseY && mouseY <= y + h;
    }

}

 

これでブロックの移動ができるようになりました!

 

f:id:kirari0831:20180515113449g:plain

 

③ブロックをくっつけさせる

ブロック型のビジュアルプログラミング言語は複数のブロックを組み合わせることによってプログラムを表現します。組み合わせるために、様々な場面でブロックを「くっつける」作業が必要になるのですが、これが一番ブロックをつくっていく中で難しい事ではないかと思っています。

ただ、今回扱うのはブロックの上下くっつけだけですので全く難しくはないのでご安心を。

 

今回のくっつけ方は、二つのブロックを上下に接続するだけですので、クリックを離したときに上にブロックがあれば直下に移動させるという単純な考え方です。

ブロックをくっつけるためにブロックの追加が必要になったため、keyPressed()を用いて、キーボードのpを入力したときにブロックを追加するようにしました。

 

ArrayList<Block> blockList;
Block selectedBlock;    //マウスでドラッグしているブロック

//プログラム開始時に一度だけ実行される関数
void setup(){
    size(800,450);  //画面の幅を800px, 高さを450pxに設定
    blockList = new ArrayList<Block>();
    blockList.add(new Block("print", 100, 100));
}

//setup()の後、毎秒60回呼ばれる関数
void draw(){
    background(255);    //背景を真っ白に塗りつぶす
    for(Block block : blockList){   //リストの中身をすべて取得し
        block.display();    //表示させる
    }
}

//マウスが押されたときに呼ばれる関数
void mousePressed(){
    //ここで逆順に走査しているのはリストの後ろ側にあるブロックの方が
    //画面上では手前にあるブロックであるため
    for(int i = blockList.size()-1; i >= 0; i--){
        Block block = blockList.get(i);
        if(block.isPressed()) { //マウスがそのブロック内にあれば
            selectedBlock = block;
            break;
        }
    }
}

//マウスをドラッグしている間呼ばれる関数
void mouseDragged(){
    if(selectedBlock != null){
        selectedBlock.move(mouseX - pmouseX, mouseY - pmouseY); //移動量はカーソルが前フレームから移動した量
    }
}

//マウスが離されたとき呼ばれる関数
void mouseReleased(){
    if(selectedBlock != null){
        for(Block block : blockList){
            if(selectedBlock != block &&  block.canConnect(selectedBlock)){
                //複数ブロックの移動の時のために、指定した座標に移動させるのではなく
                //必ず移動量を指定して移動させるようにする。
                int addX = block.x - selectedBlock.x;
                int addY = (block.y + block.h) - selectedBlock.y;
                selectedBlock.move(addX, addY);
            }
        }
        selectedBlock = null;   //ドラッグ終了のため選択されたブロックは必ずnullに
    }
}

//キーボードが押されている間呼ばれる関数
void keyPressed(){
    if(key == 'p'){ //キーボードの'p'が押されたら
        blockList.add(new Block("print", width/2, height/2));    //新しいブロックを画面中央に追加
    }
}

class Block {

    String name;
    int x, y, w, h;

    Block(String name, int x, int y){
        this.name = name;
        this.x = x;
        this.y = y;
        w = 120; h = 30;    //現状は決め打ちで
    }

    //描画部をここに記述
    void display(){
        fill(241,196,15);   //塗りつぶしの色を黄色に
        rect(x, y, w, h, 15);    //最後の引数は矩形の角の曲がり具合

        fill(0);    //文字色を黒色に
        textAlign(CENTER, CENTER);  //テキストの描画位置をx,yともに真ん中に
        strokeWeight(3);
        stroke(243,156,18);
        textSize(20);
        text(name, x + w / 2, y + h / 3);
    }

    void move(int addX, int addY){
        x += addX;
        y += addY;
    }

    //マウスがブロック内にあるかどうか
    boolean isPressed(){
        return x <= mouseX && mouseX <= x + w &&
               y <= mouseY && mouseY <= y + h;
    }

    int margin = 20;    //許容する二つのブロックの距離の差。適宜変更したり、xとy座標でそれぞれ分けるのもありです
    boolean canConnect(Block block){
        int bx = block.x;
        int by = block.y;
        //abs()は引数の絶対値を返す関数
        //yに関しては相手のブロックの「底」と比較するためにhを足すのを忘れないように
        return abs(x - bx) <= margin && abs(y + h - by) <= margin;
    }

}

 

これで二つのブロックをくっつけることに成功!……しましたが、不自然な動きをしていますね。つなげた後に上のブロックを動かすと、下のブロックもついてきてほしい所ですが、上のブロックが移動するだけですぐに離れてしまいます。

そう、見た目ではつながっているように見えているだけで、本当の意味ではつながっていないのです。

 

f:id:kirari0831:20180515131509g:plain

(つながったように見えて、一番上のブロックを移動させようとすると、下に続くブロックが移動しない)

 

④ブロックの接続関係をもたせる

見た目だけつなげても仕方ないので、ブロックの接続関係を持たせます。

まずBlockクラスにpostBlockとpreBlockを新たに設定し、ここにほかのブロックを代入することにより接続関係を実現します。ブロックを移動させる際には下に続いているブロック(postBlock)に対しても移動を行わせることにより、つながっているブロックを一斉に移動させます。接続だけでなくブロックが離れたときに「解除」するのも忘れずに!接続関係を解消させないと見た目上では離れていても接続関係は保ったままで、上のブロックを動かすと離れているブロックも移動してしまうといったことになります。

 

ArrayList<Block> blockList;
Block selectedBlock;    //マウスでドラッグしているブロック

//プログラム開始時に一度だけ実行される関数
void setup(){
    size(800,450);  //画面の幅を800px, 高さを450pxに設定
    blockList = new ArrayList<Block>();
    blockList.add(new Block("print", 100, 100));
}

//setup()の後、毎秒60回呼ばれる関数
void draw(){
    background(255);    //背景を真っ白に塗りつぶす
    for(Block block : blockList){   //リストの中身をすべて取得し
        block.display();    //表示させる
    }
}

//マウスが押されたときに呼ばれる関数
void mousePressed(){
    //ここで逆順に走査しているのはリストの後ろ側にあるブロックの方が
    //画面上では手前にあるブロックであるため
    for(int i = blockList.size()-1; i >= 0; i--){
        Block block = blockList.get(i);
        if(block.isPressed()) { //マウスがそのブロック内にあれば
            selectedBlock = block;
            break;
        }
    }
}

//マウスをドラッグしている間呼ばれる関数
void mouseDragged(){
    if(selectedBlock != null){
        selectedBlock.move(mouseX - pmouseX, mouseY - pmouseY); //移動量はカーソルが前フレームから移動した量
    }
}

//マウスが離されたとき呼ばれる関数
void mouseReleased(){
    if(selectedBlock != null){
        for(Block block : blockList){
            if(selectedBlock != block &&  block.canConnect(selectedBlock)){
                block.connectPostBlock(selectedBlock);  //ブロックの接続
            }
        }

        if(selectedBlock.preBlock != null){ //選択しているブロックの上でほかのぶとっくとつながっていて
            if(!selectedBlock.preBlock.canConnect(selectedBlock)){  //それがつながる位置にいなければ
                selectedBlock.disconnectPreBlock(); //ブロックのつながりを解除
            }
        }
        selectedBlock = null;   //ドラッグ終了のため選択されたブロックは必ずnullに
    }
}

//キーボードが押されている間呼ばれる関数
void keyPressed(){
    if(key == 'p'){ //キーボードの'p'が押されたら
        blockList.add(new Block("print", width/2, height/2));    //新しいブロックを画面中央に追加
    }
}

class Block {

    String name;
    int x, y, w, h;
    Block preBlock;
    Block postBlock;

    Block(String name, int x, int y){
        this.name = name;
        this.x = x;
        this.y = y;
        w = 120; h = 30;    //現状は決め打ちで
    }

    //描画部をここに記述
    void display(){
        fill(241,196,15);   //塗りつぶしの色を黄色に
        rect(x, y, w, h, 15);    //最後の引数は矩形の角の曲がり具合

        fill(0);    //文字色を黒色に
        textAlign(CENTER, CENTER);  //テキストの描画位置をx,yともに真ん中に
        strokeWeight(3);
        stroke(243,156,18);
        textSize(20);
        text(name, x + w / 2, y + h / 3);
    }

    void move(int addX, int addY){
        x += addX;
        y += addY;
        //次のブロックが存在していればそれも合わせて移動させる
        if(postBlock != null) postBlock.move(addX, addY);
    }
    //上のブロックに対して、引数に渡されたブロックを「下」に接続します
    void connectPostBlock(Block block){
        //接続関係を設定
        this.postBlock = block;
        block.preBlock = this;

        //複数ブロックの移動の時のために、指定した座標に移動させるのではなく
        //必ず移動量を指定して移動させるようにする。
        int addX = this.x - block.x;
        int addY = (this.y + this.h) - block.y;
        block.move(addX, addY);
    }

    //上にあるブロックと自分のブロックのつながりを解除
    void disconnectPreBlock(){
        this.preBlock.postBlock = null;
        this.preBlock = null;
    }

    //マウスがブロック内にあるかどうか
    boolean isPressed(){
        return x <= mouseX && mouseX <= x + w &&
               y <= mouseY && mouseY <= y + h;
    }

    int margin = 20;    //許容する二つのブロックの距離の差。適宜変更したり、xとy座標でそれぞれ分けるのもありです
    boolean canConnect(Block block){
        int bx = block.x;
        int by = block.y;
        //abs()は引数の絶対値を返す関数
        //yに関しては相手のブロックの「底」と比較するためにhを足すのを忘れないように
        return abs(x - bx) <= margin && abs(y + h - by) <= margin;
    }

}

 

これにより簡単なブロックの接続を実現することができました!

 

f:id:kirari0831:20180515145227g:plain

 

まとめ

今回は単純なビジュアルプログラミング言語で用いるブロックを作成しました。

単純なものではありますが、少し工夫をすれば色々なブロックが出来ると思います。

今後、ビジュアルプログラミング言語の実行部の作成であったり、引数ブロックや引数フィールドの設定の仕方、そして難易度の高い入れ子ブロックの作成などについての記事を上げられればいいかなと思っていますが、結構気まぐれなので更新は未定です。

 

今回上げたソースコードにバグなどがありましたらコメントで教えていただけると嬉しいです。また、要望などあればそちらもぜひ。