いつクリはてブロ

いつになったらクリエイティブするの?

YAMLでゲーム内データを定義する

この記事はDXRuby Advent Calendar 2014 13日目の記事です。
昨日はfourxzさんの『DXRuby、IntelliJで入力補完の巻』でした。
DXRuby Advent Calendar 2014 - Adventar
DXRuby、IntelliJで入力補完の巻 - fourxzの日記

私は大体コードを書くときは自動補完の無いエディタで手書き&リファレンスからコピペマンなので、自動補完でガンガン省力化、時間短縮を図っていきたいところ。

こういうのは典型的な「いつかやろう」系の作業なので、この記事をきっかけにいろいろやってみようと思います。(フラグ)

前回の補足とか

前回の記事の内容に関して、テストを書くなら正直ジャンプ云々よりもポーカーの役判定の方がよほど例として適切でした。

詳しくは11日目のうのはなさんの記事『DXRubyでポーカーを作った』をご覧ください。

DXRubyでポーカーを作った - せらぴんブログ

なんでもかんでもテストするのではなく、とりあえず手動でやるのが面倒ではあるが自動化するが楽な部分から書き始めるのがいいのではないかと思います。

※前回記事

〜ゲーム制作者だってテストがしたい!〜DXRubyユーザー向けRspec入門 - いつクリはてブロ

はじめに ゲーム内データの定義

さて、本題に入りましょう。

皆さんはゲームを作るとき、データをどのように用意しているでしょうか。

特にRPGシミュレーションゲームなど込み入ったゲームを作るためには、ゲームのデータをどう記述するかはかなり重要な問題です。

今回はfourxzさんの予言通り、YAMLというデータ記述法を紹介したいと思います。

YAMLの書き方

見てもらった方が早いと思うので、とりあえずそれっぽいデータを書いてみましょう。

omikuji.yml

- 大吉
- 中吉
- 吉
- 小吉
- 末吉
- 凶
- 凶
- 凶
- 凶

Rubyで実行するときは、yamlライブラリをrequireする必要があります。
omikuji.rb

require 'yaml'
p omikuji = YAML.load(File.open("./omikuji.yml"))
p omikuji.sample

入門用らしくおみくじプログラムを書いてみました。実行結果は以下のようになります。

["大吉", "中吉", "吉", "小吉", "末吉", "凶", "凶", "凶", "凶"]
"大吉"

このように、ハイフン+半角スペースに続けて内容を書くことで、配列として扱ってくれるようになります。

配列の要素は入れ子にできます。また、ハッシュを要素にすることができるので、次のようにゲーム用のデータをかなり楽に記述することができます。

- :id: :goblin
  :name: ゴブリン
  :hp: 20
  :power: 5

- :id: :slime
  :name: スライム
  :hp: 10
  :power: 2

- :id: :orc
  :name: オーク
  :hp: 80
  :power: 12

これをRubyでロードし、そのまま出力すると次のようになります。

[{:id=>:goblin, :name=>"ゴブリン", :hp=>20, :power=>5}, {:id=>:slime, :name=>"スライム", :hp=>10, :power=>2}, {:id=>:orc, :name=>"オーク", :hp=>80, :power=>12}]

このように、ハッシュの配列として認識されます。

文字列の先頭にコロンを付けるとシンボル扱いになりますし、整数は整数として扱ってくれます。

もちろんハッシュの要素も入れ子にすることができます。

- :id: goblin
  :name: ゴブリン
  :hp: 20
  :power: 5
  :skill:
    - :name: 仲間を呼ぶ
      :text: call_troop(:goblin)
    - :name: 突撃
      :text: assault
[{:id=>"goblin", :name=>"ゴブリン", :hp=>20, :power=>5, :skill=>[{:name=>"仲間を呼ぶ", :text=>"call_troop(:goblin)"}, {:name=>"突撃", :text=>"assault"}]}]

このとき注意しなければならないのは、インデントをしっかり揃えるということです。どこかでズレるとエラーが出ます。

YAMLでこんなこともできる

オブジェクトとして読み込む

YAMLはオブジェクトとしてそのまま読み込むこともできます。これも見てもらった方が早いですね。

- !ruby/object:MonsterData
  :id: :goblin
  :name: ゴブリン
  :hp: 20
  :power: 5

- !ruby/object:MonsterData
  :id: :slime
  :name: スライム
  :hp: 10
  :power: 2

実行するコードの中に、対応するクラス(MonsterData)を用意しておきます。

require 'yaml'

class MonsterData
  def initialize
    @name = "monster"
    @skill = "skill"
  end
end

p YAML.load(File.open("./data.yml"))

実行結果はこんな感じ。

[#<MonsterData:0x007fb3b90e2418 @id=:goblin, @name="ゴブリン", @hp=20, @power=5>, #<MonsterData:0x007fb3b90e10e0 @id=:slime, @name="スライム", @hp=10, @power=2>]

YAMLからオブジェクトを生成したときはコンストラクタを通らないようですね。

データ内のデータを参照する

YAMLはアンカーとエイリアスの設定ができ、データ内の別のデータを参照することができます。

- !ruby/object:MonsterData
  &bigslime
  :id: :bigslime
  :name: ビッグスライム
  :hp: 100
  :power: 25

- !ruby/object:MonsterData
  &slime
  :id: :slime
  :name: スライム
  :hp: 10
  :power: 2
  :levelup: *bigslime
[#<MonsterData:0x007faf1306de38 @id=:bigslime, @name="ビッグスライム", @hp=100, @power=25>, #<MonsterData:0x007faf1306cab0 @id=:slime, @name="スライム", @hp=10, @power=2, @levelup=#<MonsterData:0x007faf1306de38 @id=:bigslime, @name="ビッグスライム", @hp=100, @power=25>>]

この場合、スライムのインスタンス変数levelupにビッグスライムが代入されています。

注意:YAMLは上から順に解釈するので、アンカーが先に記述されている必要があります。スライムとビッグスライムを入れ替えるとエラーが出ます。

まとめ

Rubyで扱う複雑な構造を持ったデータを記述するなら、YAMLがよいでしょう。

  • 見やすい
  • データの追加や修正がしやすい
  • データを読み込んだ後にRubyで処理がしやすい
  • プログラムが分からない人でもデータを追加・編集できる(ので、分担作業がしやすい)

DXRubyに全然関係無い内容になってしまいましたが、ゲームを作るとなるとデータの扱いはかなり重要になってくると思うので、皆さんも是非YAMLを使ってみてください。

参考

Rubyist Magazine - プログラマーのための YAML 入門 (初級編)
library yaml



明日はあおいたくさんの記事です。3回目の記事、お疲れ様です。
よろしくお願いします。