#!ruby -Ks # -*- mode: ruby; encoding: sjis -*- # Last updated: <2014/12/01 05:35:17 +0900> require 'rubygems' require 'dxruby' require 'json' require 'pp' # Spriter保存ファイル(.scon)を解析してアニメを描画するクラス # # @example DXRubyを使って描画する例 # require 'dxruby' # require_relative 'spritersconparser' # ani = SpriterSconParser.new("img00/girl_spriter.scon") # ani.set_anime("default", "run") # Window.loop do # ani.draw(320, 240 + 120) # ani.update # end # # @note DXRuby, json が必要 # @note ruby spritersconparser.rb IN_FILE.scon で表示テストができます。 # # @version 0.0.2 # class SpriterSconParser # @return [Array] entity名の配列 attr_accessor :entity_list # @return [String] entity名 attr_accessor :entity_name # @return [String] アニメパターン名 attr_accessor :anime_name # @return [Integer] アニメの長さ(単位はms) attr_accessor :anime_length # @return [Hash] アニメパターンが持つデータ attr_accessor :animation # @return [Array] 各キーフレームのIDと長さをハッシュにした配列 attr_accessor :keyframe_list # @return [Integer] 現在のキーフレーム attr_accessor :keyframe # @return [Integer] キーフレーム個数 attr_accessor :keyframe_length # @return [Integer] 現在の時間。単位はms。(ただし実時間ではない) attr_accessor :current_time # @return [Boolean] 水平反転フラグ attr_accessor :hflip # @return [Boolean] 垂直反転フラグ attr_accessor :vflip # @return [Number] 全体の回転角度。単位は度。プラスで反時計回り。0-360の範囲 attr_accessor :angle # コンストラクタ # @param [String] filepath Spriter保存ファイル(.scon)のパス def initialize(filepath) f = open(filepath) @json_data = f.read f.close @anime_data = JSON.parse(@json_data) @entity_name = "" @anime_name = "" @anime_length = 0 @animation = nil @entity_list = [] @keyframe_list = [] @keyframe = 0 @keyframe_length = 0 @current_time = 0 @entity = {} @hflip = false @vflip = false @angle = 0 @anime_data["entity"].each do |et| # pp et.keys # ["animation", "character_map", "id", "name", "obj_info"] adata = {} et["animation"].each do |a| # pp a.keys # ["gline", "id", "interval", "length", "mainline", "name", "timeline"] aname = a["name"] mls = arrange_mainline(a["mainline"]) tls = arrange_timeline(a) adata[aname] = { :id => a["id"], :gline => a["gline"], :interval => a["interval"], :length => a["length"], :mainline => mls, :timeline => tls } end odata = arrange_objinfo(et["obj_info"]) id, name = et["id"], et["name"] @entity[name] = { :id => id, :character_map => et["character_map"], :animation => adata, :obj_info => odata } end # dump_entity load_images(filepath, @anime_data["folder"]) @entity_list = @entity.keys ename = @entity_list[0] aname = anime_list(ename)[0] set_anime(ename, aname) end # @private # 補間種類を示す文字列を定数に変換 # @param [String] str 補間種類を示す文字列。linear, instant, cubic の3種類 # @return [Integer] 補間種類を示す定数を返す。未対応の文字列ならnil def get_curve_constant(str) case str when "linear" return :curve_linear when "instant" return :curve_instant when "cubic" return :curve_cubic end return nil end # @private # mainlineデータを処理しやすいように改変 # @param [Hash] mlh mainlineデータ # @return [Hash] 改変後のmainlineデータをハッシュで返す def arrange_mainline(mlh) mlh[:key] = mlh["key"] mlh[:key].each do |key| key[:time] = key.fetch("time", 0) if key.has_key?("curve_type") key[:curve_type] = get_curve_constant(key["curve_type"]) else key[:curve_type] = :curve_linear end key[:c1] = key.fetch("c1", 0) key[:c2] = key.fetch("c2", 0) br = {} key["bone_ref"].each do |d| id = d["id"] d[:key] = d["key"] d[:parent] = d["parent"] if d.has_key?("parent") d[:timeline] = d["timeline"].to_i br[id] = d end key[:bone_ref] = br oref = {} key["object_ref"].each do |d| id = d["id"] d[:key] = d["key"] d[:parent] = d["parent"] if d.has_key?("parent") d[:timeline] = d["timeline"].to_i d[:z_index] = d.fetch("z_index", 0).to_i oref[id] = d end key[:object_ref] = oref end return mlh end # @private # timelineデータを処理しやすいように改変 # @param [Array] a animationデータ # @return [Hash] 改変後のtimelineデータをハッシュで返す def arrange_timeline(a) tls = {} a["timeline"].each do |t| id = t["id"] tls[id] = t tls[id][:key] = t["key"] tls[id][:name] = t["name"] tls[id][:key].each do |tkey| tkey[:time] = tkey.fetch("time", 0).to_i tkey[:spin] = tkey.fetch("spin", 1).to_i if tkey.has_key?("curve_type") tkey[:curve_type] = get_curve_constant(tkey["curve_type"]) tkey[:c1] = tkey["c1"] if tkey.has_key?("c1") tkey[:c2] = tkey["c2"] if tkey.has_key?("c2") end d = nil if tkey.has_key?("bone") # bone d = tkey["bone"] tkey[:bone] = true tkey[:data] = d else # image d = tkey["object"] tkey[:bone] = false tkey[:data] = d d[:pivot_x] = d.fetch("pivot_x", 0.0).to_f d[:pivot_y] = d.fetch("pivot_y", 1.0).to_f d[:folder] = d["folder"] d[:file] = d["file"] end d[:x] = d.fetch("x", 0.0).to_f d[:y] = d.fetch("y", 0.0).to_f d[:angle] = d.fetch("angle", 0.0).to_f d[:scale_x] = d.fetch("scale_x", 1.0).to_f d[:scale_y] = d.fetch("scale_y", 1.0).to_f d[:a] = d.fetch("a", 1.0).to_f end end return tls end # @private # obj_infoデータを処理しやすいように改変 # @param [Array] objinfo obj_infoデータ # @return [Hash] 改変後のデータをハッシュで返す def arrange_objinfo(objinfo) odata = {} objinfo.each do |o| # pp o.keys # ["h", "name", "type", "w"] oname = o["name"] w = o["w"] h = o["h"] odata[o["name"]] = { :type => o["type"], :w => w, :h => h, :image => make_bone_image(w, h) } end return odata end # @private # 画像読み込み。folderデータ群に挿入して返す # @param [String] filepath .sconファイルのパス # @param [Array] dt folderデータ # @return [Hash] 改変後のfolderデータをハッシュで返す def load_images(filepath, dt) srcdir = File.dirname(filepath) @folder = {} dt.each do |d| files = {} d["file"].each do |dd| # pp dd.keys # ["height", "id", "name", "pivot_x", "pivot_y", "width"] fpath = File.join(srcdir, dd["name"]) dd[:image] = Image.load(fpath) dd[:id] = dd["id"].to_i dd[:name] = dd["name"] dd[:width] = dd["width"].to_f dd[:height] = dd["height"].to_f dd[:pivot_x] = dd["pivot_x"].to_f dd[:pivot_y] = dd["pivot_y"].to_f files[dd["id"]] = dd end @folder[d["id"]] = files end return @folder end # @private # ボーン用画像を作成 # @param [Integer] w 画像横幅 # @param [Integer] h 画像縦幅 # @return [Object] ボーン用Image def make_bone_image(w, h) img = Image.new(w, h) c1 = C_BLACK c2 = [128, 255, 255, 255] xx = w / 8 x1, y1 = 0, h / 2 x2, y2 = xx, 0 x3, y3 = xx, h - 1 img.triangleFill(x1, y1, x2, y2, x3, y3, c2) img.line(x1, y1, x2, y2, c1) img.line(x1, y1, x3, y3, c1) x1, y1 = xx, 0 x2, y2 = w - 1, h / 2 x3, y3 = xx, h - 1 img.triangleFill(x1, y1, x2, y2, x3, y3, c2) img.line(x1, y1, x2, y2, c1) img.line(x2, y2, x3, y3, c1) return img end # アニメパターン名の配列を取得 # @param [String] entname entity名 # @return [Array] アニメパターン名の入った配列 def anime_list(entname) return @entity[entname][:animation].keys end # キーフレームを1つ進める def inc_keyframe @keyframe = (@keyframe + 1) % @keyframe_length end # キーフレームを1つ戻す def dec_keyframe @keyframe = (@keyframe + @keyframe_length - 1) % @keyframe_length end # アニメパターンを指定する # @param [String] entname entity名 # @param [String] aniname アニメパターン名 # @param [Boolean] h_flip 水平方向反転フラグ。trueなら反転、falseならそのまま # @param [Boolean] v_flip 垂直方向反転フラグ。trueなら反転、falseならそのまま # @param [Boolean] angle 全体の回転角度(単位は度) def set_anime(entname, aniname, h_flip = false, v_flip = false, angle = 0) @entity_name = entname @anime_name = aniname @animation = @entity[@entity_name][:animation][@anime_name] @hflip = h_flip @vflip = v_flip @angle = 0 reset_time end # 現在時間とキーフレーム値をリセット def reset_time @current_time = 0 @keyframe = 0 @animation = @entity[@entity_name][:animation][@anime_name] @anime_length = @animation[:length] @keyframe_length = @animation[:mainline]["key"].length @keyframe_list.clear @keyframe_length.times do |i| ml = @animation[:mainline]["key"][i] @keyframe_list.push({"id" => i, "time" => ml.fetch("time", 0).to_i}) end end # @private # 現在時間に対応するキーフレーム値を取得 # @param [Integer] ctime 現在時間(単位はms。ただし実際の時間ではない) # @return [Integer] キーフレーム値 def get_keyframe_id(ctime) kmax = @keyframe_list.length return 0 if kmax <= 1 return 0 if ctime >= @anime_length (kmax - 1).downto(0) do |i| return @keyframe_list[i]["id"] if @keyframe_list[i]["time"] <= ctime end return 0 end # @private # 補間計算用の割合を返す # @param [Integer] tmnow 現在時間 # @param [Integer] tmstart キーフレーム開始時間 # @param [Integer] tmend キーフレーム終了時間 # @param [Integer] tmlen アニメパターンの長さ # @param [String] curve_type 曲線種類。linear,cubic,instant の3つがあり得る # @param [Number] c1 曲線用制御値その1 # @param [Number] c2 曲線用制御値その2 # @return [Number] 割合を、0.0から1.0の範囲内で返す def get_ratio(tmnow, tmstart, tmend, tmlen, curve_type = :curve_linear, c1 = 0, c2 = 0) return 1.0 if tmnow == tmend return 0.0 if tmnow == tmstart return 0.0 if curve_type == :curve_instant a = (tmstart <= tmend)? (tmend - tmstart).to_f : (tmlen - tmstart).to_f r = ((tmnow - tmstart).to_f / a) return r if curve_type != :curve_cubic return get_cubic_bezier(r, c1, c2) end # @private # Cubic曲線(Cubic-bezier)を用いて補間用の割合を返す # @param [Number] t 元になる割合。0.0から1.0の範囲 # @param [Number] c1 曲線制御値その1 # @param [Number] c2 曲線制御値その2 # @return [Number] Cubic曲線に基づいた割合。0.0から1.0の範囲内で返す def get_cubic_bezier(t, c1, c2) # x1, y1 = 0.0, 0.0 # x2, y2 = (1.0 / 3.0), c1 # x3, y3 = (1.0 / 3.0) * 2, c2 # x4, y4 = 1.0, 1.0 # x = (t ** 3) * x4 + (3 * (t ** 2) * u) * x3 + (3 * t * (u ** 2)) * x2 + (u ** 3) * x1 # y = (t ** 3) * y4 + (3 * (t ** 2) * u) * y3 + (3 * t * (u ** 2)) * y2 + (u ** 3) * y1 u = 1.0 - t y = (t ** 3) + (3 * (t ** 2) * u) * c2 + (3 * t * (u ** 2)) * c1 return y end # @private # 割合に基づいて座標の補間値を求める # @param [Number] startv 開始値 # @param [Number] endv 終了値 # @param [Number] ratio 割合。0.0から1.0の範囲 # @return [Number] 補間値 def get_value_ratio(startv, endv, ratio) return endv if ratio == 1.0 return startv + ((endv - startv).to_f * ratio) end # @private # 割合に基づいて角度の補間値を求める # @param [Number] startv 開始値 # @param [Number] endv 終了値 # @param [Number] ratio 割合。0.0から1.0の範囲 # @param [Integer] spin 回転方向指示用。0,1,-1があり得るが現在未使用 # @return [Number] 補間値 def get_value_ratio_angle(startv, endv, ratio, spin = nil) return endv if ratio == 1.0 # angle speed = -180 ... +180 a = (endv - startv).to_f a += 360.0 if a < 180.0 a -= 360.0 if a > 180.0 return startv + a * ratio end # @private # ボーンや画像の位置、角度等の値を補間して求める # @param [Hash] ml mainlineデータ # @param [Integer] id ボーン、もしくは画像のID # @param [Integer] ct 現在時間 # @param [Boolean] is_bone trueならボーン、falseなら画像が対象 # @param [Boolean] is_keyframe trueならキーフレームのみを処理、falseなら補間処理 # @return [Hash] 該当ボーン、もしくは該当画像の、位置、角度、その他をハッシュで返す def timeline_now(ml, id, ct, is_bone = true, is_keyframe = false) mld = (is_bone)? ml[:bone_ref][id] : ml[:object_ref][id] nowkid = mld[:key].to_i t = @animation[:timeline][mld[:timeline].to_i] k0 = t[:key][nowkid] d0 = k0[:data] # 親の情報を取得 parent = (mld.has_key?(:parent))? mld[:parent].to_i : nil p = nil if parent != nil p = timeline_now(ml, parent, ct, true, is_keyframe) else p = get_root_bone end # 現在のキーフレーム情報を取得 x, y, ang = d0[:x], d0[:y], d0[:angle] scale_x, scale_y = d0[:scale_x], d0[:scale_y] a = d0[:a] pvx, pvy = 0, 0 pvx, pvy = d0[:pivot_x], d0[:pivot_y] unless is_bone cv, c1, c2 = ml[:curve_type], ml[:c1], ml[:c2] if !is_keyframe and t[:key].length > 1 and k0[:time] != ct # 次のキーフレーム情報を取得 nextkid = (nowkid + 1) % t[:key].length k1 = t[:key][nextkid] d1 = k1[:data] # 補間種類を決定 if k0.has_key?(:curve_type) cv, c1, c2 = k0[:curve_type], k0.fetch(:c1, 0), k0.fetch(:c2, 0) elsif p != nil if p.has_key?(:curve_type) cv, c1, c2 = p[:curve_type], p.fetch(:c1, 0), p.fetch(:c2, 0) end end # 補間の割合を得る r = get_ratio(ct, k0[:time], k1[:time], @anime_length, cv, c1, c2) # 補間値を得る ang = get_value_ratio_angle(ang, d1[:angle], r, k0[:spin]) x = get_value_ratio(x, d1[:x], r) y = get_value_ratio(y, d1[:y], r) scale_x = get_value_ratio(scale_x, d1[:scale_x], r) scale_y = get_value_ratio(scale_y, d1[:scale_y], r) a = get_value_ratio(a, d1[:a], r) unless is_bone pvx = get_value_ratio(pvx, d1[:pivot_x], r) pvy = get_value_ratio(pvy, d1[:pivot_y], r) end end if p != nil # 親の情報を反映 x *= p[:scale_x] y *= p[:scale_y] rad = p[:angle] * Math::PI / 180.0 nx = x * Math.cos(rad) - y * Math.sin(rad) ny = x * Math.sin(rad) + y * Math.cos(rad) x = nx + p[:x] y = ny + p[:y] scale_x *= p[:scale_x] scale_y *= p[:scale_y] ang = p[:angle] + ((scale_x * scale_y < 0)? -ang : ang) ang %= 360.0 a *= p[:a] end if is_bone r = { :name => t[:name], :spin => k0[:spin], :x => x, :y => y, :angle => ang, :scale_x => scale_x, :scale_y => scale_y, :a => a, :curve_type => cv, :c1 => c1, :c2 => c2 } else r = { :name => t[:name], :spin => k0[:spin], :x => x, :y => y, :angle => ang, :scale_x => scale_x, :scale_y => scale_y, :a => a, :curve_type => cv, :c1 => c1, :c2 => c2, :pivot_x => pvx, :pivot_y => pvy, :z_index => mld[:z_index], :folder => d0[:folder], :file => d0[:file] } end return r end # @private # 水平垂直反転や全体の回転角度を決めるroot相当のボーン情報を返す def get_root_bone scale_x = (@hflip)? -1.0 : 1.0 scale_y = (@vflip)? -1.0 : 1.0 ang = @angle % 360.0 # ゲーム用途なら、反転→回転のほうが使いやすそうなので、以下の処理はしない。 # ang *= -1 if (scale_x * scale_y) < 0 r = { :name => nil, :spin => 1.0, :x => 0, :y => 0, :angle => ang, :scale_x => scale_x, :scale_y => scale_y, :a => 1.0, } return r end # 内部の現在時間を1フレーム分進める # @param [Number] fps_value FPS値。デフォルトは60 # @param [Boolean] is_inc trueなら1フレーム進める、falseなら1フレーム戻す # @note 内部で管理してる現在時間等は、実際の時間ではない。 # 60FPSなら本来1フレームは16.67msだが、内部では16msで計算してる。 # Spriterが、60FPSピッタリでデータを作成できないため。 # @note 呼んだ途端に1フレーム分進んでしまうので、 # 先頭フレームをちゃんと描画したいなら、 # 描画前ではなく描画後に呼んだほうがいい。 def update(fps_value = 60.0, is_inc = true) ct = @current_time iv = (1000.0 / fps_value.to_f).truncate if is_inc ct = ct + iv ct = 0 if ct >= @anime_length else ct = ct - iv ct += @anime_length if ct < 0 end @current_time = ct @keyframe = get_keyframe_id(@current_time) end # アニメを描画 # @param [Number] bx 描画位置 x # @param [Number] by 描画位置 y # @param [Number] z 描画優先順位。値が小さければ奥に、大きければ手前に描画 # @param [Boolean] bone_draw trueならボーンも描画、falseなら画像のみ描画 # @param [Boolean] kf_only trueならキーフレームのみ描画、falseなら補完して描画 def draw(bx, by, z = 0, bone_draw = false, kf_only = false) ml = @animation[:mainline]["key"][@keyframe] ct = (kf_only)? 0 : @current_time # draw bone if bone_draw ml[:bone_ref].each_key do |id| d = timeline_now(ml, id, ct, true, kf_only) x = d[:x] y = d[:y] name = d[:name] if name != nil img = @entity[@entity_name][:obj_info][name][:image] Window.drawEx(bx + x, by - y, img, :z => z + 20, :center_x => 0, :center_y => img.height / 2, :scale_x => d[:scale_x], :scale_y => d[:scale_y], :angle => -d[:angle], :offset_sync => true, :alpha => d[:a] * 255) # debug_draw_cross(bx + x, by - y, C_CYAN, z + 21) end end end # draw image ml[:object_ref].each_key do |id| d = timeline_now(ml, id, ct, false, kf_only) imgdt = @folder[d[:folder]][d[:file]] img = imgdt[:image] x = d[:x] y = d[:y] pvx = (!imgdt.has_key?(:pivot_x) or d[:pivot_x] != 0.0)? d[:pivot_x] : imgdt[:pivot_x] pvy = (!imgdt.has_key?(:pivot_y) or d[:pivot_y] != 1.0)? d[:pivot_y] : imgdt[:pivot_y] pvx *= img.width pvy = (1.0 - pvy) * img.height Window.drawEx(bx + x, by - y, img, :z => z + d[:z_index], :center_x => pvx, :center_y => pvy, :scale_x => d[:scale_x], :scale_y => d[:scale_y], :angle => -d[:angle], :offset_sync => true, :alpha => d[:a] * 255) # debug_draw_cross(bx + x, by - y, C_CYAN, z + 21) end end # 開発用。使用画像を描画 def debug_dump_disp_image x, y = 128, 240 @folder.each do |id, files| files.each do |fid, d| name = d["name"] w, h = d["width"], d["height"] px, py = d["pivot_x"], d["pivot_y"] Window.draw(x, y, d["image"]) draw_cross_line(x + px, y + py, C_CYAN) x += 64 end end end # 開発用。指定された座標に十字を描画 # @param [Number] x x座標 # @param [Number] y y座標 # @param [Array] col 色配列。[R,G,B]。それぞれ0から255の範囲で指定 # @param [Number] z 描画優先順位 def debug_draw_cross(x, y, col, z = 0) w = 3 Window.drawLine(x-w, y, x+w, y, col, z) Window.drawLine(x, y-w, x, y+w, col, z) end # 開発用。画面にグリッドを描画 # @param [Number] bx 原点描画x座標 # @param [Number] by 原点描画y座標 def debug_draw_grid(bx, by) w, h = Window.width, Window.height sw, sh = 16, 16 c = 128 col = [128, c, c, c] bx.step(w, sw) { |x| Window.drawLine(x, 0, x, h, col) } bx.step(0, -sw) { |x| Window.drawLine(x, 0, x, h, col) } by.step(h, sh) { |y| Window.drawLine(0, y, w, y, col) } by.step(0, -sh) { |y| Window.drawLine(0, y, w, y, col) } col = [128, 0, 0] Window.drawLine(bx, 0, bx, h, col) Window.drawLine(0, by, w, by, col) end end if $0 == __FILE__ # 動作確認用 modenum = 1 case modenum when 0 # ---------------------------------------- # 利用例 ani = SpriterSconParser.new("img00/girl_spriter.scon") ani.set_anime("default", "run") Window.loop do break if Input.keyPush?(K_ESCAPE) ani.draw(320, 240 + 120) ani.update end when 1 # ---------------------------------------- # アニメパターン表示テスト if ARGV.length != 1 puts "usage: ruby #{$0} IN_FILE" puts " IN_FILE .scon file (Spriter save file)" exit end scon_file = ARGV[0] ani = SpriterSconParser.new(scon_file) is_bone = false mode_list = ["keyframe", "anime", "frame"] mode_id = 0 fnt = Font.new(18, "Consolas") # fnt = Font.new(14, "Lucida Console") entname = ani.entity_list[0] animelist = ani.anime_list(entname) aid = 0 ani.set_anime(entname, animelist[aid]) bgcol = [ [C_BLACK, C_WHITE], [[96, 96, 96], C_WHITE], [C_WHITE, C_BLACK], [C_GREEN, C_BLACK], [C_BLUE, C_WHITE] ] bgcoln = 0 basex, basey = 320, 240 # main loop Window.loop do break if Input.keyPush?(K_ESCAPE) # scroll basex -= 32 if Input.keyPush?(K_A) basex += 32 if Input.keyPush?(K_D) basey -= 32 if Input.keyPush?(K_W) basey += 32 if Input.keyPush?(K_S) # anime pattern inc or dec if Input.keyPush?(K_UP) aid = (aid + 1) % animelist.length ani.set_anime(entname, animelist[aid]) end if Input.keyPush?(K_DOWN) aid = (aid - 1 + animelist.length) % animelist.length ani.set_anime(entname, animelist[aid]) end # bone display on/off is_bone = !is_bone if Input.keyPush?(K_B) # h, v flip on/off if Input.keyPush?(K_H) or Input.keyPush?(K_V) ani.hflip = !ani.hflip if Input.keyPush?(K_H) ani.vflip = !ani.vflip if Input.keyPush?(K_V) end # root angle change if Input.keyDown?(K_E) or Input.keyDown?(K_Q) ani.angle += 1 if Input.keyDown?(K_E) ani.angle -= 1 if Input.keyDown?(K_Q) ani.angle %= 360.0 end # play mode change if Input.keyPush?(K_M) mode_id = (mode_id + 1) % mode_list.length end # background color change if Input.keyPush?(K_C) bgcoln = (bgcoln + 1) % bgcol.length Window.bgcolor = bgcol[bgcoln][0] end # reset time if Input.keyPush?(K_R) ani.hflip = false ani.vflip = false ani.angle = 0 ani.reset_time end # draw grid ani.debug_draw_grid(basex, basey) # draw image case mode_list[mode_id] when "keyframe" # keyframe id inc or dec ani.dec_keyframe if Input.keyPush?(K_LEFT) ani.inc_keyframe if Input.keyPush?(K_RIGHT) # draw keyframe only ani.draw(basex, basey, 20, is_bone, true) when "frame" ani.update(60, false) if Input.keyPush?(K_LEFT) ani.update(60, true) if Input.keyPush?(K_RIGHT) # draw anime ani.draw(basex, basey, 20, is_bone) when "anime" # draw anime ani.draw(basex, basey, 20, is_bone) ani.update(60, true) end # dump keyframe time list pp ani.keyframe_list if Input.keyPush?(K_P) # draw information if true y = 4 col = bgcol[bgcoln][1] hstr = (ani.hflip)? "On" : "off" vstr = (ani.vflip)? "On" : "off" [ sprintf("keyframe: %4d / time: %5d/%5d ms", ani.keyframe, ani.current_time, ani.anime_length), "up down) anime pat +/- [#{aid.to_s}: #{ani.anime_name}]", "m) play mode [#{mode_list[mode_id]}]", "right left) +/-", "h) H flip [#{hstr}]", "v) V flip [#{vstr}]", "e q) angle +/- [#{ani.angle}]", "b) bone", "c) bg color", "p) dump time list", "r) reset", "wasd) scroll", "ESC) exit" ].each do |s| Window.drawFont(8, y, s, fnt, :color => col, :z => 100) y += 20 end end # draw CPU load (red bar) cpuload = Window.getLoad() * Window.height / 100.0 Window.drawBoxFill(0, 0, 4, cpuload, C_RED) end end end