マリオカートを目指す試練
前回より見た目がしょぼくなったのはご愛嬌。
サンプルのミニレーシングのコースにランダムにSpriteを配置してみた。3D空間の計算式はDirectXに合わせて右に+x、上に+y、奥に+z(修正:奥だった)の左手座標系としてSpriteと地面を統一した。3Dのイメージはそのほうがしやすいだろう。
カメラの制御用にMatrix.look_atを導入。カメラの3D座標と画面中心にしたい3D座標と、カメラの上方向を引数で渡せばそのビューが返ってくる。この例では進行方向にちょっと進んだ場所を見るようにして、返ってきたビューを更にx軸で少し傾けてやっている。
一応、カーソルキーで左右に向きを変えたり前後に移動できるようにはなっているが、レースぽい動きにはなっていない。ミニレーシングのアルゴリズムを突っ込んでやればなんとなく走ってる感覚になったりするかもしれない。
あと、Spriteで壁やらドラム缶やらを配置するとたぶんそれっぽい雰囲気になってくるはず。
おまけでSpriteのzによるクリッピングも入れておいた。これがないと後ろにあるSpriteが変なふうに描画されてしまう。
require 'dxruby' class View attr_accessor :m, :sw, :sh, :zn, :zf def initialize(zn, zf, target = nil) if target == nil target = Window end @m = Matrix.new @zn = zn @zf = zf @sw = target.width @sh = target.height end end class Sprite3D < Sprite attr_accessor :v def self.set_transform(view) @@view = view @@proj = Matrix.new([[2.0 * view.zn / view.sw, 0.0, 0.0, 0.0], [0.0, 2.0 * view.zn / view.sh, 0.0, 0.0], [0.0, 0.0, view.zf / (view.zf - view.zn), 1.0], [0.0, 0.0, -view.zn * view.zf / (view.zf - view.zn), 0.0]]) @@vp = Matrix.new([[ view.sw / 2.0, 0.0, 0.0, 0.0], [ 0.0, -view.sh / 2.0, 0.0, 0.0], [ 0.0, 0.0, -1.0, 0.0], [ view.sw / 2.0, view.sh / 2.0, 0.0, 1.0]]) @@zn = view.zn end def initialize(v, image) @v = v self.image = image self.offset_sync = true self.center_x = nil self.center_y = image.height end def update temp = (Vector.new(0.0,0.0,0.0,1.0) + @v) * @@view.m * @@proj self.scale_x = self.scale_y = @@zn / temp.w self.xyz = temp / temp.w * @@vp self.visible = (self.z < 0 and self.z > -1.0) end end class Polygon attr_accessor :image, :view, :m, :z hlsl = <<EOS float4x4 g_world, g_view, g_proj; texture tex0; sampler Samp = sampler_state { Texture =<tex0>; // MinFilter = POINT; // MagFilter = POINT; }; struct VS_OUTPUT { float4 pos : POSITION; float2 tex : TEXCOORD0; }; VS_OUTPUT VS(float4 pos: POSITION, float2 tex: TEXCOORD0) { VS_OUTPUT output; output.pos = mul(mul(mul(pos, g_world), g_view), g_proj); output.tex = tex; return output; } float4 PS(float2 input : TEXCOORD0) : COLOR0 { return tex2D( Samp, input ); } technique { pass { VertexShader = compile vs_2_0 VS(); PixelShader = compile ps_2_0 PS(); } } EOS @@core = Shader::Core.new(hlsl, {:g_world=>:float, :g_view=>:float, :g_proj=>:float}) def initialize(image, view, z) @shader = Shader.new(@@core) @view = view @image = image @z = z @m = Matrix.new end def update @shader.g_world = @m.to_a @shader.g_view = @view.m.to_a @shader.g_proj = [2.0 * @view.zn / @view.sw, 0.0, 0.0, 0.0, 0.0, 2.0 * @view.zn / @view.sh, 0.0, 0.0, 0.0, 0.0, @view.zf / (@view.zf - @view.zn), 1.0, 0.0, 0.0, -@view.zn * @view.zf / (@view.zf - @view.zn), 0.0] end def draw Window.draw_shader(0, 0, @image, @shader, -@view.zf) end end view = View.new(1000.0, 50000.0) Sprite3D.set_transform(view) objects = Array.new(100) { Sprite3D.new(Vector.new(rand()*640*40, 0.0, -rand()*480*40), Image.new(200, rand()*300+100, [rand()*255, rand()*255, rand()*255])) } grand = Polygon.new(Image.load("shader_sample/bgimage/course.png"), view, -3000.0) grand.m = Matrix.scaling(40.0,40.0,40.0) * Matrix.rotation_x(-90.0) y_angle = 0.0 x = 0.0*40 y = 1500.0 z = 0.0*40 angle = 0 Window.loop do angle -= Input.x * 2 dx = Vector.new(50, 0).rotate(angle).x dz = Vector.new(50, 0).rotate(angle).y if Input.key_down?(K_UP) x += dx z += dz end if Input.key_down?(K_DOWN) x -= dx z -= dz end view.m = Matrix.look_at(Vector.new(x, y, z), Vector.new(x + dx, y, z + dz), Vector.new(0, 1, 0)) * Matrix.rotation_x(-5) Sprite.update(objects) grand.update Sprite.draw(objects) grand.draw break if Input.key_push?(K_ESCAPE) end