2014/05/31(土) [n年前の日記]
#1 [dxruby][cg_tools] DXRubyでswfアニメを再生する
DXRubyでswf(Flashアニメ)を再生してみたり。ビットマップ画像を表示するswfしか再生できないけど、ある程度動いてるので一応アップロード。
◎ スクリーンショット。 :
◎ 実験の動機。 :
「DXRubyに動画再生機能をつけてほしい」という要望をどこかで目にして、「動画ファイル再生は無理でも、swf再生ならできるんじゃないかな…。60FPSで画像をグリグリ動かせるんだし…」と思ったのがキッカケ。
実際使うかどうかは別にして、自分の思いつきが、本当に実現可能かどうか、なんだか気になったわけで。
実際使うかどうかは別にして、自分の思いつきが、本当に実現可能かどうか、なんだか気になったわけで。
◎ 処理の概要。 :
- swfmill で swf を xml に変換。
- Ruby + Nokogiri を使って xml を解析しつつ、DXRuby で描画。
◎ 制限事項。 :
以下は対応済み。
以下は未対応。
- ビットマップ画像の移動・拡大縮小・回転・傾斜・半透明表示
- 輝度変更 (明度変更)、色変更
- 簡素な階層のスプライト (MovieClip)
- 静止テキスト (デバイスフォント "_ゴシック", "_明朝", "_等幅" 等を指定していて、入れ子になってない簡素なもの)
以下は未対応。
- シェイプ(ベクターデータ)
- サウンド
- ActionScript
- 複雑な階層のスプライト(MovieClip)
- テキストの拡大縮小・回転・傾斜、入れ子構造
◎ ソースやサンプルデータ一式。ライセンス。 :
zip にして置いときます。
swfや画像のファイルサイズが大き過ぎたので、Dropbox に置いてみました。
Dropboxのサービス内容が一部変わったので自宅サーバに置き直しました。(2017/03/19)
_swf_parse_20140531.zip (67MB)
自分が書いたソースに関しては、Public Domain ってことでよろしくです。
_swf_parse_20140531.zip (67MB)
自分が書いたソースに関しては、Public Domain ってことでよろしくです。
◎ ソース。 :
_playxmlmovie.rb
使い方の例。
その他詳細は、 _readme.html を読んでください。
require "nokogiri" require "dxruby" require "find" require 'benchmark' # swfから変換したxmlを読み込んでDXRubyで再生するクラス # # @note swf から xml への変換は、swfmill を利用。 # @note DXRuby、Nokogiri が必要。 # @note License : Public domain # # @example # require "playxmlmove" # m = PlayXmlMovie.new("hoge.xml") # m.init # Window.loop do # break unless m.play # end # class PlayXmlMovie DBG = false TMDBG = false BMDBG = false # 論理ピクセル値 TWIP = 20.0 # ---------------------------------------- # 色変更をするためのShader # class SpriteShader < Shader hlsl = <<EOS float4 g_mul; float4 g_ofs; texture tex0; sampler Samp0 = sampler_state { Texture =<tex0>; }; float4 PS(float2 input : TEXCOORD0) : COLOR0 { float4 output; output = tex2D( Samp0, input ); output.r = (output.r * g_mul[0]) + g_ofs[0]; output.g = (output.g * g_mul[1]) + g_ofs[1]; output.b = (output.b * g_mul[2]) + g_ofs[2]; output.a = (output.a * g_mul[3]) + g_ofs[3]; return output; } technique { pass { PixelShader = compile ps_2_0 PS(); } } EOS @@core = Shader::Core.new(hlsl, {:g_mul => :float, :g_ofs => :float}) def initialize super(@@core, "TShader") self.g_mul = [1.0, 1.0, 1.0, 1.0] self.g_ofs = [0.0, 0.0, 0.0, 0.0] @col = [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0] class << self protected :g_mul, :g_mul=, :g_ofs, :g_ofs= end end def set_color(a) v = 256.0 self.g_mul = [ a[0] / v, a[1] / v, a[2] / v, a[3] / v] v = 255.0 self.g_ofs = [ a[4] / v, a[5] / v, a[6] / v, a[7] / v] @col = a end end # ---------------------------------------- # MATRIX情報格納用クラス # class TMatrix attr_accessor :trans_x, :trans_y attr_accessor :scale_x, :scale_y attr_accessor :skew_x, :skew_y attr_accessor :color def initialize(trans_x = 0, trans_y = 0, scale_x = 1, scale_y = 1, skew_x = 0, skew_y = 0, mul_r = 256, mul_g = 256, mul_b = 256, mul_a = 256, ofs_r = 0, ofs_g = 0, ofs_b = 0, ofs_a = 0) self.trans_x, self.trans_y = trans_x,trans_y self.scale_x, self.scale_y = scale_x, scale_y self.skew_x, self.skew_y = skew_x, skew_y self.color = [ mul_r, mul_g, mul_b, mul_a, ofs_r, ofs_g, ofs_b, ofs_a] end # # 属性値を更新 # # @param [Hash] v 属性と値のハッシュ # def set_value(v) self.trans_x = v["transX"].to_f if v.has_key?("transX") self.trans_y = v["transY"].to_f if v.has_key?("transY") self.scale_x = v["scaleX"].to_f if v.has_key?("scaleX") self.scale_y = v["scaleY"].to_f if v.has_key?("scaleY") self.skew_x = (v.has_key?("skewX"))? v["skewX"].to_f : 0 self.skew_y = (v.has_key?("skewY"))? v["skewY"].to_f : 0 [ "factorRed", "factorGreen", "factorBlue", "factorAlpha", "offsetRed", "offsetGreen", "offsetBlue", "offsetAlpha" ].each_with_index do |s, i| self.color[i] = v[s].to_f if v.has_key?(s) end end # # 座標変換 # # @param [Number] x x座標 # @param [Number] y y座標 # @return [Array] 座標変換後のx,y座標 # def trans(x, y) nx = (x * self.scale_x) + (y * self.skew_y) + self.trans_x ny = (x * self.skew_x) + (y * self.scale_y) + self.trans_y return nx, ny end # # 色変換パラメータを更新 # # @param [Array] c 色変更情報の配列 # @return [Array] 変更後の色変更情報配列 # def trans_color(c) nc = Array.new(8) for i in 0..3 do nc[i] = c[i] * self.color[i] / 256.0 nc[i] = 512 if nc[i] > 512 nc[i] = -512 if nc[i] < -512 end for i in 4..7 do nc[i] = c[i] + self.color[i] nc[i] = 255 if nc[i] > 255 nc[i] = -256 if nc[i] < -256 end return nc end # # 色変換が必要かどうかを返す # # @param [Array] c 色変換情報配列 # @return [Boolean] trueなら色変換が必要。falseなら色変換は不要。 # def TMatrix.trans_color?(c) [ [0, 256.0], [1, 256.0], [2, 256.0], [4, 0.0], [5, 0.0], [6, 0.0] ].each do |n| return true if c[n[0]] != n[1] end return false end # # 色変換情報を格納するハッシュを初期化する # # @param [Hash] v 色変換情報格納用ハッシュ # @return [Hash] 初期化したハッシュを返す # def TMatrix.init_color(v) v.merge!({ "factorRed" => 256, "factorGreen" => 256, "factorBlue" => 256, "factorAlpha" => 256, "offsetRed" => 0, "offsetGreen" => 0, "offsetBlue" => 0, "offsetAlpha" => 0 }) return v end end # ---------------------------------------- # 画像を格納するキャラobjクラス # class CharaObjImage attr_accessor :id, :src, :image def initialize(id, fname) self.id = id self.src = fname self.image = Image.load(fname) # puts "id[#{id}] Load Image ok #{fname}" end end # ---------------------------------------- # シェイプを格納するクラス # class CharaObjShape attr_accessor :id, :imgs def initialize(id, imgs) self.id = id self.imgs = imgs end end # ---------------------------------------- # スプライト(MovieClip)を格納するクラス # class CharaObjSprite attr_accessor :id attr_accessor :start_node def initialize(id, start_node) self.id = id self.start_node = start_node end end # ---------------------------------------- # フォント名を格納するクラス # class CharaObjFont attr_accessor :id, :fontname def initialize(id, fontname) self.id = id self.fontname = fontname puts "id[#{id}] font [#{fontname}]" if PlayXmlMovie::DBG end end # ---------------------------------------- # テキストを格納するクラス # class CharaObjText attr_accessor :id, :font, :text, :size, :fontname, :col def initialize(id, text, size, fontname, col = [0, 0, 0, 255]) self.id = id self.text = text self.size = size self.fontname = fontname self.font = Font.new(self.size, fontname) self.col = col if PlayXmlMovie::DBG puts "id[#{id}] DefineEditText \"#{text}\",#{size},\"#{fontname}\"" end end end # ---------------------------------------- # 描画用クラス # class MySprite attr_accessor :objs attr_accessor :cobj attr_accessor :pobj attr_accessor :z attr_accessor :mat attr_accessor :sprs attr_accessor :rt attr_accessor :shader attr_accessor :enable_loop # コンストラクタ # # @param [Hash] objs キャラobj情報が入ったハッシュ # @param [Number] cid 発生させたいキャラobjのID # @param [Object] pobj 親スプライト。nilなら親は居ない # @param [Number] depth 描画深度 # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ # def initialize(objs, cid, pobj, depth, v) self.objs = objs self.cobj = self.objs[cid] self.pobj = pobj self.z = depth self.mat = TMatrix.new self.sprs = Hash.new self.rt = nil self.shader = nil update(depth, v) end # 描画に必要な値を設定 # # @param [depth] depth 描画深度 # @param [Hash] v MATRIX情報と半透明度情報を格納したハッシュ # def update(depth, v) self.z = depth self.mat.set_value(v) end # RenderTargetを解放 # def dispose if self.sprs != nil self.sprs.keys {|k| self.sprs[k].dispose } end if self.rt != nil self.rt.dispose unless self.rt.disposed? end end # 拡大縮小、半透明、回転描画 # # @param [Object] img Imageクラス # @param [Object] im_mat TMatrixクラス # def draw_image(img, img_mat) pobj = self.pobj # 矩形の4点を配列にする w = img.width h = img.height p = [0, 0, w, 0, w, h, 0, h] c = Marshal.load(Marshal.dump(self.mat.color)) # Shapeに割り当てられたMATRIXで変形 4.times do |i| p[i * 2], p[i * 2 + 1] = img_mat.trans(p[i * 2], p[i * 2 + 1]) end # 自身のMATRIXで変形 4.times do |i| p[i * 2], p[i * 2 + 1] = self.mat.trans(p[i * 2], p[i * 2 + 1]) end # 親スプライトのMATRIXで変形 while pobj != nil 4.times do |i| p[i * 2], p[i * 2 + 1] = pobj.mat.trans(p[i * 2], p[i * 2 + 1]) end c = pobj.mat.trans_color(c) pobj = pobj.pobj end # 論理ピクセル値から画面座標に変換 8.times {|i| p[i] /= PlayXmlMovie::TWIP } if TMatrix.trans_color?(c) # 色変換が必要 # 色変更用のShaderはアルファチャンネル付き画像描画がおかしいので、 # 色変更が必要な時だけ使うことにする。 self.rt = RenderTarget.new(Window.width, Window.height) if self.rt == nil self.shader = SpriteShader.new if self.shader == nil self.rt.drawMorph(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], img, :dividex => 3, :dividey => 3) self.rt.update self.shader.set_color(c) Window.drawEx(0, 0, self.rt, :shader => self.shader) else # 色変換は不要 a = (255 * c[3] / 256.0).to_i if a != 255 # 半透明描画 Window.drawMorph(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], img, :dividex => 3, :dividey => 3, :alpha => a, :blend => :alpha) else # 不透明描画 Window.drawMorph(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], img, :dividex => 3, :dividey => 3) end end end # フォントを描画 # # @param [String] text 描画文字列 # @param [Object] font Fontオブジェクト # @param [Array] c RGBA配列 # def draw_font(text, font, c) x = self.mat.trans_x / PlayXmlMovie::TWIP y = self.mat.trans_y / PlayXmlMovie::TWIP Window.drawFontEx(x, y, text, font, :color => [c[0], c[1], c[2]], :alpha => c[3], :blend => :alpha, :aa => true) end # 再生描画処理 # # @param [Hash] objs キャラobj情報が入ったハッシュ # @return [Boolean] trueなら再生中, falseなら再生終了 # def play return true end end # ---------------------------------------- # 画像描画obj # class ImageObj < MySprite # コンストラクタ # # @param [Hash] objs キャラobj情報が入ったハッシュ # @param [Number] cid 発生させたいキャラobjのID # @param [Object] pobj 親スプライト。nilなら親は居ない # @param [Number] depth 描画深度 # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ # def initialize(objs, cid, pobj, depth, v) super(objs, cid, pobj, depth, v) end # 再生描画処理 # # @return [Boolean] trueなら再生中, falseなら再生終了 # def play draw_image(self.cobj.image, TMatrix.new) return true end end # ---------------------------------------- # シェイプ描画obj # class ShapeObj < MySprite # コンストラクタ # # @param [Hash] objs キャラobj情報が入ったハッシュ # @param [Number] cid 発生させたいキャラobjのID # @param [Object] pobj 親スプライト。nilなら親は居ない # @param [Number] depth 描画深度 # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ # def initialize(objs, cid, pobj, depth, v) super(objs, cid, pobj, depth, v) end # 再生描画処理 # # @return [Boolean] trueなら再生中, falseなら再生終了 # def play self.cobj.imgs.each do |v| draw_image(v[0].image, v[1]) end return true end end # ---------------------------------------- # スプライト描画obj # class SpriteObj < MySprite attr_accessor :node attr_accessor :now_frame attr_accessor :enable_loop # コンストラクタ # # @param [Hash] objs キャラobj情報が入ったハッシュ # @param [Number] cid 発生させたいキャラobjのID # @param [Object] cobj 発生させたいキャラobj # @param [Object] pobj 親スプライト。nilなら親は居ない # @param [Number] depth 描画深度 # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ # @param [Boolean] looped trueならループ再生 # def initialize(objs, cid, pobj, depth, v, looped = true) super(objs, cid, pobj, depth, v) self.node = cobj.start_node self.now_frame = 0 self.enable_loop = looped end # 再生描画処理 # # @return [Boolean] trueなら再生中, falseなら再生終了 # def play pobj = self endfg = false until endfg case self.node.name when "PlaceObject2" # シェイプやスプライトを配置、もしくは移動 nn = self.node rn = nn["replace"].to_i depth = nn["depth"].to_i id = nn["objectID"] id = id.to_i if id != nil # 属性を取り出す v = {} nn.children.each do |n| n.children.each do |m| if m.name == "Transform" # 変形情報 m.keys.each do |k| a = m.attribute(k) v[a.name] = a.value.to_f end elsif m.name == "ColorTransform2" # 色変換情報 v = TMatrix.init_color(v) m.keys.each do |k| a = m.attribute(k) v[a.name] = a.value.to_f end end end end if id != nil abort "Error: Not found ChrObj[#{id}]" unless self.objs.has_key?(id) case self.objs[id] when PlayXmlMovie::CharaObjImage self.sprs[depth] = ImageObj.new(self.objs, id, pobj, depth, v) when PlayXmlMovie::CharaObjShape self.sprs[depth] = ShapeObj.new(self.objs, id, pobj, depth, v) when PlayXmlMovie::CharaObjSprite self.sprs[depth] = SpriteObj.new(self.objs, id, pobj, depth, v) when PlayXmlMovie::CharaObjText self.sprs[depth] = TextObj.new(self.objs, id, pobj, depth, v) end elsif rn == 1 self.sprs[depth].update(depth, v) if self.sprs.has_key?(depth) end if TMDBG s = "PLACE, replace=#{rn}, depth=#{depth}, id=#{id}, {" v.each_key {|k| s += "#{k} => #{v[k]}, "} s += "}" puts s end when "RemoveObject2" # シェイプやスプライトを削除 depth = node["depth"].to_i if self.sprs.has_key?(depth) self.sprs[depth].dispose self.sprs.delete(depth) end puts "REMOVE, depth=#{depth}" if TMDBG when "ShowFrame" # 現在フレームを描画 self.now_frame += 1 puts "ShowFrame #{now_frame}" if TMDBG endfg = true when "End" # タグが無くなった endfg = true puts "End" if TMDBG break else # puts node_name end # 次のノードを読む self.node = self.node.next end # ウインドウに描画 key_list = self.sprs.keys.sort # depthでソート key_list.each {|k| self.sprs[k].play } return true if self.node.name != "End" # 再生終了 sprs = Hash.new if self.enable_loop self.node = self.cobj.start_node self.now_frame = 0 end return false end end # ---------------------------------------- # テキスト描画obj # class TextObj < MySprite # コンストラクタ # # @param [Hash] objs キャラobj情報が入ったハッシュ # @param [Number] cid 発生させたいキャラobjのID # @param [Object] pobj 親スプライト。nilなら親は居ない # @param [Number] depth 描画深度 # @param [Hash] v MATRIX情報+半透明度を格納したハッシュ # def initialize(objs, cid, pobj, depth, v) super(objs, cid, pobj, depth, v) end # 再生描画処理 # # @return [Boolean] trueなら再生中, falseなら再生終了 # def play o = self.cobj draw_font(o.text, o.font, o.col) return true end end # ---------------------------------------- # コンストラクタ # # @param [String] infile xmlファイルのパス # def initialize(infile) @infile = infile abort "Error : Not found #{infile}" unless File.exist?(infile) if BMDBG Benchmark.bm(15) do |x| x.report("xml parse:") { @doc = Nokogiri::XML(File.open(infile)) {|config| config.noblanks} } x.report("get fps") { @framerate, @frames = get_framerate(@doc) @wdw_w, @wdw_h = get_widnow_size(@doc) @bgcol = get_bgcolor(@doc) } @objs = Hash.new x.report("load image:") { load_image(@doc, infile) } x.report("load font:") { load_font(@doc) } x.report("load shape:") { load_shape(@doc) } x.report("load sprite:") { load_sprite(@doc) n = @doc.at("//swf/Header/tags/PlaceObject2") @objs[0] = CharaObjSprite.new(0, n) } end else # xmlを解析 @doc = Nokogiri::XML(File.open(infile)) {|config| config.noblanks} @framerate, @frames = get_framerate(@doc) @wdw_w, @wdw_h = get_widnow_size(@doc) @bgcol = get_bgcolor(@doc) # 画像、シェイプ、スプライトを登録 @objs = Hash.new load_image(@doc, infile) load_font(@doc) load_shape(@doc) load_sprite(@doc) # rootスプライトを登録 puts "\nid[0] DefineSprite" if DBG n = @doc.at("//swf/Header/tags/PlaceObject2") @objs[0] = CharaObjSprite.new(0, n) end end # # フレームレート、フレーム数を取得 # # @param [Object] doc xmlノード情報 # @return [Array] フレームレート, フレーム数を配列で返す # def get_framerate(doc) n = doc.at("//Header") fpsv = n["framerate"].to_i frms = n["frames"].to_i puts "framerate=#{fpsv} , frames=#{frms}" if DBG return fpsv, frms end # # 画面サイズを取得 # # @param [Object] doc xmlノード情報 # @return [Array] 画面横幅, 画面縦幅を配列で返す # def get_widnow_size(doc) n = doc.at("//Header/size/Rectangle") # x = n["left"].to_i / TWIP # y = n["top"].to_i / TWIP w = n["right"].to_i / TWIP h = n["bottom"].to_i / TWIP puts "Window size : #{w}, #{h}" if DBG return w, h end # # 背景色を取得 # # @param [Object] doc xmlノード情報 # @return [Array] 背景色red, green, blueを配列で返す # def get_bgcolor(doc) n = doc.at("//SetBackgroundColor/color/Color") c = [] ["red", "green", "blue"].each {|k| c.push(n[k].to_i)} return c end # # 画像をロード # # @param [Object] doc xmlノード情報 # @param [String] infile xmlファイルパス # def load_image(doc, infile) imglist = get_img_file_list(infile) # 画像ファイルパスの一覧を取得 # 検索するタグ名を列挙 taglst = "//DefineBitsJPEG2|//DefineBitsLossless2|//DefineBitsLossless" doc.xpath(taglst).each do |n| id = n["objectID"].to_i n.content = "" unless imglist.has_key?(id) # IDと一致する画像ファイルパスが存在しない puts "Not found image file : id = #{id}" next end # IDと一致する画像ファイルパスが存在する fn = imglist[id] n["src"] = fn @objs[id] = CharaObjImage.new(id, fn) puts "id[#{id}] Load Image : #{fn}" if DBG end end # # xmlファイルパスを元にして、全画像ファイルのパス一覧を取得 # # @param [String] xmlファイルパス # @return [Hash] 画像ファイルパスを格納したハッシュ # def get_img_file_list(infile) bsn = File.basename(infile, ".xml") # ファイルの拡張子を取り除いた文字列を取得 dir = File::dirname(infile) # ファイルのフォルダパスを取得 imglist = {} # サブフォルダ以下も探索して、拡張子が画像ファイル形式なら記録していく Find.find(dir) do |f| next unless FileTest.file?(f) imglist[$1.to_i] = f if /#{bsn}\[(\d+)\]\.(png|jpg|bmp|gif)/ =~ f end return imglist end # # フォント情報やテキスト情報を読み込み # # @param [Object] doc xmlノード情報 # def load_font(doc) lst = Hash.new lst.merge!( { "_ゴシック" => "MS UI Gothic", "_明朝" => "MS P明朝", "_等幅" => "MS ゴシック", "_sans" => "Arial", "_serif" => "Times New Roman", "_typewriter" => "Courier" }) # 使用している全フォント名を取得 doc.xpath("//DefineFont2|//DefineFont3").each do |n| id = n["objectID"].to_i fnt = n["name"].encode("Windows-31J") # xmlはutf-8なのでsjisに変換 if lst.has_key?(fnt) fnt = lst[fnt] @objs[id] = CharaObjFont.new(id, fnt) else @objs[id] = CharaObjFont.new(id, fnt) end end # 使用している静止テキスト情報を取得 doc.xpath("//DefineEditText").each do |n| id = n["objectID"].to_i fid = n["fontRef"].to_i # フォント種類ID abort "Error: Not found font [#{fid}]" unless @objs.has_key?(fid) size = n["fontHeight"].to_f / PlayXmlMovie::TWIP # Flash CS5で作成したswfは、initialText に html が入っている。 # htmlタグを除去して描画文字列を取得する nm = Nokogiri::HTML(n["initialText"]) text = nm.content.encode("Windows-31J") col = [0, 0, 0, 255] c = n.at(".//color/Color") if c != nil col[0] = c["red"].to_i col[1] = c["green"].to_i col[2] = c["blue"].to_i col[3] = c["alpha"].to_i end fontname = @objs[fid].fontname @objs[id] = CharaObjText.new(id, text, size, fontname, col) end end # # 画像を内包するShapeを生成 # # @param [Object] doc xmlノード情報 # def load_shape(doc) doc.xpath("//DefineShape|//DefineShape2").each do |n| id = n["objectID"].to_i sn = n.xpath(".//bounds/Rectangle")[0] # x = sn["left"].to_i / TWIP # y = sn["top"].to_i / TWIP w = sn["right"].to_i / TWIP h = sn["bottom"].to_i / TWIP imgs = [] bn = n.xpath(".//styles/StyleList/fillStyles") bn.xpath(".//ClippedBitmap|.//ClippedBitmap2").each do |m| img_id = m["objectID"].to_i if img_id == 65535 # objectID が 65535 の時がある。正体不明 puts "id[#{id}] Unknown Image [#{img_id}]" if DBG next end unless @objs.has_key?(img_id) # 画像が存在しない abort "Error : id[#{id}] Not found Image [#{img_id}] #{w} x #{h}" end # 画像が存在するので、シェイプに割り当てる # MATRXI情報も反映させる puts "id[#{id}] Shape = bitmap[#{img_id}]" if DBG dic = {} mn = m.at(".//matrix/Transform") mn.keys.each do |k| atr = mn.attribute(k) dic[atr.name] = atr.value.to_f end v = TMatrix.new v.set_value(dic) imgs.push([@objs[img_id], v]) end @objs[id] = CharaObjShape.new(id, imgs) end end # # スプライトを登録 # def load_sprite(doc) doc.xpath("//DefineSprite").each do |n| id = n["objectID"].to_i frames = n["frames"].to_i puts "\nid[#{id}] DefineSprite, frames=#{frames}" if DBG node = n.at(".//PlaceObject2") @objs[id] = CharaObjSprite.new(id, node) end end # 再生開始のための初期化処理 # # @note fpsや背景色が変更されるので、再生が終わったら本来のfps・背景色に戻すこと # # @param [Boolean] init_size ウインドウサイズをxmlに従って変更するかどうか # @param [Boolean] init_fps フレームレートをxmlに従って変更するかどうか # @param [Boolean] init_bgcol 背景色をxmlに従って変更するかどうか # def init(init_size = false, init_fps = true, init_bgcol = true) @now_frame = 0 @root_sprite = SpriteObj.new(@objs, 0, nil, 0, {}) Window.resize(@wdw_w, @wdw_h) if init_size Window.fps = @framerate if init_fps Window.bgcolor = [@bgcol[0], @bgcol[1], @bgcol[2]] if init_bgcol end # 再生描画処理 # # @note メインループから毎フレーム呼ぶこと # # @return [Boolean] trueなら再生中, falseなら再生終了 # def play return @root_sprite.play end # # 多少整理したxmlを標準出力にダンプする # def dump puts @doc.to_xml end end if $0 == __FILE__ # ---------------------------------------- # 使い方の例 m = PlayXmlMovie.new("swfdata/movie0.xml") # m = PlayXmlMovie.new("swfdata/movie1.xml") # m = PlayXmlMovie.new("swfdata/movie2.xml") # m = PlayXmlMovie.new("swfdata/movie3.xml") # m = PlayXmlMovie.new("swfdata/movie4.xml") # 再生開始のための初期化 # fpsや背景色が変更されるので、 # 再生が終わったら本来のfps・背景色に戻すこと m.init # メインループ Window.loop do break if Input.keyPush?(K_ESCAPE) # ESCキーで終了 # 1フレーム分描画 unless m.play m.init # 再生が終了したら、再度頭から再生 end end end
使い方の例。
require_relative "playxmlmovie" m = PlayXmlMovie.new("hoge.xml") m.init Window.loop do break unless m.play end
その他詳細は、 _readme.html を読んでください。
◎ 備考。 :
サンプルswf内の静止画像作成については、
_マウスコンピューター G-Tuneシリーズ公式キャラクター G-Tuneちゃん (Tuneちゃん?)
を使わせてもらいました。…と書いておけばいいのかな? リンクも貼ってあるから、これで利用条件は満たしてるはず。たぶん。
◎ ここまで書いといてアレだけど。 :
自分でコレを使うかというと、たぶん使わないような気がする…。たしかにグリグリ動いたけど…。
ただ、コレを書いてみたことで、巷で見かける swf → HTML5 + JavaScript 変換ツールが、大体どんな感じのことをしているのか、そのあたりぼんやり分かってきたように思います。結構勉強になったというか。
Ruby でSWFバイナリを直接扱える、 _SwfRuby なるライブラリも使えたら、swfmill で一旦xmlに変換しなくてもいいのかもしれんですけど。
ただ、コレを書いてみたことで、巷で見かける swf → HTML5 + JavaScript 変換ツールが、大体どんな感じのことをしているのか、そのあたりぼんやり分かってきたように思います。結構勉強になったというか。
Ruby でSWFバイナリを直接扱える、 _SwfRuby なるライブラリも使えたら、swfmill で一旦xmlに変換しなくてもいいのかもしれんですけど。
- SwfRuby は RMagick が必要。
- しかし、Windows で RMagick を導入するのは面倒。ImageMagick の特定バージョンを要求される。
◎ 2017/03/19追記。 :
DropboxのPublicフォルダが死んだのでファイルを置き直し。
[ ツッコむ ]
#2 [flash][neta] Flashについて(余計な)一言言っとくか
「今頃Flashかよwww」と言い出す人が居そうな気もするので、一応、今の自分の考えをメモ。
◎ Flashにも色々あって。 :
Flashにも色々あって。
Webブラウザ上で再生するための Flash (swf) は、たしかに死滅寸前だと自分も思っているのですが。
しかし、「簡易アニメ制作ソフト」としての Flash や、別の最終フォーマットに変換するための「中間ファイル」としての Flash (swf)は、今でも使えるわけで。
例えばの話。psdファイルはブラウザで開けないけど。だからと言って「Photoshopは死んだ」と口走ってたら馬鹿丸出し。同様に、制作環境としての Flash は、今後も(しばらくは)選択肢としてフツーにアリで。Webブラウザで開けないからソレは死んだ、てな結論は、あまりに視野が狭過ぎる。
それに、昔は Adobe製の有償製品しか無かったですけど、今はフリーで使えるFlash制作ソフトも存在してますから、お金の無い学生さんにも優しいし。これがもし、HTML5 + JavaScript用の何かを吐き出してくれる簡易アニメ制作ソフトだったら、Flash と比べたら、まだちょっと弱いよなと。
とは言え、ならばFlashがアニメ制作ツールとして至高の存在かと問われたら、自分は首を捻ってしまうのですけど。どうしてあんなに使いづらいのですかね…。たぶん、絵描きさんの視点で作ってないよな…。古い版しか触ったことないけど、最新版は改良されてるのだろうか…。
- 最終的な再生ファイル形式としての Flash (swf)
- 中間ファイルとしての Flash (swf)
- アニメ制作アプリとしての Flash
- デスクトップアプリを作成できる基幹技術としての Flash (Adobe AIR)
- その他色々。
Webブラウザ上で再生するための Flash (swf) は、たしかに死滅寸前だと自分も思っているのですが。
- 皆が持ってるスマホのブラウザ上で再生できないってのは、やっぱり痛い。
- アニメを再生したいなら、そもそも動画ファイルを再生しちゃえばいい。
しかし、「簡易アニメ制作ソフト」としての Flash や、別の最終フォーマットに変換するための「中間ファイル」としての Flash (swf)は、今でも使えるわけで。
例えばの話。psdファイルはブラウザで開けないけど。だからと言って「Photoshopは死んだ」と口走ってたら馬鹿丸出し。同様に、制作環境としての Flash は、今後も(しばらくは)選択肢としてフツーにアリで。Webブラウザで開けないからソレは死んだ、てな結論は、あまりに視野が狭過ぎる。
それに、昔は Adobe製の有償製品しか無かったですけど、今はフリーで使えるFlash制作ソフトも存在してますから、お金の無い学生さんにも優しいし。これがもし、HTML5 + JavaScript用の何かを吐き出してくれる簡易アニメ制作ソフトだったら、Flash と比べたら、まだちょっと弱いよなと。
とは言え、ならばFlashがアニメ制作ツールとして至高の存在かと問われたら、自分は首を捻ってしまうのですけど。どうしてあんなに使いづらいのですかね…。たぶん、絵描きさんの視点で作ってないよな…。古い版しか触ったことないけど、最新版は改良されてるのだろうか…。
◎ 何が作れるか云々。 :
「Flashでろくなアニメなんか作れるわけねえだろwww」とdisる人も居るかもしれないなと。そういう人は、URA氏がディレクターをした「猫物語」OPや「ニセコイ」の2番目のOPを見るべき。
*1
有名アニメーターのりょーちも氏の仕事ぶりも確認すべき。
「Flashでろくなアニメなんか」発言は、「Photoshopを買えばボクもイラストレーターになれる」発言と大差ないよなと。どのツールを使うかで作品の中身が劇的に変わるようなら誰も苦労しねえんだよクソが! と思うわけですよ。ツールの選択で変わってくるのは、作業効率ぐらいなもんでして。
これがもし、「Flashでアニメ作るの結構シンドイよ」という発言だったら、自分もちょっと同意するかも。なんだかサクサクと作業できないというか、トライ&エラーが面倒臭いUI仕様があちこちに点在していて…。
「Flashでろくなアニメなんか」発言は、「Photoshopを買えばボクもイラストレーターになれる」発言と大差ないよなと。どのツールを使うかで作品の中身が劇的に変わるようなら誰も苦労しねえんだよクソが! と思うわけですよ。ツールの選択で変わってくるのは、作業効率ぐらいなもんでして。
これがもし、「Flashでアニメ作るの結構シンドイよ」という発言だったら、自分もちょっと同意するかも。なんだかサクサクと作業できないというか、トライ&エラーが面倒臭いUI仕様があちこちに点在していて…。
◎ JavaScriptも死にかけてましたな。 :
JavaScript の歴史を思い返せば、「今頃Flashかよ」発言って危ないよなと。一時期、JavaScript って死滅寸前でしたよ。まさかこういう使い方もできたとは。当時 JavaScript をdisってた人達って、一体何なんだろうと…。自分もその中に含まれてますけど。
なので、Flash も、「え? そんな場所で、そんな使い方が?」てな展開があるかもしれないなと。そもそも、最初はアニメ制作ソフトだったはずが、どうして ActionScript が云々、てな話に…。既に現状ですら、俺の知ってる Flash と違う…。
それにしても、HTML5 + JavaScript で書いた何かしらが、ブラウザの互換性の問題で大変なことになってる話を見かけるたびに、「Flash が使えたら、こんなことにはならなかったのに…」と思えてしまって。Adobeだけが互換性問題で苦労して、こっちは楽ができたのになあ。ジョブズを筆頭に、無意味に Flash をdisってた人達のせいで、より多くのプログラマー・デザイナーが地獄を見る展開に。
なので、Flash も、「え? そんな場所で、そんな使い方が?」てな展開があるかもしれないなと。そもそも、最初はアニメ制作ソフトだったはずが、どうして ActionScript が云々、てな話に…。既に現状ですら、俺の知ってる Flash と違う…。
それにしても、HTML5 + JavaScript で書いた何かしらが、ブラウザの互換性の問題で大変なことになってる話を見かけるたびに、「Flash が使えたら、こんなことにはならなかったのに…」と思えてしまって。Adobeだけが互換性問題で苦労して、こっちは楽ができたのになあ。ジョブズを筆頭に、無意味に Flash をdisってた人達のせいで、より多くのプログラマー・デザイナーが地獄を見る展開に。
*1: 今もFlash使ってるか分からんし、もしかするとAEなのかなと思わないでもないけど、動かし方やシルエットで見せていくソレはどう見てもFlashアニメ文化だよなと。
この記事へのツッコミ
[ ツッコミを読む(1) | ツッコむ ]
#3 [cg_tools] Google Web Designer、Adobe Edge Animate を試用
前々からどういうツールか気になってたので試用してみようかなと。ちなみに環境は、Windows7 x64。
◎ 結論から先に書く。 :
個人的に、この手のツールは以下の2点が気になっていて。
連番画像を出力する機能はどちらも持ってなかった。しかしこれは、ツールの性質からして仕方ないのかも。
これらのツールは、Flashと違って、タイムラインには時間しか表示されてなくて。つまり、「フレーム」「コマ」という概念が無い。フレームが無いのだから、フレームを連番画像として出力できるはずもなく。 *1 なので、現状では本質的に、既存のアニメ制作ツールと異なる存在かもしれないなと。「アニメ制作ツール」として紹介されることが多いけど、コレは、アニメ映像ではないアニメを作るツールではないかと。
また、出力ファイルは html だったり js だったりするのだけど…。出力ファイルは難読化がされていたりして、さっぱり読めないのだけど。作業ファイル自体は難読化がされてないので、まだなんとか頑張れば解析できそうな気もしたり。
- 連番画像をレンダリングできるか。これができれば、動画編集ソフト等に渡す素材や、スプライトシートが作れる。
- 出力したファイルを、Webブラウザ以外でも再生することができそうか。
連番画像を出力する機能はどちらも持ってなかった。しかしこれは、ツールの性質からして仕方ないのかも。
これらのツールは、Flashと違って、タイムラインには時間しか表示されてなくて。つまり、「フレーム」「コマ」という概念が無い。フレームが無いのだから、フレームを連番画像として出力できるはずもなく。 *1 なので、現状では本質的に、既存のアニメ制作ツールと異なる存在かもしれないなと。「アニメ制作ツール」として紹介されることが多いけど、コレは、アニメ映像ではないアニメを作るツールではないかと。
また、出力ファイルは html だったり js だったりするのだけど…。出力ファイルは難読化がされていたりして、さっぱり読めないのだけど。作業ファイル自体は難読化がされてないので、まだなんとか頑張れば解析できそうな気もしたり。
◎ Google Web Designerを試用した際のメモ。 :
_Google Web Designer
から、googlewebdesigner_win.exe をDL。897KBのファイルなので、たぶんダウンローダーなのだと思う。実行したら、おそらくは本体のダウンロードとインストールが行われた。
インストール直後の現在のバージョンは、1.0.6.0428。
アニメ作成作業は簡単。
ファイル → 公開を選べば、htmlファイルとして保存できる。何を出力してくれるのか、そこがそもそも分かってなかったのだけど。どうやらhtmlファイルを1つ出力するツールだった模様。
出力されたhtmlを眺めてゲロを吐きそうに。チンプンカンプン。
作業用に保存される html ファイルをぼんやり眺めていたら、なんだかちょっと分かってきた。
解析してどうにかしようという気にはならないけれど、やろうと思えばどうにかできるのかもしれない。バイナリファイルではなくテキストファイルで保存されてると、こういう時にありがたい。今時、バイナリファイルで出力することは極力避けるべきなのだと再認識しました。
連番画像の出力機能は無し。
現状、広告制作ツールのベータ版として公開されているのだし、CGツールとして捉えるほうが間違ってる気もしてきたり。見た目それっぽいけど、ヨチヨチ歩きみたいなものだよな…。どこかのまとめサイトで、「GoogleがAdobeとWebデザイナー殺しに来た」なんて煽りタイトル記事を見かけたけど。仮にそうだとしても、幼児が水鉄砲でピュピュッと撃ってる段階のような…。今後の成長に期待、みたいな。
インストール直後の現在のバージョンは、1.0.6.0428。
アニメ作成作業は簡単。
- 下のサムネイル横の「+」ボタンを押していけばキーフレームを増やせる。
- そのキーフレームでオブジェクトの位置変更等をすれば、その間の位置移動等を補間してくれる。
- サムネイルとサムネイルの間のボタンを押せば、何秒間、どんな動きをさせるのかグラフ表示してくれる。
- 秒数の変更もできるし、グラフ部分をマウスでクリックすれば動きの種類も順々に変わっていく。
- タイムライン上で右クリックすれば、キーフレームの挿入、動きの種類の変更などができる。
- タイムライン上でキーフレームのマークをドラッグすれば、キーフレーム位置を変更できる。
ファイル → 公開を選べば、htmlファイルとして保存できる。何を出力してくれるのか、そこがそもそも分かってなかったのだけど。どうやらhtmlファイルを1つ出力するツールだった模様。
出力されたhtmlを眺めてゲロを吐きそうに。チンプンカンプン。
作業用に保存される html ファイルをぼんやり眺めていたら、なんだかちょっと分かってきた。
- @-webkit-keyframes なる記述がキーフレームの情報だと予想。
- キーフレームに、12.7451% とか 49.0196% とか書いてあるのは、全体時間に対してどこにキーフレームがあるのかを割合で示しているのではないか。
- html本体部分の canvas てのがシェイプに相当するらしい。シェイプの形状はcssで定義。
解析してどうにかしようという気にはならないけれど、やろうと思えばどうにかできるのかもしれない。バイナリファイルではなくテキストファイルで保存されてると、こういう時にありがたい。今時、バイナリファイルで出力することは極力避けるべきなのだと再認識しました。
連番画像の出力機能は無し。
現状、広告制作ツールのベータ版として公開されているのだし、CGツールとして捉えるほうが間違ってる気もしてきたり。見た目それっぽいけど、ヨチヨチ歩きみたいなものだよな…。どこかのまとめサイトで、「GoogleがAdobeとWebデザイナー殺しに来た」なんて煽りタイトル記事を見かけたけど。仮にそうだとしても、幼児が水鉄砲でピュピュッと撃ってる段階のような…。今後の成長に期待、みたいな。
◎ Adobe Edge Animate を試用した際のメモ。 :
最新版の Adobe Edge Animate CC はおそらく有償ソフトらしいのだけど。Adobe Edge Animate 1.5 までは、Adobe ID を持ってる人なら無料で使えるらしいので、試しにDLしてインストールしてみたり。ちなみに、そのうちDLできなくなるという話も見かけました。
インストール時に、「PCの再起動が要求されてる」云々のエラーメッセージが表示されて。OS再起動しないとダメなのかなと再起動してから試したけれど、やはり同じメッセージが。とりあえず「無視」ボタンを押したら、インストールが先に進んだ。何だったんだろう…。まあ、以前 Flash CS5 をインストールした時も、謎のエラーが出てなかなか解決できなかったし。 *2 Adobe製だから、どうせこんなもんだろう…。
Google Web Designer に比べると、さすがに随分作り込んである印象。操作方法は Flash のノリに近いので、戸惑う部分はほとんど無かった。
こちらも、連番画像の出力機能は無し。
パブリッシュされたファイルを確認したら、1つの html と、複数のjsになっていた。jsファイルは難読化されていて何が何だか。
作業用ファイルは難読化がされてなかったので、なんとか理解できそう。hoge_edge.js を開くと、timelines なる記述が見えたので、たぶんこのファイルにアニメさせるための情報が入っているのではないかなと。もしかして、これって json だったりするのかな?
今思い出したけど、もしかして Adobe は、この手のファイルフォーマットの解析作業を禁止してたりしないかな…。禁止してるのだとしたら、そういう面から、他のソフトとの連携は無理ってことになりそうな。
インストール時に、「PCの再起動が要求されてる」云々のエラーメッセージが表示されて。OS再起動しないとダメなのかなと再起動してから試したけれど、やはり同じメッセージが。とりあえず「無視」ボタンを押したら、インストールが先に進んだ。何だったんだろう…。まあ、以前 Flash CS5 をインストールした時も、謎のエラーが出てなかなか解決できなかったし。 *2 Adobe製だから、どうせこんなもんだろう…。
Google Web Designer に比べると、さすがに随分作り込んである印象。操作方法は Flash のノリに近いので、戸惑う部分はほとんど無かった。
こちらも、連番画像の出力機能は無し。
パブリッシュされたファイルを確認したら、1つの html と、複数のjsになっていた。jsファイルは難読化されていて何が何だか。
作業用ファイルは難読化がされてなかったので、なんとか理解できそう。hoge_edge.js を開くと、timelines なる記述が見えたので、たぶんこのファイルにアニメさせるための情報が入っているのではないかなと。もしかして、これって json だったりするのかな?
今思い出したけど、もしかして Adobe は、この手のファイルフォーマットの解析作業を禁止してたりしないかな…。禁止してるのだとしたら、そういう面から、他のソフトとの連携は無理ってことになりそうな。
[ ツッコむ ]
#4 [anime] ライダー鎧武を視聴
今日も何話か消化。
ドリアンライダーの人が生身で変なポーズを取りながらジャンプして出現したり、弟子の人が気をつけポーズで放物線を描いていく図の突飛さに笑いました。どうしてこんな光景を思いついたのか…。素晴らしい。子供さんの反応が気になるところ。
森について解説する回で、それぞれ別の場所に居るはずのキャラクター達が、謎についての説明を繋げていくあたりに感心。こういう見せ方は、自分、なんだか好きでして。たしかコードギアスでもやっていて当時感心した記憶が。こういうことができるあたりが ―― 場所も時間も跳躍して一つに繋げることができてしまうのが映像作品の持つ面白さ、のような気がしたり。…まあ、映像作品において、あらゆるシーンは、異なるカットの繋がりで構成されているわけで。それを脚本レベルでもやってみた事例の一つ、かもしれないのですが。
ドリアンライダーの人が生身で変なポーズを取りながらジャンプして出現したり、弟子の人が気をつけポーズで放物線を描いていく図の突飛さに笑いました。どうしてこんな光景を思いついたのか…。素晴らしい。子供さんの反応が気になるところ。
森について解説する回で、それぞれ別の場所に居るはずのキャラクター達が、謎についての説明を繋げていくあたりに感心。こういう見せ方は、自分、なんだか好きでして。たしかコードギアスでもやっていて当時感心した記憶が。こういうことができるあたりが ―― 場所も時間も跳躍して一つに繋げることができてしまうのが映像作品の持つ面白さ、のような気がしたり。…まあ、映像作品において、あらゆるシーンは、異なるカットの繋がりで構成されているわけで。それを脚本レベルでもやってみた事例の一つ、かもしれないのですが。
[ ツッコむ ]
以上、1 日分です。
「DXRubyで動画が再生できたら云々」などとこっそり発言していた一人です。
いつも日記の更新を楽しみにさせていただいています。
Flashは一時期結構使っていただけに、衰退しつつあるのは私も残念に思います。
はやりの「艦これ」がFlashで動作したりとまだ完全に死に向かっているわけではなさそうですが、
だからといって第一線に戻れそうな空気でもないですよね。
DXRubyでのswf再生の一連の日記、まだ細部は読めていませんがすごくワクワクしました。
特に何か意見があるというわけでないのですが、この一連の日記ですごい喜んだ奴がいるということをお伝えしたかった次第でした。