マリオカートを目指す試練

前回より見た目がしょぼくなったのはご愛嬌。

サンプルのミニレーシングのコースにランダムに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