Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
DXRuby Advent Calendar 16日目  DXRubyでRPGを作りたくて

DXRubyでRPGを作りたくて

この記事はDXRuby Advent Calendarの16日目の記事です。昨日はmirichiさんのDXRubyの怪しげで不自然な挙動について語るでした。DirectXをCで直接叩いているそうで、かなり大変そうですね。しかし全て把握しているのもまた、凄いと思います。

# coding: Introduction

どうも、GameKazuです。DXRubyという使いやすいライブラリを使って、RPGという最も複雑なゲームの1つを作ろうとしている学生です。プログラミング歴は3年と少し、RubyとDXRubyは2年と少しなので、今回の参加者の中でも短い方だと思います。

昨年に引き続き、今年はさらに、AdventCalendarの記事をレベルが高く、読んで実践できる物が続いていますが、私の記事は少し趣向が異なると思います。言うならば、これからプログラミングを始める人に向けて、通り道の一例を見せるものでしょうか。DXRubyの記事と言えるかは微妙ですが、お付き合いいただければ嬉しいです。

require 'dxruby'

ここに辿り着かれた方に、「Rubyって?」「DXRubyって?」という方がいらっしゃるかも知れないので、少し長所を説明すると、

  • Ruby
    • まつもとゆきひろさんが開発したプログラミング言語。
    • 日本人が日本で作ったので、日本語のドキュメントが豊富。
    • プログラミングから「機械っぽい」面倒事を減らすべく、書きやすく生産性が高くなっている。
    • オープンソースで、開発が活発。近くバージョンアップするかも?(2014.12現在)
  • DXRuby

といった感じです。とにかく書きやすい。興味が湧いた方は、まず文法を見てみましょう。プログラミング教本でお決まりの「ここは『おまじない』と思って下さい」が殆ど無いでしょう。(「Ruby 入門」で検索したり、DXRuby Wikiの入門ページを見たりしてください。)

begin; purpose = Image.load('./of_yours.png')

ここからは、DXRubyを使うと決め、基本的な文法を学んだ後の話です。

まず大事なのが、プログラミングで作る物を決めることです。どんな目標が適しているかですが、多くの人は「小さな物から作っていけ」と言います。それに加えて、1つ大きな目標を持つと良いと思います。この時、小さな目標が「少し調べれば作れる」ぐらいになるように意識すると、調べて→書いて→見直して→調べて…といったサイクルがうまく回り、自然に書く力が伸びていく、というのが私の経験です。大きな目標が固まっている場合は、それを細分化して小さな目標にしても良いでしょう。

「小さな」作りたい物が決まったらどんどんコードを書いて作っていく。RubyやDXRubyは書いただけ応えてくれますから、とにかく書く。そして同時に、他の人のコードを読んでみましょう。細かな部分、例えば「この変数はどういった値をとるか」「このメソッドはいつどこで何の為に使われて何を返すのか」をじっくり読み込めば、自分の知らなかった実装をしていたり、知っていたけれど美しく応用していたりと、勉強になるはずです。

そうして新しいことを学ぶと、過去の自分のコードが醜く思えてきます。これはいつになっても起こることです。私は、そうなったら躊躇わずに書き直してしまうことにしています。新しいことも自分で試してみないと本当の価値や役目が分からないからです。

require_relative './dxrubyws'

私の話をします。まず、DXRubyに出会った瞬間に「これでRPGが作れる!」って言い出した人でして、玄関を開けた瞬間に方向を間違っていたかもしれません。冒頭で話した通りRPGを作ろうとしているのですが、データを扱う場所や情報伝達を考えるなど、かなり複雑です。昔のコードを読むと、1つのファイルが肥大化し、Window.loop(つまりゲームループ)の中に長い長いコードが挟まっていました。

そんなクソコードを書いていても基本文法は数か月で身につき、その頃には「Rubyって本当に全部オブジェクトなんだな」とオブジェクト指向まで感じていました。しかし程なくして壁にぶつかり、その時に見つけたのがDXRubyWSでした。これはDXRubyでウィンドウシステムを作ってみようとmirichiさんが始めたもので、クラスと継承・モジュールとMix-in・オブジェクトツリーとシグナルハンドラ、といった構造が使われています。どうしてもDXRubyWSを使いたくなった私は、そのコードを全てプリントして学校に持って行き、解読しました。それを基に簡易マップエディタを作ったのが、DXRubyを初めて1年でした。

GitHubという、プログラムを一般に公開する場所を見つけたのもDXRubyWSで、その開発に加われると知った私は、マップエディタを作る途中に自作したクラスを早速Pull Requestしました(GitHubで、開発者にプログラムの変更を提案することです)。この時から、Twitterでも活動を始めてプログラマとコミュニケーションを取りつつ、自分のプログラムを全て公開し始めました。

require_relative './rpg'

そして、今作っているプログラムもGitHub上に有ります。完成しているのはマップの描画処理のみです。

マップの描画使用している素材を考えて、このスクショを載せても大丈夫か分からないですが、少なくともこれ以上は無理です……。

RPGツクール用の素材をそのまま使えるようにしたので、海タイルや滝タイルは3枚のアニメーションです。この静止画を見ても分かりませんが、

  • 海の部分は一定速度でアニメーションさせています。
  • 加えて、マップの描画先を指定できたり、
  • タイルセットを一定種類使うと、最初に使った方からImageオブジェクトを開放して自動でメモリ節約したり、
  • ゲームループ内の描画処理はMap.draw(mx, my)の一行で、mx, myで中心に描画したいマスを指定するだけだったり、

今の自分が実現できるユーザビリティを追求してみました。現在はタイルセットやマップのエディタを作っていて、ウディタ用の素材も使用できるように考えています。それらが完成すればアイテムなどの管理を作り、それを基にバトル処理を作成して、最後にイベントシステムを構築します。

rescue; raise $!.class, $!.message, caller(2)

DXRubyのAdvent Calendarですし、これまでや今の気を付けていることと悩んでいることをここで書きたいと思います。

まずRubyでゲームを、特にRPGのような大規模なゲームを作る際、速度が厳しいと言います。DXRubyはその内描画処理(とSpriteクラスで衝突判定)を高速でやってくれるのが強みですが、無理はできません(当たり前)

マップの描画処理で処理落ちを何パターンか経験しました。まず、

  • 単純に描画させすぎると落ちる。

手頃な再現方法だと、300px*400pxぐらいの画像をまず普通に描画してみて下さい。

image = Image.load('画像のパス')

Window.loop do
  Window.draw(0,0,image)
  Window.caption = Window.real_fps.to_s
end

ウィンドウのタイトルに60前後の値が表示されます。これはつまり60FPSで動けているという事です。では、馬鹿をやってみましょう。

image = Image.load('画像のパス')
w = image.width
h = image.height
image_arr = image.sliceTiles(w, h)
map_arr = Array.new(h){|y| Array.new(w){|x| y * w + x}}

Window.loop do
  Window.drawTile(0, 0, map_arr, image_arr, nil, nil, nil, nil)
  Window.caption = Window.real_fps.to_s
end

簡単に言うと、あなたの選んだ画像を1pxごとに分割して描画します。多分60なんて数字は表示されてくれないはず。何個の画像を描画しているか考えれば、この結果は当たり前ですが、DXRubyだから何しても速いなんてことはありません。

また、DXRubyで描画するのはImageのみではなくRenderTargetもあります。これはWindowのように毎フレーム描画することで更新できするタイプの画像で、詳しくはDXRubyのリファレンスに説明が有ります。とても便利な機能なのですが、

  • RenderTargetは毎フレーム更新する分Imageよりも少し重い。大量に使うとかなり重い。

私はタイルのアニメーションにそれを使おうとした結果、毎フレーム658個ものRenderTargetを使用しました。結果はお分かりですね。

なんだか馬鹿なことをしているだけに見えるかもしれませんが、例えば「RenderTargetは毎フレーム更新しているので多く使うと思い」ということを、馬鹿真面目に教えてくれるのは中々ありません。分かれば単純ですが、分からないといつまでも分からない。一度やって失敗を味わうまでは分からないんです。他にも例えば、WindowRenderTargetがサポートするタイル描画#drawTileでは画像を格納した配列を平滑化しているとか、2次元配列を連結する時にArray#flattenではなくArray#inject(:+)の方が速いとか、細かいこと程知る機会は少ないでしょう。

これらが実行時の問題だとすれば、開発時の問題もあります。人間が1度に把握できる行数は10000行と聞きました。因みに先のマップ描画だけで1900行でした。それに、時間が経過すれば人はどんどん忘れていきますし、「過去の自分は他人」という現象はプログラマなら頷けるものです。

ですから、

  • 大規模なプログラムになると分かったなら、未来の自分に分かるように心がける。

それは他人に分かるように心がけるのが近道だと思います。私はこの点を過剰とも言える位に気にしていて、ふと気が付いた時にインデントを直したり、想定できるだけ例外処理を設定したり、GitHubにMarkDown方式でドキュメントを残したり、ソース中にもコメントを残したりしています。

end

DXRubyWSで多くを学んで、RPGを作ろうと迷走し、今はRPGを作るためのプログラムであるエディタを作ろうとDXRubyWSに戻ってきました。WSは本当に楽しいです。現在進行形で発展していくDXRubyを体現しているような気がします。しかし残念なことに、ユーザがとても少ない。WS製のエディタを完成させて少しでも恩返しができたら良いな、なんてことまで考えながら、今日も楽しくコードを書いています。

そう、楽しむことが最重要です。Rubyも「Enjoy Programming」を掲げていますし、ACの参加者たちも皆が楽しくてプログラミングをしています。これから始める人もそんな風に楽しんで欲しいです。

Window.loop do

ここまでお付き合いいただきありがとうございました。あなたのゲームはどんな形でしょうか。そのWindow.loop doが最高のendを迎えられますように。

17日の記事はvivit_jcさんの「遺伝的アルゴリズムでAIを作る(仮)」のようです。AIと言えば、RPGのバトルを面白くしたり快適にしたり、大事なものですね。期待!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment