使用自定义可视组件的长按跳过功能
在游戏结束的时候,往往存在一个很长的制作人员名单,虽然有些人会主动看完,但是大部分情况下玩家都更需要一个跳过的选项。在游玩黑神话悟空的时候,我发现许多CG都可以长按跳过,于是准备抄一下这个交互。
首先,将滚动制作人员名单的暂停交互逻辑以Screen的形式封装——在一个确定的滚动时长之后自动结束,大概像是这样调用:
"虽然我看不见自己的脸,但我猜,那个时候我的脸应该一瞬间变得通红。我用手想遮住我的嘴角,可还是无法控制的笑起来。"
"然后,我奔向她——"
call screen holdTimer()
jump afterTheRoc
使用holdTimer来阻止主控流程的前进,直到手动返回交互值。
screen holdTimer(StaffImage=None,RollingTime=25,ringColor="#659282"):
modal True
add StaffImage
add HodingMask(Progress(percent=0,type="circle",ringColor=ringColor),time=3000):
xysize (1920, 1080)
timer RollingTime action [With(Dissolve(4)), Return()]
on "show" action [SetVariable("renpy.config.skipping", None),SetVariable("_skipping", False)]
on "hide" action SetVariable("_skipping", True)
其中,使用timer
关键字和入参 StaffImage
配合,构造自动播放的部分。值得一说的是,为了阻止玩家使用快进,在展示和隐藏的事件时手动设置_skipping
,它用于阻止快进。而 "renpy.config.skipping"
则是中断玩家的快进状态(如果不中断的话,在播放完毕名单后会继续快进)
接下来就是关键的长按跳过的逻辑了,这里我使用了 Creator-Defined Displayables
,即创作者定义的可视组件来完成这部分的需求。
import pygame
class HodingMask(renpy.Displayable):
def __init__(self, child,time,refreshRate=0.025, **kwargs):
# 向renpy.Displayable构造器传入额外的特性(property)。
super(HodingMask, self).__init__(**kwargs)
# 保存需要长按的时间
self.targetTime = time
# 初始化已经按住的时间
self.holdTime = 0
# 子组件
self.child = renpy.displayable(child)
# 鼠标状态
self.isHold = False
# 百分比
self.percent = 0
# 上一次绘制的时间点
self.lastRenderTime = 0
# 默认的刷新速度
self.refreshRate = refreshRate
# 提示
self.tip = Text(f"长按{int(time/1000)}秒跳过滚动字幕")
# 上一次按下的时间
self.lastMouseDownTime = 0
# 上一次抬起的时间
self.lastMouseUpTime = 0
def render(self, width, height, st, at):
self.lastRenderTime = st
alpha = 0
dissolve_time = 0.5
if self.isHold and self.lastMouseDownTime + dissolve_time < st:
alpha = 1
elif self.isHold and self.lastMouseDownTime + dissolve_time >= st:
alpha = (st - self.lastMouseDownTime) / dissolve_time
elif not self.isHold and self.lastMouseUpTime + dissolve_time > st:
alpha = 1 - (st - self.lastMouseUpTime) / dissolve_time
alpha = max(0, min(1, alpha))
child_render = renpy.render(Transform(child=self.child,alpha=alpha), width, height, st, at)
render = renpy.Render(width, height
render.blit(child_render, (40, 22))
renpy.timeout(self.refreshRate)
# 返回渲染器。
return render
def event(self, ev, x, y, st):
if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
self.isHold = True
self.lastMouseDownTime = st
elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
self.isHold = False
self.lastMouseUpTime = st
if ev.type == pygame.USEREVENT:
self.update(st)
return self.child.event(ev, x, y, st)
def visit(self):
return [ self.child,self.tip ]
def update(self,st):
if self.percent == 0 and not self.isHold:
return renpy.redraw(self.child, 1)
if self.percent == 100 or self.holdTime >= self.targetTime:
self.percent = 100
renpy.end_interaction(True)
self.percent = 0
self.holdTime = 0
self.lastRenderTime = 0
self.isHold = False
return None
during = int((st - self.lastRenderTime) * 1000)
if self.isHold:
self.holdTime += during
elif self.holdTime > 0:
self.holdTime -= during
self.percent = max(0, min(100,int(self.holdTime / self.targetTime * 100)))
if self.child and self.child.setPercent:
self.child.setPercent(self.percent)
renpy.redraw(self.child, 0)
忽略 setPercent
部分,那是另外一个cddProgress
,用于展示进度。
cdd需要实现 event
和 render
两个关键方法,前者用于接受事件(包括renpy
自定义事件和基于pygame
的用户事件),后者则是用于绘制本可视组件及其子组件。
具体业务逻辑不详细说明了,关键在于在 renpy.redraw()
和 renpy.timeout()
两个API,redraw
会调用 cdd的render
方法,而 timeout
则会手动触发一次自定义事件,可以直接认为是调用 event
方法,这样就构成了一个事件循环绘制的流程,我们无需在意Renpy
如何实现绘制线程和事件传递。