用 Python 做一个 Reanim 动画(PvZ1 动画)播放器(一:普通动画播放)

代码见 这里 。使用 python -m anp 可以打开一个 .reanim 动画播放器。在加载全部资源(耗时略长)后可以播放几乎所有动画。播放简单的动画不用加载资源。

.reanim 简介

Reanim 文件是 PopCap 公司曾用于描述动画的文件。比如,PvZ 解包(见百度植物大战僵尸吧置顶帖)后 reanim 文件夹下就是游戏中用到的所有动画(但粒子效果不算)。比如,Blover.reanim 是三叶草空闲和吹的动画,显示的效果是(实际速度更快一点,转成 gif 之后变慢了):
Blover.reanim

Reanim 文件类似于 Flash,描述一个动画中每一个元素如何运动和如何显示,也可以生成 .mp4 等格式的普通视频。可以在两帧之间线性插值,理论上可以有无限的帧率。其实现细节在最后一节。

动画数据

一个动画中包含多个元素,以一定顺序堆叠。每个元素在不同时刻有不同图片、透明度、大小、平移等。我把一个元素一帧的状态用 @dataclass ItemData 保存。那么,需要求出一个 ItemData 的矩阵,在 QPainter 上应用这个矩阵绘图。简单起见,要求出两帧之间的 ItemData,我们把除了 img 的所有属性都线性插值。为了使动画自然,我们就不能直接用矩阵的加法和数乘。如果这样,就不能实现旋转了。我们需要计算出一条单位长水平线段映射后的旋转角和长度(记作 x_rotatex_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_attransform_athidden_at

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值