Jewel-mmo開発日記

RubyでMMORPGを作る過程を記録する日記。 Yokohama.rb をよろしくお願いします。

いま改めて静的なブログシステムを採用してはどうか

新しいblog環境案について考える。

ふりかえり

いまのblog環境の不満点。

  • 重い
    • SinatraをCGIで動かしているから
  • システムを全然メンテできてない
  • 大量の写真を気軽に扱いたい

いいところとしては、

  • 記事の投稿が楽。
    • ローカルのdraft.txtに記事を書いて(複数エントリも書ける)、make updateするだけ

かな。いいところは他にもあるんだろうけど特には思い浮かばない。

静的か動的か

さて。

まず最大の不満点は重いことなのだけど、これはCGIで無理やり動かすから遅いわけで、ふつうにサーバー起動させれば負荷はほぼゼロにできる。しかし、今使っているさくらのプランではサーバー起動が禁止されているし、ブログのために新たなサーバー環境を用意するのもどうかなと思っている。

そもそもなんでblogを動的なシステムにしてるんだろうか。静的なシステムじゃだめだろうか。

静的なシステムでも、新しい記事をアップするたびに毎回ブログ全体を構築し直せば、今と同じUIを実現できるはず。動的でなければ成り立たない仕組みは今は使ってない。全体を再構築しなければならない理由は、「前の5件」で順々にページをめくる機能があるからだ。これはカテゴリ表示のときも同じ。

いっそのこと記事単体のhtmlとそのファイル名のリストをサーバー側に置いて、あとはブラウザ側のJavaScriptでUIを構築すればいいじゃんって考えたんだけど、そうするとJavaScriptが不十分な環境だと記事がひとつも読めなかったり、SEO的に問題がでそう…。

もろもろ含めて見えてくるのは、基本静的で一部JavaScriptを使って動的に見せるというラインかな。JavaScriptが動作しない環境では人間にとって便利な一部のUIがなくなる感じ。この場合CGIや動的な仕組みは一切なし。ただしrssの生成等にcronを使う。

静的なhtmlベースでの運用は、なんといっても安心感がある。運用コストも低い。

システムのメンテを楽にしたい

Sinatraにしてもそれなりに進化の速度が早いので、便利な半面、最新版追従の作業がしばしば発生する。

これも静的にしたい理由のひとつで、静的なhtmlの生成の方がシンプルで外部ライブラリへの依存度も減る。動的にhtmlを生成しないのであれば、haml等のテンプレートエンジンだけあればいい。

tumblrとか使ってみたら?

外部のblogサービスを使う手についても考えるんだけど、なかなか踏み切れない。

さがしあたってのネックは写真。新しいシステムでは、たくさんの写真を手軽に扱いたいのだけど、既存サービスの上に乗せようとすると容量その他で制約がでてきそう。

トラックバックやコメント機能

今のシステムでは最初からトラックバックを捨てている。今後もいらない。

コメント機能がほしいかは微妙なんだけど、メンテが面倒なのでない方がいいかな。返事を書くの苦手だし。 関連ツイートとかブックマークコメントを表示するブラウザ動作の機能ならあってもいいかもしれない。

permalink no tweet

oink雑談

ほぼ週に一度、株式会社oinkgamesの事務所でテーマを決めずに雑談をしている。 今日で4回目かな。

以下てきとーな議事ログ。

  • オレ遅刻
    • 手土産に3本のコーラ
    • 世界で売れるのはお茶じゃなくてコーラ
    • アメリカのお茶は甘い。炭酸入り
    • 「oinkはコーラじゃないですよ」
  • iPadの対戦ゲームでみんなで遊ぶ
    • 音と絵が合ってない?
    • 面白いけど売れてないんだろうなー
  • iPadウォーリーのゲーム
    • 対戦の画面の向きがダメ
  • iPad牽引ビームのゲーム
    • 慣れてくると面白そう
  • iPhoneアプリを売る
    • 面白いけどぜんぜん売れてなさそう
    • 日本のサイトは有名なゲームを紹介するけど、海外のサイトは「オレこんなのみつけたぜ」的なノリ
  • 子供用アプリを紹介するブログがない
    • ひどいアプリ。アイテム課金になっててエサとかを勝手に買えちゃう?
    • いや、15分とか過ぎれば認証がかかる
    • 設定自体によっては?
    • 海外の子供向けアプリはえげつない。のがある
    • 子供向けアプリに広告とか課金ボタンが出てほしくない

以下、iPadの話題かな。

  • よくできた子供向けアプリ
  • 今日の残念アプリ
    • 同期していない。可能性はある
    • はしに次のページボタンがあるといいのに
  • もりかわさんのアプリ
    • 間がいい
    • とてもよくできてる
  • 子供向けじゃないけど子どもが何時間でも遊べそうなアプリ
  • バランスを取るゲーム
    • ここできくちさん(でざいな)登場
    • 最長のハマり
    • 鳥がなあ。鳥がきついよ
    • 雪だるまが溶けてる!
    • こうなってきたら最後がみたい
    • おっさんでた!
permalink no tweet

RubyのFiberを使ってマイクロスレッドでタスクを管理する

@fum1h1ro の開発した社内スクリプト言語ではコルーチンが簡単に書ける。 この言語を使ってゲームを開発するようになって、ようやくこのコルーチンの便利さが理解できた。

RiteVMが出ることだし、RubyのFiberについて調べてみるとまさにこれ!ということがわかり、 ちょっと触ってみたのでメモしておく。

そもそもFiberがどんな時に便利なのかピンと来ない人も多いかもしれない。 オレ自身ごく最近までその便利さが理解できなかった。 だが、もはやこれがないと(ゲームの)プログラムが書けないと言っていいくらい必須なものとなりそうなのだ。

Fiber(コルーチン)が使えるのはこんなケースだ。

1回ボタンが押されると3発のミサイルが発射される仕様のゲームがあったとする。

  1. 1発目のミサイルはボタンを押した瞬間に発射される
  2. 2発目のミサイルはボタンを押した1秒後に発射される
  3. 3発目のミサイルはボタンを押した2秒後に発射される
  4. ボタンが押されてから3秒間はミサイルを発射することができない

今までならカウンタ変数を設けて、カウンタが一定値に達したことを条件としてミサイルの発射処理を実行していた。

ここでFiberを用いると上記のような処理を直接的かつシンプルに表現できる。

以下にFiberを使ったコードを具体的に示す。TaskクラスはFiberを継承したクラスでその小さな実装はあとで紹介する。

maintask = Task.new do
  is_ready = true
  is_over = false
  Task.create { Task.sleep 5; is_over = true }

  until is_over
    if is_ready #and pad_pushed?(:a)
      is_ready = false
      puts "fire1!"
      Task.create do
        Task.sleep 1
        puts "fire2!"
        Task.sleep 1
        puts "fire3!"
        Task.sleep 1
        is_ready = true
      end
    end
    Task.yield
  end

  Task.join
  Task.yield true
end

until maintask.resume
  sleep 0.1
  puts "."
end

タスクは親子関係を持っていて、タスクの中ではTask.createを用いていくつでも子タスクを作ることができる。 Task.yieldが呼ばれるとそこでいったん処理が中断し、 Task.sleepは与えられた秒数だけ処理を中断する。

パッドのボタンが押されたとき、ミサイルの発射処理を行うのは以下の部分だ。

      is_ready = false
      puts "fire1!"
      Task.create do
        Task.sleep 1
        puts "fire2!"
        Task.sleep 1
        puts "fire3!"
        Task.sleep 1
        is_ready = true
      end

簡単に説明すると、まず最初のミサイルを発射した後、新しいタスク(fiber) 作成している。 タスクの中では1秒待って次のミサイルを発射し、もう1秒待ってミサイルを発射し、もう1秒待ってミサイル発射準備を整えているわけだ。is_readyがtrueになるまではここに来ないので次のミサイルを発射することはできない。

ここで作られたタスクは裏で動くことになるので、Task.createメソッド自体はすぐに処理が終了して帰ってくる。

is_overがtrueになるとループが終了するが、 メインタスクが起動してから5秒後にis_overをtrueにするという処理も冒頭でタスク化している。

ちなみに、ミサイルの発射部分のタスクは次のようにそれぞれ別のタスクとして書くこともできる。

      Task.create { Task.sleep 1; puts "fire2!" }
      Task.create { Task.sleep 2; puts "fire3!" }
      Task.create { Task.sleep 3; is_ready = true }

このすれば、最初にパッドが押されたタイミングを基準として発射時間を管理することができる。

maintaskの最後に次の2行がある。

  Task.join
  Task.yield true

joinはタスクの終了を待つメソッドだ。引数を省略すると子スレッドすべての終了を待つ。 最後でTask.yieldにtrueを渡しているのはmaintask.resumeの戻り値をプログラムの終了条件としているためだ。

Fiberを使って処理を書いていくといろんなものがタスク化出来ることに気づく。 きっと発射されたミサイルもそれぞれが独立したタスクとして管理されることだろう。

このように少なくともゲームにおけるキャラクター操作、そしてエフェクト表示やサウンド再生のタイミングなどは、Fiberを用いることでとてもシンプルに管理できるようになるのだ。

RiteVMがでればゲームにRubyを組み込むことはこれまでとは比較にならないほど容易になるだろう。 Rubyで本格的なゲームを作れる日は近いはず。

最後に上記コードを走らせるのに必要なTaskクラスの実装を載せておく。 joinとjoinに関連した部分で長くなっているが、それでも50行ほどの小さなコードだ。

class Task < Fiber
  @@current_task = nil
  attr_accessor :tasks, :status

  def initialize(*args)
    super
    @tasks = []
    @status = :run
  end

  def alive?
    @status
  end

  def resume(*args)
    @@current_task = self
    r = super(self)
    @@current_task = nil
    dead_tasks = []
    @tasks.each do |t|
      begin
        t.resume(t)
      rescue FiberError
        t.status = nil
        dead_tasks << t
      end
    end
    @tasks -= dead_tasks
    r
  end

  def create_task(*args, &block)
    @tasks << Task.new(*args, &block)
  end

  def join(*args)
    args = tasks if args.empty?
    Task.yield while args.flatten.any?(&:alive?)
  end

  def self.create(*args, &block)
    @@current_task.create_task(*args, &block)
  end

  def self.join(*args)
    @@current_task.join(*args)
  end

  def self.sleep(sec)
    t = Time.now
    self.yield while Time.now - t < sec
  end
end

ツッコミは @dan5ya まで。

permalink no tweet

組み込みスクリプト言語とRiteVM勉強会開催のお知らせ

今週の土曜日の夕方から組み込みスクリプト言語の勉強会をやります。

同僚の @fum1h1ro が自社のゲーム開発用に開発したスクリプト言語の実装や開発に至った経緯について発表してくれる予定です。 この社内スクリプト言語は、JavaSriptぽい(ハッシュとかないのでもっとシンプル)言語で、コンパイルにはRaccを使い、VMはCで実装しています。

後半は「RiteVM勉強会」をやりたいと思います。 今年公開されると期待される組み込みRubyについて思いを巡らせながら想像で勉強してみたり、どんな環境で動いて欲しいかの要望をMatzに送るべくまとめてみたりする予定です。

場所はいつもYokohama.rbでも使っている東神奈川の地区センターの和室です。

よろしくお願いします。

permalink no tweet

Rubyのmapメソッドの使い方

(この記事はRuby Advent Calendar jp: 2010 : ATNDの6日目です。前日はokkezさんでした。)

Rubyにはmapメソッドというたいへん便利なメソッドがあります。

配列aにはいくつかの文字列が入ってるとします。

a = ["a", "abc", "abcdef"]

この文字列を次のように右寄せで表示したいとき、どう書けばいいでしょう。

     a
   abc
abcdef

いろんなやり方がありそうすが、ここではmapメソッドを使った例を紹介すると同時に mapメソッドの使い方について詳しく解説します。

まずeachメソッドで書いてみる

まずその前に。

mapメソッドを使った書き方を紹介する前により易しいeachメソッドを使って書いてみます。

a = ["a", "abc", "abcdef"]
l = 6
a.each {|e| puts e.rjust(l) }

実行結果

     a
   abc
abcdef

eachメソッドで配列の値をひとつひとつ順番に取り出してputsしています。 配列から取り出した値は変数eに代入され、要素数だけブロックが繰り返し実行されます。 (eはただの変数名です。別にeでなくても任意の好きな名前をつけることができます。)

文字列をレシーバにしてrjustメソッドを呼び出すと、rjustメソッドは文字列を右詰にして返します(左の余白はスペースで埋められます)。右詰めしたあとの文字列の長さは引数で渡しています。

"a".rjust(6) #=> "     a"

rjustメソッドの挙動は上記のような感じです。

同じ処理をCやJavaで書くと、このeachの例のように繰り返しを使うことが多いのではないでしょうか。

mapメソッドを使ってみる

さてここからが本番です。

mapメソッドを用いた書き方を紹介します。

a = ["a", "abc", "abcdef"]
l = 6
a = a.map {|e| e.rjust(l) }
puts a

実行結果

     a
   abc
abcdef

配列の内容が変数eに順々に入ってくるのはeachのときと同じですが、 mapメソッドは戻り値で配列を返します。

上の例でmapメソッドが返す配列は、もとの配列aの要素それぞれにrjustメソッドを適用した結果になります。 具体的には配列aの中の文字列は、それぞれ次のように置き換えられて、その結果が新しい配列として作られます。

"a"      → "     a"
"abc"    → "   abc"
"abcdef" → "abcdef"

上の例ではmapメソッドの戻り値を改めてaに代入して全体を新しい配列に置き換えていますが、 ここでmap!メソッドを使えば、もとの配列aを直接書き換えてしまうこともできます。

a = ["a", "abc", "abcdef"]
l = 6
a.map! {|e| e.rjust(l) }
puts a

map!は、レシーバの内容を変更してしまう破壊的なメソッドです。

(おっと、説明を忘れていましたが、putsメソッドは引数に配列を渡すと要素それぞれを改行しながら表示してくれます。putsに渡すとき「a.join(\n)」として配列をひとつの文字列に変換してもいいのですが、幸いその必要がありません。)

実はaを置き換えなくても次のようにmapの戻り値を直接putsの引数にすることも可能です。

puts a.map {|e| e.rjust(l) }

(こうすると、aは置き換えられることなく最初の内容が保たれます。)

慣れないうちは結合の優先順位をイメージしにくいかもしれません。参考までに省略されているカッコを書くとこうなります。

puts(a.map {|e| e.rjust(l) })

もっとmapメソッドを使ってみる

さっきのコードでは、文字列の長さを6と決めて右詰の処理を行っていました。

l = 6

この6という数値は、配列の中に含まれる文字列の最も長い文字列の長さを表したものです。

ここでもmapメソッドを使えば最も長い文字列の長さを簡単に求めることができます。

a = ["a", "abc", "abcdef"]
b = a.map {|e| e.length }
l = b.max
puts l #=> 6が表示される

aの要素に対してlengthメソッドを適用した結果をbに代入しています。 bは次のような配列になります。

[1, 3, 6]

maxメソッドは配列の中から最も大きい値を探し出して返してくれます。

[1, 3, 6].max #=> 6

なお、上の例は変数bを省略して次のように書くこともできます。

l = a.map {|e| e.length }.max

まとめ

まとめます。

a = ["a", "abc", "abcdef"]

上記のようにaが文字列の配列だった場合、mapを使うと…

l = a.map {|e| e.length }.max
puts a.map {|e| e.rjust(l) }

このような2行で、当初の目的を達成することができます。 特に1行目の最も長い文字列の文字数を求めるところでは、eachよりmapを使ったほうがすっきり書けます。

mapメソッドはとっても便利でかつ使えるケースが多いので、ぜひともこの機会に覚えてください!

permalink no tweet