ワンボタンゲームをたくさん作ったので、その作り方をおさらいしたい

English version: How to realize various actions in a one-button game

はじめに

自作ゲームライブラリcrisp-game-libを使ったミニゲームを最近たくさん作っているが、特に多く作っているのがワンボタンゲームだ。ここで言うワンボタンゲームは、レバーによる移動の他のボタンが1つ、といったものではなくて、純粋に1つのボタンしか操作に使わないゲームを指す。

ワンボタンゲームの利点は、操作が分かりやすく、タッチデバイスでも操作しやすい点にある。とにかく何かボタンを押せばそれがプレイヤーが取れる動作の全てであり、操作説明がほぼ不要である。またタッチデバイスでも画面中のどこかをタップあるいはホールドすれば操作ができるので、バーチャルパッドでよく起こる、ボタンを押した感触が無いので操作がしづらいという問題が発生しない。

欠点は、当然のことながら、動作にバリエーションを与えるのが難しいこと。レバーがあれば簡単に実現できる左右移動さえ、ボタンを押すと進行方向が反転する、というちょっと特殊な操作にせざるを得ない。

なのでワンボタンゲームを作る際に問題になるのは、たった1つのボタンによるゲームへのインタラクションに、どのようにバリエーションを与えられるか、という点になる。その点に関して、今まで作ったいくつかのワンボタンゲームを例に整理してみたい。

特殊な動作を取り入れる

ボタンを押した瞬間にプレイヤーが以下の動作を行う、というのがワンボタンゲームでは良く見られる。

  • 移動方向が反転する・90度回る
  • ジャンプ・羽ばたく
  • ショットを撃つ

ボタンで移動方向が反転する例としては、

THUNDER

というゲームを作った(スクリーンショットをクリックすれば、そのままブラウザで遊べます)。2方向に移動するだけのゲームであれば、障害物やボーナスアイテムなどを工夫して、ワンボタンゲームに仕上げることはできる。が、ワンボタンならではの特徴があるゲームとはあまり言えない。

ボタン操作で発生する動作を、通常のゲームではあまり見かけないものにすれば、ワンボタン操作ならではのゲームであることを、プレイヤーに強く印象づけることができる。

瞬間移動

CYWALL

プレイヤーが移動可能な点を画面上に用意しておいて、押した瞬間に一番近い点へ瞬間移動する。ワンボタンでテンポ良く移動できるのがこの動作の良いところである。

分裂

DIVARR

ボタンを押すたびにミサイルが分裂する。ボタンをバシバシ押すだけで攻撃力アップが楽しめるが、それを防ぐために撃ってはいけないものを混ぜるなどの工夫が必要になる。

選択

NOT TURN

JUMP ON

あるポイントで曲がる、床に飛び移るなどの動作を選択する。ここにどっちにいくのかを選択する場所・瞬間がある、というのがプレイヤーにはっきり分かる必要があるので、ゲームフィールドとその見せ方には工夫が必要である。

属性反転

NS CLIMB

ボタンを押すたびに属性、例えばN極とS極が入れ替わる。属性になにを用いるか、それが外界とどのように関わるかを考えるところがゲームの肝になる。

その他特殊なもの

LADDER DROP

左右に移動している床とはしごをタイミング良く落とす。落ち物ゲームのワンボタンゲーム化でもある。

NUMBER LINE

流れている数字を合計する。これはあまりに特殊で参考にならないが、ボタンに割り当てる動作はいくらでも考えられるという一例だ。

ボタン押しっぱなしの活用

ワンボタンゲームはボタンを押した瞬間に何かが起こるのが普通だが、押している間に継続的に何かが起きる、という動作をさせることもできる。

角度や距離を調整する

NUMBER BALL

ゴルフゲームでよくある操作だが、ボタンを押している間打ち出す角度が徐々に増えるので、ちょうどよいタイミングでボタンを放して打ち出す。このゲームは打ち出した数字と床の数字が一致すると床が消えるという謎のルールで、ゴルフとは差別化している。

FROOOOG

カエルが飛ぶ距離を、ボタンを押している時間で決定する。ワンボタンでプレイヤーを上下左右に動かすことは難しいということを強引に解決した一例である。

伸縮する

PIN CLIMB

ボタンを押している間、棒が伸びて、放すと縮む。伸びるだけでは何も良いことはないので、伸びたものがなにかに引っかって先に進めるなど、なんらかの地形と組み合わせて使われることが多い。

SQUARE BAR

幾何学図形と組み合わせると、伸縮だけで複雑な動きを実現することもできる。

TAPE J

伸ばせば伸ばすほど点が伸びるが、リスクも高まるようにするのも、リスク・リワードのバランスが取れて良い。

防御する・当たらなくなる

EMBATTLED

ボタンを押している間、砲弾に当たらなくなる。そのままだと戦車にひかれて終わるので、ある程度ひきつけたら防御を解いて上下に避けると、戦車同士が勝手に戦ってくれる。

REFLECTOR

敵弾を反射してくれる防御壁が常に下に付いているが、ボタンを押している間は強力な反撃ができる代わりに、防御壁が小さくなる。

BAMBOO

ボタンを押していると竹に当たらなくなり、竹の裏をすり抜けることができる。そうすると竹の間に入り込んでバウンドすることができるようになり、素早く竹を刈ることができる。

その他特殊なもの

CHARGE BEAM

エネルギーをチャージする。チャージ量をうまく調整することに意味があるゲームにしないと成り立たない。

LASER FORTRESS

薙ぎ払う。ショットを撃つの超強力な亜流。敵に味方を混ぜることで、超強力な攻撃が仇になるように調整している。

SHINY

雨が降る・晴れる。雨が降っている間は人が早く移動してくれるので、それを利用して早めに右端まで退避させる。あまりに特殊な動作で、他のゲームには転用できそうに無い。

複数の動作の組み合わせ

上記動作を組み合わせるのも王道の作り方だ。

SCRAMBIRD

羽ばたき+ショットや、

TILTED

多段ジャンプ+移動方向反転、

UP SHOT

ショット+停止など。

また、ある動作がゲーム内に複数の影響を与えるようにする手もある。

BOMB UP

これはボタンで爆弾を落とす・爆発させるという動作に加えて、その爆風でプレイヤーを吹き飛ばすという影響を与えるようにしてある。そのために爆発させる位置とプレイヤーの位置を調整することで、プレイヤーの移動を制御することができる。こうすることによってかなり複雑な動作をワンボタンで実現できるが、やりすぎると制御が難しくなりすぎるので、加減が重要になる。

回転運動との組み合わせ

角度調整に似ているが、プレイヤーや砲身が常に回っていて、タイミングよく飛び出したり撃ったりという、タイミング重視のゲームにする手もある。

ORBIT MAN

飛び出し方向が回転しているので、星がある方向にタイミング良く飛び出す。

ARCFIRE

砲身が回転しているので、敵を向いている時にタイミングよく撃つ。このゲームでは押しっぱなしで射程と攻撃範囲が調整できたり、発射と同時にその方向に少し前進するなど、いろいろな行動がワンボタンでできるようになっている。

地形の活用

入力によって動作にバリエーションをもたせるのが難しいのならば、プレイヤーの立っている地形によって動作が変わるようにするのも良い。

TURBULENT

ボタンでジャンプする、という動作に対しても、そのジャンプする地形を荒れた海面にすることで、タイミングによって飛び出す方向を変化させることができる。

SUB JUMP

画面下半分を海中、上半分を空中、とすることで海中ではボタンで上昇、空中ではジャンプと、複数の役割をボタンに持たせることができる。

アイテムの活用

地形以外にもアイテムを活用する方法もある。アイテムを取るとなにかのモードが変わるようにして、アイテムを取る・取らないという選択を入力の代わりに利用する。

MIRROR FLOOR

コインを取るたびに重力方向が切り替わる。次の床の位置をよく見て取るか取らないかを選択しないと、次の床に飛び移ることができなくなる。

LIFT UP

アイテムを取ると進行方向が変わる。左右のトゲトゲにぶつかる前に反転アイテムをうまく取りつつ、コインを集める必要がある。

REBIRTH

トラックにひかれて逆の世界へ移動する。トラックがアイテムであるかは議論の余地があるが、次のダイヤの位置を見てトラックにひかれるかひかれないかを選択する。

R WHEEL

アイテムを取ると下方向にレーザーを発射して、障害物のトゲを破壊する。このゲームはその他にジャンプすると下方向にトゲを生やすレーザーを発射する、トゲはぐるっと回ってもう一度プレイヤーのところに来る、アイテムはジャンプすることにより出現する、など複数のことがボタンを押すとジャンプするという動作に対して発生するようになっている。

さいごに

このように、ワンボタンだと大した動作バリエーションを作ることはできないだろう、と考えるのは早計であり、ここに挙げただけでもかなり多様なタイプのゲームを作ることができることが分かった。ワンボタンゲームの持つポテンシャルを活かして、今後も様々なゲームを作ってみたい。

ワンボタンゲームは操作が簡単であることが利点だが、それ故に思わぬ罠にかかる場合がある。それは、ボタンを連打するだけ・押しっぱなしにするだけでいくらでも点が入る、いわゆる永久パターンがあるゲームがたまに出来上がることだ。なので作った後に、必ず連打プレイ・押しっぱなしプレイを行うこと。本人が意図していなくても、そこそこの確率でそういうゲームになることがあるので注意したほうが良い。

あと大前提として、ワンボタンゲームであることと、そのゲームが面白いかつまらないか、ということに因果関係はない。そのため、そのゲームが爽快感・緊張感を備えていること、リスク・リワードのバランスが取れていること、などゲームとしての面白さはちゃんと追求しないといけない。その上で、そのゲームをワンボタンゲームに落とし込めるかを考えることは必要である。

ミニゲームに物語性を加えることで得られるコク

自作ライブラリcrisp-game-libを使ったミニゲーム作りは、まだ継続して行っている。

そんな中、最近作ったゲームの評判が良かった。

評判が良かった理由は、

のように、ミニゲームにちょっと物語性を入れ込んだところがウケたようだ。

私には作ったゲームにあとから物語性を入れる器用さは無いので、私の作ったゲームになんらかの物語性があったとしたら、それはその物語性を元ネタに発想したゲームだということだ。

今回は、

スタジアムでトラックにひかれるところから始まる異世界転生バトルが生まれるほど鉄板化したお約束、「トラックにひかれて異世界転生する」を元ネタに考えたものだ。

  • トラックにひかれて飛ばされた先が別の世界になるよう、2つの世界を左右に配置する
  • 左右の世界でそれぞれボーナスアイテムのダイヤを配置し、世界を行き来しつつダイヤを集めて点を得るようにする
  • トラックにひかれてゲームオーバーにするわけにはいかないので、代わりにダイヤを取り逃すことを終了条件にする
  • こうすることでどのトラックを避けてどのトラックにひかれるべきかの駆け引きが生まれて、なんとかゲームになる

という流れで作った。

というわけで、ミニゲームにおいても物語性を加えることでコクや深みを与えることが重要である、ということをいまさらながら再認識した次第である。ただ、一般的にゲームに物語性を加えるのは、凝ったグラフィックスやサウンドを施すことで行われることが普通で、低コストに作りたいミニゲームで行うことは難しい。今回は「トラック」「異世界転生」というミームを説明文に加えるという、極めて低コストな方法をたまたま思いついたからできただけであって、あんまり再現性がある手法にも思えない。

あと、

こういう物語性のかけらも無い、ゲームルールむき出しのミニゲームを作るのも好き、という個人的な嗜好もある。なんか、こういう素朴なゲームが面白くできると、ゲームルールだけで勝った!、みたいな謎の達成感がある、んだけど、この感覚ってゲーム開発者に普遍的なものなのかなあ。

なので物語性の重要性は再認識しつつも、ゲームルールそれ自体の楽しさを試行錯誤するゲーム作りをしたい、というスタンスはあまり変えずにまたミニゲーム制作を続けようと思いました。すごいどっちつかずの結論だけど、ゲーム作りは自由だからこれでいいのだ。

1日1個、11日で11個のミニゲームを作る

そしてTwitterに放流する。

全てのゲームはcrisp-game-libを使って作った。PCとモバイルのブラウザで動く。

毎日1個作っては放流、としていたわけでは無く、実際はいくつか作り貯めておいてそれを1日ずつ放流していたわけだが、それでもこのペースで作るためには、ゲームのアイデア出しと実装をそれなりのスピードでする必要がある。

イデア出しには、

のようにレトロゲームの一部を切り出して強引にミニゲーム化したり、

昔の自作ゲーム資産があればそれを食いつぶすのも良い。短時間でゲームアイデアを思いつくための話は前にも書いた。

いろんなゲームを遊ぶこと・作ることの経験を蓄積していくと、このゲームをこうアレンジしたら、このゲームのこの部分を切り出してみたら、このゲームとこのゲームをこうかけ合わせたら、という発想のバリエーションを増やすことができる。あと、元にしたゲームから、楽しめるポイントがどこか、リスクとリワードのバランスをどのように取っているか、などがあらかじめ分かるので、ちゃんと遊べるゲームになる打率が上がる。敵の出現頻度とか、難度に応じたゲームスピードアップ比率とかのパラメタをチューニングして、遊びやすいゲームにすることも大事。

あと最近は、ワンボタン、左右へのマウス・タッチ移動、画面上のタップ、いずれかで操作できるゲームしか作っていない。こういった縛りを入れることも、ゲームのアレンジ方法を考えることに役立つ。

実装については、使う言語・ライブラリについて十分に習熟しておいて、ゲームを作るにあたってなんの学びも必要としないくらいまで、こなれていることが望ましい。何もググらずに手癖でコードを書けるようになると、実装スピードが稼げる。

あとゲーム内オブジェクトについて、以下のコードのようにあらかじめ型を規定してから書くと、ゲームロジックを書く際のガイドになる。このへんのデータ設計も、数をこなせば定型的なパターンが見えてくる。

今まで作ったブラウザミニゲームは、以下のページにスクリーンショットとともにリストアップしてあるので、適当に遊んでもらえるとうれしい。

残りゲーム制作体力10%な人のためのずぼらゲームライブラリcrisp-game-lib

を目指してまた自作ゲームライブラリを作っている。

ゲーム制作体力が無いならゲーム作りをやめればいいのだが、ウン十年とゲームを作り続けているゲーム制作ジャンキーはそんなことでは立ち止まれないので、より短時間で体力が尽きる前にゲームを完成させるためのライブラリを再発明し、今日もゲーム制作RTAを走るのだ。1時間くらいで完走できるのが理想。

crisp-game-libは短時間で手軽にブラウザゲームを作るためのJavaScriptライブラリだ。2014年に50個のミニゲームを作ったが、そのときにはそれらゲームを作るためHaxeライブラリmglCoffeeScriptライブラリmgl.coffeを合わせて作った。その後も懲りずにミニゲームとライブラリ作りを続けた経験を活かし、クラシックでアーケードライクなミニゲームを作るために必要最小限な機能を備えたライブラリを目指して、crisp-game-libは作られている。

crisp-game-libを使ったゲームもすでに40個以上作っていて、十分にドッグフーディングされていると思う。ゲームのtitledescription、1秒間に60回呼びだされるupdate関数を単一のJavaScriptファイルとして書くだけで、PCとモバイルで動作するブラウザゲームが作れる(下の画像をクリックすると遊べます)。

pressm screenshot

箱・線・弧・テキスト・キャラクタなどの描画機能と、

ref_drawing screenshot

それら描画機能と一体化した衝突判定機能

ref_collision screenshot

マウス・タッチパネル両対応の入力取得機能、鳴らしたい音名を選択するだけの効果音機能と、ミニゲームに必要な機能を一通り用意した。

また、オプションisPlayingBgmtrueにするだけで自動生成されるBGM、isReplayEnabledtrueにするだけで有効になるリプレイ機能、themeを設定するだけでレトロなCRT風やドット絵風などに見た目を変化させる機能、characters配列で簡単に定義できるドット絵など、ゲームをジューシーにするための機能も備えている。生成された音が気に入らなかったらseedに適当な整数を与えると音が変わるので、気に入った音が出るまで乱数シードガチャを引き続けて欲しい。

TypeScriptでの型定義も備えているので、VSCode上でインテリセンスの恩恵を十分に受けることができるのも、短時間でのコーディングに役立つと思う。

Super HexagonVVVVVVの開発者として知られるTerry Cavanaghさんが書いてくれたcrisp-game-libに関する記事もあるので、合わせて読んでもらいたい。

ゲーム制作体力と合わせてゲーム遊ぶ体力も落ちてきているとなると、サクッと遊べるゲームをサクッと作れる環境が欲しくなる。なのでこんな感じのゲームライブラリ車輪再発明を繰り返している気がする。本来この辺の役割を担うのは、PICO-8に代表されるファンタジーコンソールやScratch、HSPなどで、これらのほうがより高機能なのは間違いない。だが、より機能を削ぎ落とし、自分に合った形に自作ゲームライブラリを作るのも、それはそれで楽しいので良しとしたい。皆もゲームライブラリ自作沼に肩まで浸かって温まりましょう。

個人サイトをリニューアルして自作ゲーム一覧を作ったら250個くらいあった

ABA Games

前々から今まで作ったゲームを整理して、その一覧が見られるようにしたいと思っていたので、覚悟を決めてホームページのリニューアルをした。作ったゲーム(一部ゲームじゃないのもあるけど)は250個くらい。ミニゲームを量産していたのでそれなりの数はあるだろうな、と思っていたけど、思ったより多かった。

ゲームはその動作プラットフォーム別に分けて見られるようにした。

うちのゲームで比較的ちゃんとしているたぐいのものはだいたいWindows用だから、ここがメインページ。

最近作っている、ブラウザで遊べて数分で終わるミニゲーム群はこのページ。アニメーションGIFでだいたいどんなゲームか分かるようにしているつもり。

今は亡きFlashのページ。ミニゲームとはいえ、それなりの数作ってるので、これらがそのまま遊べなくなるのはちょっと悲しい。Ruffleとかの互換Flash Playerの成熟に期待している。

その他。昔作った弾幕記述言語BulletMLとか、いにしえ+マイナーなプラットフォーム、WonderWitch、P/ECEPalm向けゲームなど。この辺のゲームをいまさら動かす人はあまりいないだろうから、自分史的な感じだ。本当はMSX時代とかに作ったゲームもリストアップしたいんだけど、そんなものが手元に残っているはずもなく、それらがそのまま失われてしまうのはしょうがないね。ベーマガとかのプログラム投稿雑誌にバリバリ掲載されていた人は記録が残っていそう。

最近は作ったものをGitHub上に置いていたりしていて、自作ゲームもいろんなサイトにバラバラにある感じだったので、こういう形でリスト化できたのは良かった。継続は力なり、もしくは三つ子の魂百までを可視化できている。

これからもゲームをちまちま作っていってリストを拡充していきたい。願わくばブラウザというかHTML5というかJavaScriptが安定して将来も互換性を保って動作してくれて、作ったゲームを簡単に遊び続けられる時代が続いて欲しいね。

クイックソート VS 挿入ソート、ファイッ!

コードとライブデモはこちら。

アルゴリズムの王道ソートアルゴリズムでコードバトリングをしてみたかったので作った。左(赤)のコードが昇順に、右(青)のコードが降順に同一の配列をソートしようとして戦う。昇順に揃ったら左の勝ち、降順に揃ったら右の勝ち。

コードは普通のJavaScriptとして書く。以下の2つの特殊な関数がある。

  • get(i): 配列からi番目の要素を取得する
  • swap(i, j): 配列のi番目とj番目の要素を交換する

setはできない。setを許すとコード内のメモリに配列を逃しておいてソート、一気に書き込むというインチキができるから。右の降順側のgetは要素のマイナスの値が帰ってくるので、右のコードもアルゴリズム自体は昇順にするものを書けば良い。

コードは左と右で交互に実行する。実行はJS-Interpreterのstep()で行う。

コードはなるべく実行速度が速く、頻繁にswapして配列をかきまわせる方が強い。速度はJS-Interpreterのstep()の解釈粒度に依存するが、コードがコンパクトな方がもちろん速い。

クイックソートと挿入ソートだと挿入ソートの方が圧倒的にコンパクトで、配列の長さが10くらいだとその実行速度でクイックソートを圧倒して勝つことが多い。配列が長くなると多分勝敗は変わってくるんだろうけど、試していない。

ソートアルゴリズムは別のアルゴリズムが配列をいじるなんてことは想定していないので、実行が終わった時点でちゃんとソートされている保証は無い。なので実行が終わったあとは実行状態をリセットして再度実行される。クイックソートは実装によっては外部から配列をいじられると配列のバウンダリを超えた操作が発生してエラーになることもある。その場合も再実行。

で、これがちゃんとしたゲームとして成り立つかというと、どうだろう。このアルゴリズムがこのアルゴリズムに強い、みたいな三すくみ的な面白さがあるかというと、微妙だ。あとは相手方向に揃いそうだったら積極的に崩す、とかいう防御的仕組みを入れたバトリング向け特殊ソートアルゴリズムが強かったりするなら面白そうなんだけど、そういった工夫をする余地があるかなあ。

いい感じのビジュアルをランダムになんとなく作り出す

なにかを作った。めざせジェネレーティブアートジェネレータ!

screenshot

クリック/タップで別の絵を作ります。

この手のものの難しさは、

という具合に自由度を高くしていろんなバリエーションを作ろうとすると見てて面白いものができあがる打率が減って、逆に打率を上げようと面白くなさそうなものをフィルタリングしていくと自由度が低くなること。

自由度を高くするにはランダムにするものを増やせばいい。今回は座標と線の太さと色を表す数式を乱数で作った。といってもあまりに適当にすると打率が下がるから、

パラメトリック方程式

この辺に出てくる数式をぐっとにらんでよく使われてそうな関数を適当にチョイスする。今回は

a + b, a - b, a * b, a / b, 
sin(a), cos(a), exp(a), pow(a, b), noise(a), 
a > b ? c : d

を使った。noiseはp5.js組み込みのパーリンノイズ。あと、sinとcosはパラメトリック方程式に頻出しているような印象があるので多少出現頻度を上げる。

a, b, c, dのところには、

  • 上記関数を再帰的に、ただし再帰が深くなるにつれ抑制する
  • 開始からの秒数を表す変数t
  • 先頭から何番目の点であるかを表す変数i
  • 2から10のランダム整数aまたはb

のどれかを当てはめる。これで簡単にランダムな数式が作れる。

この数式をつかって点のX/Y座標、線の太さ、色(HSB)を算出する。点は10~100個用意し、その間を線でつなぐ。

あとX/Y座標の数式のパターンとして、X座標のsin, cosをcos, sinに入れ替えてY座標の数式とするという小細工を半分の確率でする。数式を作っているコードは以下。

formula.ts

打率を制御するのは難しい。面白いかどうかはコンピュータには分からないから。

まあでも画面に何も写っていないのは面白くないだろう。なので数式から出る数値を多少いじる。

X/Y座標、線の太さ、色の各点の数値の最小値と最大値を記録しておいて、それら数値が所望の範囲に収まるように補正する。X/Y座標は画面サイズの横/縦のピクセル数、先の太さは画面サイズの1/8くらいまで、色を表すHSBは0~360, 50~100, 50~100に。以下のコードのformulaRangesを使ってその辺の調整をしている。

main.ts

それ以外の調整はしてない。あとはひたすらクリック。なので現状たまに面白いビジュアルが出るのは、たまたまだね。たまたま出た面白パターンをGitHubのREADMEに置いてある。

https://github.com/abagames/folmura

こうやって何かを作る手法を、個人的にガチャ指向プログラミングと呼んでいる。ランダムに絵や音やレベルやゲームが出てくる仕組みを作って、あとはひたすら面白いものが出るまでガチャを引き続ける。プログラムを書くことなくいろんなバリエーションを得ることができるから最高に楽だ。問題は、そのガチャの仕組みを作るのが楽じゃないところだね。というかこれはプログラムパラダイムではないね。