前回の演習4の解答例を示す.細かい挙動はサンプルとは異なるが,プログラムが長くなるため省いた.
Ball ball0; final float GRAVITY = 9.8 / 60; final float ELAS = 0.98; void setup() { size(500, 500); ball0 = new Ball(); ball0.setX(width / 2); ball0.setY(height / 2); ball0.setVX(10); ball0.setVY(-10); ball0.setD(50); ball0.setOnBigger(true); } void draw() { background(255, 255, 255); ball0.move(); ball0.draw(); } class Ball { float x; float y; float vx; float vy; float d; boolean onBigger; boolean prevIsCursorIn; void setX(float x) { this.x = x; } void setY(float y) { this.y = y; } void setVX(float vx) { this.vx = vx; } void setVY(float vy) { this.vy = vy; } void setD(float d) { this.d = d; } void setOnBigger(boolean b) { this.onBigger = b; } boolean isCursorIn() { float dx = mouseX - this.x; float dy = mouseY - this.y; float r = this.d / 2; return dx * dx + dy * dy < r * r; } void move() { boolean iscursorin = this.isCursorIn(); if (iscursorin) { if (!this.prevIsCursorIn) this.onBigger = !this.onBigger; if (this.onBigger) this.d += 1; else this.d -= 1; } else { this.x += this.vx; this.vy += GRAVITY; this.y += this.vy; } if (this.x < (this.d / 2)) { this.x = this.d / 2; this.vx = ELAS * -this.vx; } if (this.x > (width - (this.d / 2))) { this.x = width - (this.d / 2); this.vx = ELAS * -this.vx; } if (this.y > height - (this.d / 2)) { this.y = height - (this.d / 2); this.vy = ELAS * -this.vy; } this.prevIsCursorIn = iscursorin; } void draw() { ellipse(this.x, this.y, this.d, this.d); } }
ボールが大きく,または小さくなる状態なのか記憶しておくための属性(メンバ変数)
boolean onBigger;
と,1フレーム前にマウスカーソルがボール内にあったかどうか記憶しておくための属性(メンバ変数)
boolean prevIsCursorIn;
をボールクラスに追加している.
またボール内にマウスカーソルがあるかどうかをtrue,またはfalseで返す,ボールクラス内部で使用されるメソッド
boolean isCursorIn() { float dx = mouseX - this.x; float dy = mouseY - this.y; float r = this.d / 2; return dx * dx + dy * dy < r * r; }
が追加されている.
ボールクラスのmoveメソッドは以下が変更されている.
void move() { boolean iscursorin = this.isCursorIn(); if (iscursorin) { if (!this.prevIsCursorIn) this.onBigger = !this.onBigger; if (this.onBigger) this.d += 1; else this.d -= 1; } else { this.x += this.vx; this.vy += GRAVITY; this.y += this.vy; } ...//あたり判定部分は同じなので省略 this.prevIsCursorIn = iscursorin; }
moveメソッドの最初,
boolean iscursorin = this.isCursorIn();
で,現在ボール内にマウスカーソルが存在するか計算する.存在すればiscursorinローカル変数がtrue,なければfalseとなる.
そして,もしボール内にカーソルが入っているのであれば,ボールを動かさず直径(属性d)の値を変更する処理を行い,入っていないのであれば通常通りボールを動かす.
if (iscursorin) { ...//ボールの直径を変更する処理 } else { //ボールを動かす this.x += this.vx; this.vy += GRAVITY; this.y += this.vy; }
ボールの直径を変更する処理ですぐに行われる
if (!this.prevIsCursorIn) this.onBigger = !this.onBigger;
このif文は何をしているかというと,
かつ
ということは,今マウスカーソルはボール内に入った瞬間である,ということである.
そこで,今マウスカーソルがボール内に入った瞬間であるのであれば,
this.onBigger = !this.onBigger;
で,ボールが大きくなる,小さくなるを切り替える,というわけである.if文がなく,
this.onBigger = !this.onBigger;
これだけでは,マウスカーソルがボール内にある間は,ボールは大きくなる,小さくなるの切り替えを毎フレーム繰り返してしまいボールのサイズは変わらなくなってしまう.
あとは
if (this.onBigger) this.d += 1; else this.d -= 1;
で,onBiggerがtrueであれば直径を1大きくするし,falseであれば-1する.
また,moveメソッド最後の
this.prevIsCursorIn = iscursorin;
を忘れてはならない.
これにより1フレーム前に,マウスカーソルがボール内にあったかどうかを記憶しておく.これがなければ前述の
if (!this.prevIsCursorIn) this.onBigger = !this.onBigger;
が正しく動かない.
解答例では
this.prevIsCursorIn = iscursorin;
をmoveメソッドの最後に書いている.しかし,
void move() { boolean iscursorin = this.isCursorIn(); if (iscursorin) { if (!this.prevIsCursorIn) this.onBigger = !this.onBigger; if (this.onBigger) this.d += 1; else this.d -= 1; } else { this.x += this.vx; this.vy += GRAVITY; this.y += this.vy; } this.prevIsCursorIn = iscursorin; ...//あたり判定部分は同じなので省略 }
でも,特に問題はない.なぜ解答例ではmoveメソッドの最後の行に
this.prevIsCursorIn = iscursorin;
を書いているかというと,
this.prevIsCursorIn = iscursorin;
のあとでは,this.prevIsCursorInはすでに,”現在”ボール内にマウスカーソルがあるかどうかの情報になってしまっているので,変数の意味とは異なる.それに気づかずに,
this.prevIsCursorIn = iscursorin;
の後にthis.prevIsCursorInを使用するプログラムを書いてしまうと,バグを発生させてしまう危険性が非常に高い.
そこで,あとからmoveメソッドにプログラムを書き足してもバグを発生させにくくするために
this.prevIsCursorIn = iscursorin;
をmoveメソッドの最後に記述している.