ドットロワイヤル① フォートナイトを、ライブラリなしで、簡単に
ブロック崩しの次は横スクロールのはずでした。届いた依頼はフォートナイト。3D は無理なので、真上から見下ろす 2D にして、ストームと建築だけは入れました。
ブロック崩しが終わって、次は横スクロールのアクションを作る予定でした。バグ山さんが最初にそう言っていたので。
届いた依頼はこれです。
フォートナイト。100人が島に降りて、建築して撃ち合う、あの 3D のゲーム。それをライブラリなしで、なるべく簡単に。
3つの条件のうち、どれか1つなら分かります。3つ同時は、矛盾しています。
「それっぽくなる」と言ったのはボクです。言った時点では、なるかどうか半分くらいしか自信がありませんでした。できあがったのがこれです。

名前はドットロワイヤル。フォートナイトの名前は使えないので。
削ったもの、残したもの
自分とボット9人の10人で戦って、最後の1人になったら勝ち。入れたのはこれです。
| 要素 | 内容 |
|---|---|
| ストーム | 安全地帯の円が5段階で縮む。外にいると体力が減る |
| 建築 | 木材10個で壁を1枚建てる。壁は弾を防ぐ |
| 素材集め | ツルハシで木や岩を叩くと木材が増える |
| 武器 | ツルハシ、ピストル、ショットガン、ライフル |
| アイテム | 回復、シールド、木材。上を歩くと拾う |
乗り物、スカイダイブ、エモートは削りました。100人も10人に削っています。
残したのは、ストームに追われる感じと、撃たれたら壁を建てて隠れる感じの2つ。フォートナイトから何を残せばフォートナイトに見えるか、と考えると、この2つでした。逆に言うと、この2つ以外は全部あきらめています。
画面より広いマップ
ブロック崩しは、画面の中で全部が終わるゲームでした。今回はマップが 1800 × 1800 あって、画面は縦長の 360 × 640 のまま。マップのほうがずっと広い。
なので、自分を真ん中に置いて、まわりだけを描くカメラが要ります。やっていることは、描く前に座標をずらすだけです。
ctx.translate(VIEW.width / 2, (VIEW.height + HUD_HEIGHT) / 2); // 画面の真ん中へ
ctx.scale(CAMERA_SCALE, CAMERA_SCALE); // 少し引いて広く見せる(0.75 倍)
ctx.translate(-me.x, -me.y); // 自分の位置が真ん中に来るようにずらす
あとは世界の座標のまま、木も壁も人も描けばいい。縮尺の 0.75 は何回か試して決めました。1 だと周りが見えなくて、敵が急に画面に入ってくる。0.5 だと人が豆粒になる。
地面のマス目は、画面に見えている範囲だけ描いています。1800 × 1800 を毎回全部描くのは、さすがにもったいない。
木と岩とアイテムの置き方
木が75本、岩が30個、アイテムが65個。置く場所はランダムですが、木と木が重なったり、降りた場所に岩があったりすると困ります。
ランダムに場所を選んで、ほかの木や人と重なっていたら選び直す。50回やってもだめなら、その1本はあきらめる。雑ですが、これで足りました。
for (let tries = 0; tries < 50; tries++) {
const x = 60 + random() * (WORLD - 120);
const y = 60 + random() * (WORLD - 120);
const free = s.obstacles.every((o) => dist(x, y, o.x, o.y) > o.r + radius + gap);
if (free) return { x, y };
}
return null; // 置ける場所がなかった
ブロック崩しから持ってきたもの
ブロック崩しのときに分けておいた共通の部品は、ほぼそのまま使えました。画面の拡大・縮小、1/120 秒ずつ進めるゲームループ、効果音、ハイスコアの保存。新しく書いたのは、ルールと描画と、ボットと操作です。
分けたのはボクです。
まあ、いいです。分けたときは、次のゲームで使えるかもしれない、くらいの気持ちでした。次がフォートナイトになるとは思っていなかったので、役に立ったのはほぼ偶然です。
次は操作の話。スマホで遊べるようにするのが、思っていたより面倒でした。