Pygame 粒子特效

2 投票
2 回答
8993 浏览
提问于 2025-04-17 15:43

我有一个小粒子效果,它会产生向上移动的圆圈。我想让它看起来像烟雾,但我遇到了很多问题。

  1. 首先,我想让这个效果是递归的,也就是说,当粒子到达顶部时,它能返回到最初的位置,然后重新开始。现在的效果有点像,但还不完全符合我的想法。
  2. 其次,我觉得它看起来并不太像烟雾,有没有人能建议一些改进的方法,让它看起来更好?
  3. 最后,我想把这个效果放到我的游戏里,我该怎么做才能让它在游戏中调用?也就是说,我想只需要调用粒子效果,并给它一个位置,它就能出现在那里。有没有人能帮我解决这些问题?

我的代码

import pygame,random
from pygame.locals import *

xmax = 1000    #width of window
ymax = 600     #height of window

class Particle():
    def __init__(self, x, y, dx, dy, col):
        self.x = x
        self.y = y
        self.col = col
        self.ry = y
        self.rx = x
        self.dx = dx
        self.dy = dy

    def move(self):
        if self.y >= 10:
            if self.dy < 0:
                self.dy = -self.dy

        self.ry -= self.dy
        self.y = int(self.ry + 0.5)

        self.dy -= .1
        if self.y < 1:
            self.y += 500

def main():
    pygame.init()
    screen = pygame.display.set_mode((xmax,ymax))
    white = (255, 255, 255)
    black = (0,0,0)
    grey = (128,128,128)

    particles = []
    for part in range(25):
        if part % 2 > 0: col = black
        else: col = grey
        particles.append( Particle(random.randint(500, 530), random.randint(0, 500), 0, 0, col))

    exitflag = False
    while not exitflag:
        for event in pygame.event.get():
            if event.type == QUIT:
                exitflag = True
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    exitflag = True

        screen.fill(white)
        for p in particles:
            p.move()
            pygame.draw.circle(screen, p.col, (p.x, p.y), 8)

        pygame.display.flip()
    pygame.quit()

if __name__ == "__main__":
    main()

2 个回答

1

我对你的代码做了很多修改,特别是在Particle类上。
虽然这个代码里有些地方让人困惑,但它会比你现在的代码更灵活。

在这里,
我几乎是重新写了你的Particle类。
除了把__init__方法改成接受很多参数(具体来说是7个),
我还用了三角函数和math模块来让粒子移动,这样管理起来会更简单(如果你对数学有点了解的话!)。我还给Particle类添加了bouncedraw方法,让代码更容易阅读。
就像@PygameNerd所做的,我也添加了一个时钟,用来限制最大帧率(fps)。我没有改变任何事件处理的部分,但在for p in particles:循环中使用了bouncedraw函数。

import pygame, random, math

def radians(degrees):
    return degrees*math.pi/180

class Particle:
    def __init__(self, (x, y), radius, speed, angle, colour, surface):
        self.x = x
        self.y = y
        self.speed = speed
        self.angle = angle
        self.radius = radius
        self.surface = surface
        self.colour = colour
        self.rect = pygame.draw.circle(surface,(255,255,0),
                           (int(round(x,0)),
                            int(round(y,0))),
                           self.radius)
    def move(self):
        """ Update speed and position based on speed, angle """
        # for constant change in position values.
        self.x += math.sin(self.angle) * self.speed
        self.y -= math.cos(self.angle) * self.speed
        # pygame.rect likes int arguments for x and y
        self.rect.x = int(round(self.x))
        self.rect.y = int(round(self.y))
        
    def draw(self):
        """ Draw the particle on screen"""
        pygame.draw.circle(self.surface,self.colour,self.rect.center,self.radius)
        
    def bounce(self):
        """ Tests whether a particle has hit the boundary of the environment """
        
        if self.x > self.surface.get_width() - self.radius: # right
            self.x = 2*(self.surface.get_width() - self.radius) - self.x
            self.angle = - self.angle
            
        elif self.x < self.radius: # left
            self.x = 2*self.radius - self.x
            self.angle = - self.angle            

        if self.y > self.surface.get_height() - self.radius: # bottom
            self.y = 2*(self.surface.get_height() - self.radius) - self.y
            self.angle = math.pi - self.angle
            
        elif self.y < self.radius: # top
            self.y = 2*self.radius - self.y
            self.angle = math.pi - self.angle

def main():
    xmax = 640    #width of window
    ymax = 480     #height of window
    white = (255, 255, 255)
    black = (0,0,0)
    grey = (128,128,128)
    
    pygame.init()
    screen = pygame.display.set_mode((xmax,ymax))
    clock = pygame.time.Clock()

    particles = []
    
    for i in range(1000):
        if i % 2:
            colour = black
        else:
            colour = grey
        # for readability
        x = random.randint(0, xmax)
        y = random.randint(0, ymax)
        speed = random.randint(0,20)*0.1
        angle = random.randint(0,360)
        radius = 3
        particles.append( Particle((x, y), radius, speed, angle, colour, screen) )

    done = False
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
                break
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    done = True
                    break
        if done:
            break

        screen.fill(white)
        for p in particles:
            p.move()
            p.bounce()
            p.draw()
            
        clock.tick(40)
        
        pygame.display.flip()
    pygame.quit()

if __name__ == "__main__":
    main()
4

我对你的代码做了一些大改动。首先,我把你的类整理得更清晰了。我们先从参数和 __init__ 函数说起。

首先,粒子不再是往下移动500来重置,而是回到它们的起始位置。它们的起始位置现在是在 __init__ 函数里随机选择的,而不是在游戏中设置的。我也去掉了一些你不需要的参数。

在你类的 move 函数里,我简化了很多。粒子要判断是否需要重置,只需检查它是否在0以上。向上移动就是简单地把y值减1。我增加的一个变化是,x值会随机变化,向左和向右移动。这样做会让烟雾看起来更好、更真实。

我对你代码的其他部分没有做太多改动。我调整了你调用 Particle 类的方式,以适应新的参数。我还增加了很多粒子,主要是为了视觉效果。另外,我大幅度减小了绘制的圆圈的大小(你能猜到原因吗?)也是为了视觉效果。我还添加了一个时钟,以防止粒子移动得太快。

这是最终的代码。希望你喜欢。

import pygame,random
from pygame.locals import *

xmax = 1000    #width of window
ymax = 600     #height of window

class Particle():
    def __init__(self, startx, starty, col):
        self.x = startx
        self.y = random.randint(0, starty)
        self.col = col
        self.sx = startx
        self.sy = starty

    def move(self):
        if self.y < 0:
            self.x=self.sx
            self.y=self.sy

        else:
            self.y-=1

        self.x+=random.randint(-2, 2)

def main():
    pygame.init()
    screen = pygame.display.set_mode((xmax,ymax))
    white = (255, 255, 255)
    black = (0,0,0)
    grey = (128,128,128)

    clock=pygame.time.Clock()

    particles = []
    for part in range(300):
        if part % 2 > 0: col = black
        else: col = grey
        particles.append( Particle(515, 500, col) )

    exitflag = False
    while not exitflag:
        for event in pygame.event.get():
            if event.type == QUIT:
                exitflag = True
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    exitflag = True

        screen.fill(white)
        for p in particles:
            p.move()
            pygame.draw.circle(screen, p.col, (p.x, p.y), 2)

        pygame.display.flip()
        clock.tick(50)
    pygame.quit()

if __name__ == "__main__":
    main()

更新

要在你的代码中添加粒子,只需像上面的代码那样做。运行得很好。如果你想显示烟雾的开始,只需在参数中设置一个暂停时间,并在这段时间内禁止烟雾的移动。新类的代码如下:

class Particle():
    def __init__(self, startx, starty, col, pause):
        self.x = startx
        self.y = starty
        self.col = col
        self.sx = startx
        self.sy = starty
        self.pause = pause

    def move(self):
        if self.pause==0:
            if self.y < 0:
                self.x=self.sx
                self.y=self.sy

            else:
                self.y-=1

            self.x+=random.randint(-2, 2)

        else:
            self.pause-=1

你需要的创建新粒子的代码:

for part in range(1, A):
    if part % 2 > 0: col = black
    else: col = grey
    particles.append( Particle(515, B, col, round(B*part/A)) )

A和B是变量(我建议A大约为300,B将是Y值)

新代码将使粒子在起始位置生成,并持续上升,没有间断。希望你喜欢。

撰写回答