如何实现粒子引擎

use*_*231 5 python refactoring pygame particle-system

所以我制作了一个吸烟的粒子引擎,我很高兴,我认为它非常适合我的游戏.
我现在需要将它实现到我的游戏中,我遇到了一些麻烦.我想知道是否有人可以解释我将如何在我的游戏中使用我的粒子引擎.
我在下面添加了我的代码(用于冒烟和我的游戏文件).
我希望将粒子与游戏文件分开,但在我的游戏中调用它.
最终,我想在引擎中制作更多粒子效果,我也可以调用它.

有人可以帮忙吗?它可能需要一些调整才能工作.

粒子代码:

import pygame,random
from pygame.locals import *

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

class Smoke():
    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(-1, 2)

def main():
    pygame.init()
    screen = pygame.display.set_mode((xmax,ymax))
    black = (0,0,0)
    grey = (145,145,145)
    light_grey = (192,192,192)
    dark_grey = (183, 183, 183)

    clock = pygame.time.Clock()

    particles = []
    for part in range(600):
        if part % 2 > 0: col = grey
        #elif part % 5 > 0: col = dark_grey
        elif part % 3 > 0: col = dark_grey
        else: col = light_grey
        particles.append( Smoke(0, 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(black)
        for p in particles:
            p.move()
            pygame.draw.circle(screen, p.col, (p.x, p.y), 15)

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

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

游戏代码

import pygame
from pygame import *

WIN_WIDTH = 1120 - 320
WIN_HEIGHT = 960 - 320
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)

DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 0
FLAGS = 0
CAMERA_SLACK = 30

levels = {0: {'level': [
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                         E  ",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "               PPPPP        PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPP           P",
                    "                            PPPP           P",
                    "                            PPPP     PPPPPPP",
                    "                      PPPPPPPPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "       PPPP                 PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "PPPPP                       PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP         PPPPP           PPPP     PPPPPPP",
                    "PPP                                     PPPP",
                    "PPP                                     PPPP",
                    "PPP                                     PPPP",
                    "PPP                       PPPPPPPPPPPPPPPPPP",
                    "PPP                       PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",],
             'enemies': [(9, 38)]},
             1: {'level': [
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                         E  ",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "               PPPPP        PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPP           P",
                    "                            PPPP           P",
                    "                            PPPP     PPPPPPP",
                    "                      PPPPPPPPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "       PPPP                 PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "PPPPP                       PPPP     PPPPPPP",
                    "PPP                  PPPPPPPPPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP             PPPPPPPP    PPPP     PPPPPPP",
                    "PPP                                     PPPP",
                    "PPP                                     PPPP",
                    "PPP          PPPPP                      PPPP",
                    "PPP          P            PPPPPPPPPPPPPPPPPP",
                    "PPP          P    PPPPPPPPPPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",],
             'enemies': [(9, 38), (18, 38), (15, 15)]}}

class Scene(object):
    def __init__(self):
        pass

    def render(self, screen):
        raise NotImplementedError

    def update(self):
        raise NotImplementedError

    def handle_events(self, events):
        raise NotImplementedError

class GameScene(Scene):
    def __init__(self, levelno):
        super(GameScene, self).__init__()
        self.bg = Surface((32,32))
        self.bg.convert()
        self.bg.fill(Color("#0094FF"))
        up = left = right = False
        self.entities = pygame.sprite.Group()
        self.player = Player(5, 40)
        self.player.scene = self
        self.platforms = []

        self.levelno = levelno

        levelinfo = levels[levelno]
        self.enemies = [Enemy(*pos) for pos in levelinfo['enemies']]

        level = levelinfo['level']
        total_level_width = len(level[0]) * 32
        total_level_height = len(level) * 32

        # build the level
        x = 0
        y = 0
        for row in level:
            for col in row:
                if col == "P":
                    p = Platform(x, y)
                    self.platforms.append(p)
                    self.entities.add(p)
                if col == "E":
                    e = ExitBlock(x, y)
                    self.platforms.append(e)
                    self.entities.add(e)
                x += 32
            y += 32
            x = 0

        self.camera = Camera(complex_camera, total_level_width, total_level_height)
        self.entities.add(self.player)
        for e in self.enemies:
            self.entities.add(e)

    def render(self, screen):
        for y in range(20):
            for x in range(25):
                screen.blit(self.bg, (x * 32, y * 32))

        for e in self.entities:
            screen.blit(e.image, self.camera.apply(e))

    def update(self):
        pressed = pygame.key.get_pressed()
        up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)]
        self.player.update(up, left, right, self.platforms)

        for e in self.enemies:
            e.update(self.platforms)

        self.camera.update(self.player)

    def exit(self):
        if self.levelno+1 in levels:
            self.manager.go_to(GameScene(self.levelno+1))
        else:
            self.manager.go_to(CustomScene("You win!"))

    def die(self):
        self.manager.go_to(CustomScene("You lose!"))

    def handle_events(self, events):
        for e in events:
            if e.type == KEYDOWN and e.key == K_ESCAPE:
                self.manager.go_to(TitleScene())

class CustomScene(object):

    def __init__(self, text):
        self.text = text
        super(CustomScene, self).__init__()
        self.font = pygame.font.SysFont('Arial', 56)

    def render(self, screen):
        # ugly! 
        screen.fill((0, 200, 0))
        text1 = self.font.render(self.text, True, (255, 255, 255))
        screen.blit(text1, (200, 50))

    def update(self):
        pass

    def handle_events(self, events):
        for e in events:
            if e.type == KEYDOWN:
                self.manager.go_to(TitleScene())

class TitleScene(object):

    def __init__(self):
        super(TitleScene, self).__init__()
        self.font = pygame.font.SysFont('Arial', 56)
        self.sfont = pygame.font.SysFont('Arial', 32)

    def render(self, screen):
        # ugly! 
        screen.fill((0, 200, 0))
        text1 = self.font.render('Crazy Game', True, (255, 255, 255))
        text2 = self.sfont.render('> press space to start <', True, (255, 255, 255))
        screen.blit(text1, (200, 50))
        screen.blit(text2, (200, 350))

    def update(self):
        pass

    def handle_events(self, events):
        for e in events:
            if e.type == KEYDOWN and e.key == K_SPACE:
                self.manager.go_to(GameScene(0))

class SceneMananger(object):
    def __init__(self):
        self.go_to(TitleScene())

    def go_to(self, scene):
        self.scene = scene
        self.scene.manager = self

def main():
    pygame.init()
    screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
    pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    timer = pygame.time.Clock()
    running = True

    manager = SceneMananger()

    while running:
        timer.tick(60)

        if pygame.event.get(QUIT):
            running = False
            return
        manager.scene.handle_events(pygame.event.get())
        manager.scene.update()
        manager.scene.render(screen)
        pygame.display.flip()

class Camera(object):
    def __init__(self, camera_func, width, height):
        self.camera_func = camera_func
        self.state = Rect(0, 0, width, height)

    def apply(self, target):
        try:
            return target.rect.move(self.state.topleft)
        except AttributeError:
            return map(sum, zip(target, self.state.topleft))

    def update(self, target):
        self.state = self.camera_func(self.state, target.rect)

def complex_camera(camera, target_rect):
    l, t, _, _ = target_rect
    _, _, w, h = camera
    l, t, _, _ = -l + HALF_WIDTH, -t +HALF_HEIGHT, w, h

    l = min(0, l)                           # stop scrolling left
    l = max(-(camera.width - WIN_WIDTH), l)   # stop scrolling right
    t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling bottom

    return Rect(l, t, w, h)

class Entity(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

class Player(Entity):
    def __init__(self, x, y):
        Entity.__init__(self)
        self.xvel = 0
        self.yvel = 0
        self.onGround = False
        self.image = Surface((32,32))
        self.image.fill(Color("#0000FF"))
        self.image.convert()
        self.rect = Rect(x*32, y*32, 32, 32)

    def update(self, up, left, right, platforms):
        if self.rect.top > 1440 or self.rect.top < 0:
            self.scene.die()
        if self.rect.left > 1408 or self.rect.right < 0:
            self.scene.die()
        if up:
            if self.onGround:
                self.yvel = 0
                self.yvel -= 10 # only jump if on the ground
        if left:
            self.xvel = -10
        if right:
            self.xvel = 10
        if not self.onGround:
            self.yvel += 0.3 # only accelerate with gravity if in the air
            if self.yvel > 80: self.yvel = 80 # max falling speed
        if not(left or right):
            self.xvel = 0

        self.rect.left += self.xvel # increment in x direction
        if self.collide(self.xvel, 0, platforms): # do x-axis collisions
            self.rect.top += self.yvel # increment in y direction
            self.onGround = False; # assuming we're in the air
            self.collide(0, self.yvel, platforms) # do y-axis collisions

    def collide(self, xvel, yvel, platforms):
        for p in platforms:
            if pygame.sprite.collide_rect(self, p):
                if isinstance(p, ExitBlock):
                    self.scene.exit()
                    return False
                if xvel > 0: self.rect.right = p.rect.left
                if xvel < 0: self.rect.left = p.rect.right
                if yvel > 0:
                    self.rect.bottom = p.rect.top
                    self.onGround = True
                if yvel < 0:
                    self.rect.top = p.rect.bottom
        return True

class Enemy(Entity):
    def __init__(self, x, y):
        Entity.__init__(self)
        self.yVel = 0
        self.xVel = 2 # start moving immediately
        self.image = Surface((32,32))
        self.image.fill(Color("#00FF00"))
        self.image.convert()
        self.rect = Rect(x*32, y*32, 32, 32)
        self.onGround = False

    def update(self, platforms):
        if not self.onGround:
            self.yVel += 0.3

        # no need for right_dis to be a member of the class,
        # since we know we are moving right if self.xVel > 0
        right_dis = self.xVel > 0

        # create a point at our left (or right) feet 
        # to check if we reached the end of the platform
        m = (1, 1) if right_dis else (-1, 1)
        p = self.rect.bottomright if right_dis else self.rect.bottomleft
        fp = map(sum, zip(m, p))

        # if there's no platform in front of us, change the direction
        collide = any(p for p in platforms if p.rect.collidepoint(fp))
        if not collide:
            self.xVel *= -1

        self.rect.left += self.xVel # increment in x direction
        self.collide(self.xVel, 0, platforms) # do x-axis collisions
        self.rect.top += self.yVel # increment in y direction
        self.onGround = False; # assuming we're in the air
        self.collide(0, self.yVel, platforms) # do y-axis collisions

    def collide(self, xVel, yVel, platforms):
        for p in platforms:
            if pygame.sprite.collide_rect(self, p):
                if xVel > 0: 
                    self.rect.right = p.rect.left
                    self.xVel *= -1 # hit wall, so change direction
                if xVel < 0: 
                    self.rect.left = p.rect.right
                    self.xVel *= -1 # hit wall, so change direction
                if yVel > 0:
                    self.rect.bottom = p.rect.top
                    self.onGround = True
                if yVel < 0:
                    self.rect.top = p.rect.bottom

class Platform(Entity):
    def __init__(self, x, y):
        Entity.__init__(self)
        #self.image = Surface([32, 32], pygame.SRCALPHA, 32) #makes blocks invisible for much better artwork
        self.image = Surface((32,32)) #makes blocks visible for building levels
        self.image.convert()
        self.rect = Rect(x, y, 32, 32)

    def update(self):
        pass

class ExitBlock(Platform):
    def __init__(self, x, y):
        Platform.__init__(self, x, y)
        self.image = Surface((32,32)) #makes blocks visible for building levels
        self.image.convert()
        self.rect = Rect(x, y, 32, 32)




if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

slo*_*oth 14

谨防!长帖!

粒子

首先,我们来看看你的Smoke课程.它包含一些烟雾行为,但也包含你的主循环.让我们通过创建一个Particle除了表示粒子之外什么都不做的泛型类来解决这个问题:

class Particle():
    def __init__(self, col, size, *strategies):
        self.x, self.y = 0, 0
        self.col = col
        self.alive = 0
        self.strategies = strategies
        self.size = size

    def kill(self):
        self.alive = -1 # alive -1 means dead

    def move(self):
        for s in self.strategies:
            s(self)   
Run Code Online (Sandbox Code Playgroud)

这堂课没什么用.它是通用的,它将所有行为(简单函数)传递给它的__init__函数,并且粒子在move方法中将这些函数应用于自身.

粒子的行为

现在我们的粒子类是灵活的,让我们考虑粒子应该如何表现,以便它们看起来像烟雾.

烟雾粒子应该上升,所以让我们创建一个向上移动粒子的函数:

def ascending(speed):
    def _ascending(particle):
        particle.y -= speed
    return _ascending
Run Code Online (Sandbox Code Playgroud)

烟雾粒子应该在某个时刻死亡,所以让我们写一个在某种条件下杀死它的函数:

def kill_at(max_x, max_y):
    def _kill_at(particle):
        if particle.x < -max_x or particle.x > max_x or particle.y < -max_y or particle.y > max_y:
            particle.kill()
    return _kill_at
Run Code Online (Sandbox Code Playgroud)

我们需要跟踪粒子的存活时间(后来派上用场),所以我们需要一个让粒子老化的函数:

def age(amount):
    def _age(particle):
        particle.alive += amount
    return _age
Run Code Online (Sandbox Code Playgroud)

当烟雾上升时,它不应该是一条直线(多么无聊!),但它应该散开:

def fan_out(modifier):
    def _fan_out(particle):
        d = particle.alive / modifier
        d += 1
        particle.x += random.randint(-d, d)
    return _fan_out
Run Code Online (Sandbox Code Playgroud)

太好了!现在我们的烟雾可以散开,但它仍然有点无聊,所以让我们写一个模拟一点风的功能:

def wind(direction, strength):
    def _wind(particle):
        if random.randint(0,100) < strength:
            particle.x += direction
    return _wind
Run Code Online (Sandbox Code Playgroud)

我们现在有一堆描述粒子行为的函数.所有这些都很小而且独立.您可以创建无限量的它们并根据需要组合它们以创建不同的粒子.

实际创建一些粒子的时间:进入烟雾机!

def smoke_machine():
    colors = {0: grey,
              1: dark_grey,
              2: light_grey}
    def create():
        for _ in xrange(random.choice([0,0,0,0,0,0,0,1,2,3])):
            behaviour = age(1), ascending(1), fan_out(400), wind(1, 15), kill_at(1000, 1000)
            p = Particle(colors[random.randint(0, 2)], random.randint(10, 15), *behaviour)
            yield p

    while True:
        yield create()
Run Code Online (Sandbox Code Playgroud)

那到底是什么意思?简单.它是一个不断发射新粒子的发生器.每当有人想要检索下一个项目时,它就会调用它的嵌套create函数.该函数依次根据输入列表返回0到3个粒子random.choice.这是一个很好的方式,它说它返回0粒子,有70%的几率,1或2或3粒子,每粒粒子有10%的几率.

在下一行中,我们定义了粒子的行为.它只是一个功能元组.注意每个函数调用如何返回其嵌套函数.这样就可以参数化这些函数.

最后一步是为粒子随机分配颜色和大小.

您可以从命令行进行测试,看看它是如何工作的:

>>> s=smoke_machine()
>>> list(next(s))
[]
>>> list(next(s))
[<particle.Particle instance at 0x02AD94B8>, <particle.Particle instance at 0x02
AD9030>]
>>> list(next(s))
[]
>>> list(next(s))
[]
>>> list(next(s))
[<particle.Particle instance at 0x02AD9030>]
>>> list(next(s))
[<particle.Particle instance at 0x02AD9418>, <particle.Particle instance at 0x02
AD93C8>]
>>> list(next(s))
[<particle.Particle instance at 0x02AD9030>]
Run Code Online (Sandbox Code Playgroud)

看看我们每次在发电机上调用下一次时,它最多返回3个粒子.

把它们放在一起

我们如何将我们的烟机与我们的游戏结合起来?让我们写一个为我们处理它的类:

class Emitter(object):
    def __init__(self, pos=(0, 0)):
        self.particles = []
        self.pos = pos
        self.factories = []

    def add_factory(self, factory, pre_fill=300):
        self.factories.append(factory)
        tmp = []
        for _ in xrange(pre_fill):
            n = next(factory)
            tmp.extend(n)
            for p in tmp:
                p.move()
        self.particles.extend(tmp)

    def update(self):
        for f in self.factories:
            self.particles.extend(next(f))

        for p in self.particles[:]:
            p.move()
            if p.alive == -1:
                self.particles.remove(p)

    def draw(self, screen, position_translater_func):
        for p in self.particles:
            target_pos = position_translater_func(map(sum, zip((p.x, p.y), self.pos)))
            pygame.draw.circle(screen, p.col, target_pos, int(p.size))
Run Code Online (Sandbox Code Playgroud)

发射器可以容纳一堆工厂函数(比如我们的smoke_machine),每次更新它时,它都会将这些工厂的粒子添加到它,self.particles这样它就可以将它们绘制到屏幕上.让我们详细看一些函数:

如果我们添加一个新工厂add_factory,我们称之为factorypre_fill提前移动其粒子300(或任何).这样,一些粒子已经存在.

If we want to draw the particles, we have to calculate thier positions using the position of the Emitter. Also, we need to adjust this position using the camera of the game, so we just accept a function as parameter that translates our position to the correct final position so that our particles scroll accordingly within the game.

We don't need big changes to the game class to use the Emitter now. We just create a new list in the GameScene called self.emitter, and an Emitter and add the smoke_machine factory to it.

In the render method we call

for e in self.emitter:
    e.draw(screen, self.camera.apply)
Run Code Online (Sandbox Code Playgroud)

and in the update method we call

for e in self.emitter:
   e.update()
Run Code Online (Sandbox Code Playgroud)

and we are done!

Action !

enter image description here

complete listing:

game.py

omitted some classes because I hit the 30000 character limit :-)

import pygame
from pygame import *
from particle import Emitter, smoke_machine
WIN_WIDTH = 1120 - 320
WIN_HEIGHT = 960 - 320
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)

DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 0
FLAGS = 0
CAMERA_SLACK = 30

levels = {0: {'level': [
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                         E  ",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "               PPPPP        PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPP           P",
                    "                            PPPP           P",
                    "                            PPPP     PPPPPPP",
                    "                      PPPPPPPPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "       PPPP                 PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "PPPPP                       PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP         PPPPP           PPPP     PPPPPPP",
                    "PPP                                     PPPP",
                    "PPP                                     PPPP",
                    "PPP                                     PPPP",
                    "PPP                       PPPPPPPPPPPPPPPPPP",
                    "PPP                       PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP    S      PPPPPPPPPPPPPPPPPP",],
             'enemies': [(9, 38)]},
             1: {'level': [
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                            ",
                    "                                         E  ",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "               PPPPP        PPPPPPPPPPPPPPPP",
                    "                            PPPPPPPPPPPPPPPP",
                    "                            PPPP           P",
                    "                            PPPP           P",
                    "                            PPPP     PPPPPPP",
                    "                      PPPPPPPPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "       PPPP                 PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "                            PPPP     PPPPPPP",
                    "PPPPP                       PPPP     PPPPPPP",
                    "PPP                  PPPPPPPPPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP                         PPPP     PPPPPPP",
                    "PPP             PPPPPPPP    PPPP     PPPPPPP",
                    "PPP                                     PPPP",
                    "PPP                                     PPPP",
                    "PPP          PPPPP                      PPPP",
                    "PPP          P            PPPPPPPPPPPPPPPPPP",
                    "PPP          P    PPPPPPPPPPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",
                    "PPPPPPPPPPPPPPP           PPPPPPPPPPPPPPPPPP",],
             'enemies': [(9, 38), (18, 38), (15, 15)]}}

...

class GameScene(Scene):
    def __init__(self, levelno):
        super(GameScene, self).__init__()
        self.bg = Surface((32,32))
        self.bg.convert()
        self.bg.fill(Color("#0094FF"))
        up = left = right = False
        self.entities = pygame.sprite.Group()
        self.player = Player(5, 40)
        self.player.scene = self
        self.platforms = []

        self.levelno = levelno

        levelinfo = levels[levelno]
        self.enemies = [Enemy(*pos) for pos in levelinfo['enemies']]

        level = levelinfo['level']
        total_level_width = len(level[0]) * 32
        total_level_height = len(level) * 32

        self.emitter = []

        # build the level
        x = 0
        y = 0
        for row in level:
            for col in row:
                if col == "P":
                    p = Platform(x, y)
                    self.platforms.append(p)
                    self.entities.add(p)
                if col == "E":
                    e = ExitBlock(x, y)
                    self.platforms.append(e)
                    self.entities.add(e)
                if col == "S":
                    e = Emitter((x, total_level_height))
                    e.add_factory(smoke_machine())
                    self.emitter.append(e)
                x += 32
            y += 32
            x = 0

        self.camera = Camera(complex_camera, total_level_width, total_level_height)
        self.entities.add(self.player)
        for e in self.enemies:
            self.entities.add(e)

    def render(self, screen):
        for y in range(20):
            for x in range(25):
                screen.blit(self.bg, (x * 32, y * 32))

        for e in self.emitter:
            e.draw(screen, self.camera.apply)

        for e in self.entities:
            screen.blit(e.image, self.camera.apply(e))

    def update(self):
        for e in self.emitter:
            e.update()

        pressed = pygame.key.get_pressed()
        up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)]
        self.player.update(up, left, right, self.platforms)

        for e in self.enemies:
            e.update(self.platforms)

        self.camera.update(self.player)

    def exit(self):
        if self.levelno+1 in levels:
            self.manager.go_to(GameScene(self.levelno+1))
        else:
            self.manager.go_to(CustomScene("You win!"))

    def die(self):
        self.manager.go_to(CustomScene("You lose!"))

    def handle_events(self, events):
        for e in events:
            if e.type == KEYDOWN and e.key == K_ESCAPE:
                self.manager.go_to(TitleScene())

...

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

particle.py

import pygame,random

def ascending(speed):
    def _ascending(particle):
        particle.y -= speed
    return _ascending

def kill_at(max_x, max_y):
    def _kill_at(particle):
        if particle.x < -max_x or particle.x > max_x or particle.y < -max_y or particle.y > max_y:
            particle.kill()
    return _kill_at

def age(amount):
    def _age(particle):
        particle.alive += amount
    return _age

def fan_out(modifier):
    def _fan_out(particle):
        d = particle.alive / modifier
        d += 1
        particle.x += random.randint(-d, d)
    return _fan_out

def wind(direction, strength):
    def _wind(particle):
        if random.randint(0,100) < strength:
            particle.x += direction
    return _wind

class Particle():
    def __init__(self, col, size, *strategies):
        self.x, self.y = 0, 0
        self.col = col
        self.alive = 0
        self.strategies = strategies
        self.size = size

    def kill(self):
        self.alive = -1 # alive -1 means dead

    def move(self):
        for s in self.strategies:
            s(self)        

black = (0,0,0)
grey = (145,145,145)
light_grey = (192,192,192)
dark_grey = (183, 183, 183)

def smoke_machine():
    colors = {0: grey,
              1: dark_grey,
              2: light_grey}
    def create():
        for _ in xrange(random.choice([0,0,0,0,0,0,0,1,2,3])):
            behaviour = ascending(1), kill_at(1000, 1000), fan_out(400), wind(1, 15), age(1)
            p = Particle(colors[random.randint(0, 2)], random.randint(10, 15), *behaviour)
            yield p

    while True:
        yield create()

class Emitter(object):
    def __init__(self, pos=(0, 0)):
        self.particles = []
        self.pos = pos
        self.factories = []

    def add_factory(self, factory, pre_fill=300):
        self.factories.append(factory)
        tmp = []
        for _ in xrange(pre_fill):
            n = next(factory)
            tmp.extend(n)
            for p in tmp:
                p.move()
        self.particles.extend(tmp)

    def update(self):
        for f in self.factories:
            self.particles.extend(next(f))

        for p in self.particles[:]:
            p.move()
            if p.alive == -1:
                self.particles.remove(p)

    def draw(self, screen, position_translater_func):
        for p in self.particles:
            target_pos = position_translater_func(map(sum, zip((p.x, p.y), self.pos)))
            pygame.draw.circle(screen, p.col, target_pos, int(p.size))
Run Code Online (Sandbox Code Playgroud)

Conclusion

We have successfully integrated the particles in the game. We have done this by writting small, self-contained functions that describe different behavioral patterns, and composed them by using a factory function.

And while doing this, we learned about closures, generators, the SRP, the factory- and the strategy pattern.

We could now easily add new behaviours, e.g. changing colors, or create new particle factories that behave totally different, but use our exiting functions.

For example, try using this function that will let the particles grow

def grow(amount):
    def _grow(particle):
        if random.randint(0,100) < particle.alive / 20:
            particle.size += amount
    return _grow
Run Code Online (Sandbox Code Playgroud)

by adding grow(0.5) to the list of behaviours in the smoke_machine

enter image description here

Small change, impressive effect.

What a nice day!

P.S.: You can find a faster version (and bugfixes) at this repository. It uses numpy, itertools, psyco and pygame.surfarray and eschews the random module for massive speed improvement.

  • +1很棒(很长)回答,真棒!工厂和发电机很棒!喜欢这个答案! (3认同)