动画效果制作——CDD篇

2022/6/8

本节文档介绍如何制作动画效果,本节会使用比较高级的方法来做,适合动画中图片数量比较多的情况,性能比较好
本篇由 Luckykeeper (opens new window)WorldlineChanger 共同编写

参考资料:https://zhuanlan.zhihu.com/p/362449324

官方文档:https://www.renpy.cn/doc/udd.html

# 前置知识

Python 知识是必备的,如果不熟悉 Python 的话,理解本部分将会非常困难

# 需求描述

如前篇基础篇所述,需要制作动画,但是偶尔会遇到超过100张图片做动画的情况,比如本篇的 STAFF 表,是由 1496 张图片构成的,像这种情况就不能使用基础篇讲到的方法了,而是要使用 Python 来定义,提升播放效率,这里的 CDD 叫做 Creator-Defined Displayables ,简称就是 CDD 啦

# 实现


以下由 WorldlineChanger 编写


# 正式型 初号机-Initial

# 代码参考 Ver1.0 Script.rpy 992-1056行
# ######################################################################################
# CDD Part By WorldlineChanger
# CDD-自定义可视化组件 定义 STAFF 动画
# 正式型 初号机-Initial

init python:

    class StaffAnimator(renpy.Displayable):

        # 文件扩展名由Ren'Py预处理,加油捏
        # 其他3项(前缀 prefix、分隔符 separator、序列帧 begin & end_index)为 StaffAnimator 的3种必备入参
        # 序列帧应是两个整型入参,或者一个二元元组,分别表示起始帧序列号和结束帧序列号(此处使用2个整型入参)
        # 用4个入参找到符合规范的一组图片
        def __init__(self, prefix, separator, begin_index, end_index, interval, loop=False, **kwargs):
            super(StaffAnimator, self).__init__(**kwargs)
            # 前缀
            self.prefix = prefix
            # 分隔符
            self.separator = separator
            # 起始帧序列号
            self.begin_index = begin_index
            # 结束帧序列号
            self.end_index = end_index
            # 序列帧长度
            self.length = end_index - begin_index + 1

            # 可视组件列表
            self.sequence = []
            for i in range(begin_index, end_index+1):
                # 将前缀、分隔符和序列号拼接,对应名称的可视组件顺序添加到列表中
                self.sequence.append(renpy.displayable(self.prefix + self.separator + str(i)))

            # 当前帧在可视组件列表中的索引
            self.current_index = 0
            # 关键帧时间轴
            self.show_timebase = 0
            # 播放间隔
            self.interval = interval
            # 循环播放
            self.loop = loop

        # 根据时间渲染对应的可视组件
        def render(self, width, height, st, at):
            if (st >= (self.show_timebase + self.interval)):
                self.show_timebase = st
                self.current_index += 1
                if self.current_index >= self.length:
                    # 若循环播放,将可视组件列表索引归零
                    if self.loop:
                        self.current_index = 0
                    else:
                        self.current_index = self.length - 1

            # 默认所有序列帧图片都具有相同尺寸
            render = renpy.render(self.sequence[self.current_index], width, height, st, at)
            renpy.redraw(self, 0)

            return render

# 创建实例并使用,序列帧命名规则:【staff_1.webp ~ staff_1457.webp】
image staff = StaffAnimator("staff", "_", 1, 1457, 0.0166666666666667)

# 播放
label start:
   show staff at truecenter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66


# 正式型 贰号机-"LuckyWL"

上面的代码存在一定问题:在播放 staff 表时读档回到选项前再次选择播放 staff 表,会导致 staff 表卡在某一帧不动(见Issues #19 (opens new window))所以需要对上面的代码进行改造

改造思路:在开始播放动画前,将当前播放图片计数手动置0(第二次播放的时候肯定不是在第一张图片了),从头播放,这里把 current_index 注册为全局变量,方便调用

改造后代码如下:

# 代码参考 GitHub dev-renpy7.4.6 分支 Script.rpy 1058-1124行
#######################################################################################
# CDD Part By WorldlineChanger and Modified By Luckykeeper
# CDD-自定义可视化组件 定义 STAFF 动画
# 正式型 贰号机-"LuckyWL"
# 尝试解决读档后 staff 表不能从头播放问题 By Luckykeeper
# https://github.com/luckykeeper/LOVE69_renpy_remaster/issues/19
# 解决方法:将 current_index 注册为全局变量,在播放前手动置 0 一次
init -1 python:
    current_index = 0 # 将 current_index 注册为全局变量,这个过程需要在 StaffAnimator 类之前加载完成
init python:

    class StaffAnimator(renpy.Displayable):

        # 文件扩展名由Ren'Py预处理,加油捏
        # 其他3项(前缀 prefix、分隔符 separator、序列帧 begin & end_index)为 StaffAnimator 的3种必备入参
        # 序列帧应是两个整型入参,或者一个二元元组,分别表示起始帧序列号和结束帧序列号(此处使用2个整型入参)
        # 用4个入参找到符合规范的一组图片
        def __init__(self, prefix, separator, begin_index, end_index, interval, loop=False, **kwargs):
            super(StaffAnimator, self).__init__(**kwargs)
            # 前缀
            self.prefix = prefix
            # 分隔符
            self.separator = separator
            # 起始帧序列号
            self.begin_index = begin_index
            # 结束帧序列号
            self.end_index = end_index
            # 序列帧长度
            self.length = end_index - begin_index + 1

            # 可视组件列表
            self.sequence = []
            for i in range(begin_index, end_index+1):
                # 将前缀、分隔符和序列号拼接,对应名称的可视组件顺序添加到列表中
                self.sequence.append(renpy.displayable(self.prefix + self.separator + str(i)))

            # 当前帧在可视组件列表中的索引
            global current_index
            current_index = 0
            # 关键帧时间轴
            self.show_timebase = 0
            # 播放间隔
            self.interval = interval
            # 循环播放
            self.loop = loop

        # 根据时间渲染对应的可视组件
        def render(self, width, height, st, at):
            global current_index
            if (st >= (self.show_timebase + self.interval)):
                self.show_timebase = st
                current_index += 1
                if current_index >= self.length:
                    # 若循环播放,将可视组件列表索引归零
                    if self.loop:
                        current_index = 0
                    else:
                        current_index = self.length - 1

            # 默认所有序列帧图片都具有相同尺寸
            render = renpy.render(self.sequence[current_index], width, height, st, at)
            renpy.redraw(self, 0)

            return render

# 创建实例并使用,序列帧命名规则:【staff_1.webp ~ staff_1457.webp】
image staff = StaffAnimator("staff", "_", 1, 1457, 0.0166666666666667)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

在 Scene 中播放这样写

# 代码参考 GitHub dev-renpy7.4.6 分支 scene09.rpy 2529-2545行
    if show_staff:
        # https://github.com/luckykeeper/LOVE69_renpy_remaster/issues/19
        $ current_index = 0
        # show screen staff
        show staff at truecenter
        # play sound bgmone
        # https://github.com/luckykeeper/LOVE69_renpy_remaster/issues/19
        # 参考:https://www.renpy.cn/doc/audio.html?highlight=music#renpy.music.play
        $ renpy.music.play("/bgm/bgm01.ogg", channel='music', loop=False, fadeout=None, synchro_start=False, fadein=0, tight=None, if_changed=False)
        $ persistent.playStaff =  True # variable value(注:这句实际在本作没有发挥实际作用可以忽略)
        $ check_playthrough()
        # 考虑到 STAFF 表的加载时间过长,这里选择再重复播放一次
        # queue sound bgmone
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这里的 $ current_index = 0 就是把当前播放计数手动置0

# 正式型 叁号机-"LuckyMirai"

感谢知乎@被诅咒的章鱼 (opens new window)大佬指点(就是下面评论区的那位大佬哒),上面的做法其实有点儿问题:没有同步更新 SquenceAnimator 和 SquenceAnimator2 类的 show_timebase 成员变量,这里直接放上修改完成的代码供有需要的同学使用,想了解为什么这样修改可以的话可以用本页最顶上的链接(参考资料)查看大佬文章

######################################################################################
# CDD Part By WorldlineChanger and Modified By Luckykeeper
# CDD-自定义可视化组件 定义 STAFF 动画
# 正式型 叁号机-"LuckyMirai"
# 解决 SquenceAnimator 和 SquenceAnimator2 类的 show_timebase 成员变量没有同步更新的问题,感谢 知乎@被诅咒的章鱼 大佬指点
# https://github.com/LOVE69-Renpy-Remaster-Project/Doc/issues/51#issuecomment-1859531366

init python:

    class StaffAnimator(renpy.Displayable):

        # 文件扩展名由Ren'Py预处理,加油捏
        # 其他3项(前缀 prefix、分隔符 separator、序列帧 begin & end_index)为 StaffAnimator 的3种必备入参
        # 序列帧应是两个整型入参,或者一个二元元组,分别表示起始帧序列号和结束帧序列号(此处使用2个整型入参)
        # 用4个入参找到符合规范的一组图片
        def __init__(self, prefix, separator, begin_index, end_index, interval, loop=False, **kwargs):
            super(StaffAnimator, self).__init__(**kwargs)
            # 前缀
            self.prefix = prefix
            # 分隔符
            self.separator = separator
            # 起始帧序列号
            self.begin_index = begin_index
            # 结束帧序列号
            self.end_index = end_index
            # 序列帧长度
            self.length = end_index - begin_index + 1

            # 可视组件列表
            self.sequence = []
            for i in range(begin_index, end_index+1):
                # 将前缀、分隔符和序列号拼接,对应名称的可视组件顺序添加到列表中
                self.sequence.append(renpy.displayable(self.prefix + self.separator + str(i)))

            # 当前帧在可视组件列表中的索引
            self.current_index = 0
            # 关键帧时间轴
            self.show_timebase = 0
            # 播放间隔
            self.interval = interval
            # 循环播放
            self.loop = loop

        # 根据时间渲染对应的可视组件
        def render(self, width, height, st, at):
            ## st为0时,表示组件重新显示
            if (st == 0):
                self.show_timebase = 0
                self.current_index = 0
            if ((st) >= (self.show_timebase + self.interval)):
                self.show_timebase = st
                self.current_index += 1
                if self.current_index >= self.length:
                    if self.loop:
                        self.current_index = 0  
                    else:
                        self.current_index = self.length - 1

            render = renpy.render(self.sequence[self.current_index], width, height, st, at)
            renpy.redraw(self, 0)

            return render

# 创建实例并使用,序列帧命名规则:【staff_1.webp ~ staff_1457.webp】
image staff = StaffAnimator("staff", "_", 1, 1457, 0.0166666666666667)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65