はじめに
ブラウザ上でピッチャーが投げたボールの軌道を見て、瞬時に球種を当てるミニゲーム「PitchSense」を開発しました!
ストレートやスライダーといった現代野球の球種だけでなく、なぜか「消える魔球」や「分身魔球」まで飛んでくる完全なるネタアプリです。専用アプリのインストールは不要で、PC・スマホどちらのブラウザからでもサクッと遊べます。
👉 実際に遊んでみる(公開URL)
(※画面をクリック/タップしてプレイボール!)
主な機能とゲーム性(ネタ要素)
ただのクイズゲームではなく、アクション性とネタ要素を盛り込んでいます。
1. 全9球種!魔球を見極めろ
一般的な7球種(ストレート、スライダー、カーブ、フォーク、チェンジアップ、シンカー、カットボール)に加えて、以下の魔球がランダムで飛んできます。
- 🌫️ 消える魔球: ホームベース手前で完全に視界から消え去ります。消える前のわずかな軌道変化で予測する必要があります。
- 🌀 分身魔球: 途中でボールが3つに分裂し、螺旋を描きながら迫ってきます。
2. 見極めタイム(Reaction Time)によるスコア変動
ボールがホームベースを通過した「後」に答えても正解にはなりますが、得点は低いです。
リリース直後の軌道から予測し、ホームベースを通過する「前」に素早くボタンを押すことで、EXCELLENT (+3点) などの高得点ボーナスが入る仕組みになっています。
3. 難易度(Difficulty)システム
難易度を上げると球速が上がるだけでなく、ボールの軌跡(残像)が見えにくくなります。HARDモードはもはや動体視力テストです。
技術スタック・無駄にガチな実装の工夫点
「ただのネタアプリ」ですが、技術的にはフレームワーク(React等)や描画ライブラリ(Three.js等)を一切使わず、1つのHTMLファイル(Vanilla JS + Canvas 2D + Tailwind CSS)で実装しています。無駄にこだわった内部アーキテクチャを紹介します。
① 内部物理エンジン「UCD-F Core」と固定小数点演算
描画レイヤーとは完全に分離した内部シミュレータを持っています。
浮動小数点による演算誤差を嫌い、ビットシフトを用いた固定小数点演算(Fixed-Point Arithmetic)を実装して、完全決定論的な物理演算を行っています。
// Fixed-Point Arithmetic (完全決定論的演算)
const FIXED_SCALE = 65536;
const toFixed = (f) => Math.floor(f * FIXED_SCALE);
const toFloat = (fx) => fx / FIXED_SCALE;
② TypedArrayとSoAによるメモリ管理(ゼロアロケーション)
毎フレームのオブジェクト生成によるガベージコレクション(GC)スパイクを防ぐため、ボールの座標や速度、加速度はオブジェクトの配列(AoS)ではなく、SoA(Structure of Arrays)として Int32Array にパックして管理しています。
class UCDF_Core {
constructor() {
this.MAX_ENTITIES = 64;
// 座標や速度をすべてTypedArrayで平坦化して確保
this.posX = new Int32Array(this.MAX_ENTITIES);
this.posY = new Int32Array(this.MAX_ENTITIES);
this.posZ = new Int32Array(this.MAX_ENTITIES);
this.velX = new Int32Array(this.MAX_ENTITIES);
// ...
}
// リングバッファで過去の軌跡を管理し、newを一切使わない
}
③ Canvas 2Dでの自前3Dプロジェクション
Three.jsなどの3Dライブラリは使わず、焦点距離(focalLength)とカメラのZ座標を用いたシンプルな透視投影変換関数を自作し、Canvas 2D APIだけで奥行き(Z軸)を表現しています。
分身魔球の螺旋軌道も、Z座標をベースにした三角関数でリアルタイムに計算して投影しています。
const focalLength = 4.0;
const cameraZ = -2.0;
const project = (x, y, z) => {
if (z < cameraZ) return null;
const scale = focalLength / (focalLength + z - cameraZ);
return {
x: width / 2 + x * scale * pixelsPerMeter,
y: height / 2 - (y - cameraY) * scale * pixelsPerMeter,
scale: scale
};
};
④ Web Audio APIによるプロシージャルサウンド
効果音(SE)も外部ファイルを使っていません。すべて AudioContext を使ってリアルタイムに波形合成しています。
ストレートや変化球はシンプルな波形ですが、「分身魔球」が飛んでくる時はLFO(Low Frequency Oscillator)を使ってピッチにビブラートをかけ、「消える魔球」の時は矩形波(Square)を使ってファミコン風の不穏な音を出すなど、音の処理だけで魔球を演出しています。
おわりに
「もしブラウザで魔球を打つゲームがあったら面白いのでは」という深夜のテンションで作ったネタアプリですが、気付けば内部のメモリ管理や物理演算をゴリゴリに最適化する謎の技術検証プロジェクトになっていました。
HARDモードでパーフェクト(全EXCELLENT)を出せる猛者がいたら、ぜひ教えてください!
