找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 6074|回复: 0

[转载] Ren'Py引擎从入门到放弃(支线3·续)——简单粒子效果

[复制链接]
发表于 2019-4-25 19:05:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 BuErShen 于 2019-4-25 18:50 编辑

Ren'Py引擎从入门到放弃(支线3·续)——简单粒子效果

世上无难事,只要肯放弃。

支线系列是独立于基础之外的内容,会引用一些外部平台大佬的内容,感觉有困难的同学可以暂时不(fang)看(qi)。

本篇内容将继续讲解一个简单的粒子系统。请善用官方文档的搜索功能,能解决大部分疑问。

第一个问题:上次的 Particle Burst 代码没看懂,是啥意思呢?

答:看来还是需要分解一下。那段代码的本质上一个“创作者定义的可视组件”(Creator-Defined Displayable),以前叫做“用户定义的可视组件”(User Defined Displayable),所以缩写还是使用旧称UDD。UDD使用Python编写,需要我们掌握 Python基础,比如变量类型、语法和常用库,如果有 Pygame基础就更好。完全没有Python基础的同学可以暂时放弃,先学习Python。udd的定义和样例可以参考官方文档。

下面开始拆解 Particle Burst的代码:

开头的 transform particle是自定义的变换,几个入参有默认值。在后面的代码中,在每一个例子的动画中都应用了该transform。不过这个transform写得有点问题,我们会在后面更改。

class ParticleBurst(renpy.Displayable)定义表示从renpy.Displayable继承。后面的 __init__rendervisit 三连是ParticleBurst继承后覆盖(override)的3个成员函数。官方文档样例里还有个 event函数,是用来响应事件消息的,一般用在按钮之类的组件上。
[RenPy] 纯文本查看 复制代码
def __init__(self, displayable, interval=(0.02, 0.04), speed=(0.15, 0.3), around=(config.screen_width/2, config.screen_height/2), angle=(0, 360), radius=(50, 75), particles=None, mouse_sparkle_mode=False, **kwargs)

__init__ 函数有一堆入参,self 表示自身的指针,**kwargs 表示额外参数列表指针,这两个是固定的。其他几个参数的定义如下:

  • displayable:Ren'Py中可以显示的任意内容(除了包含随意内容的可视组件或者可视组件容器。)
  • interval: :每次生成粒子的时间间隔,单位为秒。入参格式是一个二元的元组,元素值是浮点数。在两个数值之间生成一个随机数作为时间间隔。
  • speed:粒子发射速度。也是二元的元组,两个数值之间生成一个随机数作为速度。
  • angle:粒子发射角度。也是二元的元组,两个数值之间生成一个随机数。
  • radius:粒子发射距离。也是二元的元组,两个数值之间生成一个随机数。
  • around:发射中心坐标,也是二元的元组,(x, y)形式的坐标值。所有粒子都会从这个点发射。
  • particles:发射粒子总数,默认为无限。
  • mouse_sparkle_mode:是否将鼠标位置设置为发射中心。这是一个布尔值。

后面几条语句是将入参分别赋值给ParticleBurst类的内部成员变量。

render函数是重点。开头的函数声明中出现了 stat ,其中 st表示显示时基(show timebase),at表示动画时基(animation timebase)。一般st不做设置,是否显示也由 at控制。ParticleBurst作者在 st0 时初始化了3个变量:

  • next:下一个例子的编号。整型
  • particle:生成的粒子总数。整型
  • shown:待显示粒子集。字典型

接下去的代码每次生成一个粒子(及粒子的速度、角度和运动半径),并添加到 shown字典中。
[RenPy] 纯文本查看 复制代码
if not self.msm:
    self.shown[st + speed] = particle(rp.random.choice(self.d), st, speed, self.around, angle, radius)
else:
    self.shown[st + speed] = particle(rp.random.choice(self.d), st, speed, rp.get_mouse_pos(), angle, radius)

这个if-else语句中,定义了每个粒子的动画。两种粒子的差别仅在于发射中心是否跟随鼠标。粒子应用了之前定义的particle变换(transform)。所以,后续我们要修改粒子的动画效果只需要修改particle变化。

最后在for循环中的使用render.blit函数将shown字典中的粒子加入到待渲染列表中。因为粒子是运动的,所以使用redraw每次更新。

visit函数是固定的,没啥可说。

第二个问题:ParticleBurst发射出来的粒子都是螺旋运动的,有什么办法让粒子直线发射呢?

答:根据上一个问题的分析,我们可以确认这个锅肯定是在particle这个变换上。particle的语法有点糟糕,很容易混淆入参和特性(property)。我们自己定义入参名称时,最好不要与各个特性名称相同。我做了点修改:
[RenPy] 纯文本查看 复制代码
transform particle(d, delay, speed=1.0, around_pos=(config.screen_width/2, config.screen_height/2), particle_angle=0, particle_radius=200):
    d
    pause delay
    subpixel True
    around around_pos
    angle particle_angle
    radius 0
    linear speed radius particle_radius angle particle_angle

这样粒子就是直线发射了。原先的代码中,问题出在angle特性上。由于没有设置初始中,所以Ren'Py默认值就是0。注意最后一行的 linear,同时作用于radius angle,必须使用这个写法。parallel block语句都将无法达到需要的效果。

第三个问题:还有什么骚操作吗?

答:没有骚操作,只有基础!ParticleBurst类 的几个入参需要使用二元元组。如果入参写一个数值,或者元组的两个相同值都会报错。这样的类不太友好,所以就修改了一下。
[RenPy] 纯文本查看 复制代码
init python:
    class ParticleBurst(renpy.Displayable):
        def __init__(self, displayable, interval=(0.02, 0.04), speed=(0.15, 0.3), around=(config.screen_width/2, config.screen_height/2), angle=(0, 360), radius=(50, 75), particles=None, mouse_sparkle_mode=False, **kwargs):

            super(ParticleBurst, self).__init__(**kwargs)
            self.d = [renpy.easy.displayable(d) for d in displayable] if isinstance(displayable, (set, list, tuple)) else [renpy.easy.displayable(displayable)]
            self.interval = interval
            self.speed = speed
            self.around = around
            self.angle = angle
            self.radius = radius
            self.particles = particles
            self.msm = mouse_sparkle_mode

        def render(self, width, height, st, at):

            rp = store.renpy

            if not st:
                self.next = 0
                self.particle = 0
                self.shown = {}

            render = rp.Render(width, height)

            if not (self.particles and self.particle >= self.particles) and self.next <= st:
                if isinstance(self.speed, (int, long, float)):
                    speed = self.speed
                elif isinstance(self.speed, tuple):
                    if (self.speed[0] == self.speed[1]):
                        speed = self.speed[0]
                    else:
                        speed = rp.random.uniform(self.speed[0], self.speed[1])
                if isinstance(self.angle, (int, long, float)):
                    angle = self.angle
                elif isinstance(self.angle, tuple):
                    if (self.angle[0] == self.angle[1]):
                        angle = self.angle[0]
                    else:
                        angle = rp.random.randrange(self.angle[0], self.angle[1])
                if isinstance(self.radius, (int, long, float)):
                    radius = self.radius
                elif isinstance(self.radius, tuple):
                    if (self.radius[0] == self.radius[1]):
                        radius = self.radius[0];
                    else:
                        radius = rp.random.randrange(self.radius[0], self.radius[1])
                if not self.msm:
                    self.shown[st + speed] = particle(rp.random.choice(self.d), st, speed, self.around, angle, radius)
                else:
                    self.shown[st + speed] = particle(rp.random.choice(self.d), st, speed, rp.get_mouse_pos(), angle, radius)
                if isinstance(self.interval, (int, long, float)):
                    self.next = st + self.interval
                elif isinstance(self.interval, tuple):
                    if (self.interval[0] == self.interval[1]):
                        self.next = st + self.interval[0]
                    else:
                        self.next = st + rp.random.uniform(self.interval[0], self.interval[1])
                if self.particles:
                    self.particle = self.particle + 1

            for d in self.shown.keys():
                if d < st:
                    del(self.shown[d])
                else:
                    d = self.shown[d]
                    render.blit(d.render(width, height, st, at), (d.xpos, d.ypos))

            rp.redraw(self, 0)

            return render

        def visit(self):
            return self.d

这样可以指定某个动画特性的值,而不需要浮动范围。或者浮动范围的上下限可以相等。

然后再修改一下particle变换和粒子的颜色,得到下图效果:
再修改一下particle变换和粒子的颜色.gif

吐槽部分:Ren'Py 7.2貌似有不少坑,甚至运行“教程”都会报错。旧版本升级请谨慎。

预告部分:简单粒子部分暂告一段落。下次可能会研究下层叠式图像(LayeredImage)。这部分的难点在于——抠图……我看看能否抓到个苦力帮忙……



“Ren'Py引擎从入门到放弃”系列教程


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|RenPy中文空间 ( 苏ICP备17067825号|苏公网安备 32092302000068号 )

GMT+8, 2024-4-27 01:51 , Processed in 0.077300 second(s), 15 queries , File On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表