世の中にワンボタンゲームってどんなのがあるの

って聞いて教えてもらった。ワンボタンゲームってのはプレイ中に使う操作がボタン一つだけのゲーム。

スタートリゴン

星の周りをグルグル回るホシ・ワタル。ボタンを押すと飛び出して他の星へラインを張って移動、三角ネットが出来たらOk。

バッドランズ

LDゲーム。ボタンでタイミングよく拳銃を撃ち敵を倒す。LDゲームは基本タイミングゲーなので、それを極限まで簡略化するとワンボタンになる。

Canyon Bomber

自機は左右に自動移動。ボタンを押すと爆弾投下。得点岩をたくさん破壊できればOk。

魚ポコ

正確にはワンボタンゲームでは無くレバー下だけゲーム。ピンボールのプランジャーのように引いて玉を撃ち出す。

あとはハイパーオリンピックみたいな連打系があるかもしれないけどそれは今回は除外。

わざわざゲーセンに限定して探したのは、ブラウザゲームスマホゲームに広げるとおそらく無数にあるだろうから。フラッピーバードのような酔っぱらいゲーム系はたくさんありそうだし、

One Button games on Kongregate

KongregateにもONE BUTTONタグが付いたゲームはたくさんある。

Canabalt

ワンボタンジャンプゲームとしてとても有名なCanabalt。

10 More Bullets

ワンボタンでショットして連鎖ゲー。これ楽しいな。

あと個人的に好きなワンキーゴルフとワンキーフロッガー

1B Nanogolf

F

昔いくつか自作もした。

DOT CAR

FIGURE OF EIGHT

ボタンに割り当てられたアクションは、撃つ、ジャンプ、加減速くらいのものだけど、その制約があってもこれだけバリエーションのあるゲームが作れるんだから人類は偉大だ。どこかワンボタンゲーム機とか酔狂なもの作ってくれませんかね。

セルオートマトンでゲームは作れるか

作れそうだけど大変そう。

consomaton-game-lib

screenshot

前にセルオートマトンプログラミングパズルゲームconsomaton作った時から、セルオートマトンのルールを書くだけでゲームが作れたらお気軽ではないかと思っていた。ただセルオートマトンのルールは、あるセルとその周辺の状態の変化のみしか書けないという強烈な縛りがあるので、書けるゲームはかなり限られるだろうという予想はあった。でもまあ試すだけ試してみた。

ルールは左3列がbefore、右3列がafterとしてconsomatonと同様に書く。'==='はルール間のセパレータ。

===
v
   v

こう書くとvが毎フレーム一つ下に落ちるようになる。

ゲームだからランダムに出現する敵とか必要。なのでルールをランダムに発火させるための'r'コマンドってのを作った。

===r10
.  .
   v

コマンドはセパレータの右に書く。'r10'って書くと1/10の確率で発火する。

あとゲームにはスコアも必要。なので次はスコアを追加する's'コマンド。

===r10-s1
.  .
   v

's1'って書くとこのルールが発火したときに1点入る。

パッドやキーの入力を受け付けてそれに応じて発火するのも必要。'p'コマンドってのを作って'p>'で右入力で発火にした。

===p>
@   @

@を自機として右に動くルールができた。

同様に'p<'で左、だけど右のルールがすでに発火している時はこのルールは発火しなくても良い。なので'---'っていうセパレータにすると前のルールが発火済みの時は発火しないようにした。

---p<
 @ @

あとゲームオーバー条件も必要。プレイヤーがいなくなったらを表すための'n'コマンドと、発火したらゲームオーバになる'o'コマンドってのを追加。

===n@-o

ここまで書くとこれができる。

FALL V

このようにお気軽にゲームが書ける。

お気軽ではない。ただでさえルールで書けることに限りがあるのにさらにそれを謎のコマンド群で拡張しないとごく簡単なゲームも作れない。ルールの書く順番や謎のセパレータの使い分けも必要だったりして、手軽に書けると言えるかというと微妙である。

ついでに前に作ったサウンドジェネレータsounds-some-soundsで音が付けられるようにして、ドット絵ジェネレータpixel-art-genで絵が付けられるようにした。それらを駆使して、一番上のスクリーンショットにあるクロスハイウェイみたいのを作るソースコードは以下。

CROSSMAN source code

長いね。これを書くこと自体がパズルゲームだね。パズルゲームを解く苦行とゲームを作る苦行が同時に味わえるという点ではお得感があると言えましょう。

プログラミングパズルゲームが作りたかった

セルオートマトンプログラミングパズルゲームconsomatonというのを作った。

consomaton_screenshot
consomaton screenshot

ブラウザで遊べます

ソースコードはこちら (GitHub)

ゲームプログラミングを趣味としている者として、昔から作ってみたかったのがプログラミングパズルゲームだった。プログラミングパズルゲームってのはたとえばGoogleがアランチューリング生誕100周年で公開していたチューリングマシンのロジックパズルとかメイドイン俺くみたて道場とかTIS-100とか。カルネージハートとかよりはよりプログラミング感が全面に出ているタイプのゲーム。

ゲームとしてプログラミングを扱うにはなるべく簡単にコードが書けることが望ましい。スクラッチとかのパネルを置くタイプもいいんだけどこれですらちょっと面倒。なのでライフゲームに代表されるセル・オートマトンのルールを書くものにした。

ルールの書き方はVISCUITメガネに影響を受けている。ルールを適用するパターンとそれを適用した後のパターンを並べて記述することで、動作ルールをビジュアルに示すことができる。

問題の出し方はくみたて道場と同じ穴あき方式にした。プレイヤーはルールに文字を入れるだけで答えが作れるのでだいぶ手軽だ。

現バージョンにはいろいろ問題があるのだが、特に問題なのが回答に抜け穴が多すぎること。例えば10問目はパックマンっぽい動作をするのが回答の予定だったのだが、こうすると一撃でクリアー!だいぶひどい。変更不能なルールとか途中ゴール状態とかを導入する必要があるのだろう。

まあでも当初の目的のお手軽コーディング感は出せたのでそこは良かったと思います。理想的にはこの仕組みと同じ感じでゲームが作れるくらいの自由度があるとより良いのだけどね。

ES2015のProxyを使ってJavaScriptを改造して遊ぼう

ES2015にはProxyという仕組みがある。Proxyを使えばオブジェクトへの各種操作に割り込んで好き勝手な動作を定義できる。

ただProxyには問題が合って、サポートするプラットフォームが少ない。

どうもProxyはpolyfillやトランスパイラで実現するのが難しいらしく、BabelやTypeScriptではES5に変換できない。

なのでブラウザで対応してもらうしかないのだが、長らくFirefoxでしか動かなくて最近やっとChromeが49で対応した。Chromeで動くならギリギリ使っても良いか、というレベルにやっとなったところだ。

今回はProxyを使って関数を呼び出すだけでゲーム内アクターを生成できるようにすることで、アクター生成を簡単に書けるようにならないか、というのをやってみた。

Proxyは動作をフックした時の挙動を定義するオブジェクトを与えて作る。

            protoSpawn = new Proxy({}, functionToActorHook);

関数が呼ばれた時をフックするにはオブジェクトに対するgetをフックして、そのターゲットがfunctionの時の動作を書く。Actorを生成するとか。

const functionToActorHook = {
    get: function(target, name) {
        const targetObj = target[name];
        if (typeof targetObj === 'function') {
            return function(...args) {
                if (args.length > 0 && args[0].rename != null) {
                    name = args[0].rename;
                    args[0].rename = null;
                }
                const actor = new Actor(name);
                actor.generator = targetObj.apply(actor, args);
                return actor;
            }
        }
        return targetObj;
    }
}

こうしておけばこのProxyを介して関数呼び出しをした時に上記コードが呼ばれるようになる。

import {protoSpawn as ps, p5js as p, mech as m} from './protospawn';
import Actor from './actor';

function setPsCode() {
    ps.main = function*() {
        ps.ship({isPlayer: true, pos: {x: 50}});
        ps.ship({isPlayer: false});
    }
    ps.ship = function*(prop) {

ps.shipを呼ぶだけでActorが生成される。あとES2015を使っているのでついでにGeneratorも使ってyieldすることで関数途中で1フレーム待つのもできるようにした。

    ps.explosion = function*(prop) {
        this.set(prop);
        this.stroke = 'red';
        for (let i = 0; i < 15; i++) {
            this.size += 2;
            yield;
        }
        for (let i = 0; i < 15; i++) {
            this.size -= 2;
            yield;
        }
        this.remove();
    }

で、作ってみてなんなのだが、このアプローチはいろいろと問題が。

  • アクター生成を関数で置き換えること自体にそんなに価値がない。new Ship()とか書けばいいだけだし。あとアクター定義が複雑になってきたら素直にclassで書いたほうがコード補完とかの面で有利
  • yieldを使ってアクター動作を中断する仕組みを使う場面ってそんなにない。従来の1フレームごとのupdate関数がある方が便利なことも多い。必要ならtweeningっぽい仕組みを別途作れば良さそう
  • Generator関数の中のクロージャからyieldが呼べない。なのでlodashの_.timesとかが実質使えなくなる

別の方法を考えた方が良いね。

汎用ビデオゲーム記述言語VGDLとその処理系PyVGDL

2Dビデオゲームのメカクニスを記述するための言語VGDL (Video Game Description Language)というものを研究している人達がいる。

ゲーム内AIなどを研究するために必要な言語仕様の提案として始まったようだ。それを実際にパースして実行する処理系PyVGDLも作られている。

VGDLはパックマンとかフロッガーとかバルダーダッシュとかの古典的アクションゲームが簡単にかけるという触れ込みである。例えばフロッガーをVGDLで書いたコードは以下だ。

まずレベルの定義がある。

wwwwwwwwwwwwwwwwwwwwwwwwwwww
w           wGw            w
w00==000000===0000=====000=2
w0000====0000000000====00012
w00===000===000====0000===02
www   ww   www    www  wwwww
w   ----   ---   -  ----   w
w-     xxx       xxx    xx w
w -   ---     -   ---- --  w
w       A                  w
wwwwwwwwwwwwwwwwwwwwwwwwwwww

なんとなく見覚えのある画面がテキストで書いてある。次はゲーム内キャラの定義。

    SpriteSet
        forest > SpawnPoint stype=log prob=0.4  cooldown=10
        structure > Immovable
            water > color=BLUE
            goal  > color=GREEN
        log    > Missile   orientation=LEFT  speed=0.1 color=BROWN
        safety > Resource  limit=2 color=BROWN
        truck  > Missile   orientation=RIGHT 
            fasttruck  > speed=0.2  color=ORANGE
            slowtruck  > speed=0.1  color=RED
        wall > Immovable color=BLACK 

林とか水とかゴールとか丸太とか、フロッガー内のキャラクタ(スプライト)が定義されている。後で説明するが、SpawnPointとか、Missileとか、この辺の記述がキャラクタの動作を決めている。

その次はキャラ同士の衝突時に何が起こるかの定義。

    InteractionSet
        goal avatar  > killSprite
        avatar log   > changeResource resource=safety value=2
        avatar log   > pullWithIt
        avatar wall  > stepBack
        avatar water > killIfHasLess  resource=safety limit=0
        avatar water > changeResource resource=safety value=-1
        avatar truck > killSprite
        log    EOS   > killSprite
        truck  EOS   > wrapAround

avatarってのは自機キャラのことですなわちカエルだ。ゴールにカエルが突っ込んだらゴールを消す(killSprite)とか、壁に突っ込んだら戻る(stepBack)などの動作が書いてある。

次はゲームの終了条件。

    TerminationSet
        SpriteCounter stype=goal   limit=0 win=True
        SpriteCounter stype=avatar limit=0 win=False

画面上からゴールが無くなったら勝ち、カエルが無くなったら負け、ということだ。

最後は最初に書いたレベルがどのキャラに相当するか。

    LevelMapping
        G > goal
        0 > water
        1 > forest water
        2 > forest wall log
        - > slowtruck
        x > fasttruck
        = > log water

以上。非常にシンプルに書けている。

ただ本当にこれで汎用かと言うとちょっと何なところもある感じがする。SpriteSetとInteractionSetの部分がメカニクスというかゲームルール記述の肝なんだけど、ここにあるMissileとかstepBackとかの動作の定義はあらかじめ決められたセットから選ばれているようだ。

このコード内にある動作がその全てで、これの作り込みによって定義できるゲームのバリエーションは規定されているように思える。で、これら動作も汎用的かというと、どうかなあ、killIfSlowとか、これルナランダー専用動作ではないのかという気がしないでも。まあここの動作が十分な数揃っていればいいのかもしれないが。

最初に思ったのは、これPuzzleScriptに似ているなということ。

PuzzleScriptはその名の通りパズルを作るための専用言語。例えば倉庫番は以下で書ける。

LEVELS、RULES、WINCONDITIONS、LEGENDの順に見ればVGDLのサンプルに非常に似ている感じがする。PuzzleScriptのルール定義は本当にシンプルで、状態のパターンマッチと、その後の状態を記述するだけ。プレイヤーが荷物に向かって歩いたら、荷物を押す、は

[ > Player | Crate ] -> [ > Player | > Crate ]

こうだ。VISCUITのメガネにも少し似ている。この1行書くだけで、上下左右どこから押されても同じように動作するみたいなことはPuzzleScriptの処理系がよろしくやってくれるので、うまく使えば少ない記述量で複雑な動作も書ける。

PuzzleScriptはキャラクタの形や効果音などもちゃんと記述することができて、これだけでちゃんと体裁が整ったゲームを作ることができる。VGDLはあくまでゲームルールを記述するだけなのでこれだけではゲームとして色々足りない部分がある。

この2つどっちが先にできたのかと言うと、PyVGDLのinitial commitが2012年6月でPuzzleScriptのinitial commitが2013年9月だからPuzzleScriptのほうが後発だ。それらの間に関係があるかというと、よく分からない。よく分からないが、比較記事があった。

PuzzleScriptはターンベースのパズルゲーム記述にかなり特化、対してVGDLはもっと広くアクションゲームを書こうとしている点で異なるけど、ゲームの形式化という点でアプローチは似ているよね、という内容。アクションやパズル系ゲームの簡易記述を考えると、この2つのようなフォーマットに落ちるのが一つの妥当なアプローチなんだろう。

ゲームのルールのエッセンスさえ記述できれば良いという割り切りのもと、どれだけ簡易なゲーム記述言語を作ることができるか、というのを考えてみるのも楽しそうだ。VGDLの発展形や、それとは全く別な記述方式、いろんなアプローチが思いつけるのが理想だが、簡易だけど汎用的というバランスの良い言語を考えるのはたぶんそう簡単では無いね。

斬新なゲームメカニクスを目指した時の「やらかし」と「もがき」の制作過程が分かる本「組み立て×分解!ゲームデザイン」

筆者のkuniさんから献本いただいた。

ひどくおおざっぱに言うと、斬新なルール、ゲームメカニクスを持つゲームを作るに向けて、これは面白いだろうと思って作ったルールがイマイチな時、そこからどうやって工夫することで面白くすることができるか、それを書いた本。

題材のゲームはkuniさんが作ったいくつかのゲームを用いている。

例えば2章はmosserことまるぼうしかく内のしかくことフレイムテイルが題材。炎が燃え広がるというアルゴリズムを元に、単にクリックしたところが燃える凡ゲーから、炎をつける自機の導入、燃やせるテトロミノの導入などの様々な試行錯誤を経て、最終形のお尻に火が着いたスネークゲームというところに到達するまでが丁寧に解説されている。

こういった新しいルールを導入したパズルゲームやアクションゲームを作るためには何回もの試行錯誤を経てルールが出来上がっていくものだが、その制作過程を細かに語っている本は珍しく思う。こういったところは明文化されない個々人のノウハウになりがちなので、それが具体的に記載されていることがありがたい。その他にも、ルールを引き算や足し算で作る方法、ゲーム内の緩急の付け方、制約を設けることでゲームをゲームとして仕立てていく方法などなどが、具体的なゲーム制作体験に沿って書かれている。5章はみんな大好きパネキットでのいくつかの制約がなぜ必要だったのかにもちょいと触れられているよ。

定番のゲームではなく、ゲームの根幹のメカニクスがちょいと変わったゲームを作りたい人にはオススメ。記載が具体的な分、ゲームデザインに対して網羅的な内容では無いし、引用された豊富な既存ゲームに対する説明はおっさんゲーマーにとっては自明の内容で無駄な感じもするところはちょっと欠点だけど、それを差し引いても読んでいてとても楽しい本です。あとパネキットを代表とするkuniゲーが好きな人はぜひ手に取ると良いと思うよ。

ES2015のProxyを使った関数呼び出しのフック

ES2015にはProxyという仕組みがある。

Proxyを使うことでオブジェクトへの書き込みや読み込みをフックすることができ、メタプログラミング的なことがJavaScript上で実現できるようになる。

例えば、関数呼び出しをフックして呼び出し前に何か別の処理をしたい、と思ったら以下のように書ける。

const obj = {
  foo: (x) => {
    console.log(x);
  }
};

const proxy = new Proxy(obj, {
  get: function(target, name) {
    const targetObj = target[name];
    if (typeof targetObj === 'function') {
      return function(...args) {
        console.log(`call ${name} ${args}`);
        const result = targetObj.apply(this, args);
        return result;
      };
    } else {
      return targetObj;
    }
  }
});

window.onload = () => {
  proxy.foo(42);
}

Proxyのgetハンドラで値の読み込みをフックし、関数を読み込もうとしたら別の関数に差し替える。差し替えた関数の中ではapplyで元の関数を呼び出すけど、その前や後に任意の処理を挟み込むことができる。

ちなみにES5の制約でBabelはProxy未サポートなのでブラウザが対応しないかぎりProxyは使えない。

今までProxyはFirefoxかEdgeでしか使えなかったのだけど、Chromeも49から対応してくれるらしい。

だとするとそろそろProxyを使ったメタプログラミングに手を出してもいいかなあとも思う。あとTypeScriptでもES6ターゲットにすればProxy書いても文句言わないので使えそうだ。