✏️
JavaScript でジョイスティックもどきを作ってみよう!
こんにちは、株式会社palan の xR事業部 でエンジニアをやっている 笹井 です。
概要
この動画にあるジョイスティックもどきを JavaScript で作っていきます!
必要な知識は三角関数とベクトルです!
一瞬「無理だ...」と思った皆さん、諦めないでください!
こういった実装は、
コードの意味を理解しようとするのではなく
手順だけを理解していく姿勢がオススメです!
いろんな用語が出てきますが、
このくらいフワッとした認識くらいがちょうど良いです!
内積: 求めておくと便利な値
外積: 求めておくと便利な値
コサイン: 角度を求めることができる値 & x成分を表現する値
サイン: 角度を求めることができる値 & y成分を表現する値
高校数学永遠赤点だった自分と一緒に頑張りましょう!
手順
1: ベクトル → 内積(dot) → コサイン → ラジアン → 角度を求める
分かっている値
求める値
1-1: 2つのベクトルから内積(dot)を求める
const v1 = { x: 0, y: 1 };
const v2 = { x: 1, y: 1 };
const dot = v1.x * v2.x + v1.y * v2.y;
1-2: 内積(dot)からコサインを求める
const absV1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
const absV2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
const cos = dot / (absV1 * absV2);
1-3: コサインからラジアンを求める
const radians = Math.acos(cos);
1-4: ラジアンから角度を求める
const degrees = Math.floor(radians * 180 / Math.PI);
1-5: でも...
ここまでで求めることができる角度の範囲は 0〜180度
ですが、サインを求めることで -180〜180度 = 360度 求めることができます!
次はその方法です!
2: ベクトル → 外積(cross) → サイン を求める
分かっている値
求める値
2-1: 2つのベクトルから外積(cross)を求める
const v1 = { x: 0, y: 1 };
const v2 = { x: 1, y: 1 };
const cross = v1.x * v2.y - v1.y * v2.x;
2-3: 外積(cross)からサインを求める
const absV1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
const absV2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
const sin = cross / (absV1 * absV2);
3: 組み合わせて正確な角度を求める
const radians = Math.acos(cos);
const oldDegrees = Math.floor(radians * 180 / Math.PI);
const newDegrees = sin > 0 ? oldDegrees: -oldDegrees;
実際のコード
// MEMO: コントローラーの中央の値
let centerX = uiControllerRect.left + uiController.clientWidth / 2;
let centerY = uiControllerRect.top + uiController.clientHeight / 2;
// MEMO: コントローラーの中央からどれだけ離れているかの値
let touchX =
Math.abs(event.touches[0].clientX - centerX) < 35
? event.touches[0].clientX - centerX
: event.touches[0].clientX - centerX > 0 ? 35 : -35;
let touchY = Math.abs(event.touches[0].clientY - centerY) < 35
? event.touches[0].clientY - centerY
: event.touches[0].clientY - centerY > 0 ? 35 : -35;
// MEMO: 縦のベクトル
const v1 = { x: 0, y: -1 };
// MEMO: 操作中のベクトル
const v2 = { x: touchX, y: touchY };
// MEMO: 内積
let dot = v1.x * v2.x + v1.y * v2.y;
// MEMO: 外積
let cross = v1.x * v2.y - v1.y * v2.x;
// MEMO: 縦のベクトルの大きさ
let absV1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
// MEMO: 操作中のベクトルの大きさ
let absV2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
// MEMO: コサイン
let cos = dot / (absV1 * absV2);
// MEMO: サイン
let sin = cross / (absV1 * absV2);
// MEMO: コサインからラジアン
let radians = Math.acos(cos);
// MEMO: ラジアンからの角度
let degrees = Math.floor(radians * 180 / Math.PI);
uiControlerChild.style.left = `${touchX}px`;
uiControlerChild.style.top = `${touchY}px`;
uiInfo.innerText = `degree(角度): ${sin > 0 ? degrees: -degrees}
length(速度): ${absV2}`
参考
Discussion