読者です 読者をやめる 読者になる 読者になる

テキストエディタで完結したゲーム開発環境は無いかね

と書いたが要はテキストエディタだけを入力として完結したゲーム開発環境が欲しいなという話。グラフィカルなドット絵エディタや3Dモデラー、ミュージックシーケンサなどは無し、さらにいうと外部リソースインポートも無し、ゲームに必要なリソースは全てコードで示す。

ここまで制約を加えるとなかなか所望のものは見つからない。そもそもグラフィックスをコードで示そうとなると、ドット絵を文字列の配列で示すとか、モデルの頂点を数値配列で示すとか、えらく前時代的な作りになって、それはそれでどうかと思う。

他のアプローチとしては、グラフィックスを生成することだ。ドット絵で言えば

Procedurally generated enemies

Identicon

のように何もないところから敵っぽい形やアイコンを生成したり、

pixel-sprite-generator

のように大まかな形を与えるとそれっぽい形にしてくれる方法がある。

3Dでも

Spaceship Generator

のような宇宙船ジェネレータがあったりする。あとは

Supershapes Generator

のような数式による形状作成という方法もあるな。シェーダもこの手の話の一種。

POV-Ray

POV-Rayのようにプリミティブの配置をテキストで記述する方法もある。

こういった方法を使えば、ランダムシードや数式、または種となる簡単なパターンのみをコードに記述して、そこからキャラクタのグラフィックスを生成することができる。ただ作られる形のバリエーションにはかなりの制約があり、人手で作られたコンテンツのレベルには遠くおよばない。

音の方はどうだろう。テキストでの音記述と言えば、もちろんMMLがある。

Music Macro Language

音符はもちろん、ものによってはFM音源パラメタや波形データなども書けたはず。歴史のある伝統的な手法だ。

音をごくコンパクトな数式で表す方法もある。

Bytebeat

デモとかで使われることのあるBytebeat。時間を入力とした数式の出力をほぼそのまま波形にするという乱暴な方法だが、数式を工夫することで長時間の展開を含む複雑な曲を作ることもできる。ただちゃんと曲の鳴る数式を作るのはかなり大変。適当な式を入れると大抵はとんでもない騒音が出てくる。もうちょっと整った方法だとSuperColliderなど。

SuperCollider

効果音だけならパラメタの組み合わせで簡単に作れる方法がいろいろある。

jsfx

ChipTone

この辺の話はまとめるとGenerative artの一種ということにはなるんだよな。

Generative art

ただGenerative artはゲームに限った文脈ではないから、キャラクタとか効果音とかいう観点でのコンテンツ生成についてはそれほどカバーされてないかもしれん。

この辺の方法をうまく取り入れれは、コード一つでゲームロジックから絵から音から全て書けるようになって、プログラマにとってのお気楽なゲーム開発環境が手に入る。それを実現している一例としては、PuzzleScriptがあるな。

PuzzleScript (Objects, Sounds)

この手のお気楽さが他のジャンルのゲームでも使えるようになっくると嬉しいのだが。

お手軽にゲームプレイAIを試してみる

最近はゲームもAIがプレイしてくれる時代だ。

ゲーム攻略で人間を超えた人工知能、その名は「DQN」

有名なDQN。フルネームはdeep Q-networkと呼ばれる強化学習の一種だ。こういう機械学習系の仕組みはマシンパワーでもって学習をぶん回して動かさないといけないので、それなりの準備が必要なのが普通だ。だけど最近はこの手の物をブラウザ上で簡単に試せるようになっている。

REINFORCEjs

例えばREINFORCEjs。これはDQNJavaScriptで実装したもの。使い方もえらく簡単。

// DQNエージェントにゲームの状態を与えると
var action = agent.act(state); 
// アクションとしてどう行動すればよいかが帰ってくるので
// それに従って行動して
// その行動が正しかったどうかを示す報酬をDQNエージェントに教える
agent.learn(reward);

これだけ。状態、アクション、報酬ってのは例えば以下のようなもの。

  • 状態:プレイヤーから見た敵の方向と距離、ボーナスアイテムの方向と距離
  • アクション:上下左右どっちに動くか
  • 報酬:敵にぶつかると-1、ボーナスアイテムを取ると+1

ディープマインドがAtariのゲームを攻略した時みたいに、画面のピクセル全体を入力として操作を学習する、みたいなことをしようとするとそれなりの学習時間とマシンパワーが必要だが、入力する状態を限定すればそれほど頑張らなくても学習できる。

DQNとは異なるアプローチもある。

Neuroevolution

Neuroevolution。ニューラルネットワークにスコアを与え、それを元に遺伝的アルゴリズムを適用しネットワークを進化させていく。これもブラウザ実装がある。

Flappy Learning

Flappy BirdをNeuroevolutionを使って攻略している。NeuroEvolution.js部分に学習アルゴリズムがまとまっている。これも使い方は簡単。

// 複数のニューラルネットワークを取得して
var networks = ne.nextGeneration();
...
// ネットワークにゲームの状態を与えると、
var action = network.compute(state);
// アクションとしてどう行動すればよいかが帰ってくるので
// それに従って行動
...
// 一通り行動したらそれぞれのネットワークにスコアを与える
ne.networkScore(network, totalReward);

NeuroEvolution.jsは一通り行動してからその行動にまとめてスコア付けして進化、REINFORCEjsは行動のたびに報酬を与えて学習、というふうに動作サイクルは若干異なるけど、両方とも強化学習の一種なので外からみれば使い方はほとんど同じだ。

だからこいつらを一つのゲームに混ぜて競わせることもできる。

dqn-cross-road

dqn-cross-road

いわゆるハイウェイをクロスさせてみた。

  • 状態として周辺3車線の直近の車までの距離と車線のタイプ(普通、スタート地点、ゴール)を与える
  • アクションは前に進む、後ろに戻る、何もしないの3つ
  • 報酬は渡りきると+1、車に当たると-5
  • REINFORCEjsはデフォルト設定のまま、NeuroEvolution.jsはネットワークを入力ノード数6(3車線*距離とタイプ)、中間ノードを10ノード2層、出力ノード数2(前、後ろ)とする
  • 最初にREINFORCEjs5つ、NeuroEvolution.js5つのプレイヤーを作る
  • 2秒ごとに報酬が一番低いプレイヤーが排除される。全プレイヤーが排除されると次の世代へ
  • REINFORCEjsは最後まで生き残っていたエージェントのクローンが次の世代で作られる

こういう学習して動作する系のものってバグ無くうまく動いているかどうかが分からないのが困り者なのだが、一応ちょっとは車を避けているっぽい動作をしているので動いているのではないだろうか。あと状態とか報酬とかネットワーク構成とかは適当。本当はチューニングが必要なのだろうが。

ちょっと動かしてみるとREINFORCEjsはフラフラしながらも一応避けて進めているっぽい。NeuroEvolution.jsは立ち止まる、突っ込むみたいな単純な動作になりがち。これは単純にネットワークの複雑さの差なのかしらん。だけどNeuroEvolution.jsでもたまに賢げに振る舞うのが出てきたりするのが面白い。ただ放っておけばどんどん賢くなっているかと言われるとあんまりそんな風には見えないね。

という感じに簡単にゲームプレイAIを試すことができるように最近はなってきている。これどう使おうかね。とりあえずデモプレイを代わりにやってもらおうか。

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

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

スタートリゴン

星の周りをグルグル回るホシ・ワタル。ボタンを押すと飛び出して他の星へラインを張って移動、三角ネットが出来たら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というのを作った。

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の発展形や、それとは全く別な記述方式、いろんなアプローチが思いつけるのが理想だが、簡易だけど汎用的というバランスの良い言語を考えるのはたぶんそう簡単では無いね。