Hatena::ブログ(Diary)

土屋つかさのテクノロジーは今か無しか

2014-12-23

外部で作ったゲームのデータをRubyで読み込むTIPS

 DXRuby Adbent Calendar 2014 の24日目です。23日はみれいゆーさんのDXRubyでノベルエンジン作ってみた(AC用記事)でした。ノベルエンジンと言えば去年のAC汎用テキストレイヤライブラリDXRuby_text_layerの紹介をしているので、よろしければどうぞです。

■言い訳と反省

 先に言っておくと今回はDXRuby使いません(爆)。あとショートショートでもないです。年末ホント時間なくてなんもできんかった……。来年は頑張りたい。

 「来年は頑張りたい」と言えば、今年はDXRubyは愚か、Rubyプログラミング自体ほとんど出来なかったんじゃないかな……。理由は色々ありすぎて言えないんですが、可処分時間をプログラムに割く余裕が無かったというのはちょっと残念でした。文章を書くのもゲームを作るのも仕事になった今、趣味と言えるのってプログラミングくらいなんですけどねー。

 その意味では、趣味なので興が乗らないとブーストが懸からないというのがあって、多分、この数年はこんな感じ。

2011 ゲームエンジンRAGの開発開始(このバージョンは凍結して作り直すことに)
2012 プログラム出来ず
2013 テキストレイヤライブラリDXRuby_text_layer開発(これは完成)
2014 プログラム出来ず

 この流れでいけば来年はなにか作る筈……多分。ちなみに、最近はUnityに興味が出つつあるんですが、やっぱり趣味活動だとRubyで書くのが一番ストレスが少ないんですよね。DXRubyで必要なゲームライブラリを作りつつ、Unityに移植する形にできないかなーとか考えています。

■データを取り込む

 さて今回は、ゲームプログラミングにおける、データの持ち方を考えてみます。例えばこんな感じのデータがあったとします。今机から手が伸びた所に唯一置いてあったゲーム攻略本が「タクティクスオウガ運命の輪公式コンプリートガイド」だったのでそれをベースに改変します。ちなみに面倒なので数値も含めて全部文字列にしています(よく考えたらこれは失敗でした)。

f:id:t_tutiya:20141221121009p:image

 さて、このデータを自作のゲームで使いたい場合、どうすれば良いでしょうか?

 ソースコードにこのデータを直接書き込みますか? Rubyならリコンパイル時間をほぼ無視出来るのでそれでも構わないと言えば構わないのですが、プログラマとデータ設計者が別れている時や、プログラムとデータの版管理を分離したい場合、それでは困りますよね。

 けれど、プログラムの外に存在するデータ群をプログラムで取り込むにはどうすればいいのでしょう? しかも、ゲームではこれ一個や二個ではありません。商業ゲームの場合こういうテーブルが数百に及ぶこともあります

■Cなどの場合

 通常、こういうデータを取り込む時はメモリマップドという手法を使います(土屋が所属していたチームがそうだったというだけで、業界標準なのかは知りません)。

 例えばさきほどのデータをプログラム内で読み込む為に、以下のような構造体からなる配列を用意します(C風の疑似ソースです。動作確認していません)

struct monster {
	char name[20];
	int  move_power;
	int  hit_point;
	double intelligence;
};

struct monster_data monster[4];

 monster_dataはポインタなので、この配列の先頭のアドレスを持っています。構造体配列がプログラム中で宣言された時、そのアドレスから「構造体monster1個分×配列宣言した個数」の番地までのメモリ空間が確保された事になります(中身は未定義です)。

【注意:ネイティブコードの場合、実際にはエンディアンとか、64bitアラインとか、コンパイラ実装依存とかあるので、コード中の構造体宣言と実際のメモリ上の各変数の並び順やサイズは一致しないことが多いです。むしろ隙間無くデータがメモリに格納されていることはほとんどないと思います(bool値やビットフラグを使う時に嵌りやすいので注意)。ダミーのunionを使ってアラインを揃えるみたいなテクニックもあります】

 ということは、元のデータをこの形になるように変換してバイナリファイルとして保存し、使う時には既に確保した構造体配列のメモリ領域にベタに配置して、強制的にポインタを合わせれば、プログラム側からは、必要なデータが全て構造体配列に格納されているように見えますよね? これがメモリマップドという手法です。

 Excelのデータをバイナリファイルに変換する方法は、xlsxファイル内に同梱したVBAでやるとか、CSV形式で保存した後Cのネイティブアプリを動かすとか、それこそRubyで書くとか、コミュニティによって様々です。最初からCSVファイルで管理する所もありますが、それだとExcelのポテンシャルを生かし切れないかなと思います。

■Rubyの場合

 さて、ではRuby/DXRubyではどうすればいいでしょうか。Rubyにはメモリに直接データをマップし、それをオブジェクト配列とリンクさせるという事は出来ません(できるのかもしれませんが土屋は知らないです)。こういう時にはMarshalを使います。これが実に便利です。

 まずバイナリファイルを作るプログラムはこうです(さっきのファイルは事前に"1223_dxruby_ac.csv"という名前で、CSV形式で保存してあるとします)。

#! ruby
require 'csv'

class Monster
  attr_accessor :名前 , :HP , :移動力 , :知性
end

モンスターリスト = Array.new

#CSVを読み込んで配列に格納
CSV.foreach("1221_dxruby.csv") do |CSV行|
  モンスター = Monster.new

  モンスター.名前   = CSV行[0].to_s
  モンスター.HP     = CSV行[1].to_i
  モンスター.移動力 = CSV行[2].to_i
  モンスター.知性   = CSV行[3].to_f

  モンスターリスト.push(モンスター)
end

#配列をマーシャルダンプして保存
open("monster.data", "wb") do |ファイルハンドル|
  ファイルハンドル.write(Marshal.dump(モンスターリスト))
end

 保存したファイルをバイナリエディタで見てみるとこんな感じ。

f:id:t_tutiya:20141222091739p:image

 このバイナリファイルを読み込んで配列に格納するプログラムは以下のとおり。

#! ruby
require "pp"

class Monster
  attr_accessor :名前 , :HP , :移動力 , :知性
end

#ファイルをオープン
open("monster.data", "rb") do |file|
  #マーシャルで展開
  pp Marshal.load(file.read)
end

出力結果
f:id:t_tutiya:20141222091740p:image

 まあなんて簡単なんでしょう!(実際にやってみると色んな制約があってキーってなるかもだけど) 一列目も読んでいるのはどうにかしたい所。これはバイナリファイルを作る際に飛ばすのが良いでしょう(更に言えば、csvではなくxlsxファイルを直接操作したいですね)。どうすればいいのか是非考えてみて下さい。また、これ実際には読み込む時にかなりのCPUコストを消費している筈で、データの量が莫大になると起動が凄く重くなったりするのかなと思うのですが、最近のPCなら別に気にならないんじゃないかな……と適当に思っていたり(昔は結構気にしていました)。

■おわりに

 コードがシンプルすぎて「これだったらCSVのまま読み込めばいいんじゃないか?」と思われるかもしれません。それは確かにその通りなのですが、商業ゲームの場合CSVライブラリを同梱できないとか、データをイージーに解析されるのを避ける目的で難読化(あるいは暗号化)するなどの理由でバイナリ化を行います。参考になれば幸いです。

 また、土屋の知識が古いだけで、もしかしたら現在のゲーム開発ではリリース直前までデータはRDBMSに格納していたりして、上記のような手法レガシーな技術だったりするのかもしれません(いや、そんなことないかな……)。ソシャゲではトランザクション管理があるので、恐らくそうなっているんじゃないかと思います(いや、そんなことないかな……)。

 ゲームというのはデータの塊なのですが、データをどう管理するのかというのは、あまり広まっていないのかなと思います(土屋もメーカーに所属するまで知りませんでした)。今回紹介したのはあくまで基礎中の基礎ですが、ここから自分なりのデータ管理手法を発展させていって下さい。土屋つかさでした。メリーリスマス!

■捕捉

 サンプルコードはutf-8で保存すれば動きます。日本語プログラミング万歳。

■参考サイト

 CSV周りについて以下のサイトのコード参考にさせて頂きました。感謝します。

RubyCSVを扱うときの基礎の基礎(via がらくたブログ
http://utagawakiki.hatenablog.com/entry/2013/04/27/205115

 Marshalについてはバイナリデータ化目的のサンプルコードを上手く見つけられませんでした。そういう使い方が一般的でないのか、あるいはもしかしてこれレガシーライブラリだったりします……?

 Marshalでダンプしたバイナリファイルのデータ構造については下記ドキュメントを参考。これ前に探した時は見つけられず、今日ツイッターで教えてもらいました。感謝!

http://docs.ruby-lang.org/ja/2.0.0/doc/marshal_format.html

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/t_tutiya/20141223/1419347752