代码见 这里 。使用 python -m anp
可以打开一个 .reanim 动画播放器。在加载全部资源(耗时略长)后可以播放几乎所有动画。播放简单的动画不用加载资源。
.reanim 简介
Reanim 文件是 PopCap 公司曾用于描述动画的文件。比如,PvZ 解包(见百度植物大战僵尸吧置顶帖)后 reanim 文件夹下就是游戏中用到的所有动画(但粒子效果不算)。比如,Blover.reanim 是三叶草空闲和吹的动画,显示的效果是(实际速度更快一点,转成 gif 之后变慢了):
Reanim 文件类似于 Flash,描述一个动画中每一个元素如何运动和如何显示,也可以生成 .mp4 等格式的普通视频。可以在两帧之间线性插值,理论上可以有无限的帧率。其实现细节在最后一节。
动画数据
一个动画中包含多个元素,以一定顺序堆叠。每个元素在不同时刻有不同图片、透明度、大小、平移等。我把一个元素一帧的状态用 @dataclass ItemData
保存。那么,需要求出一个 ItemData
的矩阵,在 QPainter
上应用这个矩阵绘图。简单起见,要求出两帧之间的 ItemData
,我们把除了 img
的所有属性都线性插值。为了使动画自然,我们就不能直接用矩阵的加法和数乘。如果这样,就不能实现旋转了。我们需要计算出一条单位长水平线段映射后的旋转角和长度(记作 x_rotate
和 x_scale
),对单位竖直线段也一样,如图:
一个单位正方形(黑色)被线性变换后成为了一个平行四边形(红色)。AD 的长就是 x_scale
,AD 与 x 轴夹角(或者说,把 AD 顺时针旋转这个角的度数,会使线段 AD 在 x 轴上)就是 x_rotate
。原点 O 被映射到了点 A,所以平移量是向量 A。
灰色线是与 x 轴平行的一条直线。这里标出来的角就是 x_rotate
。同样,y_rotate
是使竖直线逆时针旋转后与映射后的线重合的角度。
有时,一个元素会被隐藏,比如路障僵尸的路障。所以需要一个 is_hidden
属性来判断这个元素在某一帧会不会被隐藏。
使用这些参数就可以计算出转换矩阵;也可以使用转换矩阵计算出这些参数。后者对于人类用户的编辑帮助不大。
这是 ItemData
的实现:
def interpolate(a: float, b: float, progress: float):
return a + (b - a) * progress
@dataclass
class ItemData:
opacity: float = 1. # 不透明度
x: float = 0.
y: float = 1.
x_scale: float = 1.
y_scale: float = 1.
x_rotate: float = 0. # 角度制
y_rotate: float = 0. # 角度制
img: QPixmap = QPixmap() # 默认为空图片
is_hidden: bool = False
img_name: str = '' # 图片的名字
def interpolated(self, other: ItemData, progress: float):
return ItemData(
interpolated(self.opacity, other.opacity, progress),
interpolated(self.x, other.x, progress),
interpolated(self.y, other.y, progress),
interpolated(self.x_scale, other.x_scale, progress),
interpolated(self.y_scale, other.y_scale, progress),
interpolated(self.x_rotate, other.x_rotate, progress),
interpolated(self.y_rotate, other.y_rotate, progress),
# self.img, # 图片不能插值
# self.is_hidden,
# self.img_name,
)
def to_transform(self):
x_rotate = math.radians(self.x_rotate)
y_rotate = math.radians(self.y_rotate)
a = self.scale_x * cos(x_rotate)
b = self.scale_x * sin(x_rotate)
c = -self.scale_y * sin(y_rotate)
d = self.scale_y * cos(y_rotate)
tx = self.x
ty = self.y
matrix = QTransform(
a, b, 0,
c, d, 0,
tx, ty, 1,
)
# 点 (x, y) 经映射后变为:
# (a * x + b * y + tx, b * x + d * y + ty)
return matrix
播放动画
有了数据,我们需要考虑如何组织数据。我用 Item
表示一个元素,用 Animation
表示一个动画。
元素需要有属性 name
表示名字。动画的属性有:fps
帧率和 _items
元素的列表。
元素需要提供 image_at
、transform_at
、hidden_at