カスタム図形を作ってみよう

今回はカスタム図形を描いてみましょう。
カスタム図形とは、私が勝手に名付けた造語ですが、基本図形(線分、四角、円など)を組み合わせて作ったオリジナルの図形のことです。
ポイントは、一つの関数にカスタム図形の描画をまとめてしまうことです。関数としてまとめると、関数呼び出しをするだけで複雑な図形を何度も 描くことができます
また、基本図形同様、描画位置やサイズを引数として渡すようにします。

今回のゴールとして、次のような肉球スタンププログラムを作ってみましょう。
四角枠の内部をクリックしてみてください。肉球の大きさは、画面下のスライダーで変更できます。

ファイルの準備

さて、新しくファイル群(index.htmlとindex.js)を準備し、お決まり文句を書いていきましょう。
JavaScriptファイルの中身は次のようになります。


var w = 300;
var h = 300;
function setup() {
  createCanvas(w, h);  // キャンバス(描画領域)の生成
  background(255);  //背景色を白に設定
}

function draw() {
}
        

インターフェースを決めよう

これから肉球を一つ描く関数を作っていくのですが、関数を作っていくときは、まず「どんな風に使いたいか」を意識するといいです。
どんな値が返ってきて、どんな引数を必要とするのか決めたものをインターフェースといいますが、いきなり関数の中身を考えていくの ではなく、「どんな風に使いたいか」外側の部分を先に考えます。
今回の関数のインターフェースは、返り値が無く(正確には必要ない)、引数には表示位置(x,y)とサイズ(size)が必要になります。
関数名はfootprint(足跡)にしましょう。


var w = 300;
var h = 300;
function setup() {
  createCanvas(w, h);
  background(255);
}

function draw() {
}

function mouseClicked(){
  fill(color(255,200,200)); //ピンク色に塗りつぶす
  noStroke();               //境界線は描かない 
  footprint(mouseX, mouseY, 50);
}
        

footprint関数自体はまだ定義すらしていないですが、使い方としてはこのような感じになるように作っていきます。
足跡は、マウスがクリックされた時に、クリック位置に描画したいので、mouseClicked関数の中から描画関数を呼び出したいと思います。
mouseClickedは、マウスがクリック(押して離したタイミング)されたときに自動的に呼ばれる関数です。

このプログラムではdraw関数は特に書くものはありません。

カスタム図形を作る

いよいよカスタム図形を作っていくわけですが、カスタム図形の設計ポイントは、sizeを使ってパーツの位置調整を行うことです。
「右へ24ピクセルの位置に表示する」のように絶対値を使って位置調整するコードを書いてしまうと、 サイズを変えたときに想定していた図形と異なる図形になってしまうからです。
このことに注意して設計していきましょう

今回作る肉球は6つの円から成り立っています。

まずはラフでいいので描きたい図形を円や楕円、四角形、三角形、直線に分解します。
その後、次のように図形の中心を決め、それぞれの基本図形の位置が中心からどれくらいずれているのか、 大きさは全体に比べてどれくらいなのか、ざっと測ります。

先ほども書きましたが、中心からの位置調整、全体に対しての大きさは全てsize引数を使って表現します。

設計図ができたら今度はそれをもとにプログラミングしていきます。
footprint関数を作りましょう


var w = 300;
var h = 300;
function setup() {
  createCanvas(w, h);
  background(255);
}

function draw() {
}

function mouseClicked(){
  fill(color(255,200,200));
  noStroke();
  footprint(mouseX, mouseY, 50);
}

function footprint(x, y, size){
  var l = x - size/2;  // 図形の右端となるx座標
  var u = y - size/2;  // 図形の上端となるy座標
  
  // 中央の円
  ellipse(x,y,size*0.5,size * 0.45);

  // 中央少し下の円
  ellipse(x, y + size * 0.15, size * 0.8, size* 0.5);
  
  // 一番左の円
  ellipse(l + size * 0.1, u + size * 0.3, size /5, size/3);
  
  //一番右の円
  ellipse(l + size * 0.9, u + size * 0.3, size /5, size/3);
  
  // 左から2番目の円
  ellipse(l + size * 0.35, u + size * 0.1, size /4, size/3);
  
  // 左から3番目の円
  ellipse(l + size * 0.65, u + size * 0.1, size /4, size/3);
}
        

少し面倒ですが、円を一つ一つ描いていきます。
復習ですが、円を描く際の関数はellipseでした。引数は順に中心x座標、中心y座標、円の横幅、円の縦幅です。

円の位置や大きさは中心座標とサイズのみを使って表現しています。
設計からプログラムを書く際に、計算した通りにプログラムを書いて一発で想像通りの図形が描ければいいのですが、 そうでなくても数値や表現を少しずつ変えていき、しっくりくる場所や大きさを実験的に探していくのもありです。
むしろ、プログラムを書いてからの微調整は必須です。なので、最初からしっかりと計算するのではなく、 おおよその位置やサイズでプログラムを書き始めてもいいでしょう。 この肉球の場合でしたら、ざっくりと計算し、プログラムを書いた後、数値をちょっとずつ変えていきいい場所を探しました。

スライダーで肉球の大きさを変えよう

以上で肉球を画面にポチポチ押せるようになったと思います。
作成したfootprint関数には第3引数にサイズも渡せるので、せっかくだからスライダーから肉球の大きさを変えれるようにしましょう。

スライダーはcreateSlider関数で作ることができ、スライダーで選択している値はvalue関数で取得できます。


var w = 300;
var h = 300;
var slider;
function setup() {
  createCanvas(w, h);
  background(255);
  slider = createSlider(20, 100, 50);  //スライダーを生成
  slider.position(10, 310);            // スライダーの表示位置を調整
}

function draw() {
}

function mouseClicked(){
  fill(color(255,200,200));
  noStroke();
  footprint(mouseX, mouseY, slider.value()); //スライダーの値を使うように修正
}

function footprint(x, y, size){
  var l = x - size/2;
  var u = y - size/2;
  
  // 中央の円
  ellipse(x,y,size*0.5,size * 0.45);

  // 中央少し下の円
  ellipse(x, y + size * 0.15, size * 0.8, size* 0.5);
  
  // 一番左の円
  ellipse(l + size * 0.1, u + size * 0.3, size /5, size/3);
  
  //一番右の円
  ellipse(l + size * 0.9, u + size * 0.3, size /5, size/3);
  
  // 左から2番目の円
  ellipse(l + size * 0.35, u + size * 0.1, size /4, size/3);
  
  // 左から3番目の円
  ellipse(l + size * 0.65, u + size * 0.1, size /4, size/3);
}
        

まず3行目でスライダーを格納しておくための変数を準備しています。
ここで、setup関数の中でvar sliderとしてしまうと、mouseClicked関数の中からはsliderにアクセスできないので注意しましょう。
複数の関数の中から使う変数は、このようにグローバル変数として定義しておきましょう。

次にsetup関数の中でスライダーの生成と位置調整を行っています。
createSliderの引数は、前から最小値、最大値、デフォルト値です。positionは見てすぐわかると思いますが、表示位置のxy座標です。

最後に、mouseClicked関数内のfootprint関数を呼び出すところで、第3引数の50をslider.value()に変更し、スライダーの値をfootprint関数へ 渡すようにしています。

描画範囲を制限しよう

スライダーを実装したことで、実は新たな問題が発生しました。
実際スライダー実装時点でのコードを実行してもらえると分かるのですが、スライダーを動かした際に肉球が描かれてしまうのです。
下記画像は、スライダーを動かし終わり、マウスを離したときに肉球が赤枠のところに描かれた様子です。

そこで、マウスがキャンバス内(300x300)の中にいるときだけ肉球を描くように改良したいと思います。
イメージとしては次のような感じです。


var w = 300;
var h = 300;
var slider;
function setup() {
  createCanvas(w, h);
  background(255);
  slider = createSlider(20, 100, 50); 
  slider.position(10, 310);
}

function draw() {
}

function mouseClicked(){
  fill(color(255,200,200));
  noStroke();
  if("マウスがキャンバス内にいる"){
    footprint(mouseX, mouseY, slider.value());
  }
}

function footprint(x, y, size){
  var l = x - size/2;
  var u = y - size/2;
  
  // 中央の円
  ellipse(x,y,size*0.5,size * 0.45);

  // 中央少し下の円
  ellipse(x, y + size * 0.15, size * 0.8, size* 0.5);
  
  // 一番左の円
  ellipse(l + size * 0.1, u + size * 0.3, size /5, size/3);
  
  //一番右の円
  ellipse(l + size * 0.9, u + size * 0.3, size /5, size/3);
  
  // 左から2番目の円
  ellipse(l + size * 0.35, u + size * 0.1, size /4, size/3);
  
  // 左から3番目の円
  ellipse(l + size * 0.65, u + size * 0.1, size /4, size/3);
}
        

footprint関数を「マウスがキャンバス内にいる」という条件のif文の中にいれました。

最も簡単に条件式を書くと次のようになります。


(mouseX > 0 && mouseX < w) && (mouseY > 0 && mouseY < h)
        

あるいは、次のような書き方でもOKです。


abs(mouseX - w/2) < w/2 && abs(mouseY - h/2) < h/2
        

これは、「0から300の範囲にmouseXがある」というのを、「範囲の中心からmouseXへの距離が150未満」へ置き換えて実装したものです。

さて、いよいよプログラムを完成させましょう。
条件文をif文に入れると完成です。


var w = 300;
var h = 300;
var slider;
function setup() {
  createCanvas(w, h);
  background(255);
  slider = createSlider(20, 100, 50); 
  slider.position(10, 310);
}

function draw() {
}

function mouseClicked(){
  fill(color(255,200,200));
  noStroke();
  if((mouseX > 0 && mouseX < w) && (mouseY > 0 && mouseY < h)){
    footprint(mouseX, mouseY, slider.value());
  }
}

function footprint(x, y, size){
  var l = x - size/2;
  var u = y - size/2;
  
  // 中央の円
  ellipse(x,y,size*0.5,size * 0.45);

  // 中央少し下の円
  ellipse(x, y + size * 0.15, size * 0.8, size* 0.5);
  
  // 一番左の円
  ellipse(l + size * 0.1, u + size * 0.3, size /5, size/3);
  
  //一番右の円
  ellipse(l + size * 0.9, u + size * 0.3, size /5, size/3);
  
  // 左から2番目の円
  ellipse(l + size * 0.35, u + size * 0.1, size /4, size/3);
  
  // 左から3番目の円
  ellipse(l + size * 0.65, u + size * 0.1, size /4, size/3);
}
        

まとめ

いかがでしたでしょうか。
カスタム図形として図形の描画を括りだすことで、「図形の描画(カスタム図形)」と「図形をどう描画するか(drawでの処理など)」を分けて考えることができ、 コード全体がすっきりとします。
肉球スタンププログラムの例で、カスタム図形の威力を知っていただけると幸いです。

  • 何度も基本図形を組み合わせた図形を描く場合は、カスタム図形として関数にしよう
  • 関数にする際には、表示位置とサイズを渡せるようにしよう
  • 色や境界線の有り無しは関数に含めず、呼び出し時に変更できるようにしよう

Prev