Есть ли способ оптимизировать поверхности с прозрачностью в pygame?

#python #pygame

#питон #pygame

Вопрос:

Я пытаюсь произвести взрыв с частицами в pygame, но это очень, очень медленно. Одна из самых медленных частей кода-это часть, которая создает поверхности с требуемым цветом и прозрачностью. Я публикую здесь весь код для ясности, но функция вызывается makeSurface . Это и в SmokeParticle том, и Spark в другом классе.

 import pygame import math import random  ##draw functions##  def drawSpark(window, spark):  if not spark.dead():   window.blit(spark.surface, spark.position)  def drawParticle(window, particle):  window.blit(particle.surface, particle.position)  def drawCluster(window, cluster):  for particle in cluster.particles:  drawParticle(window, particle)  def drawSmoke(window, smoke):  for cluster in smoke.clusters:  drawCluster(window, cluster)  def drawExplosions(window, em):  for s in em.smoke:  drawSmoke(window, s)  for spark in em.sparks:  drawSpark(window, spark)   ##other functions##  #thank you rabbid76 def randomDistBiasedMiddle(_min, _max):  r = lambda : random.uniform(-1, 1)  r1, r2 = r(), r()  bias = lambda _r: _r**3 * (_max - _min) / 2   (_min   _max) / 2  return (bias(r1), bias(r2))  ##########################################  class Spark:  def __init__(self, travel, position, rotation, scale):  self.position = list(position)  self.rotation = rotation  self.goTowards = (math.sin(rotation), math.cos(rotation))  self.scale = scale  self.speed = random.randint(70, 130) * scale  self.maxTravel = travel * self.scale * 2  self.maxAlpha = 150  self.minAlpha = 0  self.randomColor = random.choice((  (188, 98, 5),  (255, 215, 0),  (255, 127, 80),  (255, 140, 0)))  self.travelled = 0  self.makeSurface()   def shoot(self):  if self.travelled lt; self.maxTravel:  self.travelled  = 5 #could use actual distance but performance :( . 5 seems to be a good fit tho  self.position[0]  = self.goTowards[0] * (3/self.travelled) * self.speed  self.position[1]  = self.goTowards[1] * (3/self.travelled) * self.speed   def makeSurface(self):  self.alpha = ((1 - (self.travelled / self.maxTravel)) * (self.maxAlpha - self.minAlpha))   self.minAlpha  self.color = (*self.randomColor, self.alpha)  if self.alpha gt; 0:  self.surface = pygame.Surface((3, 15), pygame.SRCALPHA)  self.surface.fill(self.color)  self.surface = pygame.transform.rotate(self.surface, math.degrees(self.rotation))   def dead(self):  return self.alpha lt; 1  class SmokeParticle:  def __init__(self, position, distFromcentre, explosionRange, centre):  self.position = list(position)  self.distFromCentre = distFromcentre #distance from centre of explosion, not the cluster  self.possibleMoves = (  (-1, -1), (0, -1), (1, -1), (1, 0),  (1, 1), (0, 1), (-1, 1), (-1, 0)  )   self.blowSpeed = 2  self.blowDir = (self.position[0] - centre[0], self.position[1] - centre[1])  self.blowDir = (self.blowDir[0] / distFromcentre, self.blowDir[1] / distFromcentre)  self.blowDistance = math.hypot(*self.blowDir)    self.explosionRange = explosionRange  self.maxAlpha = 200  self.minAlpha = 80  self.cf = (255, 140, 0)#smoke color farthest from the explosion centre  self.cn = (20, 20, 20)#smoke color closest to the explosion centre  self.makeSurface()   def makeSurface(self):  self.alpha = ((1 - (self.distFromCentre / self.explosionRange)) * (self.maxAlpha - self.minAlpha))   self.minAlpha  findColor = lambda c : max(0, min(((1 - (self.distFromCentre / self.explosionRange)) * (self.cf[c] - self.cn[c]))   self.cn[c], 255))  self.color = (findColor(0), findColor(1), findColor(2), self.alpha)  self.surface = pygame.Surface((8, 8), pygame.SRCALPHA)  if self.alpha gt; 0:  self.surface.fill(self.color)    def blowAway(self, dt):  move = random.choice(self.possibleMoves)  self.position[0]  = move[0]  self.position[1]  = move[1]  self.position[0]  = self.blowDir[0] * self.blowSpeed   self.position[1]  = self.blowDir[1] * self.blowSpeed   self.distFromCentre  = self.blowDistance * self.blowSpeed    def dead(self):  return self.distFromCentre gt; self.explosionRange   class SmokeCluster:  def __init__(self, position, explosionRange, distFromCentre, centre):  self.particles = []  self.explosionRange = explosionRange  self.radius = 5  self.nParticels = 10  self.position = position  self.distFromCentre = distFromCentre    for i in range(self.nParticels):  randomPos = randomDistBiasedMiddle(-self.radius, self.radius)  pos = (self.position[0]   randomPos[0], self.position[1]   randomPos[1])  self.particles.append(SmokeParticle(pos, self.distFromCentre, self.explosionRange, centre))   def do(self, dt):  for particle in self.particles:  particle.blowAway(dt)  particle.makeSurface()   def removeDeadParticles(self):  self.particles = [particle for particle in self.particles if not particle.dead()]   def dead(self):  return not self.particles  class SmokeMass:  def __init__(self, explosionRange, mousePos):  self.clusters = []  self.explosionRange = explosionRange   self.nClusters = int(200 * (explosionRange * 0.1))  for i in range(self.nClusters):  randomPos = randomDistBiasedMiddle(-self.explosionRange, self.explosionRange)  clusterPos = (randomPos[0]   mousePos[0], randomPos[1]   mousePos[1])  self.clusters.append(SmokeCluster(clusterPos, self.explosionRange, math.dist(clusterPos, mousePos), mousePos))    def do(self, dt):  for cluster in self.clusters:  cluster.do(dt)   def removeDeadClusters(self):  for cluster in self.clusters:  cluster.removeDeadParticles()  self.clusters = [cluster for cluster in self.clusters if not cluster.dead()]     class ExplosionManager:  def __init__(self, scale):  self.sparks = []  self.smoke = []  self.scale = scale  self.cloudPatches = []  self.maxSpread = 100  self.removeSparkles = pygame.USEREVENT   0  pygame.time.set_timer(self.removeSparkles, 2500)    def generateSparks(self, mousePos):  for i in range(int(self.scale * 20)):  rotation = random.uniform(0, 2 * math.pi)  self.sparks.append(Spark(self.maxSpread, mousePos, rotation, self.scale))   def generateSmoke(self, mousePos):  self.smoke.append(SmokeMass(200 * self.scale, mousePos))   def triggered(self, events):  for event in events:  if event.type == pygame.MOUSEBUTTONDOWN:  return True   def explode(self, dt):  for spark in self.sparks:  spark.shoot()  spark.makeSurface()   for s in self.smoke:  s.do(dt)   def removeDeadSparks(self, events):  for event in events:  if event.type == self.removeSparkles:  self.sparks = [spark for spark in self.sparks if not spark.dead()]   def removeDeadSmoke(self):  for s in self.smoke:  s.removeDeadClusters()    pygame.init() winSize = (600, 600) window = pygame.display.set_mode(winSize) clock = pygame.time.Clock() fps = 100  explosionScale = 1 explosionScale = min(max(explosionScale, 0), 1)#because i never know what I will do em = ExplosionManager(explosionScale)   while True:  events = pygame.event.get()  mousePos = pygame.mouse.get_pos()  mousePressed = pygame.mouse.get_pressed()  dt = clock.tick(fps) * 0.001  pygame.display.set_caption(f"FPS: {clock.get_fps()}")  gen = False  for event in events:  if event.type == pygame.QUIT:  pygame.quit()  raise SystemExit   if em.triggered(events):  em.generateSparks(mousePos)  em.generateSmoke(mousePos)   em.explode(dt)  em.removeDeadSparks(events)  em.removeDeadSmoke()   window.fill((40, 50, 50))  drawExplosions(window, em)  pygame.display.flip()  

Ответ №1:

Очевидное улучшение заключается не в том, чтобы создавать поверхность для каждой частицы каждый раз, когда она движется. Создайте поверхность только один раз и залейте ее новым цветом, когда цвет изменится

 class SmokeParticle:  def __init__(self, position, distFromcentre, explosionRange, centre):  # [...]   self.surface = pygame.Surface((8, 8), pygame.SRCALPHA)  self.color = None  self.makeSurface()   def makeSurface(self):  self.alpha = ((1 - (self.distFromCentre / self.explosionRange)) * (self.maxAlpha - self.minAlpha))   self.minAlpha  findColor = lambda c : max(0, min(((1 - (self.distFromCentre / self.explosionRange)) * (self.cf[c] - self.cn[c]))   self.cn[c], 255))  color = (findColor(0), findColor(1), findColor(2), self.alpha)  if self.alpha gt; 0 and self.color != color:  self.color = color  self.surface.fill(self.color)  

Этого все равно будет недостаточно для повышения производительности, но это будет лучше.

Комментарии:

1. о, я не знал, что функции pygame.draw могут принимать цвета с альфа-значениями. Спасибо. Я подожду некоторое время, чтобы посмотреть, получу ли я какие-либо другие ответы, и приму ваш ответ, если я этого не сделаю

2. @Cool_Cornflakes Моя вина. pygame.draw.rect невозможно нарисовать прозрачные прямоугольники. Я изменил ответ.