2016/12/22(木) [n年前の日記]
#1 [ruby][dxruby] 疑似3Dの初歩の基礎を(Ruby+DXRubyを使いつつ)書いてみる
_Ruby Game Developing Advent Calendar 2016
の12/19が空欄だったので、「何も入ってないよりは何か入ってたほうがええんじゃないか…」的に、この記事を登録してみました。「Rubyでゲーム制作」というテーマから全然ずれてる内容だよなと思わないでもないですが、DXRubyを使って実験してるし…いいよね? ダメ? Advent Calendar って後からこうしてアップ・登録してもいいのかどうかも分かりませんが…。
さておき。なんとなく、2Dゲームにおける疑似3Dの初歩というか基礎をメモしておこうかなと思い立ったりして。
いやまあ、「あらゆるゲームが3DCGで描画されてるこの御時勢にwww疑似3Dってwwwちょおまwww」と笑われそうな気もしますが。なんかその手の記事をぼんやり眺めてたら、式を求めるあたりで「三角形の相似」の一言で終わらせてる事例が結構あって。「説明がちょっと足りてないんじゃ」「図解しとけばすぐ分かるのに」と思えてきたので、せめてそこだけでもメモしておこうかなと。
疑似3Dと言っても、一般的には…。内部では x,y,z の3次元座標で管理していて、描画に使える何かしらが当時はスプライトかBGしかなかったからスプライト or BGで描画、みたいなことをやってたんだろうと思うわけですよ。もっとも中にはコレ全然3D計算してないだろうなって思えるアレなタイトルもありますが…。それっぽく見えてたタイトルは、内部では3次元座標で管理して、ということを多少はしてただろうと。
で、3次元空間に点があるとして、その点は画面上のどのへんに描かれるのか、ソレさえ分かれば疑似3Dはできるのですが。ソレを求める処理というか、図法のことを、 _透視投影 と呼ぶわけで。
まあ、近くのモノも遠くのモノも同じ大きさに見える _平行投影 てのもあるし、平行投影を使ってるゲームタイトルもありますが、ソレは奥行き情報であるz座標値を無視して描画すれば済んじゃうから横に置いとくとして。
透視投影をしたいなー、と思った時、実際どういう計算式になるかは、下の図を見てもらえば分かるかなと。ちなみに、画像をクリックすれば原寸大で見れます。
分かりやすくするために、見ている人の目の位置を原点(0,0,0)として考えてます。また、この図では画面上のy座標しか求めてませんが、x座標も同じように考えて式を求めることができます。
ということで結局は、巷の解説記事でフツーに見かける…
さておき。なんとなく、2Dゲームにおける疑似3Dの初歩というか基礎をメモしておこうかなと思い立ったりして。
いやまあ、「あらゆるゲームが3DCGで描画されてるこの御時勢にwww疑似3Dってwwwちょおまwww」と笑われそうな気もしますが。なんかその手の記事をぼんやり眺めてたら、式を求めるあたりで「三角形の相似」の一言で終わらせてる事例が結構あって。「説明がちょっと足りてないんじゃ」「図解しとけばすぐ分かるのに」と思えてきたので、せめてそこだけでもメモしておこうかなと。
疑似3Dと言っても、一般的には…。内部では x,y,z の3次元座標で管理していて、描画に使える何かしらが当時はスプライトかBGしかなかったからスプライト or BGで描画、みたいなことをやってたんだろうと思うわけですよ。もっとも中にはコレ全然3D計算してないだろうなって思えるアレなタイトルもありますが…。それっぽく見えてたタイトルは、内部では3次元座標で管理して、ということを多少はしてただろうと。
で、3次元空間に点があるとして、その点は画面上のどのへんに描かれるのか、ソレさえ分かれば疑似3Dはできるのですが。ソレを求める処理というか、図法のことを、 _透視投影 と呼ぶわけで。
まあ、近くのモノも遠くのモノも同じ大きさに見える _平行投影 てのもあるし、平行投影を使ってるゲームタイトルもありますが、ソレは奥行き情報であるz座標値を無視して描画すれば済んじゃうから横に置いとくとして。
透視投影をしたいなー、と思った時、実際どういう計算式になるかは、下の図を見てもらえば分かるかなと。ちなみに、画像をクリックすれば原寸大で見れます。
分かりやすくするために、見ている人の目の位置を原点(0,0,0)として考えてます。また、この図では画面上のy座標しか求めてませんが、x座標も同じように考えて式を求めることができます。
ということで結局は、巷の解説記事でフツーに見かける…
画面上のx座標 sx = 画面までの距離 sz * 点のx座標 px / 点のz座標 pz 画面上のy座標 sy = 画面までの距離 sz * 点のy座標 py / 点のz座標 pzこの式になるわけですね。
◎ 実践その1。 :
本当にこの式でそれらしくなるのか、実際に試してみましょう。Ruby + DXRuby でサンプルを書いてみましたよ。
_fake3d_a.rb
画像は _tmp_dot.png を使ってください。
ruby fake3d_a.rb で実行したら、こういう結果になりました。
一見それらしく見えてますね。座標の変換は上手く行ってるみたい。
ただ、ちょっと問題が。
_fake3d_a.rb
require "dxruby" img = Image.load("tmp_dot.png") # 点の座標群を作成 py = 600.0 scale = 200.0 pos = [] 10.times do |z| 10.times do |x| pos.push([x * scale, py, z * scale]) pos.push([x * -scale, py, z * scale]) end end # 画面までの距離 sz = 75 Window.loop do break if Input.keyPush?(K_ESCAPE) # ウインドウの中心位置を取得 cw = Window.width / 2 ch = Window.height / 2 # 画像横幅、縦幅を取得 iw = img.width ih = img.height # マウス座標を点群の移動量にする mx = (Input.mousePosX - cw) * 5 my = (Input.mousePosY - ch) * 5 # 点の座標を透視投影で求めて描画 pos.each do |px, py, pz| px += mx pz += my if pz > 0 sx = sz * px / pz sy = sz * py / pz x = sx - iw / 2 + cw y = sy - ih / 2 + ch Window.draw(x, y, img, -pz.to_i) end end end
画像は _tmp_dot.png を使ってください。
ruby fake3d_a.rb で実行したら、こういう結果になりました。
一見それらしく見えてますね。座標の変換は上手く行ってるみたい。
ただ、ちょっと問題が。
◎ 実践その2。 :
前述のスクリプトでは、各スプライト相当がどれも同じ大きさなので、なんだか不自然ですな。描画サイズも、z座標に応じて変化させてみませう。
_fake3d_b.rb
ruby fake3d_b.rb で実行してみると…。
これならイイ感じじゃないですかね。
_fake3d_b.rb
require "dxruby" img = Image.load("tmp_dot.png") # 点の座標群を作成 py = 600.0 scale = 200.0 pos = [] 10.times do |z| 10.times do |x| pos.push([x * scale, py, z * scale]) pos.push([x * -scale, py, z * scale]) end end # 画面までの距離 sz = 75 Window.loop do break if Input.keyPush?(K_ESCAPE) # ウインドウの中心位置を取得 cw = Window.width / 2 ch = Window.height / 2 # 画像横幅、縦幅を取得 iw = img.width ih = img.height # マウス座標を点群の移動量にする mx = (Input.mousePosX - cw) * 5 my = (Input.mousePosY - ch) * 5 # 点の座標を透視投影で求めて描画 pos.each do |px, py, pz| px += mx pz += my if pz > 0 ssz = sz / pz sx = px * ssz sy = py * ssz scale = 5.0 * ssz x = sx + cw - iw / 2 y = sy + ch - ih / 2 Window.drawEx( x, y, img, :scalex => scale, :scaley => scale, :center_x => iw / 2, :center_y => ih / 2, :z => -pz.to_i) end end end
ruby fake3d_b.rb で実行してみると…。
これならイイ感じじゃないですかね。
◎ その他色々。 :
たったこれだけのことが分かってるだけでも、色々な見せ方ができます。
_mieki256's diary - DXRubyで通路の奥に進むようなソレ
_mieki256's diary - DXRubyで円柱っぽいBG描画をしてみたり
_mieki256's diary - DXRuby+Shaderで床ラスタースクロールっぽいことができたような気がする
_mieki256's diary - 床ラスター処理をもうちょっと修正
それぞれの処理を書く際に使った知識(?)は、基本的には一番最初に乗せた図のソレだけです。ソレの応用で全部やってます。
ということで、スプライトとBG描画しかできない2Dゲームライブラリでも、基礎というか初歩というかそのへん分かってるだけでも、こういうことができますよ、てな話でした。
_mieki256's diary - DXRubyで通路の奥に進むようなソレ
_mieki256's diary - DXRubyで円柱っぽいBG描画をしてみたり
_mieki256's diary - DXRuby+Shaderで床ラスタースクロールっぽいことができたような気がする
_mieki256's diary - 床ラスター処理をもうちょっと修正
それぞれの処理を書く際に使った知識(?)は、基本的には一番最初に乗せた図のソレだけです。ソレの応用で全部やってます。
ということで、スプライトとBG描画しかできない2Dゲームライブラリでも、基礎というか初歩というかそのへん分かってるだけでも、こういうことができますよ、てな話でした。
◎ OpenGL使ったほうが早いんじゃないかという気もする。 :
DXRubyというライブラリは2Dゲーム用のライブラリなんで、ちょっと変わった見た目にしたいなと思ったら、昔の2Dゲームで活用してたこういうソレを使うことになったりするんですけど。
これが、Ruby + gosu だったら、そのものズバリの、opengl というライブラリを追加して使えるようでして。
_gosu-examples というサンプル群の中に、 _opengl_integration.rb というサンプルがあるんですよ。以下に動作画面のキャプチャを貼ってみますが。
ちょっと分かりづらいけど、背景が3Dで描画されてますよね。
なので、「手前のオブジェクト群は分かりやすい2Dゲームのノリで」「でも背景だけは今風っぽく3Dで」てな希望がある時は、Ruby + gosu + opengl で作ってみるのも手かもしれないな、とも思ったりしました。
まあ、最初から3D描画をガシガシ使いたいならUnityあたりを選ぶのが妥当かも、とも思うんですけど。
これが、Ruby + gosu だったら、そのものズバリの、opengl というライブラリを追加して使えるようでして。
_gosu-examples というサンプル群の中に、 _opengl_integration.rb というサンプルがあるんですよ。以下に動作画面のキャプチャを貼ってみますが。
ちょっと分かりづらいけど、背景が3Dで描画されてますよね。
なので、「手前のオブジェクト群は分かりやすい2Dゲームのノリで」「でも背景だけは今風っぽく3Dで」てな希望がある時は、Ruby + gosu + opengl で作ってみるのも手かもしれないな、とも思ったりしました。
まあ、最初から3D描画をガシガシ使いたいならUnityあたりを選ぶのが妥当かも、とも思うんですけど。
[ ツッコむ ]
以上です。