Jewel-mmo開発日記

RubyでMMORPGを作る過程を記録する日記。

RubyのtimesとかeachをC言語のマクロで実装してみた

Rubyのtimes的なことをするのにいちいちfor文を書かなくていいようにならないかなと考えてみた。 ついでにeachとeach_with_indexも。

#include <stdio.h>

#define TIMES_DO(n, i) { int i; for(i = 0; i < n; i++) {
#define EACH_DO(a, e) { int __i__; typeof(a[0])* e; for(__i__ = 0; __i__ < sizeof(a) / sizeof(a[0]); __i__++) { e = &a[__i__];
#define EACH_WITH_INDEX_DO(a, e, i) { int i; typeof(a[0])* e; for(i = 0; i < sizeof(a) / sizeof(a[0]); i++) { e = &a[i];
#define END } }

int main(void)
{   
    char array[][8] = { "hello", "hi!", "bye" };

    TIMES_DO(5, i)
        printf("%d\n", i);
    END

    EACH_DO(array, e)
        printf("%s\n", *e);
    END

    EACH_WITH_INDEX_DO(array, e, i)
        printf("%d: %s\n", i, *e);
    END

    return 0;
}

実行結果

0
1
2
3
4
hello
hi!
bye
0: hello
1: hi!
2: bye

EACHの方はtypeofを使ってるのでgcc依存になってしまうみたい。

C++ならテンプレートを使ってtypeofをなくすことができそう。

permalink
category: Ruby(91)

MyGameで書いた3Dぽいテニスゲームのプロトタイプ

よくあるタイプのテニスゲームで座標を3Dで管理してる。 カメラを設けてシンプルに3Dを管理するという原理はこれと同じ。

Rubyでやりたい3D制御をイメージして書いたコードだったと思う。 たぶん3年くらい前に書いたコード。 当時は動かしながら書いていたけど、今はどうかな。動くかどうかは試してない。動かない可能性が高いと思う。

DxRubyが3Dに対応しつつあるようなので、もしかしたら何かの参考になるかもしれないと思って古いコードを引っ張りだしてみた。

# tennis.rb
require 'mygame/boot'
require 'tennis_helper'

COURT_H = 1189
COURT_W = 1097

class Player < Character
  image_resource 'images/player.bmp'
end

class Net < Character
  image_resource 'images/net.bmp'
end

class BallShadow < Character
  image_resource 'images/ball_shadow.bmp'
  attr_accessor :parent
  def update
    position.x = parent.position.x
    position.z = parent.position.z
  end
end

class Ball < Character
  image_resource 'images/ball.bmp'
  def initialize
    super
    start
  end

  def start
    @position = Vector3D[400, -200, 1300]
    @v = Vector3D[-4 - rand(6), -16 + rand(4), -10 - rand(20)]
    #@position *= [-1, 1, -1]; @v *= [-1, 1, -1]
  end

  def update
    @v.y += 0.5
    @position += @v
    if position.y > -10
      position.y = -10
      e = 0.7
      @v *= [e, -e, e]
    end
    if position.z.abs > 1500 or @v.z.abs < 0.1
      start
    end
  end
end

camera = Camera.new
players = Array.new(4) {|i|
  a = Player.new
  a.position.x = i % 2 == 0 ? -COURT_W / 2 : COURT_W / 2
  a.position.z = i / 2 == 0 ? -COURT_H : COURT_H
  a
}
players = []
net = Net.new
ball = Ball.new
ball_shadow = BallShadow.new
ball_shadow.parent = ball
bg = Image.new('images/bg.bmp')

MyGame.background_color = nil
g = Ground.new(camera)
main_loop(60) do
  [ball, ball_shadow].each {|e| e.update }
  bg.render
  camera.render(players, net, ball_shadow, ball)
  g.render
end

2Dの絵を使うのでcameraの向きが固定になっている(背景も2Dなので視点はほぼ固定)。なのでcameraに注視点の指定やアングルの指定はない。 cameraに注視点またはアングルが指定できれば、上記スクリプトで一応ぐりぐり回るはずだ(もちろんその場合は2D画像でなく3Dモデルを読み込む必要がある)。

当時何をやりたかったかと言うと、こんな風に書けるライブラリがあれば3Dのプログラミングも全然難しくないでしょって言いたかったんだと思う。

上記ソースが使うヘルパの実装もはっておく。

# tennis_helper.rb
class Vector3D < Array
  %w(x y z).each_with_index do |e, i|
    eval "def #{e} ; self[#{i}] ; end"
    eval "def #{e}=a ; self[#{i}] = a ; end"
  end
  %w(+ - * /).each do |e|
    eval "def #{e}d
            Vector3D[self[0] #{e} d[0], self[1] #{e} d[1], self[2] #{e} d[2]]
          end"
  end

  include Math
  def rotate_zyx(angle)
    ax, ay, az = angle
    px, py, pz = x, y, z
    px, py = px * cos(az) - py * sin(az), px * sin(az) + py * cos(az)
    pz, px = pz * cos(ay) - px * sin(ay), pz * sin(ay) + px * cos(ay)
    py, pz = py * cos(ax) - pz * sin(ax), py * sin(ax) + pz * cos(ax)
    Vector3D[px, py, pz]
  end
end

class Camera
  attr_accessor :position, :angle, :scale, :screen_z
  def initialize
    @position = Vector3D[0, -200, -2000]
    @angle = Vector3D[Math::PI / 20, 0, 0]
    @screen_z = 800.0
    @scale = 1.0
  end

  def to_screen(vector3d)
    temp = (vector3d - position).rotate_zyx(angle)
    perse = screen_z / temp.z
    [temp.x * perse + screen.w / 2, temp.y * perse + screen.h / 2, perse * @scale]
  end

  def render(*objects)
    objects.to_a.flatten.each do |e|
      e.view.x, e.view.y, e.view.scale = to_screen(e.position)
      e.render
    end
  end
end

class Character
  attr_accessor :position
  attr_reader :view
  def self.image_resource(fname)
    class_eval "def image_file_name; '#{fname}'; end"
  end

  def initialize
    @view = TransparentImage.new(image_file_name)
    @position = Vector3D[0, 0, 0]
  end

  def update
    @view.update
  end

  def render
    @view.render
  end
end

class Ground
  def initialize(camera)
    @camera = camera
    @tops = [
      Vector3D[-1000, 0, -1000],
      Vector3D[ 1000, 0, -1000],
      Vector3D[ 1000, 0,  1000],
      Vector3D[-1000, 0,  1000],
    ]
  end

  def render
    tops_2d = @tops.map {|top| @camera.to_screen(top) }
    # @view.render ...
  end
end
permalink

Advent Calendarプラグイン作ったよ!

(Ruby Advent Calendar jp: 2009の15日目! 昨日はid:m-kawatoさんでした。 明日はid:ku-ma-meさんです。)

Bilboのプラグインとして実装したよ。こんな感じ↓(RSSリーダからだとCSSがあたらないかも)。

※カレンダー単体のページはこっち

このカレンダーはBilboのプラグインとして動作するんだけど、 実は純粋なSinatraスクリプトとしてもそのまま動作するようになってるよ。 ソースはここ

gem install sinatra haml
wget 'http://github.com/dan5/bilbo/raw/master/plugins/testing/adventcalendar.rb'
ruby adventcalendar.rb

上記手順で動くよ。サーバーが起動したら /adventcalendar/2009 にアクセス!

テンプレートやBilboプラグイン化のところやATNDページパースしてリンクを引っ張ってくる実装を合わせても40数行しなかくて、Sinatraどんだけっ! (Sinatraについては公式のREADMEがとてもわかりやすいよ!)

おまけ

ところで、みんなBilboのことは知らないよね?

BilboはSinatraをベースにしたシンプルなブログシステムで ここのブログはBilboで動いているんだよ。 今日に備えて気合入れて説明を書いておいたから興味のある人は見てね!

ちなみにBilboは以下の手順でインストール&起動できるよ! (要gemとgit。)

gem install sinatra haml
git clone git://github.com/dan5/bilbo.git bilbo
cd bilbo
ruby setup.rb boot && cd boot
./server

Bilboユーザ募集!

permalink
category: Ruby(91)
no tweet

ruby1.8.7から1.9.1に移行したときのメモ(主にM17Nまわりの対応)

追記(2009/09/02): 記事中の「shift_jis」を「Windows-31J」に置き換え。私が普段WindowsXPで「Shift_JIS」と思い込んで使っていた文字コードは「Windows-31J」だったようです。コメントでご指摘いただきました。

今までもそれなりに1.9系を使ってたけど、そろそろ本格的に移行する時期かなと思い作業中。 (ちなみにこのブログはだいぶ前から1.9.1で動かしてる。)

日本語まわりで思いのほか苦労したのでメモしておく。 特にyamlとerb(コマンド)ではまった。

今回の環境ではruby1.8.7を以下のような用途に使っていた。 OSはWindowsXP。

フェーズ1

  1. Excelをwin32oleを使って読み込んで、シート全体を2次元配列に突っ込む
  2. 上記の2次元配列をyamlにしてテキストファイルとして保存

フェーズ2

またフェーズ1で生成をしたyamlファイルを次のように処理する。

  • rubyスクリプトから上記yamlを読み込んでC言語のコード(主にテーブルデータ)をジェネレート
  • erbコマンドを使ってeRubyスクリプトを実行し、この中で上記yamlを読み込んでCコードの断片をジェネレートして埋め込む

フェーズ1、フェーズ2共にrakeで自動化している。 また、扱う文字コードはすべてShift_JIS(SJIS) Windows-31Jである。 RaikfileもSJIS Windows-31Jで記述していた(1.8.7では起動batに多少手を入れている)。

全部SJIS Windows-31Jなのでそんなに苦労しないかと思ったら、全然そんなことなかった。

移行作業その1 〜マジックコメントの追加〜

まず日本語が記述されているrubyスクリプトの先頭にマジックコメントを追加する。 これは単純な話で、日本語はすべてSJIS Windows-31Jを使っているから、SJIS Windows-31Jが記述されているスクリプトの先頭行に以下を追加するだけ。

# -*- coding: Windows-31J -*-

RakefileにもSJIS Windows-31Jを記述しているので、忘れずに上記を追記しておく。 ここは特に問題ない。

移行作業その2 〜「-K」オプションを削除〜

これまで、SJIS Windows-31Jを用いるスクリプトはすべて「-Ks」オプションをつけて実行していたが、 「-K」オプションは1.9でも一応使えるものの非推奨ということなので、一切使用しないことにする。

Rakefileの中でrubyを起動する際に「-Ks」としているところはそれをすべて削除し、スクリプト先頭で、

#!ruby -Ks

としているところもこの行を削除してしまう。 ここの修正も特も問題はない。

移行作業その3 〜erbコマンドへの対応〜

ここで少しはまった。

erbコマンドにも「-Ks」を与えていたのだが、1.9.1のerbコマンドには「-K」オプションがないらしい。

どうやら次の行をeRubyスクリプトの先頭に書き加えることで、マジックコメントと同じ効果が得られるらしい。

<%# -*- coding: Windows-31J -*- %>

うーん、なるほど。 というわけで、上記をeRubyスクリプトに加えておく。

移行作業その4 〜YAML::loadした中身をforce_encodingする〜

ここもはまった。

SJISの文字列をyaml化してloadし直すとエンコーディングがUTF-8になるのだ。

# -*- coding: Windows-31J -*-
require 'yaml'
str = "あいうえお"
p YAML::load(str.to_yaml).encoding                    #=> #<Encoding:UTF-8>

原因ははっきりわからないけど、yaml化した時点でencodingがなくなっちゃうのかな?

いろいろ試してみた結果は以下。

# -*- coding: Windows-31J -*-
require 'yaml'
str = "あいうえお"
p str                                                 #=> "あいうえお"
p str.encoding                                        #=> #<Encoding:Windows-31J>
p str.to_yaml.encoding                                #=> #<Encoding:ASCII-8BIT
p YAML::load(str.to_yaml)                             #=> "\x82\xA0\x82\xA2\x82
p YAML::load(str.to_yaml).encoding                    #=> #<Encoding:UTF-8>
p YAML::load(str.to_yaml).force_encoding('Shift_JIS') #=> "あいうえお"

バグの原因に気づくまで時間がかかったが、 YAML::loadで読み込んだ文字列に対してすべて force_encoding('Shift_JIS') force_encoding('Windows-31J')を実行することで解決できた。

移行作業その5 〜機種依存文字を用いたファイル名〜

追記(2009/09/02): マジックコメントの「shift_jis」を「Windows-31J」に置き換えたら次の問題は発生しなくなりました。

ここまでの対応でおおむねうまく動いたのだが、 特定のファイルをFile.openしようとすると次のエラーが発生した。

in `initialize': "\x87@" from Shift_JIS to UTF-8 in conversion
from Shift_JIS to Windows-31J (Encoding::UndefinedConversionError)

ファイル名にWindows依存文字の「まる1」が使用されているところでひっかかっているらしい。

# -*- coding: shift_jis -*-
File.open('まる1.txt') # エラーになる

ファイル名にそんな文字使うなという話かもしれないけど、 現実にそういうファイル名を作り出す人がいるのだから仕方ない。

とはいえ解決方法がわからないので、 とりあえず、まずい文字を含むファイル名はあらかじめFileUtils.mvで安全な名前にリネームすることにした。

まとめ

今回はyamlとerbを使っていたので、ちょっとはまったけど、これらを使ってなければ割と楽にいけたのかも。

ちなみにエンコーディング絡み以外ではスクリプトを直す必要はひとつもなかった。 これは以前から1.9のコードを意識してたせいかな(最近は意識してなかったんだけど、自然と両対応のコードを書くようになっていたみたい)。 まあエンコーディング以外でははまりどころが少ないということかもね。

1.9.1だとRakefileに普通に日本語が書けたりするのがうれしいね!

permalink
category: Ruby(91)
no tweet

DXRubyについて思うこと

開発の速度や変化が速くてなかなか追いきれない。 DXRuby上でMyGameを動かしてくれいたり、その他たくさんの面白い試みに挑戦しているよう。 それぞれの挑戦に言及したいと思いながらも、スピードに追いつけなかったり、 内容がまとまらなかったりでなかなか意見を書けないでいる。

のだけど、このままだと何にも書けなそうなので、以下少し触ってみた感想や思うところを書き出してみる。

  • プラットフォームをWindowsに限定して扱いやすさや速度を追求するのはいいと思う
  • 特に速度面の追及は楽しみ。今の方向性は正しいと思うし実現できると思う
  • 描画周りは当然Cで実装するとして、ポイントは大量の描画物の扱いをCで実装しつつどれだけ柔軟にRuby側から制御できるようにするか
  • DXRubyを使ったMyGame実装ではDXRubyの速度を生かしきれないと思う
    • のであまりメリットが見えない
    • MyGameは初心者が理解しやすいよう意図的にかなり単純化したAPIにしてあり、その辺DXRubyのコンセプトと被っているのでは
    • ただしMyGameのAPIに手を入れれば、DXRubyで実装することで大幅な高速化が見込めるかもしれない
  • 個人的にDXRuby(DirectXか)が動くPCを持っていないのがちょっと辛い(最近新しく買ったPCはMacだし…)
  • 2D限定にしないで3Dをサポートしてくれたらすごーくうれしい
    • 大量の2Dオブジェクトを動かすより、大量の3Dオブジェクトを動かしたほうがインパクトがあるしインパクトの割りに実装の無理が少ないと思う

今現在DXRubyFrameworkと呼ばれている、 大量のスプライトを扱うフレームワークがポイントじゃないかな。 これを本体に組み込んでこの機能をメインに売り出すといいと思うのだけど。

さらに同じ要領で3Dモデルを扱えるようになってくれるとうれしい。 やっぱり周囲をあっと驚かせることが目標なら3Dじゃないかな。

permalink
category: Ruby(91)
no tweet