|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 Maz马 于 2026-8-30 22:48 编辑
26.8.29 更新
基于后文需求,新写了一个更为舒适封装内聚的组件
功能
1.能够手动重启的动画
2.同时提供了播放音声资源的功能,这个功能是基于“能够在image的序列帧动画里对应播放声音,而不用外部匹配计算”的想法。
[RenPy] 纯文本查看 复制代码 # ---------- 影像图片 ----------
init python:
"""
Movie Image (Ver 1.0 Ren'py 8.3.6)
Author: Maz
"""
class MovieImage(renpy.Displayable):
def __init__(self, frames, sounds=None, repeat=False, channel="sound", **kwargs):
super().__init__(**kwargs)
# 注意计算方式不同
self.frames = frames # 图片字典 frames 值 = 该帧持续时间
self.sounds = sounds # 音声字典 sounds 值 = 该帧发起时间
self.repeat = repeat
self.channel = channel # 播放通道
self.sounds_done = set() # 已经发起过的音声
# 初始化标记
self.inited = False
# 组件尺寸
self.w = 0
self.h = 0
# 时间轴
self.total_time = sum(float(d) for d in self.frames.values()) # 动画时间
self.now_time = 0 # 自体时间
self.old_time = 0 # 上帧时间
self.delta_time = 0 # 渲染间隔
def restart(self):
self.inited = False
self.sounds_done = set()
renpy.redraw(self, 0)
def render(self, width, height, st, at):
# 首次/重显 初始化
if not self.inited or st < self.old_time:
# 初始化渲染属性
first = list(self.frames)[0]
self.w, self.h = renpy.render(renpy.displayable(first), width, height, st, at).get_size()
# 初始化时间属性
self.delta_time = 0
self.now_time = 0
self.old_time = st
self.inited = True
else:
# 自维护时间属性
self.delta_time = st - self.old_time
self.now_time += self.delta_time
self.old_time = st
render = renpy.Render(self.w, self.h)
# 音效时间戳:到点且未播过才发起(sounds_done 防重复发起)
t = 0.0
for i, (name, dt) in enumerate(self.sounds.items()):
t += float(dt)
if self.now_time >= t: # 已到触发点
cur_sound = name # 当前应发起的音效
if i not in self.sounds_done: # 发起前判断:未播过才播
renpy.play(cur_sound, channel=self.channel, if_changed=True)
self.sounds_done.add(i)
# 图片时间戳
cur_img = None
t = 0.0
for name, dt in self.frames.items():
if self.now_time < t + dt:
cur_img = name
break
t += dt
if cur_img is None:
cur_img = list(self.frames)[-1] # 超时用末帧
render.blit(renpy.render(renpy.displayable(cur_img), self.w, self.h, st, at), (0, 0))
# 循环重播
if self.repeat and self.now_time >= self.total_time:
self.restart()
# 动画期间才更新
if self.now_time < self.total_time:
renpy.redraw(self, 0)
return render
# 使用方法:
#
# 可以当成 image 显示,也可以在 screen 里直接 add,以下是参数含义
#
# frames 图片字典,格式是 {帧图: 该帧持续秒}
# sounds 音效字典,格式是 {音效: 播放时间戳}
# repeat 循环播放 默认 False)
# channel 播放通道 默认 "sound")
#
# 调用实例的 xx.restart() 即可重置动画
#
# image xx = MovieImage(
# frames={"帧A": 0.06,
# "帧B": 0.06,
# "帧C": 0.06},
#
# sounds={"音效A": 0.5,
# "音效B": 0.3},
# repeat=True,
# channel="sound")
最开始的讨论点,仅记录,下文算是思路分享,新的组件已经更内聚,适用性更好了
[RenPy] 纯文本查看 复制代码 #用来刷新动画时间轴入点的动态可视组件
init python:
#假设,image循环时间为2,当st为2.3时切换出image,image会处于2.3的循环位置,而不是在2.3重新唤起循环,这是renpy的图像时间轴机制
#注意,这里说的是这个组件的显隐,而不是renpy线程下的调用,一般情况的调用,是会重新创建组件,重置st的
#可以把这里的显隐理解为一张图片能否看到,但无论能否看到,实际都在后台存在,并未真正释放
#因此需要让image时间轴和提取动画的时间轴相对异步,可以查询动画时间切片和滑窗算法来理解
#
# 用来操作动画切片入点的全局变量
start_anim = 0.0
# 重置帧动画入点的函数
def reset_animation():
global start_anim
start_anim = 0.0
# 帧动画函数
def act_animation(st,at,frames,repeat=None):
global start_anim
# 初始化
if start_anim == 0.0:
start_anim = st # 记录传入时间轴的当前时间(类似于剪辑中的入点)
index = 0 # 帧序列
meter_time = 0.0 # 当前动画帧播放的时间
total_duration = sum(frame_time for (frame, frame_time) in frames) # 计算动画总时长
# 动画已播放时间 = 当前时间 - 动画开始时间(类似于剪辑中的出点)
frame_end_time = st - start_anim
# 如果不重复播放且动画已播放完毕
if not repeat and frame_end_time >= total_duration:
image, _ = frames[-1] # 返回最后一帧
return image, None # 返回None表示不再更新
# 如果重复播放,计算循环后的剩余时间
if repeat:
frame_end_time = frame_end_time % total_duration
# 关键难点,函数本身是动态可视组件的循环调用,这里又进行循环,因此是循环嵌套
for i,(frame,frame_time) in enumerate(frames): # 因为用的是同一个时间轴,所以frame_end_time在这个循环中是另一种含义
if frame_end_time >= frame_time: # “当前帧已播放时间”大于当前帧持有的时间时
frame_end_time = (frame_end_time - frame_time) # 减去当前帧持有的时间,相对化成下一帧的“当前帧已播放时间”,继续遍历
else: # “当前帧已播放时间”小于当前帧持有的时间时
index = i # 记录当前帧的索引
meter_time = frame_time - frame_end_time # 用当前帧持有的时间减去遍历中的“当前帧已播放时间”
break # 得到 当前帧索引 和 下一次调用这个函数的间隔
# 当再次调用这个函数时,循环块外部的frame_end_time的含义会重新初始化成“动画已播放时间”
image,_= frames[index] # 获取当前帧的图像(忽略帧时间,用_接收)
return image,meter_time # 返回 当前图像 和 下次更新时间/当前帧剩余时间
#使用方法
# 抬头使用DynamicDisplayable()动态可视组件
#
# 入参repeat=True,是否循环。frames是一个帧序列表,每个元素是一个元组 (帧的路径,这一帧播放的时间)
image mon_walk_left:
DynamicDisplayable(
act_animation,repeat=True,
frames=[
("images/anim/mon_1/walk/l0.png",0.05),
("images/anim/mon_1/walk/l1.png",0.05),
("images/anim/mon_1/walk/l2.png",0.05)
])
正题:
为什么我要写这个东西而不是使用image原生的写法,看起来有点画蛇添足?
[RenPy] 纯文本查看 复制代码 image mon_walk_left:
"images/anim/mon_1/walk/l0.png"
pause 0.1
"images/anim/mon_1/walk/l1.png"
pause 0.1
"images/anim/mon_1/walk/l2.png"
pause 0.1
repeat
这里涉及到一个时间戳的问题,是我在写横板格斗的cdd轮子时出现的
所有的可视组件都会有隐含的参数st,at,也就是可视组件被显示的时间以及持续的时间
这里简单形容它为“时间入点”和“时间出点”(pr中的概念)
image本身的接口并不允许你调整时间戳,所以当你在展示这个循环动画并切换它时(也就是没有隐藏,仅仅是不显示)
image的时间轴仍然在执行,用图表展示就是:
[RenPy] 纯文本查看 复制代码 #时间轴
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
#假设你的动画在第3帧开始显示,那么这是你动画轴的位置,1234则是你循环的动画帧
[1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,]
#假设你没有隐藏它重新显示,那么时间入点仍旧是你第一次显示这个组件的st,你只是在间断的切换它,那么实际上取得值是这样的
[1,2,3,4] [3,4,1,2] [1,2,3,4]
可以看到,并不是动画重新开始,而是你截取了循环动画的一部分。[3,4,1,2]
而花里胡哨的CDD常常以常驻的形式展示在游戏中,当然,在其他情况下也有可能出现持续显示的可视组件
因此这会导致循环动画的失效或是不匹配,而这个帧动画的动态可视组件,就是提供了重新调整st的方法(并不是实际st而是相对的调整,这是时间切片的概念)
[RenPy] 纯文本查看 复制代码 reset_animation()
当你需要调整循环动画的st时,并不需要重新唤起可视组件,而是调用这个函数,那么循环动画就会重新计算
用图表展示就是:
[RenPy] 纯文本查看 复制代码 #时间轴
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
#
[1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,]
#
[1,2,3,4] [3,4,1,2] [1,2,3,4]
#当你开始截取片段时,调用函数,会相对调整st
[1,2,3,4,1,][1,2,3,4,1,][1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,]
#此时你截取的片段为
[1,2,3,4] [1,2,3,4] [1,2,3,4]
#查看我写的更多屎
时间切片的帧动画动态可视组件
https://www.renpy.cn/forum.php?mod=viewthread&tid=1740
在回放中影响当前游戏存档数据的方法
https://www.renpy.cn/forum.php?mod=viewthread&tid=1739
她的心里话!隐藏文本!
https://www.renpy.cn/forum.php?mod=viewthread&tid=1713
能力雷达图
https://www.renpy.cn/forum.php?mod=viewthread&tid=1719
暴学CDD的十四天/附虚拟摇杆
https://www.renpy.cn/forum.php?mod=viewthread&tid=1675
简单rpg地图移动
https://www.renpy.cn/forum.php?mod=viewthread&tid=1652
摇骰子小游戏
https://www.renpy.cn/forum.php?mod=viewthread&tid=1653
猜球盅小游戏
https://www.renpy.cn/forum.php?mod=viewthread&tid=1574
FontGroup() 游戏里全局使用两种字体的方法
https://www.renpy.cn/forum.php?mod=viewthread&tid=1491
粉身碎骨浑不怕,要留答辩在人间
|
评分
-
查看全部评分
|