2010-03-11
[ruby][MyGame]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