p5.js①・図形描画とイベント処理

Image from Gyazo

p5.jsとは

p5.jsは,芸術家やデザイナーのためのオープンソースのJavaScriptライブラリです. ウェブページに,キャンバスを設置し,図形やアニメーションを描画することができます. Processingと同等の機能を備えていることから,Processing経験者には学習コストが低く,プログラミングの初学者にも適したライブラリです. ここでは,vue-p5を利用することで,Vue.jsとp5.jsを共存させながら,「3目並べ」のWebアプリを開発することを目指します.

CodePenの準備

CodePenにアクセスして,Penを作成し,タイトルを設定しましょう. Penのタイトルは「Chapter11」に設定しましょう.

Image from Gyazo

前回と同様にVue.jsのライブラリをペンに追加してください. Vue.jsのバージョンは2.6.11を採用します.

Image from Gyazo

次に,Vue.jsとp5.jsを共存させるためのvue-p5を導入します. 検索してもCDNがリストアップされないので,https://unpkg.com/vue-p5を直接入力します.

Image from Gyazo

vue-p5

vue-p5を利用することで,Vueクラスのメソッドとしてシンプルにp5.jsを記述することができます. HTMLは次のように記述します. vue-p5タグで,p5.jsの実装に必要な関数setup()draw()を宣言します. 次回利用するマウスの移動を検出するmouseMoved()なども,必要があれば追加で宣言します.

<!-- HTML -->
<div id="app">
  <h1>{{ title }}</h1>
  <vue-p5
    @setup="setup"
    @draw="draw">
  </vue-p5>
</div>

JavaScriptでは,Vueクラスを定義します. このとき,methodsオプションに,上記で宣言したsetup()draw()を記述します. これらの関数の引数であるsketchを利用して,p5.jsの関数を呼び出します. 例えば,キャンバスの大きさを設定する関数はsketch.createCanvas(width, height),また,キャンバスの背景色を設定する関数はsketch.background(color)のように記述します. キャンバスの原点(0,0)は左上であり,右方向にX軸,下方向にY軸が伸びています.

// JavaScript
new Vue({
  el: '#app',
  data: {
    title: "3目並べ",
  },
  methods: {
    setup(sketch){
      sketch.createCanvas(500, 500); // キャンバスの大きさ
      sketch.background("black"); // キャンバスの背景色
    },
    draw(sketch){
    }
  }
});

Image from Gyazo

p5.jsにおける関数の実行順序に関して確認しておきましょう. 最初にsetup()が1度だけ実行されます. setup()には,キャンバスの大きさや背景色など,プログラム全体に関する設定を記述します. その後で,draw()が一定間隔で繰り返し実行されます. draw()には,図形・文字・画像など,キャンバスに表示する要素を記述します. 繰り返しごとに,位置を少し変えることで,パラパラ漫画と同じ仕組みでアニメーションを表現できます. ファイルの読込など,プログラムの実行前に完了すべき操作はpreload()に記述します. イベント検出は,それぞれに関数が用意されており,例えばマウスの移動を検出するにはmouseMoved()を定義します.

Image from Gyazo

キーポイント

  • vue-p5タグでp5.jsの関数を登録
  • methodsオプションで関数を実装
  • setup()は1回だけ実行
  • draw()は繰り返し実行

図形描画

3目並べの盤を四角形で表現します. 盤の大きさは,幅と高さが同じ450とします. 四角形を描く関数はrect(x, y, width, height)です. 引数のxyは四角形の左上の座標,widthは幅,heightは高さです. また,fill(color)は,図形の塗りつぶしの色を設定します.

draw(sketch){
  sketch.fill("green");
  sketch.rect(25, 25, 450, 450);
}

Image from Gyazo

3目並べのマス目を直線で表現します. 直線を描く関数はline(x1, y1, x2, y2)です. 引数の(x1, y1)から,(x2, y2)の間に直線が描かれます. また,strokeWeight(weight)は直線の幅を設定します.

draw(sketch){
  sketch.fill("green");
  sketch.rect(25, 25, 450, 450);

  sketch.stroke("black");
  sketch.strokeWeight(5);
  sketch.line(175, 0, 175, 500);
  sketch.line(325, 0, 325, 500);
  sketch.line(0, 175, 500, 175);
  sketch.line(0, 325, 500, 325);
}

Image from Gyazo

キーポイント

  • 直線はline()
  • 四角形はrect()
  • 線の色はstroke()
  • 線の太さはstrokeWeight()
  • 塗りつぶしの色はfill()

盤の状態の表現

盤上のマス目をクリックすると,黒(先手)または白(後手)の石を置きます. 石を置く位置はマス目の中心とするため,先に中心の座標を計算しておきます. dataオプションで,center_listという名前で配列を定義します.

data: {
  title: "3目並べ",
  center_list: [],
}

マス目の中心座標は一度だけ求めれば良いため,setup()で次のように算出します. ここで,iは行番号,jは列番号を表しています. center_listの配列の要素として,座標[center_x,center_y]を配列として追加しています(配列の入れ子構造になっていることに注意).

setup(sketch){
  sketch.createCanvas(500, 500); // キャンバスの大きさ
  sketch.background("black"); // キャンバスの背景色

  for(let i=0; i<3; i++){
    for(let j=0; j<3; j++){
      let center_x = 100 + 150 * j;
      let center_y = 100 + 150 * i;
      this.center_list.push([center_x, center_y]);

	  // コンソールに出力
	  console.log(`${center_x},${center_y}`);
    }
  }
}

算出したマス目の中心座標は次のようになります.

"100,100"
"250,100"
"400,100"
"100,250"
"250,250"
"400,250"
"100,400"
"250,400"
"400,400"

次にマス目の状態を表すために, dataオプションでstate_listという名前の配列を定義します. 各要素はマス目の状態を表しており, 0は石が置かれていない状態,1は黒の石が置かれた状態,2は白の石が置かれた状態を表します.

data: {
  title: "3目並べ",
  center_list: [],
  state_list: [0, 0, 0, 0, 0, 0, 0, 0, 0],
}

上記で定義したstate_listを利用して,盤上に石を円として描きます. 円を描く関数はcircle(x, y, diameter)です. 引数のxyは円の中心の座標,diameterは直径です. 状態が1のときは黒色,状態が2のときは白色で円を描きます.

draw(sketch){

  sketch.fill("green");
  sketch.rect(25, 25, 450, 450);

  sketch.stroke("black");
  sketch.strokeWeight(5);
  sketch.line(175, 0, 175, 500);
  sketch.line(325, 0, 325, 500);
  sketch.line(0, 175, 500, 175);
  sketch.line(0, 325, 500, 325);

  for(let i=0; i<this.state_list.length; i++){
    let x = this.center_list[i][0];
    let y = this.center_list[i][1];

	// 黒の石(円)を描く
    if(this.state_list[i] == 1){
      sketch.fill("black");
      sketch.noStroke(); // 線は描かない
      sketch.circle(x, y, 140);
    }
	// 白の石(円)を描く
    else if(this.state_list[i] == 2){
      sketch.fill("white");
      sketch.noStroke(); // 線は描かない
      sketch.circle(x, y, 140);
    }
  }
},

state_listを任意に変更して,盤面に石が配置されることを確認しましょう. 確認が終わったら,全ての状態を0に戻しておきましょう.

state_list: [0, 0, 1, 0, 2, 0, 0, 1, 0]

Image from Gyazo

キーポイント

  • 円はcircle()

イベントの処理

マウスをクリックした位置に応じて石を配置します. マウスのボタンが押されたことを検知するにはmousePressed()を利用します. HTMLのvue-p5タグで,mousePressed()を利用することを宣言します.

<!-- HTML -->
<div id="app">
  <h1>{{ title }}</h1>
  <vue-p5
    @setup="setup"
    @draw="draw"
    @mousePressed="mousePressed">
  </vue-p5>
</div>

methodsオプションで,mousePressed()を定義します. このとき,引数にはsketchを設定します. 先に算出していた「マス目の中心座標」と「マウスがクリックされた座標」の距離(マンハッタン距離を採用)を計算します. マウスがクリックされた座標はmouseXmouseYで参照できます. 距離が最短となるマス目の状態を1に設定します. ここで,...は配列を展開して引数として利用するための演算子です.

mousePressed(sketch){

  // マス目の中心座標までの距離を算出
  let distance_list = [];
  for(let i=0; i<this.center_list.length; i++){
    let x = this.center_list[i][0];
    let y = this.center_list[i][1];
    let distance = Math.abs(x - sketch.mouseX) + Math.abs(y - sketch.mouseY);
    distance_list.push(distance);
  }

  // 距離が最短となるマス目の番号
  let min_index = distance_list.indexOf(Math.min(...distance_list));

  // 状態の変更
  if(this.state_list[min_index] == 0){
    this.state_list[min_index] = 1;
  }

}

実行すると,マウスをクリックした位置に黒い石が置かれることが確認できます.

Image from Gyazo

先手は黒の石,後手は白の石を置くように修正しましょう. dataオプションに,打ち手の順番を表すturnを宣言し,“black"を代入します.

data: {
  title: "3目並べ",
  center_list: [],
  state_list: [0, 0, 0, 0, 0, 0, 0, 0, 0],
  turn: "black",
}

上記のturnblackのときは状態を1に変更し,turnwhiteのときは状態を2に変更します. これで,先手は黒色で円が描かれ,後手は白色で円が描かれます.

mousePressed(sketch){

  let distance_list = [];
  for(let i=0; i<this.center_list.length; i++){
    let x = this.center_list[i][0];
    let y = this.center_list[i][1];
    let distance = Math.abs(x - sketch.mouseX) + Math.abs(y - sketch.mouseY);
    distance_list.push(distance);
  }

  let min_index = distance_list.indexOf(Math.min(...distance_list));

  // 状態の変更
  if(this.state_list[min_index] == 0){
    if(this.turn == "black"){
      this.state_list[min_index] = 1;
      this.turn = "white"; // 順番を白に変更
    }
    else if(this.turn == "white"){
      this.state_list[min_index] = 2;
      this.turn = "black"; // 順番を黒に変更
    }
  }

}

Image from Gyazo

キーポイント

  • マウスのボタンが押されたことを検出するにはmousePressed()
  • マウスの位置はmouseXとmouseY

アプリの確認

See the Pen chapter11 by Naoto Mukai (@nmukai) on CodePen.

課題

次の図を参考に,勝利したときは円の色を金色に変更してください.

Image from Gyazo

課題を完成させたら,Penの ZIPファイルリンク を提出してください. 提出方法は初回のWebアプリの開発を参考にしてください.

参考書籍

愛知県名古屋市にある椙山女学園大学 文化情報学部 向研究室の公式サイトです. 専門は情報科学であり,人工知能やデータベースなどの技術要素を指導しています. この公式サイトでは,授業で使用している教材を公開すると共に, ベールに包まれた女子大教員のミステリアスな日常を4コマ漫画でお伝えしていきます. サイトに関するご意見やご質問はFacebookまたはTwitterでお問い合わせください.