日本語  English 
21世紀物理のおもちゃ箱
Monadius
monadius title logo

1985年5月29日、そのゲームは、全国のゲーマーに衝撃を与えた。

らしい。

僕はよく知らない。そのときまだ生まれたばかりだったからだ。 そんな僕の小学校にはPC98があったし、中学校にはFM-TOWNS、 高校にはAT互換機があった。

あたりまえのように存在するゲームとプログラミング環境の中で育った少年が、 進化したプログラミング言語からのメッセージを伝えるために、 20周年をむかえたグラディウスと、 ゲームを、ゲームプログラミングを愛する人たちに感謝を込めて贈る。


このゲームについて

Monadiusは、そんなたいしたことはないWindows用横スクロールシューティングゲームです。

Haskellで書かれているためゲーム全体がモナド で出来ています。だからモナディウス。

Monadiusは、グラディウス20周年企画であり、Haskellでゲームを作るときの問題点を 洗い出すための実験でもあります。

ルール

monadius screen shot

基本的にグラディウスです。

詳しくはreadmeをごらんください。

ダウンロード

MonadiusはGPLライセンスで配布されるフリーウェアです。

実行には、新しめのWindowsとOpenGLが必要です。

謝辞

ご意見・ご感想は

  • Mail

  • monadius revival screen shot

    実験報告・Haskellでゲームを作る

    なぜHaskellなんかでゲームを作りたがる人がいるのか、 実際にHaskellでプログラムを書くのはどういう感じなのか、 プログラミングをしらない人も意識して説明してみようと思います。

    Haskellingの雰囲気を見てみたい人はまず実験編を読んでください。

    もくじ

    1. 目的
    2. 装置
    3. 実験
    4. 結果
    5. 結論

    移植目的で使えば言語の特徴が明らかになる 〜 もくろみ

    関数型言語でゲームを書くといろいろ楽になるんじゃないか。 関数型言語は、あまり普及してないし、そのせいであまり使いこなされていないのが現状です。 が、枠組みさえ整えば、ゲームのアイデアを持っている人にとって命令型でやるより ずっと豊かな道具となるはず。

    なんですが、詳しいことは自分で実際に何か作ってみないと…。

    そんなときグラディウス20周年の話を耳にして、Haskellへの移植を思い立ったのです。 既存のゲームのフィーチャーを一定期間内でひととおりなぞってみることになり、 Haskellならほんとうに速く書けるのか、 どんなことがHaskellではやりにくいのか、 オリジナルなものを作るだけでは分からないことも分かるかと。

    いわば、写経です。

    すてきなHaskellingのための日用必需品 〜 よういするもの

    Monadiusを作るのに使ったもの。

    書きたいことの構造が見えてくる 〜 ながれ

    はじめにお断りしますと、以下のコードは説明のために実際のものと若干変えてあるところがあります。

    さてHaskellでは、ふだんは設計ノートに書いたり頭の中で覚えておくようなことを、ソースに書いてしまえます。 まず全体の流れをつくってから、足りない詳細を埋めていく、というトップダウンなやり方が自然にできるのです。

    というわけでいきなり、ゲームとは何ぞ?を定義しましょう。

     --game.hs
      
    module Game(
      Game(..)
    )where
      
    import Graphics.UI.GLUT.Callbacks.Window 
     
    class Game g where     --   gがゲームであるとは、
      update::[Key]->g->g  --   キー入力にしたがって定まるgからgへの変換によって更新することができ、
      render::g->IO ()     --   gを描画することができ、
      isGameover::g->Bool  --   gがゲームオーバーかどうか判定することができることである。 

    ステートマシンがあっさり抽象化できました。renderingというのは3DCGを描くという意味の用語です。

    つぎにMonadiusをつくり、それがGameの例になっていることを宣言します。

    Monadiusはdata、Gameはclassです。 dataとclassの関係は構造とその具体例の関係です。(行列,行列積)と群の関係、(整数,和,積)と環の関係。

     instance Game Monadius where
      update = updateMonadius 
      render = renderMonadius
      isGameover = isMonadiusOver
    
    data Monadius = Monadius (GameVariables,[GameObject])
    
    updateMonadius::[Key]->Monadius->Monadius
    renderMonadius::Monadius->IO ()
    isMonadiusOver::Monadius->Bool 

    Monadiusはデータ的には (GameVariables,[GameObject]) つまり、ゲーム変数(スコアとか)と、ゲームキャラクタ(プレイヤー機とか敵とか)のリストと の組になっています。代数っぽくいうと、そういうデータの一塊であるMonadiusが、 updateMonadius, renderMonadius, isMonadiusOverという演算に関してGameを成す、と宣言されています。

    updateMonadius, renderMonadius, isMonadiusOverの型はclass Gameの条件を ちゃんと満たしていますね。

    キー入力に対しMonadiusというゲームがどのように振舞えばいいか、updateMonadiusの中身を書きます。

     updateMonadius::[Key]->Monadius->Monadius
    updateMonadius keys (Monadius (variables,objects))
     = Monadius (newVariables,newObjects) where
     
      newObjects =                   -- キー入力後のゲームのキャラクタたちは
        (issueTag.                   --   キャラ識別タグを発行する
        (loadObjects ++).            --   新たに登場するキャラを読み込む
        (filterJust.map scroll).     --   キャラの位置をスクロールさせる
        concatMap updateGameObject.  --   それぞれのキャラを運動させる
        collide)                     --   キャラ同士の衝突を判定する
          objects                    -- らの合成関数を入力前のゲームのキャラクタにかけたもの
          
      newVariables = variables{      -- キー入力後のゲームの変数たちは
        ...                          -- 適宜更新(^^;)
      } 

    .(ピリオド)は関数合成をあらわす演算子です。 いかにも関数型言語らしい表現ですね。 このあと、合成されてる関数のそれぞれ中身を書かないといけないですが、これ以上の詳細はちょっと退屈なので省略。

    Monadiusというゲームを画面に描く、renderMonadiusのほうを見てみましょう。
     renderMonadius::Monadius->IO ()
    renderMonadius (Monadius (variables,objects)) = do
      mapM_ renderGameObject objects 
    すっきりしてます。それぞれのオブジェクトを描くだけですね。

     ここでHaskellは初めてだけどプログラミング言語は知っているという方々に、  このすっきりしている1行が実はいかに凄いかをお教えしましょう。上のソースコードで、

       
    このように、Haskellなどの関数型言語では、命令(ソースコードの地の文と思って。)も値の一種であり、関数に渡し、関数から返し、変数に代入し、クラスそのほかの要素とし… などが自由にできます。ソースコードを綺麗にするべく特殊な構文が大量に用意されているのではありません。

    int値を整理するためのあらゆる方法が、そのまま命令を整理するために使えるので、ソースコードは構造化され、読みやすくなるのです。

    ではMonadiusというゲームの終了を判定する、isMonadiusOverは。

     isMonadiusOver::Monadius->Bool
    isMonadiusOver (Monadius (variables,objects)) = flagGameover variables 
    flagGameoverは、variablesというゲーム変数の総体から、ゲームオーバーかどうかの情報だけをとりだす 射影演算子です。 Monadius (variables,objects) がゲームオーバーなのは、 flagGameover variablesが真のときだと。

    モナディウスにはもうひとつGameの例が含まれています。リプレイ記録装置です:

     instance Game Recorder where
      update = updateRecorder
      render = renderRecorder
      isGameover = recorderIsGameover 

    こうやって書いてから、updateRecorderなどの内容をあとで書きます。

    ある程度書きかけのソースコードをGlasgow Haskell Compilerに渡すと、 未定義の関数や、型エラー(物理でいえば、左辺と右辺の単位が違っていること)を調べてくれます。 これを参考にしながら、どんどん定義を埋めていきます。

    いかがでしょう。Haskellは、あなたが抱いていたプログラミング言語のイメージとは違ったかもしれませんね。 僕は、Haskell書きは、直感的に明らかなたぐいの数学の定理を フォーマルに証明する過程に似ている感じがします。

    ついにすべては定義されて 〜 できた

    コンパイラーが警告を出さなくなるまで定義を詰めれば、プログラムは完成します。

    完成したもの:

    たのしいゲームプログラミングへの道は問題山積 〜 まとめ

    今回は問題点が挙がればいい、解決するのは次以降という実験なので、問題点はほったらかしです。 あまり考えて書いてないので、関数型言語といいながら中身はほとんど古き悪しきステートマシンです。 決してソースを見て、模範的なHaskellのコードだと思わないように(誰も思わない)。

    発見:

    課題:

    Haskellでもどうにかゲームは作れるぞということは示せましたが、 実行してたら遅くなるとかいった、Haskellの仕組みをよく知らないと分からない問題はお手上げです。 もっともっとゲームを作りやすくする方法があるはず。 つぎは、みんなにまかせた!

    スポンサードリンク

    日本語  English 
    21世紀物理のおもちゃ箱