mieki256's diary



2013/12/19(木) [n年前の日記]

#1 [dxruby][game] DXRubyと疑似乱数

_DXRuby Advent Calendar 2013 の 19日目です。

17日、18日目の記事は、GameKazuさんの、 _DXRubyでRPGを作る_DXRubyでRPGを作る(2) でした。自分、RPGは作ったことが無いので、大変勉強になりました…。

今回は、「DXRubyと疑似乱数」というお題で書かせていただきます。個人的に、前から少し気になってた部分なので、せっかくだからこの機会を利用して検証させてもらおうかなと。

「疑似乱数? DXRubyと関係ないじゃん」と思われる方もおられましょうが。 という流れで、全く関係ない話ではないよね? と。そんなわけで、大目に見てもらえればと。

とりあえず。 この流れで話を進めていこうかと。中級者以上の方は、「あーハイハイ。知ってる知ってる」と、全部読み飛ばしてしまってOKですよ。

最初に謝っておきます。やたらと長い記事になってしまってゴメンナサイ…。

それでは、初心者向けの解説から。

Q. 疑似乱数って何? :

A. 要するに、サイコロです。どんな数が出てくるのか予測できない数。でたらめな数。それを乱数と言います。「乱れた数」と書きますが、つまり、そこに、ルールだの法則だのが無いように見える数、ということですね。

ただ、コンピュータ上の乱数は、パッと見では乱数に見えますが、真の乱数ではありませんので、疑似乱数と呼ばれています。

ふと思いましたが、「乱れた数」があるなら、「みだらな数」「淫数」もあるのでしょうか? 例えば、隠しコマンドを入れると残機数が「69機」になるとか…。うむ。これは「淫数」な気がする。…どうでもいいか。

Q. 疑似乱数ってゲームのどこで使われてるの? :

A. 思いつくのは…。以下のような場面で使われたりします。
  • 敵の動きや、発生位置を、乱数を使って決めてたり。
  • 爆発等のエフェクト種類や、初期位置、速度を、乱数を使って決めてたり。
  • サイコロを振る時に使ったり。
  • じゃんけんする時に使ったり。
  • カードやマージャン牌を、かき混ぜる時に使ったり。(でも、実は…ちゃんとかき混ぜてないんですけどね…)
まあ、ゲームによって、色々です。

Q. ゲームに使う疑似乱数って、何を使ってもいいの? :

A. 個人的な印象ですけど、疑似乱数をゲームに使う場合、以下の3つの条件は満たしてないとダメかなあ、と思ってます。まあ、作るゲームにもよりますけど。
  • 処理時間が短いこと。
  • 再現性があること。(疑似乱数の種が初期化できること)
  • 妙な周期性がないこと。

処理時間は…。乱数一つゲットするのに、何フレームもかかってたら、ゲームになりませんよね。ですので、とにかく一瞬で得られること。この条件は、絶対に外せません。

再現性は…。「初期化してから使えば、毎回、同じ並びの乱数が得られるか?」ということですね。プログラミング言語によっては、疑似乱数の初期化メソッドがそもそも存在しないものもありまして。その場合、当然再現性なんか期待できませんから、乱数生成器を自作することになります。

周期性は…。「似たような乱数の並びが繰り返し出てきちゃったりしないか?」ということです。

Q. 疑似乱数の再現性って、大事なの? :

A. ゲームの仕様によりますが、ほとんどの場合は、大事だと思います。

例えば。昔風の2Dゲームにおいて、乱数の再現性は、リプレイ機能、デモプレイ画面を作る時に、とても重要です。敵が、再現性のない乱数を使って動いていたら、その画面に入るたびに、全然違う動きになるわけで…。
  • 誰も居ない虚無の空間を、気でも触れたかのごとく、執拗に撃ち続けるプレイヤーキャラ。
  • そんな異常状態をスルーして、プレイヤーキャラを容赦なく殺害する、一介の雑魚敵達。
  • あっという間に残機ゼロ。わずか数秒で、デモプレイ画面終了。
  • 「…アレ? 今、一瞬、デモプレイ画面っぽいものが映ったような…気のせい?」
乱数の再現性が無いばかりに、画面の中は、そりゃもう大惨事さ! …まあ、昔のゲーセンでは、そんな大惨事のデモプレイ画面を、たまーに目にしましたが。見ていて胸が痛んだものです。 *1 *2

難易度調整や、バグチェックをする際にも、乱数の再現性が無いと苦労します。
  • プレイするたびに難易度がガラリと変わってしまう。(そこまで乱数に頼った作りをしちゃってる時点で大問題、という話も…)
  • プレイヤーキャラのワーク(変数)をいくら整えても、敵が同じ動きをしてくれなくて、最悪、何度やってもバグが再現できない。
これは地獄です…。今日も泊まり込み…。椅子寝かな…。体が臭い…。風呂入りたい…。

つまり、風呂に入れる生活をしたいなら、乱数の再現性は重要なのですね。…や、コレ、冗談めかして書いてるけど、結構マジで死活問題で。

まあ、別に乱数に限った話でもなくて。「動かすたびに全然違う結果が出てくるプログラム」では、えてして大惨事になりますので、コンピュータの世界では、再現性は重要なのですが。

Q. 疑似乱数の周期性って、あったらマズイの? :

A. ゲームによっては、致命的ですね…。製品回収にすらなってしまう場合も。以下の記事が参考になるかと。

_「カルドセプトサーガ」にダイス目が偶数と奇数を繰り返すバグ | スラッシュドット・ジャパン
_カルドセプトサーガの乱数問題 | ψ(プサイ)の興味関心空間

サイコロの目が、偶数→奇数→偶数→奇数と出てくる周期性が ―― つまり、乱数の最下位ビットが、必ず 0→1→0→1 を繰り返す、という話で…。次に出てくるサイコロの目が、そこそこ予想できちゃうのは、ちょっと厳しいですね。

もっともこのあたり、サイコロを使うゲームだから問題になったのであって、シューティングゲームやアクションゲームでは、あまり関係ないんじゃないか、という気もしますけど。でも、どうせなら、妙な周期性がないほうが、安心して使えますよね。

Q. DXRuby で使える疑似乱数って、何があるの? :

A. DXRuby は Ruby のライブラリですから…。Ruby が標準で持ってる疑似乱数を使うのが一般的でしょう。

調べてみたら、以下の2つの書き方があると知りました。
srand(整数) # 疑似乱数を初期化する(種を設定する)

rand(整数) # 0〜(整数-1)の範囲で、疑似乱数を得る。


※ 以下は、Ruby 1.8.7 では使えない。

r = Random.new(整数) # 疑似乱数を初期化する(種を設定する)

r.rand(整数) # 0〜(整数-1)の範囲で、疑似乱数を得る。
  • 整数を指定する場合は、rand() も Random#rand() も同じ処理をするらしいです。
  • 浮動小数点数(float)を指定する場合もありますが、今回はゲームに使えるかどうかで考えてますので、とりあえず整数の乱数が得られれば、なんとかなるかなと。
  • Random クラスは、Ruby 1.8.7 では使えません。ただ、Ruby 1.8.7 は、2013/06末でサポートが打ち切られましたので、これから使う人は居ないでしょうし、気にしなくていいかも?
自分は Ruby 初心者なので、このあたり自信ありませんで。間違ってたらツッコミ入れといてくださいです。

Q. じゃあ、Rubyの rand() を使えばいいんだね? :

A. さて、どうなんでしょうね…。自分が気になっていたのは、このあたりで。

上にも書きましたが。
  • 処理時間が短いこと。
  • 再現性があること。(疑似乱数の種が初期化できること)
  • 妙な周期性がないこと。
この条件を満たしていれば、安心して使える疑似乱数だろうと思います。加えて、
  • Rubyのバージョンによって、違う乱数が出てこないか。
  • rand() と Random#rand() で、処理時間が大きく違ったりしないか。
このあたりも、気になるところ。作ったゲームを Ruby 1.9.3 上で動かす分には遊べるのに、Ruby 2.0.0 になったら乱数の並びが変わっちゃって難易度ハネ上がった、なんて展開は困ります。

実はそのあたり、Rubyのドキュメントに、答えは掲載されているのですが…。

_class Random

でも、ホント? ホントにそうなの? ここがもし間違ってると、後で泣くのはこっちだぜ?

そんなわけで、一応自分の手元でも検証してみることにしたのでした。

再現性についての検証。 :

まずは再現性について、ざっくり検証してみます。

検証に使ったスクリプトソースとその結果は、随分と長くなってしまったので、この記事の一番最後に載せておきますね。

また、使った環境は、以下の通りです。
  • CPU : Intel Core i5 2500 (3.3GHz)
  • Windows7 x64
  • Ruby 1.8.7 mswin32版
  • Ruby 1.8.7 mingw32版
  • Ruby 1.9.3 mingw32版
  • Ruby 2.0.0 mingw32版

さて、検証結果ですが。
  • 再現性はある。
  • 少なくとも、Ruby 1.8.7、Ruby 1.9.3、Ruby 2.0.0 は、同じ乱数が得られる。
  • rand(整数) と Random#rand(整数) は、同じ乱数が得られる。
  • rand(整数) と Random#rand(整数) の実行時間は、どちらもほとんど同じ。
  • 処理時間も…まあ、おそらく、大丈夫そう。乱数を得る際に、めっちゃ時間がかかってるようには見えません。
良かった良かった。ここまでは、一安心。

余談ですが、ついでに以下のことも分かりました。
  • Ruby はバージョンが上がると、グングン処理速度が速くなってる。
  • mswin32版より、mingw32版のほうが、処理速度は速い。かもしれない。(Ruby 1.8.7しか検証してないので、Ruby 1.9以降は、違ってるかも?)

周期性について検証。 :

周期性も確認しておきましょう。

とりあえず、某ゲームの乱数と同様に、乱数の最下位ビットが、0→1→0→1 を繰り返してないか、画像にして眺めてみましょうか。ついでに、乱数の種も変えてみて、同じ状態が続かないか確認してしまいましょう。

ちなみに、画像生成には、DXRuby を使ってます。
# 乱数の最下位ビットを画面に描画してみる

require 'dxruby'

Window.resize(160,120)

# 画像を新規作成
img = Image.new(Window.width, Window.height)

# 乱数初期化の種
lst = [
       [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15],
       [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
      ]

# 乱数初期化の種を変えながら、画像に、乱数の最下位ビットを点として打つ
y = 0
lst.each do |dt|
  dt.each do |i|
    srand(i) # 乱数初期化
    puts "srand(#{i})"
    Window.width.times do |k|
      img[k, y] = ((rand(65536) & 0x01) == 0)? C_BLACK : C_WHITE
    end
    y += 1
  end
  y += 16
end

# 画像をファイル保存
img.save("rand_result.png", FORMAT_PNG)

Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(0, 0, img) # 画面に描画
end

結果画像は以下になりました。見づらいので4倍に拡大してあります。
最下位ビットの検証画像

0→1→0→1 の周期性があれば、綺麗な市松模様が出てくるはずですが…そうは見えませんね。どうやら、最下位ビットが妙な周期性を持ってるわけではなさそうです。

だけど、まだ不安なので、乱数を使ってひたすらドットを打ってみます。

「そんな検証をして意味あるの?」と言われそうですが、意味がある時もあったりします。と言うのも、以前、自分で乱数生成器を作った際、こういった感じの検証をしたら、整然と並んだ綺麗な模様が出現しまして。それってつまり、妙な周期性があるということで…。

まあ、その時は、ビットシフトして下位ビットを捨ててみたら、乱数っぽい状態になってくれたので、それでお茶濁ししてしまったのですけど。疑似乱数を作るアルゴリズムによっては、下位ビットが妙な周期性を持つ場合も多く、そんな時は、ビットシフトして下位ビットを捨ててしまう手も使われるのだそうです。もちろん、上位ビット分にも周期性がないか、検証する作業が必要になりますが。
# 乱数でドットを打ってみる

require 'dxruby'

# 画像を新規作成
img = Image.new(Window.width, Window.height, C_BLACK)

srand(1)
Window.height.times do |y|
	Window.width.times do |x|
		c = rand(256)
		img[x, y] = [c, c, c]
	end
end

# 画像をファイル保存
img.save("rand_result2.png", FORMAT_PNG)

Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  Window.draw(0, 0, img) # 画面に描画
end
ランダムにドットを打ってみた画像

整然と並んだ綺麗な模様は、別に出てきていませんよね…。どうやら大丈夫そうかな…。たぶん…。

Q. 結論としてどうなの? Ruby の乱数はゲーム制作に使えるの? :

A. 少なくとも、現時点では、安心して使えそうです。ただし、各シーンの、キリのいいタイミングで ―― 例えばステージ開始時の初期化処理中に、疑似乱数の種を初期化する、等をしてから使うことを忘れずに。

もっとも、コレ、ドキュメントに書いてある通りになっただけ、なんですけど…。まあ、自分で検証してその通りになりましたから、「よしっ! バッチグー!」ということで。

Ruby のバージョンが変わったら、また検証してみたほうがいいのかもしれません。もっとも、Ruby 1.8、1.9、2.0 と、結果は同じでしたから、今後も再現性は保証されるのではないかと予想しますけど。

そもそもゲームに乱数なんか使わないよ、という話。 :

ここまで、「ゲームに乱数を使う」という前提で話をしましたが。

実は、「ゲーム制作に乱数を使うべきではない」という主張もあります。以下の記事を読んでいただければ分かるかと。

_ゲーム作るのにまだ乱数使ってるの? - 2010-02-05 - ABAの日誌

要するに、「完璧なゲームデザインができているなら、乱数が入り込む隙など無いはずだ」という主張ですね。それはたしかに、そうかもしれない。自分も少し心当たりがあります。アクションゲームの類でも、敵の動きや、発生テーブルを作り込んでいくと、乱数を使う箇所がどんどん減っていく気がしますね…。 *3

とは言え。件の記事でも言及されていますが…。
  • プロトタイプをサクッと作ってる時に、なんとなく敵がそれっぽく動いてるようにしたいとか。
  • プレイヤーの必勝パターンができてしまうのを軽く防止したいとか。
等々、乱数使用が有効な場面もありますので、「絶対に乱数を使わないぞ!」と意固地になる必要もないだろうと自分は思います…。

DXRubyはゲーム制作以外にも使えますよ、という話。 :

上の方で、DXRubyを使って、検証結果を画像化したのですが。

DXRubyを使って、こういうことができるという点も、実は大事じゃないかと個人的には思っているのです。

「ゲーム制作ライブラリ」と銘打たれていると、「ゲーム制作? 俺には関係ないや」とスルーする方も多いと想像するのですが。それはおそらく早計で。

「ゲーム制作に使えるぐらいだから、こういうことだって軽くできちゃうはずだよな?」と思い直すことができれば、意外なところで、ソレを使って楽ができる…。そんな場面もあるだろうなと。

そして、それは、おそらく逆も成り立つのだろうと。「ゲーム制作には関係ないや」と思い込んでた、プログラミング言語やライブラリが、ゲーム制作を楽にしてくれる時だってあるはずだと。

そもそも、Rubyの作者様だって、Rubyを使って、グリグリ動くリアルタイムゲームが作れるなんて、夢にも思ってなかったはずですから…。DXRubyの存在自体が、「その発想は無かったわ」的事例の一つ、かもしれませんよね。

つまるところ、この記事は、検証作業に DXRuby を軽く使ってみることで、

「○○用と謳われていても、○○にしか使えないというわけじゃない」
「目の前の道具を、柔軟性を持って捉えることも、プログラマーには必要」
「そんな姿勢を心掛けていれば、いつかどこかで、その道具が自分に楽をさせてくれる、かもしれない」

―― そんな考え方を示すための、簡素な事例の一つとして書いてみたつもり、でもあるのでした。まあ、「立っている者は親でも使え」という話に過ぎませんが。

それでは、明日は、あおたくさんの _ゲーム作りました をお楽しみくださいませ。

関連資料。 :

疑似乱数、あるいは、Rubyで扱える乱数についての、解説ページも紹介しておきます。

_良い乱数・悪い乱数
_Ruby の標準乱数生成器とその改善案
_Rubyにおけるrand(乱数)の挙動について - yayuguのにっき

特に、乱数生成器を自分で実装する際には、 _良い乱数・悪い乱数 は必読ではないかなと個人的には思います。ありがたや。

再現性の検証に使ったソースと結果。 :

# 乱数の再現性を確認する

require 'benchmark'

n = 1024 * 1024 * 4 # 乱数を発生させる回数

lst_rand1 = Array.new(n)
lst_rand2 = Array.new(n)
lst_random1 = Array.new(n)
lst_random2 = Array.new(n)

# ベンチマークを取る
Benchmark.bm(14) do |x|

  unless /^1.8/ =~ RUBY_VERSION
    # Ruby 1.8.7 は Random クラスを持たないので
    # 処理をスキップする
    
    # 2回乱数を作ってみて、後で比較する
    r = Random.new(0) # 乱数を初期化
    n.times do |i|
      lst_random1[i] = r.rand(0x7fffffff)
    end
    
    r = Random.new(0) # 乱数を初期化
    n.times do |i|
      lst_random2[i] = r.rand(0x7fffffff)
    end

    tmp = 0
    x.report("random:") {
      n.times { tmp = rand(0x7fffffff) }
    }
    
    x.report("random:") {
      n.times { tmp = rand(0x7fffffff) }
    }
  end
  
  # 2回乱数を作ってみて、後で比較する
  srand(0) # 乱数を初期化
  n.times do |i|
    lst_rand1[i] = rand(0x7fffffff)
  end
  
  srand(0) # 乱数を初期化
  n.times do |i|
    lst_rand2[i] = rand(0x7fffffff)
  end

  tmp = 0
  x.report("rand:") {
    n.times { tmp = rand(0x7fffffff) }
  }

  x.report("rand:") {
    n.times { tmp = rand(0x7fffffff) }
  }

end

# 乱数を記録した配列の並びを比較してみる

if lst_rand1 == lst_rand2
  puts "rand は同じ並びです"
else
  puts "rand の並びは異なります"
end

unless /^1.8/ =~ RUBY_VERSION
  if lst_random1 == lst_random2
    puts "random は同じ並びです"
  else
    puts "random の並びは異なります"
  end

  if lst_rand1 == lst_random1
    puts "rand と random は同じ並びです"
  else
    puts "rand と random の並びは異なります"
  end
end

# バイナリファイルとして出力してみる

fn1 = "result_rand_" + RUBY_VERSION + ".bin"
File.open(fn1, 'w+b') do |file|
  file.write(lst_rand1.pack("N*"))
end

unless /^1.8/ =~ RUBY_VERSION
  fn2 = "result_random_" + RUBY_VERSION + ".bin"
  File.open(fn2, 'w+b') do |file|
    file.write(lst_random1.pack("N*"))
  end
end

結果はこうなりました。
> pik list
  187: ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mswin32]
  187: ruby 1.8.7 (2012-10-12 patchlevel 371) [i386-mingw32]
  192: ruby 1.9.2p290 (2011-07-09) [i386-mingw32]
* 193: ruby 1.9.3p484 (2013-11-22) [i386-mingw32]
  200: ruby 2.0.0p353 (2013-11-22) [i386-mingw32]

# ----------------------------------------
# Ruby 1.8.7 mswin32版とmingw32版を比較
#
# ※ Ruby 1.8.7 は、2013/06/30にサポート対象外になっている

> pik 187
Select which Ruby you want:
1. 187: ruby 1.8.7 (2012-10-12 patchlevel 371) [i386-mingw32]
2. 187: ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mswin32]
?  2

> ruby rand1.rb
                    user     system      total        real
rand:           4.305000   0.000000   4.305000 (  4.308247)
rand:           3.838000   0.000000   3.838000 (  3.845220)
rand は同じ並びです

> pik 187
Select which Ruby you want:
1. 187: ruby 1.8.7 (2012-10-12 patchlevel 371) [i386-mingw32]
2. 187: ruby 1.8.7 (2010-12-23 patchlevel 330) [i386-mswin32]
?  1

> ruby rand1.rb
                    user     system      total        real
rand:           3.915000   0.000000   3.915000 (  3.915224)
rand:           3.557000   0.000000   3.557000 (  3.558204)
rand は同じ並びです

# mswin32版よりmingw32版のほうが処理速度は速い

# ----------------------------------------
# Ruby 1.9.7 mingw32版で検証

> pik 193
> ruby rand1.rb
                     user     system      total        real
random:          2.356000   0.000000   2.356000 (  2.353134)
random:          2.356000   0.000000   2.356000 (  2.356135)
rand:            2.465000   0.000000   2.465000 (  2.451140)
rand:            2.449000   0.000000   2.449000 (  2.449140)
rand は同じ並びです
random は同じ並びです
rand と random は同じ並びです

# Ruby 1.8.7 より Ruby 1.9.3 のほうが2倍近く速い

# ----------------------------------------
# Ruby 2.0.0 mingw32版で検証

> pik 200
> ruby rand1.rb
                     user     system      total        real
random:          2.121000   0.000000   2.121000 (  2.114121)
random:          2.122000   0.000000   2.122000 (  2.123121)
rand:            2.246000   0.000000   2.246000 (  2.247129)
rand:            2.278000   0.000000   2.278000 (  2.266129)
rand は同じ並びです
random は同じ並びです
rand と random は同じ並びです

# Ruby 1.9.3 より Ruby 2.0.0 のほうが僅かに速い

# ----------------------------------------
# ファイル保存した乱数列を比較

> fc /b result_rand_1.8.7.bin result_rand_1.9.3.bin
ファイル result_rand_1.8.7.bin と RESULT_RAND_1.9.3.BIN を比較しています
FC: 相違点は検出されませんでした

> fc /b result_rand_1.8.7.bin result_rand_2.0.0.bin
ファイル result_rand_1.8.7.bin と RESULT_RAND_2.0.0.BIN を比較しています
FC: 相違点は検出されませんでした

> fc /b result_rand_1.8.7.bin result_random_1.9.3.bin
ファイル result_rand_1.8.7.bin と RESULT_RANDOM_1.9.3.BIN を比較しています
FC: 相違点は検出されませんでした

> fc /b result_rand_1.8.7.bin result_random_2.0.0.bin
ファイル result_rand_1.8.7.bin と RESULT_RANDOM_2.0.0.BIN を比較しています
FC: 相違点は検出されませんでした

# Ruby 1.8.7、Ruby 1.9.3、Ruby 2.0.0 の結果は同じ
# Kernel.#rand(整数) と Random#rand(整数) の結果は同じ

*1: もっとも、太古のTVゲームは、CPUのリフレッシュカウンタ等を読んで ―― ハードウェアで乱数を作っていたそうで。それでは再現性が得られなかったから、大惨事な画面も、仕方なかったのかもしれません。
*2: これは、60FPSで動作することが保障されてる、昔ながらの2Dゲームの場合の話でして。3Dゲームになると、可変フレームレートになるので、事情が変わってくるはずです。フレーム毎の、プレイヤーのコントローラ操作を再現しても、毎回フレームレートが同じではないため、ゲーム展開にずれが出てきます。ですから、おそらく、座標値や状態を記録して、リプレイやデモプレイを実現している場合が多いのではないかと想像していますが…。
*3: 例えばですが、プレイヤーとの間合い、プレイヤーの位置、プレイヤーの状態をチェックして、自分の動作を決定していく、そういう敵の動かし方もあるわけです。たしか、「ワンダと巨像」は、地面に、巨像の動きを決定するための判定マップを設定して動作を決定してた、という記事を読んだ記憶が…。そういう動かし方なら、乱数が入ってくる箇所も少なくなりそうですよね…。

#2 [game] プレイヤーキャラを斜め床で走らせる時のポーズ問題

最近ちょっと、モヤモヤ考えてしまう問題がありまして。ゲームのプレイヤーキャラを、斜め床の上で走らせる際のポーズって、どういうポーズが適切なのかなあ、と。

昔のTVゲーム機における、「ソニック」「ストライダー飛竜」等は、斜め床の上を走る時に、体が斜めになった…記憶があるのですけど。当時はソレを見て、不自然とは思わなかったのですが。

随分前に、Xbox 360で「ストライダー飛竜」の新作が出るぞー、てな話を見かけまして、興味津々でプレイデモ映像を見たのです。3DCGになった「ストライダー飛竜」、チョーカッコええなと。パーティクル飛ばしまくりやなと。見ていてシビレたのですが。

しかし、そこでも、プレイヤーキャラは、斜め床の上で体が斜めになっていて。「うーむ、不自然だ…」と思ってしまったのです。

件のソレは、3DCGになったから、パッと見はチョーリアルになってるわけですけど。斜め床の上では、見た目をそれほどリアルにできなかった、2Dゲーム時代の文法を踏襲してる。そこに自分はギャップを感じてしまったのでは、と思うのですけど。

さりとて、斜め床の上で、垂直(?)にして走らせるわけにもいかない。件のゲームは、斜め床の上を高速で走っていくシーンもあるわけですよ。そういう場面では、身体が斜めになってるほうが、どう考えてもカッコイイ。そういうシーンを成立させるためには、普段から、「斜め床の上では体が斜めになる」というルールを適用しておかないといかんよなと。

でも、パッと見、やっぱりなんだか不自然で。

たぶんこのあたり、 その違いも絡んでそうな気もしますけど。日本のソレって、歌舞伎みたいなところがあるよなと。リアルであることより、抽象化・デフォルメしたほうがカッコイイ、みたいな。

何にせよ、そんな流れで、「斜め床の上を走らせる時って、どういうポーズがいいんだろう?」と、たまに思い出したようにモヤモヤ考えてしまうのでした。

ちなみに、新作「ストライダー飛竜」は、そんな細かいところがどうでもよくなるぐらいに、全編が超絶カッコイイと思います。ちゃんと売れてほしいなあ…。自分、横スクロールアクションというジャンルが好きなので。

「カリ城」はお手本になりそう。 :

このあたりモヤモヤ考えてると、「ルパン三世 カリオストロの城」の、屋根の上をルパンが走るシーンを思い出すのです。…ここから先は、どなたかが話してたソレの受け売りのような気もしますけど。

あの一連のシーンには、キャラが斜め床の上を走る際の、ありえるポーズの全てが凝縮されてるよなと。それでいて、不自然な映像とは感じない。…いや、もちろん、その後の展開は「ありえねえだろ…」な漫画的展開ですけど。走ってる最中は、不自然ではないよなと。

なので、おそらく「カリ城」に何かヒントがある、ような気がするのです。

とは言っても。ゲームのソレはキャラのポーズが全て連続しているけれど。映画はモンタージュで解決してるところもあるから、そう単純ではないのかな。でも、ちょっとした「ヒント」ぐらいは、混じってる気もするなあ。

速度と絵柄。 :

「カリ城」を思い返すと、問題解決に使えそうな2つの要素がありそうだと思えてきたり。
  • ルパンは、速度に応じて、ポーズが変わってる。ゲームのソレも、速度で各ポーズを繋いでいけないか。
  • 「カリ城」はアニメ。アレが実写だったら、不自然で見ていられない、かもしれない。であれば、ゲームのソレも、リアル方向ではなく、アニメ方向の見た目・絵柄にすれば、印象が変わらないか。
ただ、この2つとも、問題がありそうで。
  • 体を斜めにすべき速度を判定するのって、結構難しそう。速度と体の角度を比例させれば済む、というわけでもないよなと。結局、「ここは斜めに立たせるべき」「ここは垂直に立たせるべき」の判定って、速度程度じゃ足りないだろうと。
  • せっかく3DCG使ってリアルに見せられる時代になったのに、昔の2Dゲームの絵柄に戻してどうするんだと。お客さんの食いつきが悪くならないか。
やってみたら、「あら、これでイケるわ」となるかもしれないけど、「やっぱりコレじゃアレだな」ともなりそうな。

まあ、このあたり、そのうち「カリ城」が再放送されたら、件のシーンをじっくり見直して考えてみよう、と…。これも、自分にとっての宿題・今後の課題ってことで。

#3 [game] 斜め床の上で立ち止まってる時のプレイヤーキャラの足の角度

上の記事を書いたついでに、このあたりの自分の記憶を整理するために、覚えてることを今のうちにとりとめなくメモ。

ファミコン版の「悪魔城ドラキュラ」は、斜め床がそもそも存在せず、階段しかなくて。しかし、階段を昇り降りする時は、一歩一歩踏みしめて、じわじわ動いていくので…。階段の上で立ち止まってる時も、階段の地形にピタリと一致した足の角度で。ファミコン版のくせして(?)、地味にそういうところで、見た目がリアル。

この文法は、PS1版のドラキュラXで破棄されます。斜め床の上をスッタカスッタカと滑るように走る仕様になり、階段も全部、斜め床の扱いになった。そして、斜め床の上でも、ソレ専用のグラフィックは用意してなかった…ような気がします。勘違いしてるかもしれないけど。 *1

「ストライダー飛竜」は、斜め床の上で立ち止まると、床の角度に合わせた足の角度になります。つまり、地形に合わせたソレ専用のグラフィックが、わざわざ用意されていて。…まあ、斜め床の上を歩くと体が斜めになる時点で、「地形に合わせたポーズでなきゃダメなんだ!」てなこだわりが感じられますが。昔のハードで体を斜めにするってことは、体が斜めになってるグラフィックをわざわざ持たなきゃいかんはずで。元がアーケードとは言え、そこまでやるか…。

「魂斗羅」MD版以降も、(ディレクターさんが「ストライダー飛竜」大好き人間だったので)斜め床の上で立ち止まると、ちゃんと斜め床に合わせたグラフィックになります。…SFC版はどうだったか自分の記憶は怪しいのですが、MD版とPS2版はそうだったはず。PS2版を見て、「おお…相変わらず、こだわってる…」と思った記憶が。

「ソニック1」は…斜め床の上で立ち止まっても、普段と同じ立ちポーズだったような? どうでしたっけか。足をピシッと閉じたポーズだから、どんな地形の上でも不自然にならなかった、そんな記憶がありますけど。最初のソレは容量が4Mbitだったから、そういうところでも地味に容量節約してたのかなと想像してみたり。

「マリオ」は…ファミコン版のマリオって、斜め床ありましたっけか? どうだったかな。たしか無かったですよね?

そんな感じで、2D横スクロールアクションゲームにおいて、「斜め床の有無」や「斜め床の上でプレイヤーキャラがどんなグラフィックになってるか」は、ゲームによって色々違っていて。そのあたり、自分の中では、どれが適切なのか、悩んじゃうのでした。

例えば、 _Flixel という2Dゲームライブラリは、地形管理クラスに、斜め床が無いんですけど。おそらく、マリオをイメージしながら作った2Dゲームライブラリは、そうなるんだろうなと。

でも、自分は、横スクロールアクションというと、「ストライダー飛竜」あたりを連想しちゃうので…。「この仕様で足りるの?」とか思っちゃって。でも、「そもそも斜め床って必要なのだろうか?」という疑問も湧いたりもして。

「2Dゲームにおける斜め床とはなんぞや?」てなあたりを、誰か考察してくれないものかしら。「斜め床は必要なのか否か?」「あると、ゲームの何が違ってくるのか?」みたいな。

スマホゲーム全盛のこの時代にそんなの考えてみてもな、というところもありますかね…。
*1: PS1版ドラキュラのメインスタッフは、PCE版ドラキュラのメインスタッフでもありますから、階段をじわじわ昇るソレが実装できないはずはなく。PCE版で既にやってますから、やろうと思えばできたはずで。なので、「このタイトルは斜め床があったほうが面白い」とか「今時、階段をじわじわ昇るのはいかがなものか」とか「マップがとにかく広いから、作業量やプレイ時間を考えたら階段じわじわはキツイ」等の判断をして、あえて文法を破棄したんじゃないのかな、と想像してますけど。

#4 [ruby] Ruby 1.9.3 を再インストールした

irb や pry の動作が怪しいあたりが気になったので、Ruby 1.9.3 p484 を再インストールした。環境は Windows7 x64。

既に入ってる版をアンインストール。 :

コントロールパネルから、アンインストール。以前のインストールフォルダはリネームしてバックアップ。

RubyInstallerをインストール。 :

_RubyInstaller for Windows から、rubyinstaller-1.9.3-p484.exe と DevKit-tdm-32-4.5.2-20111229-1559-sfx.exe をDL。

rubyinstaller-1.9.3-p484.exe を実行してインストール。今回は、C:\ruby193mingw にインストールした。

pik に登録。 :

一旦以前の版をリストから削除して、その後、インストールした版を登録。
pik remove 193
pik add C:\ruby193mingw\bin

pik は、Windows上でRubyの複数のバージョンを使うことができるツール。

DevKitをインストール。 :

  1. DevKit-tdm-32-4.5.2-20111229-1559-sfx.exe を実行して任意のフォルダに解凍。
  2. フォルダに入って、ruby dk.rb init を実行。
  3. config.yml を編集。インストールしたい Ruby の版だけ残して他はコメントアウト。
  4. ruby dk.rb install を実行。

gemをアップデート。 :

gem install rubygems-update
update_rubygems

ruby-debug-base19をインストール。 :

NetBeans 上でデバッガを使えるようにするためにインストール。
  • gem install linecache19
  • ruby-debug-base19-0.11.26.gem をDLしてくる。
  • gem install ruby-debug-base19-0.11.26.gem -- --with-ruby-include=C:\ruby193mingw\include\ruby-1.9.1\ruby-1.9.3-p484
  • gem install ruby-debug-ide19
  • gem install ruby-debug19
C:\ruby193mingw\lib\ruby\gems\1.9.1\gems\ruby-debug-ide19-0.4.12\lib\ruby-debug\xml_printer.rb に以下を追加。
class String
  def is_binary_data?
    ( self.count( "^ -~", "^\r\n" ).fdiv(self.size) > 0.3 || self.index( "\x00" ) ) unless empty?
  end
end

その他のライブラリをインストール。 :

gem install hoge で。

結局 irb や pry はどうなったかというと。 :

やっぱり固まる。なんでや。

Ubuntu Linux 上で試してみた。 :

VMware Player + Ubuntu 13.10 で、Ruby 関係のアレコレを色々インストールして、その上で irb や pry を使ったのだけど。これだと、固まらない。うーん。

ActiveScriptRuby 2.0.0をインストールしてみた。 :

こっちだと irb がするする動く。うむむ。mingw32版が問題なのか、自分がインストールした gem が問題なのか…。

ActiveScriptRuby 2.0.0 上では、pry はインストールしても動かなかった。win32console が無いよと言われる…。gem install win32console をしたら、エラーがたくさん出てきた。

ActiveScriptRuby 1.8.7 もインストールしてある環境なので、そちらでも irb を動かしてみた。コレもするする動く。むむむ。どういうことだろう。

とりあえず、普段 irb を使う場合は、ActiveScriptRuby 版を使うことにしようかな…。

#5 [dxruby] ActiveScriptRuby版は同梱のDXRubyじゃないと動かないのかな

ActiveScriptRuby 2.0.0 版に入ってる DXRuby のバージョンを確認したら ―― irb 上で require 'dxruby' 後に DXRuby::VERSION と打ったら 1.3.7dev と出てきた。結構古い…。

先日公開された、DXRuby 1.5.8dev版をインストールしようとしたら、「msvcrt-ruby200.dll が無いからプログラムを動かせないよ」とエラーダイアログが表示された。

なら、DXRuby 1.4.0 Ruby 2.0対応版はどうだろう。これも、インストールしようとしたら、同じエラーが出た。

どうやら、件の Ruby は、同梱されてる DXRuby じゃないと動かないらしい…。というか、おそらく DXRuby 公式サイトで配布されてるバイナリは、mingw32版でしか動かないのかもしれない。

なんだか環境を色々壊してしまったような気がしてきたので、とりあえず ActiveScriptRuby 2.0.0 版をアンインストールして、また再インストール。

#6 [ruby] Ruby/SDLとruby-openglをインストール

Ruby 1.9.3 p484 mingw32 版上にインストールしようと試みたのだけど、チラチラ問題が。

Rubyforge.orgに繋がらないのですが。 :

Ruby/SDL の Windows用バイナリをDLしようと思ったら、Rubyforge.org に繋がらず。困った。

「Windowsへのインストールは、バイナリを利用するのが楽」と公式サイトのドキュメントには書いてあるけど、そのバイナリが入手できないのでは…。

gem でインストール。 :

gem install rubysdl-mswin32-1.9 でインストールしても、手元のスクリプトが動いてくれず。

あ。「install_rubysdl.bat を実行せよ」とメッセージが表示されてることに、今気がつきました。

実行したら、エラーが出た。
> install_rubysdl.bat

C:/ruby193mingw/lib/ruby/gems/1.9.1/gems/rubysdl-mswin32-1.9-2.1.1.1/dll/install_rubysdl:7:in `<top (required)>': uninitialized
constant Gem::GemPathSearcher (NameError)
        from C:/ruby193mingw/bin/install_rubysdl:23:in `load'
        from C:/ruby193mingw/bin/install_rubysdl:23:in `<main>'
困ったな。

C:\ruby193mingw\lib\ruby\gems\1.9.1\gems\rubysdl-mswin32-1.9-2.1.1.1\ をDOS窓で開いて、ruby install_rubysdl.rb を実行してみたり。インストールできたっぽい。

ただ、このままだと、ruby-opengl と競合するはずだから…。 _2013/05/08の日記 を参考にして、opengl.so をリネームしたり等。

gem install opengl 後、gem install ruby-opengl を実行したら、ruby-opengl (0.61.0) が入ってくれた。

Ruby/SDL も ruby-opengl も動いてるように見える。これで大丈夫そうかな…。

まあ、Ruby/SDL は、Ruby 1.8〜1.9 上でしか動かないんだけど。

Ruby/SDLのドキュメントがグチャグチャ。 :

_File: README ・ Documentation for rubysdl (2.1.3) を眺めたら、ほとんどの文字が「???」になっていて。どういうことだろう…。

#7 [python] PyGameって開発終了してたのか…

_Pygameの後継らしいPySDL2をインストール - ばぐばぐわーるど を見て、PyGame が開発終了してたことを今頃知ったわけで。

_PySDL2 ってのが後継なのか…。入れてみるか…。

む。Python 2.7 か Python 3.2 以降じゃないと動かん、と書いてあるような。自分の環境は、gimp-python を動かす関係で、Python 2.6 がデフォルトなわけで。困った。

_#84 複数バージョンのPythonをインストールする << Python << a wandering wolf によると、バッチファイルを作成することで、複数のPythonを使い分けることもできなくもないらしい。であれば、試せるかな…。

PySDL2 のドキュメントを読むと、PyPy なるものも必要、というか、PyPyでも動かせる、と書いてあるように見える。名前だけは見かけてたけど、それもインストールしてみるか…。

PyPyをインストール。 :

PyPy というのは… Python が爆速になったもの、らしい。よく分からないけど。

とりあえずインストール。環境は Windows7 x64。

_PyPy - Download and install から、pypy-2.2.1-win32.zip をDLしてきた。解凍して、任意のフォルダにおいておく。

環境変数PATHが通ってるフォルダの中に、pypy.bat というバッチファイルを作ることにした。中身は以下のような感じ。
@echo off
C:\pypy\pypy.exe %1 %2 %3 %4 %5 %6 %7 %8 %9
とりあえず、DOS窓で、pypy と打ってみた。…なんだか python.exe を実行した時と同じ画面になった。exit() で終了。

_PyPyを試してみた @ Project Euler Problem 92 - matsulibの日記 で紹介されてるスクリプトを実行してみた。

うわ。Python 2.6 だとめちゃくちゃ待たされるのに、PyPy だとあっという間に処理が終わった。これはたしかにスゴイ…。

さておき、次は何をすれば…?

setuptools と pip をインストール。 :

なんだかよく分からないけど、setuptools と pip なるものが必要らしい。

_WindowsにPythonのツール: setuptools(easy_install), pipをインストールする | ユニキャストラボ を参考に作業。 _setuptools 2.0.1 : Python Package Index から、ez_setup.py なるファイルをDLして、
python ez_setup.py
を実行。なんか色々入った。これが setuptools だか easy_install だかなのだろうか。

easy_install pip
を実行。またなんか色々入った。これで pip がインストールされたのかな?

Python 2.6 でやったこの流れを、Python 2.7, 3.2 でも実行。環境変数PATHを書き換えて、その都度行ってみたり。

Python 2.7 と 3.2 をアップデートしたら面倒なことに。 :

Python 2.7 と 3.2 の新しい版があったので、ついでにアップデート。

やっかいなことになった。各Python をインストールしてたフォルダは、シンボリックリンクを使って、Cドライブにあるように見えながら実体はDドライブに置いていたのだけど。インストーラが、フォルダを削除、再作成をしたようで、CとDの両方に Python がある状態に。もうグチャグチャだー。

面倒臭くなったので、全部アンインストールして、もう一度再インストールすることに。モジュールの再インストール作業を考えると、気が重い。

ez_setup.pyがエラーを出す。 :

_Issue 9291: mimetypes initialization fails on Windows because of non-Latin characters in registry - Python tracker に書いてある症状と同じ。

_2013/12/19 Python-2.7.6がWindows環境でmimetypes.init()に失敗する場合がありsetuptoolsがインストールできない話 - 清水川Web に書いてあった状態になってた。レジストリの HKEY_CLASES_ROOT 以下に、日本語文字列のキーが…。
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\カート]
@="カート"

[HKEY_CLASSES_ROOT\カート\DefaultIcon]
@="C:\\Program Files (x86)\\FUJIFILM\\MyFinePix Studio\\MyFinePixStudio.exe,0"

[HKEY_CLASSES_ROOT\カート\shell]

[HKEY_CLASSES_ROOT\カート\shell\open]

[HKEY_CLASSES_ROOT\カート\shell\open\command]
@="C:\\Program Files (x86)\\FUJIFILM\\MyFinePix Studio\\MyFinePixStudio.exe \"%1\""

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\フォトブックファイル]
@="フォトブックファイル"

[HKEY_CLASSES_ROOT\フォトブックファイル\DefaultIcon]
@="C:\\Program Files (x86)\\FUJIFILM\\MyFinePix Studio\\MyFinePixStudio.exe,0"

[HKEY_CLASSES_ROOT\フォトブックファイル\shell]

[HKEY_CLASSES_ROOT\フォトブックファイル\shell\open]

[HKEY_CLASSES_ROOT\フォトブックファイル\shell\open\command]
@="C:\\Program Files (x86)\\FUJIFILM\\MyFinePix Studio\\MyFinePixStudio.exe \"%1\""

FUJIFILM製デジカメに付属してきたソフトをインストールしたときに作られてしまったものと思われます。

が、しかし、該当キーをバックアップ後削除してみても、やっぱりエラーが出る。うーん。

python-2.7.5.msi を _Python 2.7.5 Release からDLして上書きインストールしてみた。これならエラーが出ない、けど、動作が怪しくなりそうな。モジュールも含めて全てアンインストールして、Python 2.7.5 を再インストールすることに。助けて。

とりあえず PySDL2 をインストール。 :

PySDL2 も、 _Pygameの後継らしいPySDL2をインストール - ばぐばぐわーるど を参考に、インストールしてみた。

_Installing PySDL2 ・ PySDL2 0.7.0 documentation によると、SDL2 の他に、SDL2_image、SDL2_mixer、SDL2_ttf、SDL2_gfx もインストールしないといけないらしい。しかし、SDL2_gfx だけは、ソースのみの公開で。Windows用のバイナリは無いのかな…。ググってみても、「この SDL gfx ってバイナリがないぞ…」みたいな話しか出てこなくて。

とりあえず、SDL2_gfx 以外は全部入れてみた。 _Integrating PySDL2 ・ PySDL2 0.7.0 documentation に従って、環境変数 PYSDL2_DLL_PATH に、SDL2 関係の .dll が入ったフォルダを設定しておく。

おお。たしかにサンプルが動いた。と思ったけれど、DOS窓に、「今、ソフトウェアでレンダリングしてるよ」とメッセージが表示されてる。

ソフトウェアでレンダリングするのでは、PyGame と変わらず、描画が遅いのではあるまいか…。設定でどうにかできるのかもしれないけど、デフォルトでソフトウェア描画されてしまう仕様では、他の環境に持っていって動かしたときに、一々設定してもらう羽目になりそう。とは言え、最初からハードウェアを使って描画する設定だと、環境によってはそもそも起動しないとか、OSが落ちるとかありそうだし。

PySDL2 になったことで、ソースの書き方も、全然 Python らしくないノリになってるし。サンプルソースを見て萎えた人の気持ちも分かる。

現段階では、ちょっとビミョーなライブラリだなと思えてきたり。でも、もちろん、今後に期待。

GIMP 2.6 がおかしい。 :

Python 2.6.6 をインストールしたはずなので、GIMP 2.6 上で Pythonスクリプトが動くはず、と思ったのだけど、これが動かない。うーん。

以上、1 日分です。

過去ログ表示

Prev - 2013/12 - Next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project