メモリ内容等を動画(の外側)に表示させる方法
先日発表されたロックマン2 TASの動画は、普段目にすることのないメモリの内容を画面右下に表示していました。本稿ではこの方法について説明します。
作業工程にLuaを用いるため、少なからずLuaの知識が要求されます。さほど難しくもありませんし、TAS制作にも役立ちますので、興味のある方はKaretta|Luaプログラミング入門等を見ながら習得してみるのもよいでしょう。
続きを読むLuaスクリプトによる半自動マップ画像作成 - 魔導物語 はなまる大幼稚園児
「Luaスクリプトは発想次第でさまざまな可能性を開ける」先人のスクリプトの数々をみてそう思いました。そこで「わたしもなにかみんながびっくりするような案を出して、なでなでされたい!」という一心で勢いよく書いたのが今回のスクリプトです。
実際のソースコードは SNESMadou_SemiAutoMap.lua をご覧ください。
解説
ゲームのマップをスクリーンショットから構築していく作業は多くの苦労を伴います。適切な位置にキャラクターを移動させてスクリーンショットを撮ったら、位置を間違えないように注意しつつ、大きな画像の上にそれをぺたぺたと貼っていく。これがもっとも基本的な作業なのですが、これだけの作業でも結構な気力が必要になります。現実にマップを作成する場合、ほかにもスプライトのアニメーションや敵の移動を封じたり、レイヤーの表示を切り替えてキャプチャしたのち、必要な部分を選んでつなぎあわせるなど、ほかの作業もいろいろと必要になってくるので、とってもとってもたいへんです。
そこでスクリプトの出番です。基本作業の一部をスクリプトに自動処理させることで、マップ制作の手間は幾分軽減されます。
- 画像のアニメーションをある程度抑制する(メモリ書き込み操作)
- キャプチャ対象の位置までアルルを移動させる
- いったんポーズ画面に切り替えることで画面表示を更新する
- アルルを彼方へと飛ばしたのちキャプチャ、メモリに保持する巨大画像の上に貼り付け
- すべての箇所がそろうまでキャプチャを繰り返す
- 完成した画像をPNG形式などで保存する
処理される工程をリアルタイムで眺めると遅いですが、実際にエミュレータで動かす場合は動作を早回しすることができるので、負荷がかかっていなければ実際の時間はかなり短くなります。1000px四方ほどもあるマップを手でキャプチャしようと思うと30秒では不可能ですが、スクリプトならそれが可能になります。あくまで半自動なのでうまくいかない場合もありますが、そのような場合は失敗した箇所だけ手動でキャプチャすればよいようになっています。
おまけとして、部屋の移動がスムーズにおこなえるよう、ドラッグアンドドロップによる移動機能をつけました。
魔導の塔に入れるよ! やったねアルルちゃん!
Luaでメルセンヌ・ツイスタ(MT乱数)かいてみたよ
昨日LuaでXorShiftかいてみたよという記事を書いたばかりですが、ちょっとコードを流用してMTも用意してみました!
ビット演算周りにLuaBitOpモジュールを使っています(Lua for Windowsの場合5.1.4.22より付属)。その他の方向性やアプローチはXorShiftと変わらないので省略します。元にしたコードは mt19937ar.c です。init_by_array() はめんどくさがって移植してません。「ライブラリでやれ」は禁句。
2009-02-16 20時: random() の発生区間が [0,1) ではなく [0,1] になっている気がしたので修正。
2009-04-13 7時: 恥ずかしながら math.random() が引数を受け取ることを最近知りました。よって処理を追加。
mt19937.lua (pastebin link):
-- Mersenne Twister: A random number generator -- ported to Lua by gocha, based on mt19937ar.c module("mt19937", package.seeall) require "bit" -- Period parameters local N = 624 local M = 397 local MATRIX_A = 0x9908b0df -- constant vector a local UPPER_MASK = 0x80000000 -- most significant w-r bits local LOWER_MASK = 0x7fffffff -- least significant r bits local mt = {} -- the array for the state vector local mti = N + 1 -- mti==N+1 means mt[N] is not initialized -- initializes mt[N] with a seed function randomseed(s) s = bit.band(s, 0xffffffff) mt[1] = s for i = 1, N - 1 do -- s = 1812433253 * (bit.bxor(s, bit.rshift(s, 30))) + i s = bit.bxor(s, bit.rshift(s, 30)) local s_lo = bit.band(s, 0xffff) local s_hi = bit.rshift(s, 16) local s_lo2 = bit.band(1812433253 * s_lo, 0xffffffff) local s_hi2 = bit.band(1812433253 * s_hi, 0xffff) s = bit.bor(bit.lshift(bit.rshift(s_lo2, 16) + s_hi2, 16), bit.band(s_lo2, 0xffff)) -- s = bit.band(s + i, 0xffffffff) local s_lim = -bit.tobit(s) -- assumes i<2^31 if (s_lim > 0 and s_lim <= i) then s = i - s_lim else s = s + i end mt[i+1] = s -- See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. -- In the previous versions, MSBs of the seed affect -- only MSBs of the array mt[]. -- 2002/01/09 modified by Makoto Matsumoto end mti = N end local mag01 = { 0, MATRIX_A } -- mag01[x] = x * MATRIX_A for x=0,1 -- generates a random number on [0,0xffffffff]-interval function random_int32() local y if (mti >= N) then -- generate N words at one time local kk if (mti == N + 1) then -- if init_genrand() has not been called, mt19937.randomseed(5489) -- a default initial seed is used end for kk = 1, N - M do y = bit.bor(bit.band(mt[kk], UPPER_MASK), bit.band(mt[kk+1], LOWER_MASK)) mt[kk] = bit.bxor(mt[kk+M], bit.rshift(y, 1), mag01[1 + bit.band(y, 1)]) end for kk = N - M + 1, N - 1 do y = bit.bor(bit.band(mt[kk], UPPER_MASK), bit.band(mt[kk+1], LOWER_MASK)) mt[kk] = bit.bxor(mt[kk+(M-N)], bit.rshift(y, 1), mag01[1 + bit.band(y, 1)]) end y = bit.bor(bit.band(mt[N], UPPER_MASK), bit.band(mt[1], LOWER_MASK)) mt[N] = bit.bxor(mt[M], bit.rshift(y, 1), mag01[1 + bit.band(y, 1)]) mti = 0 end y = mt[mti+1] mti = mti + 1 -- Tempering y = bit.bxor(y, bit.rshift(y, 11)) y = bit.bxor(y, bit.band(bit.lshift(y, 7), 0x9d2c5680)) y = bit.bxor(y, bit.band(bit.lshift(y, 15), 0xefc60000)) y = bit.bxor(y, bit.rshift(y, 18)) return y end function random(...) -- local r = mt19937.random_int32() * (1.0/4294967296.0) local rtemp = mt19937.random_int32() local r = (bit.band(rtemp, 0x7fffffff) * (1.0/4294967296.0)) + (bit.tobit(rtemp) < 0 and 0.5 or 0) local arg = {...} if #arg == 0 then return r elseif #arg == 1 then local u = math.floor(arg[1]) if 1 <= u then return math.floor(r*u)+1 else error("bad argument #1 to 'random' (internal is empty)") end elseif #arg == 2 then local l, u = math.floor(arg[1]), math.floor(arg[2]) if l <= u then return math.floor((r*(u-l+1))+l) else error("bad argument #2 to 'random' (internal is empty)") end else error("wrong number of arguments") end end
math.randomseed(), math.random() と同じ感覚で mt19937.randomseed(), mt19937.random() が利用できます。mt19937.random_int32() は内部用に作ったつもりなので、外部からの利用はあんまりおすすめしません。でも、好きにしてください。
以下、XorShiftとおんなじ適当な利用サンプルです。
require "mt19937" local randomseed = mt19937.randomseed local random = mt19937.random randomseed(os.time()) for i = 1, 10 do local rf = random() local ri = math.floor(rf * 10) print(ri, rf) end
なにかの役に立つことがあったら、コメントで教えてもらえるときっとうれしくなれるのです。
LuaでXorShiftかいてみたよ
唐突ですがLuaスクリプトはじめました。Luaスクリプトに対する個人的な感想はさておき、乱数生成の精度というか挙動がちょっと気に入らなかったので、知る人ぞ知るXorShiftという乱数生成アルゴリズムをLuaに移植してみました。学習初日から作りたいものも決まってないのになに書いてるんでしょうね (・д・)
移植元のCソースに忠実な動作をするよう頑張りましたが、穴があるかもしれません。ビット演算周りにLuaBitOpモジュールを使っています(Lua for Windowsの場合5.1.4.22より付属)。
2009-02-16 20時: random() の発生区間が [0,1) ではなく [0,1] になっている気がしたので修正。
2009-04-13 7時: 恥ずかしながら math.random() が引数を受け取ることを最近知りました。よって処理を追加。