Pygame 粒子特效
我有一个小粒子效果,它会产生向上移动的圆圈。我想让它看起来像烟雾,但我遇到了很多问题。
- 首先,我想让这个效果是递归的,也就是说,当粒子到达顶部时,它能返回到最初的位置,然后重新开始。现在的效果有点像,但还不完全符合我的想法。
- 其次,我觉得它看起来并不太像烟雾,有没有人能建议一些改进的方法,让它看起来更好?
- 最后,我想把这个效果放到我的游戏里,我该怎么做才能让它在游戏中调用?也就是说,我想只需要调用粒子效果,并给它一个位置,它就能出现在那里。有没有人能帮我解决这些问题?
我的代码
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 个回答
我对你的代码做了很多修改,特别是在Particle
类上。
虽然这个代码里有些地方让人困惑,但它会比你现在的代码更灵活。
在这里,
我几乎是重新写了你的Particle
类。
除了把__init__
方法改成接受很多参数(具体来说是7个),
我还用了三角函数和math
模块来让粒子移动,这样管理起来会更简单(如果你对数学有点了解的话!)。我还给Particle
类添加了bounce
和draw
方法,让代码更容易阅读。
就像@PygameNerd所做的,我也添加了一个时钟,用来限制最大帧率(fps)。我没有改变任何事件处理的部分,但在for p in particles:
循环中使用了bounce
和draw
函数。
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()
我对你的代码做了一些大改动。首先,我把你的类整理得更清晰了。我们先从参数和 __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值)
新代码将使粒子在起始位置生成,并持续上升,没有间断。希望你喜欢。