Chapter 22

近づく・離れる

miku
miku
2021.11.25に更新

この章では、目標位置を決めてそこにオブジェクトを近づけたり、逆に離れたりするような動作を扱う。

近づく

近づくといっても、1フレームで目標の位置に移動してしまうと瞬間移動になるので、アニメーションとしては物足りない。つまり、近づくとは移動する幅に制限を設けることだ。このような制限を設けることでよりリアルな表現に近づくことができる。

等速で近づく

まずは等速でオブジェクトをマウス座標に近づけるコードを書いてみよう。

const speed = 5;

if (mouseX < x) {
  x -= speed;
} else if (mouseX > x) {
  x += speed;
}

if (mouseY < y) {
  y -= speed;
} else if (mouseY > y) {
  y += speed;
}

一回に移動する速さの定数を用意して、オブジェクトの左にマウスがあるなら左に移動、右にあるなら右に移動する。上下も同じ考えでコードを書く。

const speed = 5;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = random(width);
  y = random(height);
}

function draw() {
  if (mouseX < x) {
    x -= speed;
  } else if (mouseX > x) {
    x += speed;
  }
  if (mouseY < y) {
    y -= speed;
  } else if (mouseY > y) {
    y += speed;
  }

  clear();
  circle(x, y, 60);
}

実行結果を見てみると、マウスに円は近づきはするが、ある程度近づくとガタガタの移動になっている。これは xmouseX の距離が speed 未満であっても speed 分だけ移動して、xmouseX の地点を通り過ぎてしまうのが原因だ。y軸も同様のケースが起こりえる。

なので xmouseX の距離を dx だとすると、dxspeed 未満だった場合は、dx だけ移動して xmouseX に合わせてしまおう。

ガタガタ移動を修正する

const speed = 5;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = random(width);
  y = random(height);
}

function draw() {
  const dx = abs(mouseX - x);
  const dy = abs(mouseY - y);

  if (mouseX < x) {
    x -= min(speed, dx);
  } else if (mouseX > x) {
    x += min(speed, dx);
  }
  if (mouseY < y) {
    y -= min(speed, dy);
  } else if (mouseY > y) {
    y += min(speed, dy);
  }

  clear();
  circle(x, y, 60);
}

これでガタガタ移動を防ぐことができたが、もう一点問題がある。xyspeed 分だけ移動する場合は斜めに speed より大きい距離で移動することになる。斜めのときだけ移動速度が速くなるのは不自然なので三角関数を利用して移動距離を分解したい。

三角関数による移動距離の分解

進む方向を分解するには cos()sin() を利用する。横方向の移動距離を取得するには cos(θ) * speed、縦方向は sin(θ) * speed だ。

θ の取得は atan2() を利用する。オブジェクトの座標が (0, 0) になるように、マウス座標からオブジェクトの座標を引く。つまり、(mouseX - x, mouseY - y) だ。それを atan2()y, x の順番で指定することにより、原点から (mouseX - x, mouseY - y) まで結んだ直線とx軸とのなす角度を取得することができる。

const speed = 5;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = random(width);
  y = random(height);
}

function draw() {
  const d = dist(mouseX, mouseY, x, y);
  const dx = mouseX - x;
  const dy = mouseY - y;
  const angle = atan2(dy, dx);
  const tx = cos(angle) * min(speed, d);
  const ty = sin(angle) * min(speed, d);

  x += tx;
  y += ty;

  clear();
  circle(x, y, 60);
}

ベクトルによる移動

ベクトルを使用すると三角関数の計算を内部で行ってくれるので簡潔に書くことができる。

まず createVector(mouseX - x, mouseY - y) でオブジェクトからマウスカーソルまでのベクトルを作る。あとは setMag(speed) でベクトルの長さを speed にすればいい。

const speed = 5;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = random(width);
  y = random(height);
}

function draw() {
  const d = dist(mouseX, mouseY, x, y);
  const v = createVector(mouseX - x, mouseY - y);
  v.setMag(min(speed, d));

  x += v.x;
  y += v.y;

  clear();
  circle(x, y, 60);
}

割合で近づく

今までは固定の速さで近づいていたが、距離の10%だけ移動、のような割合での近づき方法もある。

移動元を pos、移動先を target、近づく割合を k だとすると下記の計算式になる。

  • (target - pos) * k

target - pos のような移動先 - 移動元の値を変位と呼ぶ。

この計算式は、後の章で扱うバネの計算式とほとんど同じで、targetpos から離れているほど一回に近づく距離が増える。なので、最初の方は速い移動で、近づくにつれて減速していく。

計算によって得た値を pos に足すと target に近づくようになっている。なので targetpos より左にあれば pos から引き・・・のような分岐処理は必要がない。

const k = 0.1;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = random(width);
  y = random(height);
}

function draw() {
  const v = createVector(mouseX - x, mouseY - y);
  v.setMag(v.mag() * k);

  x += v.x;
  y += v.y;

  clear();
  circle(x, y, 60);
}

値の変化

近づくというのは位置を変えるだけではなく、値の変化にも適用ができる。たとえばゲームのスコアやHPだ。

let v;
let av;

function setup() {
  createCanvas(windowWidth, windowHeight);

  textSize(18);
  textAlign(CENTER, CENTER);

  v = av = 10000;
}

function draw() {
  clear();

  const dv = v - av;
  av += dv / 20;

  text(av.toFixed(0), width / 2, height / 2);
}

function mouseClicked() {
  v = random(0, 10000);
}

v を用意して、画面をクリックするたびに v の値が変わる。もう一つの値 av を用意して av は毎フレーム v に近づくように値を増減させて画面に表示する。要は v は実際の値で av は演出用の値である。

離れる

離れる動きを作るには、基本的には近づく動きの逆の処理をすればいいのだが、近づくと違い、離れるというのは無限に離れることができるのですぐに画面外にオブジェクトが消えてしまう。なのである程度見せ方に注意を払う必要がある。

まずは消えることを気にせず、普通に離れる動きを書いてみよう。

const speed = 1;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = width / 2;
  y = height / 2;
}

function draw() {
  const v = createVector(x - mouseX, y - mouseY);
  v.setMag(speed);

  x += v.x;
  y += v.y;

  clear();
  circle(x, y, 60);
}

マウスに近づくときは (mouseX - x, mouseY - y) のベクトルを足し合わせていたが、離れる場合はその逆の (x - mouseX, y - mouseY) のベクトルを足し合わせればいい。

一定距離まで近づいたら離れようとする

実際にある対象から離れることを考えてみると、常に対象から離れようとするより、対象が一定距離まで近づいてきたら離れようとする方が自然な動きのように見える。

const speed = 5;
const minDist = 100;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = width / 2;
  y = height / 2;
}

function draw() {
  const d = dist(mouseX, mouseY, x, y);
  if (d < minDist) {
    const v = createVector(x - mouseX, y - mouseY);
    v.setMag(speed);
    x += v.x;
    y += v.y;
  }

  clear();
  circle(x, y, 60);
}

先程の作例に、オブジェクトとマウスの距離が一定未満の場合だけ逃げるという判定を付け加えた作例。

距離に応じて離れる速度を可変する

今までは逃げる速度が一定だったが、マウスが近づきすぎた場合は、オブジェクトもそれに合わせて一気に離れようとする方が自然に見える。なので距離に応じて離れる速度を可変にしたい。

const k = 0.1;
const minDist = 100;
let x, y;

function setup() {
  createCanvas(windowWidth, windowHeight);

  x = width / 2;
  y = height / 2;
}

function draw() {
  const d = dist(mouseX, mouseY, x, y);
  if (d < minDist) {
    const v = createVector(x - mouseX, y - mouseY);
    v.setMag((minDist - d) * k);
    x += v.x;
    y += v.y;
  }

  clear();
  circle(x, y, 60);
}

オブジェクトとマウスの距離が一定未満の場合だけ逃げるというのは一緒だが、その一定未満の距離に応じて逃げる速度を可変にする。距離は minDist - d で取れるので、これに0~1の係数 k を掛けたものを速度にする。