Jewel-mmo開発日記

RubyでMMORPGを作る過程を記録する日記。 Yokohama.rb 発起人。
2004-06-29

[開発ログ]不調

最近プログラム実装の速度が極めて遅い。企画の進行はそこそこか。 人間調子のいいときもあれば悪いときもある。それだけのことと思い特に心配はしてない。

時間が経てば調子も上がってくることだろう。

最近進めているのは、クライアント内のキャラクターをIRCのログイン情報から生成したり、 ネット(IRC)経由で複数のキャラクターを移動させるシステムの開発。

2004-06-25

[アイデア]Jewel-mmoネットワークシステムの基本理念

チャットにIRC、コマンドにCGIという構想はなかなかいい。 何よりお手軽なのがいい。

さらに発想を飛躍させればもっといいアイデアがあるのかもしれない。 例えばLinuxマシンそのものをワールドサーバと見なしてしまい、 SSHでのログインがワールドログインであるとか。 これはそのままではゲームにならないが、発想の転換で便利で面白いことが出来そうな気はする。

2004-06-24

[アイデア]ヘルムパーツ(bot)

  • BotUser … ユーザ管理
  • BotEffect … エフェクト処理
  • BotTalk … 通常会話処理
2004-06-24

[アイデア]チャットチャンネル構成

ワールド単位

  • 運営情報

エリア単位

  • エフェクト
    • キャラの存在有無
      • 認証 → エリアマスター(bot)がon_joinを検出すると認証を行い、結果をこのチャンネルで報告
    • 移動
    • アクション
      • 行動結果
      • アイテムの使用結果
  • 通常会話(エリア全域。クライアントで距離フィルタリング。シャウト指定は距離フィルタリングなし)

グループ単位

  • グループ会話
    • パーティ
    • ギルド
    • 個人

ユーザ単位(CGI)

  • 個人エフェクト
    • NPCの反応
    • イベント?
2004-06-22

[栽培日誌]台風の被害

横浜には直撃することなく過ぎ去った。今朝はすっかり晴れている。 風はやや強い。

被害があったのはトマトとミニトマト。トマトは茎がひん曲がったので支柱に縛り直した。ミニトマトは茎が一部割れた程度。あと連日の風でミニ大根の葉がおれた。

もし直撃していたらトマトは根本から折れていたことだろう。支柱への縛り方にもう少し工夫が必要かもしれない。

2004-06-22

[開発ログ]ソース公開に向けて準備中

510さんのむらさま夏休み特別企画も具体化しつつあるようなのでソース公開の準備を進める。

今までMySQLへの接続情報などが直接ソースに埋め込まれている部分があったのでconfファイルに切り出してCVSから消去。 一通り公開に向けての修正は終わったので少しチェックした後、ソースを公開する予定。 Jewel-mmoのWEBやWikiにも少し手を入れた。

久々にMrsのファイルにも修正を入れたのでなにか不具合が出るかもしれない。

2004-06-21

[栽培日誌]天候

今年は毎日の天気が非常に気になる。雨が多かったり、少なかったりすると気を揉むし、それに台風が恐ろしい。 今までは台風がどこを通ろうがどうでも良かった。むしろ近くを直撃してくれると逆にワクワクしたものだ。

この時期で一番心配なのはトマト。今年は大きな実がいくつもなってきているのでここで倒されたらたまらない。キュウリも一本くらい地這いにしておいても良かったかもしれない。今年は来年以降の台風対策を立てるための試練だ。

2004-06-17

[開発ログ]Tk3d実行速度の違い(Windows)

同じWindowsマシンでもcygwinとmswin32で速度が異なる。 tkのバージョンのせいだろうか?

mswin32

>ruby -v
ruby 1.8.1 (2003-12-25) [i386-mswin32]
ruby -rtk -e 'p Tk::TCL_VERSION'
"8.3"

loop time(1.344ms)

cygwin

$ ruby -v
ruby 1.8.1 (2003-12-25) [i386-cygwin]
$ ruby -rtk -e 'p Tk::TK_VERSION'
"8.4"

loop time(1.648ms)

tk3d.rbを更新

require("tk")

module Tk3d
  # poss : [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3],...]
  # angle : [ax,ay,az]
  def Tk3d.rotate_zyx(poss,angle)
    ax = angle[0]
    ay = angle[1]
    az = angle[2]
    poss.collect!{|px,py,pz|
      # Z軸回転
      xx = px * Math.cos(az) - py * Math.sin(az)
      yy = px * Math.sin(az) + py * Math.cos(az)
      px = xx
      py = yy
      # Y軸回転
      zz = pz * Math.cos(ay) - px * Math.sin(ay)
      xx = pz * Math.sin(ay) + px * Math.cos(ay)
      pz = zz
      px = xx
      # X軸回転
      yy = py * Math.cos(ax) - pz * Math.sin(ax)
      zz = py * Math.sin(ax) + pz * Math.cos(ax)
      py = yy
      pz = zz
      [px,py,pz]
    }
  end

  def Tk3d.rotate_xyz(poss,angle)
    ax = angle[0]
    ay = angle[1]
    az = angle[2]
    poss.collect!{|px,py,pz|
      # X軸回転
      yy = py * Math.cos(ax) - pz * Math.sin(ax)
      zz = py * Math.sin(ax) + pz * Math.cos(ax)
      py = yy
      pz = zz
      # Y軸回転
      zz = pz * Math.cos(ay) - px * Math.sin(ay)
      xx = pz * Math.sin(ay) + px * Math.cos(ay)
      pz = zz
      px = xx
      # Z軸回転
      xx = px * Math.cos(az) - py * Math.sin(az)
      yy = px * Math.sin(az) + py * Math.cos(az)
      px = xx
      py = yy
      [px,py,pz]
    }
  end

  def Tk3d.transfer(poss,position)
    mx = position[0]
    my = position[1]
    mz = position[2]
    poss.collect!{|pos|
      [pos[0]+mx,pos[1]+my,pos[2]+mz]
    }
  end

  def Tk3d.to_screen(poss,cx,cy,screen_z,scale_x=1.0,scale_y=1.0)
    npos = []
    poss.each{|px,py,pz|
      npos << [scale_x*px*screen_z/pz + cx, scale_y*py*screen_z/pz + cy]
    }
    npos
  end

  def Tk3d.av_z(poss)
    av = 0
    poss.each{|px,py,pz|
      av += pz
    }
    av /= poss.size
  end

  def Tk3d.min_z(poss)
    min = poss[0][2]
    poss.each{|px,py,pz|
      min = pz if pz<min
    }
    min
  end
end

class Tk3dCanvas < TkCanvas
  attr_accessor :scale
  attr_accessor :camera
  attr_accessor :scale

  def initialize(parent=nil, keys=nil)
    super
    @scale = 1.0
  end
  def quit
    @canvas.destroy
  end
end

class Tk3dCamera
  attr_accessor :ppos
  attr_accessor :angle
  attr_accessor :length
  attr_accessor :position
  attr_accessor :screen_z
  def initialize(screen_z=1000.0,ppos=nil,ang=nil)
    @screen_z = screen_z
    @ppos  = ppos ? ppos : [0.0,0.0,0.0]
    @angle = ang ? ang : [0.0,0.0,0.0]
  end
  def flush
    npos=[[0,0,-@length]]
    Tk3d::rotate_xyz(npos,@angle.collect{|v|v*=-1})
    npos.flatten!
    @position = [@ppos[0]+npos[0], @ppos[1]+npos[1], @ppos[2]+npos[2]]

    flag = (@position_last != @position || @angle_last != @angle || @length_last != @length)
    @position_last = @position.dup
    @angle_last = @angle.dup
    @length_last = @length
    flag
  end
end

class Tk3dOt
  attr_reader :z_rate
  attr_reader :tag_to_z

  def initialize(canvas3d,z_rate=1.0)
    @canvas3d = canvas3d
    @z_rate = z_rate
    @unused = {}
    @used = []
    @tag_to_z = {}

    @prims = {}
    # 描画の基準となるウィジェット。このウィジェットの描画順序がこのOtの描画順序となる
    @base_prim = TkcPolygon.new(@canvas3d, [])
  end
  def insert_unused(z,data)
    @unused[z] = [] unless @unused[z]
    @unused[z] << data
  end
  def clear_unused
    @unused.clear
  end
  def unused_empty?
    @unused.empty?
  end
  def add_prim(key,value)
    @prims[key] = value
  end
  def get_prim(key)
    @prims[key]
  end
  def get_prim_all
    @prims
  end

  def z_to_neartag(z)
    unless a = @used[z..-1]
      return nil
    end
    return a.compact.flatten[0]
  end

  def draw(max)
    c = @canvas3d
    n = 0
    @unused.each{|z,v|
      while !v.empty?
        (tag,color,pos2d) = v.shift
        c.coords(tag, pos2d)
#        @canvas3d.get_prim(tag).fill("#000000")

        if old_z = @tag_to_z[tag]
          if @used[old_z].size <= 1
            @used[old_z] = nil
          else
            @used[old_z].delete(tag)
          end
        end
        if ltag = z_to_neartag(z)
          prim = @prims[ltag]
        else
          prim = @base_prim
        end
        @prims[tag].raise(prim)

        @tag_to_z[tag] = z
        @used[z] = [] unless @used[z]
        @used[z] << tag

        return if (n += 1) > max
      end
      @unused.delete(z)
    }
  end
end

# Tk3dModel
# * モデリングデータ(構成要素)を保持
# * ワールド内での位置と角度を保持
# * OTに描画TAGを登録
class Tk3dModel
  attr_accessor :position
  attr_accessor :angle
  attr_reader :tag_to_data
  def initialize(canvas3d, ot, data)
    @canvas3d = canvas3d
    @ot = ot
    @tag_to_data = {}
    @data = data
    @data.collect!{|lcolor,color,pos|
      prim = TkcPolygon.new(@canvas3d, []) do
        outline(lcolor)
        fill(color)
      end
      tag = prim.id.to_s
      prim.addtag(tag)
      @ot.add_prim(tag,prim)
      @tag_to_data[tag] = [color,pos]
      [tag,color,pos]
    }
    @position = [0.0,0.0,0.0]
    @angle = [0.0,0.0,0.0]
  end

  def add_ot
    camera_angle = @canvas3d.camera.angle
    camera_position = @canvas3d.camera.position.collect{|v|v*=-1}
    screen_z = @canvas3d.camera.screen_z
    cx = TkWinfo.width(@canvas3d)/2 -2
    cy = TkWinfo.height(@canvas3d)/2 -2
    scale_x = @canvas3d.scale * cx / (@canvas3d.width/2)
    scale_y = @canvas3d.scale * cy / (@canvas3d.height/2)
    near_z = 0
    z_rate = @ot.z_rate

    @data.each{|tag,color,pos|
      npos = pos.dup
      #--ローカル変換
      Tk3d::rotate_zyx(npos, @angle)
      Tk3d::transfer(npos, @position)
      #--ワールド変換
      Tk3d::transfer(npos, camera_position)
      Tk3d::rotate_zyx(npos, camera_angle)
      #--スクリーン投影
      if Tk3d::min_z(npos) > near_z#--nearクリップ
        pos2d = Tk3d::to_screen(npos, cx, cy, screen_z, scale_x, scale_y)
        z = (Tk3d::av_z(npos)*z_rate).to_i
      else
        pos2d = [0,0,0,0]
        z = (near_z*z_rate).to_i
      end
      @ot.insert_unused(z,[tag,color,pos2d])
    }
  end
end


if $0 == __FILE__
  TkRoot.new.bind("KeyPress-Escape", proc{ @after.stop;TkRoot.new.destroy })

  @camera = Tk3dCamera.new(1000)
  @camera.length = 500
  @canvas3d = Tk3dCanvas.new do
      width(200)
      height(160)
      background("#888888")
      pack('fill' => 'both', 'expand' => true)
  end
  @canvas3d.camera = @camera
  @canvas3d.scale = 0.2
  @ot_bg = Tk3dOt.new(@canvas3d,0)
  @ot = Tk3dOt.new(@canvas3d,8.0)
  # Model
  @model = []
  8.times{|i|
    w = -15.0
    h = -40.0
    @model << m = Tk3dModel.new(@canvas3d,@ot,
      [
        ["","#777777",[[0.0,0.0,0.0],[-w,h,-w],[-w,h, w]]],
        ["","#888888",[[0.0,0.0,0.0],[-w,h, w],[ w,h, w]]],
        ["","#888888",[[0.0,0.0,0.0],[ w,h, w],[ w,h,-w]]],
        ["","#777777",[[0.0,0.0,0.0],[ w,h,-w],[-w,h,-w]]],
        ["","#999999",[[-w,h,-w],[-w,h,w],[w,h,w],[w,h,-w]]],
      ]
    )
    x = i%2==0 ? i/2*40 : -(i+1)/2*40
    m.position[0] = x
  }
  # BG Model
  n = 8
  w = 64
  data = []
  n.times{|z|
    n.times{|x|
      h = 0.0
      x1 = x4 = x*w - n*w/2
      x2 = x3 = (x+0.8)*w - n*w/2
      z1 = z2 = z*w - n*w/2
      z3 = z4 = (z+0.8)*w - n*w/2
      c = 0xAA - ((x1*x1+z1*z1)/(n*w*4)).to_i.abs
      c = 0x00 if c < 0x00
      color = sprintf("#%02x%02x%02x",c,c,c)
      data << ["",color,[[x1,h,z1],[x2,h,z2],[x3,h,z3],[x4,h,z4]]]
    }
  }
  @bg_model = Tk3dModel.new(@canvas3d,@ot_bg,data)

  @camera.angle = [0,0,0]
  @camera.ppos = [0,-20,0]
  @camera.angle[0] = Math::PI/180.0*10
  @camera.angle[1] = Math::PI/180.0*80

  draw_max = n*n
  TkScale.new {
    from(1)
    to(n*n)
    set(n*n)
    orient 'horizontal'
    command { |val| draw_max = val }
    pack
  }
  #--------------------
  counter = 0
  @ct = 0
  @t = Time.now
  @after = TkAfter.new(10, -1, proc { 
    if counter%2 == 0
      @camera.angle[1] += -Math::PI/180.0*2
    end
    if @camera.flush
      @ot_bg.clear_unused
      @bg_model.add_ot
    end
    if @ot.unused_empty?
      ct = 0
      @model.each{|m|
        ct += 1
        next if @ct%1 != ct%1
        m.angle[1] += Math::PI/180.0*(rand(4)+8)
        m.add_ot
      }
      @ct += 1
    end
    @ot.draw(50)
    @ot_bg.draw(draw_max)
    counter += 1
    if  counter%20 == 0
      puts "  loop time(#{Time.now-@t}ms)"
      @t = Time.now
    end
  })
  @after.cancel_on_exception = false
  @after.start
  #--------------------

  Tk.mainloop
end
2004-06-16

[アイデア][開発ログ]Nadokaで遊ぶ

Nadokaボディ(model-A) NDK_BodyAクラス

ユーザの目に見えるNadokaをクライアントのワールドウィンドウの中に実体化する。 Nadokaヘルムを装着することでIRCDに接続し、アイテムを用いてbot的な役割を果たす。 現在モデルAのデザインが進行中。モデルBやモデルCが存在するかは不明。

Nadokaヘルム NDK_Helmクラス

このヘルメットにIRCクライアントサーバプログラムNadokaが組み込まれている。 実体化したNadokaボディはこれを装着しなければ一切のIRC処理を行えない。 実はこれこそ本体である。ボディはワールドでの単なる虚像にすぎないのだ。

2004-06-16

[開発ログ]Tk3dとNadokaを組み込む

Tk3dとNadokaをクライアント本体に組み込んだ。 クライアントのチャットウィンドウと既存のIRCクライアント間でのチャットに成功。

total  9683 lines
 .cgi  1274
 .rb   5881
 .sql  411
 .txt  266
 .htm  636
 .html 1215
2004-06-15

[アイデア]ルビードアイランド 〜紅色のくぐつ師〜

  • 系統
    • ルビー系
    • パール系
    • パイソン系
  • パイソンは強力
    • パイソン狩り
      • パイソンは核を食らうのでの要注意
    • 蛙狩り
  • 魔法
    • 魔法⇒スクリプト
    • 魔法使い⇒スクリプター
  • 職業
    • 職業⇒クラス
    • 上級クラスはクラス継承
      • 基底クラス
      • 下位クラス
      • 上級クラス
    • クラス属性…その職業の者すべてに共通したパラメータ
2004-06-11

[Ruby][開発ログ]tk3d.rbプロトタイプ

  • Zクリップ (実装)
  • ポリゴン単位のZソート (実装)
  • 平行光源の光源計算 (未実装)

少し変わった作りをしている。部分的な描画更新が可能。具体的に言うとオーダリングテーブル単位で更新のレートを変更できる。この機能を実現するために表示済みのポリゴンの中からZ値を元に描画するポリゴンのエントリ位置を割り出す処理(z_to_neartag(z)メソッド)が入っているが、この部分が重い。サンプル実行時に下部にあるスケールバーを下げるとBGの更新間隔が変化するのがわかる。

この機能をつけた理由は、BGを初めに描画して視点変更が発生するまでキャラクターオブジェクトだけを更新する、という仕組みを実現するため。

Ruby/Tk実行環境をお持ちの方はスコアをご報告頂けるとありがたい。

手元の環境でのスコア(バーは初期値の64)

loop time(1.359ms)
P4 2.8Gz WindowsXP

tk3d.rb

追記:最新(20040617)のtk3d.rb

require("tk")

module Tk3d
  # poss : [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3],...]
  # angle : [ax,ay,az]
  def Tk3d.rotate_zyx(poss,angle)
    ax = angle[0]
    ay = angle[1]
    az = angle[2]
    poss.collect!{|px,py,pz|
      # Z軸回転
      xx = px * Math.cos(az) - py * Math.sin(az)
      yy = px * Math.sin(az) + py * Math.cos(az)
      px = xx
      py = yy
      # Y軸回転
      zz = pz * Math.cos(ay) - px * Math.sin(ay)
      xx = pz * Math.sin(ay) + px * Math.cos(ay)
      pz = zz
      px = xx
      # X軸回転
      yy = py * Math.cos(ax) - pz * Math.sin(ax)
      zz = py * Math.sin(ax) + pz * Math.cos(ax)
      py = yy
      pz = zz
      [px,py,pz]
    }
  end

  def Tk3d.rotate_xyz(poss,angle)
    ax = angle[0]
    ay = angle[1]
    az = angle[2]
    poss.collect!{|px,py,pz|
      # X軸回転
      yy = py * Math.cos(ax) - pz * Math.sin(ax)
      zz = py * Math.sin(ax) + pz * Math.cos(ax)
      py = yy
      pz = zz
      # Y軸回転
      zz = pz * Math.cos(ay) - px * Math.sin(ay)
      xx = pz * Math.sin(ay) + px * Math.cos(ay)
      pz = zz
      px = xx
      # Z軸回転
      xx = px * Math.cos(az) - py * Math.sin(az)
      yy = px * Math.sin(az) + py * Math.cos(az)
      px = xx
      py = yy
      [px,py,pz]
    }
  end

  def Tk3d.transfer(poss,position)
    mx = position[0]
    my = position[1]
    mz = position[2]
    poss.collect!{|pos|
      [pos[0]+mx,pos[1]+my,pos[2]+mz]
    }
  end

  def Tk3d.to_screen(poss,cx,cy,screen_z,scale_x=1.0,scale_y=1.0)
    npos = []
    poss.each{|px,py,pz|
      npos << [scale_x*px*screen_z/pz + cx, scale_y*py*screen_z/pz + cy]
    }
    npos
  end

  def Tk3d.av_z(poss)
    av = 0
    poss.each{|px,py,pz|
      av += pz
    }
    av /= poss.size
  end

  def Tk3d.min_z(poss)
    min = poss[0][2]
    poss.each{|px,py,pz|
      min = pz if pz<min
    }
    min
  end
end

class Tk3dOt
  attr_reader :z_rate

  def initialize(world,z_rate=1.0)
    @world = world
    @z_rate = z_rate
    @unused = {}
    @used = []
    @tag_to_z = {}

    @prims = {}
    # 描画の基準となるウィジェット。このウィジェットの描画順序がこのOtの描画順序となる
    @base_prim = TkcPolygon.new(@world.canvas, [])
  end
  def insert_unused(z,data)
    @unused[z] = [] unless @unused[z]
    @unused[z] << data
  end
  def add_prim(key,value)
    @prims[key] = value
  end
  def get_prim(key)
    @prims[key]
  end
  def empty?
    @unused.empty?
  end

  def z_to_neartag(z)
    unless a = @used[z..-1]
      return nil
    end
    return a.compact.flatten[0]
  end

  def draw(max)
    c = @world.canvas
    n = 0
    @unused.each{|z,v|
      while !v.empty?
        (tag,color,pos2d) = v.shift
        c.coords(tag, pos2d)
#        @world.get_prim(tag).fill("#000000")

        if old_z = @tag_to_z[tag]
          if @used[old_z].size <= 1
            @used[old_z] = nil
          else
            @used[old_z].delete(tag)
          end
        end
        if ltag = z_to_neartag(z)
          prim = @prims[ltag]
        else
          prim = @base_prim
        end
        @prims[tag].raise(prim)

        @tag_to_z[tag] = z
        @used[z] = [] unless @used[z]
        @used[z] << tag

        return if (n += 1) >= max
      end
      @unused.delete(z)
    }
  end
end

class Tk3dCamera
  attr_reader :ppos
  attr_reader :angle
  attr_accessor :position
  attr_accessor :screen_z
  def initialize(screen_z=1000.0,ppos=nil,ang=nil)
    @screen_z = screen_z
    @ppos  = ppos ? ppos : [0.0,0.0,0.0]
    @angle = ang ? ang : [0.0,0.0,0.0]
  end
  def length=(l)
    @length = l
    flush_position
  end
  def angle=(a)
    @angle = a
    flush_position
  end
  def ppos=(a)
    @ppos = a
    flush_position
  end
  def flush_position
    npos=[[0,0,-@length]]
    Tk3d::rotate_xyz(npos,@angle.collect{|v|v*=-1})
    npos.flatten!
    @position = [@ppos[0]+npos[0], @ppos[1]+npos[1], @ppos[2]+npos[2]]
  end
end

class Tk3dWorld
  attr_accessor :scale
  def initialize(camera, cwidth, cheight, color)
    @camera = camera
    @canvas = TkCanvas.new do
      width(cwidth)
      height(cheight)
      background(color)
      pack('fill' => 'both', 'expand' => true)
    end
    @scale = 1.0
  end
  def quit
    @canvas.destroy
  end
  def canvas
    @canvas
  end
  def camera
    @camera
  end
end

# Tk3dModel
# * モデリングデータ(構成要素)を保持
# * ワールド内での位置と角度を保持
# * OTに描画TAGを登録
class Tk3dModel
  attr_accessor :position
  attr_accessor :angle
  def initialize(world,ot, data)
    @world = world
    @ot = ot
    @data = data
    @data.collect!{|color,pos|
      prim = TkcPolygon.new(@world.canvas, []) do
        outline("#666666")
        fill(color)
      end
      tag = prim.id.to_s
      prim.addtag(tag)
      @ot.add_prim(tag,prim)
      [tag,color,pos]
    }
    @position = [0.0,0.0,0.0]
    @angle = [0.0,0.0,0.0]
  end

  def draw
    ct = 0
    camera_angle = @world.camera.angle
    camera_position = @world.camera.position.collect{|v|v*=-1}
    screen_z = @world.camera.screen_z
    cx = TkWinfo.width(@world.canvas)/2 -2
    cy = TkWinfo.height(@world.canvas)/2 -2
    scale_x = @world.scale * cx / (@world.canvas.width/2)
    scale_y = @world.scale * cy / (@world.canvas.height/2)
    near_z = 0
    z_rate = @ot.z_rate

    @data.each{|tag,color,pos|
      npos = pos.dup
      #--ローカル変換
      Tk3d::rotate_zyx(npos, @angle)
      Tk3d::transfer(npos, @position)
      #--ワールド変換
      Tk3d::transfer(npos, camera_position)
      Tk3d::rotate_zyx(npos, camera_angle)
      #--スクリーン投影
      if Tk3d::min_z(npos) > near_z#--nearクリップ
        pos2d = Tk3d::to_screen(npos, cx, cy, screen_z, scale_x, scale_y)
        z = (Tk3d::av_z(npos)*z_rate).to_i
      else
        pos2d = [0,0,0,0]
        z = (near_z*z_rate).to_i
      end
      @ot.insert_unused(z,[tag,color,pos2d])
      ct += 1
    }
  end
end

if $0 == __FILE__
  TkRoot.new.geometry("+100+100")
  TkRoot.new.bind("KeyPress-Escape", proc{ @after.stop;TkRoot.new.destroy })

  @camera = Tk3dCamera.new(1000)
  @camera.length = 500
  @world = Tk3dWorld.new(@camera,200,160,"#bbbbbb")
  @world.scale = 0.2
  @ot_bg = Tk3dOt.new(@world,0)
  @ot = Tk3dOt.new(@world,8.0)
  # Model
  @model = []
  8.times{|i|
    w = -15.0
    h = -40.0
    @model << m = Tk3dModel.new(@world,@ot,
      [
        ["#777777",[[0.0,0.0,0.0],[-w,h,-w],[-w,h, w]]],
        ["#888888",[[0.0,0.0,0.0],[-w,h, w],[ w,h, w]]],
        ["#888888",[[0.0,0.0,0.0],[ w,h, w],[ w,h,-w]]],
        ["#777777",[[0.0,0.0,0.0],[ w,h,-w],[-w,h,-w]]],
        ["#999999",[[-w,h,-w],[-w,h,w],[w,h,w],[w,h,-w]]],
      ]
    )
    x = i%2==0 ? i/2*40 : -(i+1)/2*40
    m.position[0] = x
  }
  # BG Model
  n = 8
  w = 64
  data = []
  n.times{|z|
    n.times{|x|
      h = 0.0
      x1 = x4 = x*w - n*w/2
      x2 = x3 = (x+0.8)*w - n*w/2
      z1 = z2 = z*w - n*w/2
      z3 = z4 = (z+0.8)*w - n*w/2
      c = 0xAA - ((x1*x1+z1*z1)/(n*w*4)).to_i.abs
      c = 0x00 if c < 0x00
      color = sprintf("#%02x%02x%02x",c,c,c)
      data << [color,[[x1,h,z1],[x2,h,z2],[x3,h,z3],[x4,h,z4]]]
    }
  }
  @bg_model = Tk3dModel.new(@world,@ot_bg,data)

  @camera.angle = [0,0,0]
  @camera.ppos = [0,-20,0]
  @camera.angle[0] = Math::PI/180.0*10
  @camera.angle[1] = Math::PI/180.0*80
  @camera.flush_position

  flush_num = n*n
  TkScale.new {
    from(1)
    to(n*n)
    set(n*n)
    orient 'horizontal'
    command { |val| flush_num = val }
    pack
  }
  #--------------------
  counter = 0
  @ct = 0
  @t = Time.now
  @after = TkAfter.new(10, -1, proc { 
    @camera.angle[1] += -Math::PI/180.0*1
    @camera.flush_position
    if @ot_bg.empty?
      @bg_model.draw
    end
    if @ot.empty?
      ct = 0
      @model.each{|m|
        ct += 1
        next if @ct%1 != ct%1
        m.draw
        m.angle[1] += Math::PI/180.0*(rand(4)+8)
      }
      @ct += 1
    end
    @ot.draw(50)
    @ot_bg.draw(flush_num)
    counter += 1
    if  counter%20 == 0
      puts "  loop time(#{Time.now-@t}ms)"
      @t = Time.now
    end
  })
  @after.cancel_on_exception = false
  @after.start
  #--------------------

  Tk.mainloop
end
2004-06-09

[開発ログ]Ruby/Tk3D 座標変換

簡単なプログラムだが座標変換から2D投影までは行うことが出来た。

TkcPolygonによって三角形を表示しているが、現状だと100枚がいいところだ。最適化は行っていない。

作っていて気がついたがまだ機能が必要だ。

  • ポリゴン単位のZソート
  • Zクリップ
  • 表裏の判定
  • 平行光源による光源計算

これを組み込むとさらに重くなりそうだが、この辺はC言語で書き直して拡張ライブラリに出来るので、拡張ライブラリで最適化すれば追加機能に関しての描画負荷(数百ポリゴンなら)はゼロに近い。

2004-06-08

[栽培日誌]惑星ベジータ

今週はいままでさぼっていた去年の日誌書きをある程度 終わらせてしまいたい。dGamesの開発は停滞するかもしれない。

2004-06-06

[栽培日誌]別サイトへ独立

栽培日誌を別サイトに切り出す。今のところ超手抜きサイトだが徐々にデザインを直せばいいだろう。

惑星ベジータ http://begeta.or.tp/

2004-06-05

[Ruby]Perlを使ってみる

新しい掲示板を設置するのにPerlを久しぶりに使う。既存のスクリプトだから基本的にはただ設置すればいいのだが、微妙にカスタマイズしたくてPerlスクリプトを改造する。Perlといえば。いまでこそ完全なRuby使いと化してしまったオレも、一年半前まではPerl使いだった。Perl暦は5年以上か。しかし、しかしだ。さっぱりPerlが書けない。頭がRubyモードになっているせいもあるが、それを差し引いてもPerlは使いにくい。うん、やっぱりRubyがいいと再認識する。

掲示板の改造は結局半分しか出来なかったのだが、いまさらPerlを勉強する気にはなれないし、放置するか。

2004-06-03

[栽培日誌]今年初キュウリ

今朝キュウリを一本収穫。週末に西側ネット地に移動したキュウリも順調に根付いたようだ。

バジルも昨日初めて収穫。 ミニトマトの脇目を死の乾燥エリアに挿しておく。十分に水をやっているので24時間経った今朝も萎れていない。

2004-06-01

[開発ログ]ワールドクライアントの制作開始

まずは簡単なものをつくって公開しよう。

ワールドクライアントver0.1仕様

  • ユーザサイドから見た仕様
    • Windowsで動作するexeファイル形式の配布
    • Rubyがあれば動作するrbスクリプトとしても配布
    • チャットが可能
    • CGIで登録したアカウントでログイン出来る
    • 3Dマップが表示される
    • PCが表示される
    • エリアが複数存在する
    • PCは3Dマップ上を移動できる
      • (本来PCは霊魂。実体化にはエイリアスが必要)
  • システムサイドから見た仕様
    • 認証、エリア移動はCGI
    • チャット、マップ移動はIRC
    • グラフィックシステムはRuby/Tkで実装

よし、Tkからはじめよう。

2004-06-01

[アイデア]ユーザがNPCや町を作成

一般的なNPCの簡単な機能は例えばこうだ。

  • 話しかけると決まった台詞を話す
  • あるフラグによって話す台詞が変化する。変化の条件としては
    • ○○○のアイテムを持っている
    • お金を○○○以上持っている
    • レベルが○○以上
    • ○○○フラグが立っている
  • 台詞の表示と同時になんらかの処理を行う。例えば
    • PCが持つアイテム○○○とNPCが持つアイテム△△△を交換
    • PCが持つお金とNPCが持つアイテム○○○を交換(買う)
    • PCが持つアイテム○○○とNPCが持つお金を交換(売る)
    • あるフラグを立てる
    • あるフラグを降ろす
    • NPCがPCに○○○の魔法を使う
    • NPCがPCに○○○のアイテムを使う

単純だがこれだけの機能があれば一応NPCを演じることが出来るし、 それなりのイベントを構築することも可能だ。 お使い系のクエストならこれだけで十分だ。

と、ここまでは普通のゲームなら当たり前に実装されているシステムだが、 これを一歩進めたシステム考える。 ユーザが持つキャラクタ(PC)にこの機能が自由に使えるように設定できるようにする。 これは開発するのもユーザが設定するのもそれほど難しくはない。 ユーザのパラメータ設定で作る簡単なボットだ。 このボットは台詞や上記の処理を自由に設定しておけば、 あとは他のプレイヤーが話しかけたときに、設定した通りの振る舞いをする。 そしてそのボットを複数おけるようにすれば新しい町だって作れるだろう。

これは単にアイテムを売るだけのバザーキャラより面白そうだ。

2004-06-01

[dGames]Jewel-MMO 開発日記

dGames本家も無事オープンしたのでここの名前はJewel-MMO開発日記とでもした方がいいかもしれない。

少しの間試しに変更しておく。