ファイナルファンタジー3の海のアニメーションを作ってみる
ファミコン版ファイナルファンタジー3(以下FF3)の海面は、波打つようにアニメーションしています。
(たとえば次の動画の1:29:39あたりから見られます)。
それを、Pyxelで真似して作ってみました。
しくみ
もとのFF3のアニメーションを見てみると、一ラインごとにタイミングをずらして1pxずつ右に動いているように見えます。
左がもとになるキャラクター(Pyxelのイメージバンク)、右が表示した状態(Pyxelのタイルマップ)です。
水面にあたる左の赤枠の部分を何度もマップに配置して海を表現しています。
そのため、元になるイメージバンクの絵を動かしてしまえば、マップにある絵もすべて書き換わる算段です。
FF3も、元になるキャラクターのパターンを書き換えてアニメーションさせているはず。
コード
指定したイメージバンクの部分を一ラインずつずらして表示するためのクラスです。
self.tileshift = TileShift(0, 1, 40, 16, 1, 16, 16, True)
"""
self.tileshift = TileShift(
イメージバンク番号,
ずらす量(正で右、負で左),
イメージバンクのX,
イメージバンクのY,
何フレームごとに動かすか,
動かす部分の幅,
動かす部分の高さ,
同ずらし動作(Falseで全ライン同時シフト)
)
"""
import pyxel
class TileShift:
def __init__(self, bank, speed_h, u, v, timing, width=8, height=8, delay=False):
"""
Shift an image cell on an image bank
Args:
bank (int): The number of the image bank
speed_h (int): Scrolling direction (+:left, -:right)
u (int): X position of the target image on the image bank
v (int): Y position of the target image on the image bank
timing (int): Scroll timing (per frame)
width (int): tile width(px)
height (int): tile height(px)
delay: delay shift per lines (Boolean)
"""
self.screen_ptr = pyxel.images[bank].data_ptr()
self.speed_h = speed_h
self.u = u
self.v = v
self.timing = timing
self.width = width
self.height = height
self.count = 0
self.delay = delay
def update(self):
if self.timing == 0 :
self.timing = 1
self.count = (self.count + 1) % self.height
if pyxel.frame_count % self.timing == 0:
if self.delay:
y = self.count
self.shift_line(y)
else:
for y in range(0, self.height):
self.shift_line(y)
def shift_line(self, y):
cell = self.u + (self.v + y) * 256
source = self.screen_ptr[cell:cell + self.width]
shifted = self.rotate(source, self.speed_h)
self.screen_ptr[cell:cell + self.width] = shifted
def rotate(self, list, n):
if not list: # empty list
return list
length = len(list)
n = n % length
# n<0: left / n>0: right
return list[-n:] + list[:-n]
pyxel.images[bank].data_ptr()
で、{bank}番目のイメージバンクの色データをリストで取得できます。
データは一本につながっているので、
cell = self.u + (self.v + y) * 256
source = self.screen_ptr[cell:cell + self.width]
横の指定座標u + (縦の指定座標V + y座標) * イメージバンクの幅256
で目的のデータのスタート部分を割り出した後、そこからwidth分データを取り出します。
それを、定義したrotate()で左右にデータをずらし、self.screen_ptr[cell:cell + self.width] = shifted
で、抜き出した部分に上書きしています。
ずらしありで動かすと、
self.tileshift = TileShift(0, 1, 40, 16, 1, 16, 16, True)
ずらしなしで動かすと、
self.tileshift = TileShift(0, 1, 40, 16, 10, 16, 16, False)
MSXなど昔のコンピュータ風にいうと、PCG書き換えという感じでしょうか。
うまく使うと、スクロールしていないのにスクロールしているように見える、というような演出が可能です。
余談
ツインファミコンで遊んでますが、いよいよクライマックスという段階のデータが消えました。
ファイナルファンタジーもうやめます。