File: particles.py

package info (click to toggle)
pysdl2 0.9.7%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 2,824 kB
  • sloc: python: 17,244; makefile: 195; sh: 32
file content (234 lines) | stat: -rw-r--r-- 9,921 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
"""Particle simulation"""
import sys
import random
import sdl2
import sdl2.ext
import sdl2.ext.particles

# Create a resource, so we have easy access to the example images.
RESOURCES = sdl2.ext.Resources(__file__, "resources")


# The Particle class offered by sdl2.ext.particles only contains the life
# time information of the particle, which will be decreased by one each
# time the particle engine processes it, as well as a x- and
# y-coordinate. This is not enough for us, since we want them to have a
# velocity as well to make moving them around easier. Also, each
# particle can look different for us, so we also store some information
# about the image to display on rendering in ptype.
#
# If particles run out of life, we want to remove them, since we do not
# want to flood our world with unused entities. Thus, we store a
# reference to the entity, the particle belongs to, too. This allows use
# to remove them easily later on.
class CParticle(sdl2.ext.particles.Particle):
    def __init__(self, entity, x, y, vx, vy, ptype, life):
        super(CParticle, self).__init__(x, y, life)
        self.entity = entity
        self.type = ptype
        self.vx = vx
        self.vy = vy


# A simple Entity class, that contains the particle information. This
# represents our living particle object.
class EParticle(sdl2.ext.Entity):
    def __init__(self, world, x, y, vx, vy, ptype, life):
        self.cparticle = CParticle(self, x, y, vx, vy, ptype, life)


# A callback function for creating new particles. It is needed by the
# ParticleEngine and the requirements are explained below.
def createparticles(world, deadones, count=None):
    if deadones is not None:
        count = len(deadones)
    # Create a replacement for each particle that died. The particle
    # will be created at the current mouse cursor position (explained
    # below) with a random velocity, life time, and image to be
    # displayed.
    for c in range(count):
        x = world.mousex
        y = world.mousey
        vx = random.random() * 3 - 1
        vy = random.random() * 3 - 1
        life = random.randint(20, 100)
        ptype = random.randint(0, 2)  # 0-2 denote the image to be used
        # We do not need to assign the particle to a variable, since it
        # will be added to the World and we do not need to do perform
        # any post-creation operations.
        EParticle(world, x, y, vx, vy, ptype, life)


# A callback function for updating particles. It is needed by the
# ParticleEngine and the requirements are explained below.
def updateparticles(world, particles):
    # For each existing, living particle, move it to a new location,
    # based on its velocity.
    for p in particles:
        p.x += p.vx
        p.y += p.vy


# A callback function for deleting particles. It is needed by the
# ParticleEngine and the requirements are explained below.
def deleteparticles(world, deadones):
    # As written in the comment for the CParticle class, we will use the
    # stored entity reference of the dead particle components to delete
    # the dead particles from the world.
    world.delete_entities(p.entity for p in deadones)


# Create a simple rendering system for particles. This is somewhat
# similar to the TextureSprinteRenderSystem from sdl2.ext. Since we operate on
# particles rather than sprites, we need to provide our own rendering logic.
class ParticleRenderSystem(sdl2.ext.System):
    def __init__(self, renderer, images):
        # Create a new particle renderer. The surface argument will be
        # the targets surface to do the rendering on. images is a set of
        # images to be used for rendering the particles.
        super(ParticleRenderSystem, self).__init__()
        # Define, what component instances are processed by the
        # ParticleRenderer.
        self.componenttypes = (CParticle,)
        self.renderer = renderer
        self.images = images

    def process(self, world, components):
        # Processing code that will render all existing CParticle
        # components that currently exist in the world. We have a 1:1
        # mapping between the created particle entities and associated
        # particle components; that said, we render all created
        # particles here.

        # We deal with quite a set of items, so we create some shortcuts
        # to save Python the time to look things up.
        #
        # The SDL_Rect is used for the blit operation below and is used
        # as destination position for rendering the particle.
        r = sdl2.SDL_Rect()

        # The SDL2 blit function to use. This will take an image
        # (SDL_Texture) as source and copies it on the target.
        dorender = sdl2.SDL_RenderCopy

        # And some more shortcuts.
        sdlrenderer = self.renderer.sdlrenderer
        images = self.images
        # Before rendering all particles, make sure the old ones are
        # removed from the window by filling it with a black color.
        self.renderer.clear(0x0)

        # Render all particles.
        for particle in components:
            # Set the correct destination position for the particle
            r.x = int(particle.x)
            r.y = int(particle.y)

            # Select the correct image for the particle.
            img = images[particle.type]
            r.w, r.h = img.size
            # Render (or blit) the particle by using the designated image.
            dorender(sdlrenderer, img.texture, None, r)
        self.renderer.present()


def run():
    # Create the environment, in which our particles will exist.
    world = sdl2.ext.World()

    # Set up the globally available information about the current mouse
    # position. We use that information to determine the emitter
    # location for new particles.
    world.mousex = 400
    world.mousey = 300

    # Create the particle engine. It is just a simple System that uses
    # callback functions to update a set of components.
    engine = sdl2.ext.particles.ParticleEngine()

    # Bind the callback functions to the particle engine. The engine
    # does the following on processing:
    # 1) reduce the life time of each particle by one
    # 2) create a list of particles, which's life time is 0 or below.
    # 3) call createfunc() with the world passed to process() and
    #    the list of dead particles
    # 4) call updatefunc() with the world passed to process() and the
    #    set of particles, which still are alive.
    # 5) call deletefunc() with the world passed to process() and the
    #    list of dead particles. deletefunc() is respsonible for
    #    removing the dead particles from the world.
    engine.createfunc = createparticles
    engine.updatefunc = updateparticles
    engine.deletefunc = deleteparticles
    world.add_system(engine)

    # We create all particles at once before starting the processing.
    # We also could create them in chunks to have a visually more
    # appealing effect, but let's keep it simple.
    createparticles(world, None, 300)

    # Initialize the video subsystem, create a window and make it visible.
    sdl2.ext.init()
    window = sdl2.ext.Window("Particles", size=(800, 600))
    window.show()

    # Create a hardware-accelerated sprite factory. The sprite factory requires
    # a rendering context, which enables it to create the underlying textures
    # that serve as the visual parts for the sprites.
    renderer = sdl2.ext.Renderer(window)
    factory = sdl2.ext.SpriteFactory(sdl2.ext.TEXTURE, renderer=renderer)

    # Create a set of images to be used as particles on rendering. The
    # images are used by the ParticleRenderer created below.
    images = (factory.from_image(RESOURCES.get_path("circle.png")),
              factory.from_image(RESOURCES.get_path("square.png")),
              factory.from_image(RESOURCES.get_path("star.png"))
              )

    # Center the mouse on the window. We use the SDL2 functions directly
    # here. Since the SDL2 functions do not know anything about the
    # sdl2.ext.Window class, we have to pass the window's SDL_Window to it.
    sdl2.SDL_WarpMouseInWindow(window.window, world.mousex, world.mousey)

    # Hide the mouse cursor, so it does not show up - just show the
    # particles.
    sdl2.SDL_ShowCursor(0)

    # Create the rendering system for the particles. This is somewhat
    # similar to the SoftSpriteRenderSystem, but since we only operate with
    # hundreds of particles (and not sprites with all their overhead),
    # we need an own rendering system.
    particlerenderer = ParticleRenderSystem(renderer, images)
    world.add_system(particlerenderer)

    # The almighty event loop. You already know several parts of it.
    running = True
    while running:
        for event in sdl2.ext.get_events():
            if event.type == sdl2.SDL_QUIT:
                running = False
                break

            if event.type == sdl2.SDL_MOUSEMOTION:
                # Take care of the mouse motions here. Every time the
                # mouse is moved, we will make that information globally
                # available to our application environment by updating
                # the world attributes created earlier.
                world.mousex = event.motion.x
                world.mousey = event.motion.y
                # We updated the mouse coordinates once, ditch all the
                # other ones. Since world.process() might take several
                # milliseconds, new motion events can occur on the event
                # queue (10ths to 100ths!), and we do not want to handle
                # each of them. For this example, it is enough to handle
                # one per update cycle.
                sdl2.SDL_FlushEvent(sdl2.SDL_MOUSEMOTION)
                break
        world.process()
        sdl2.SDL_Delay(1)

    sdl2.ext.quit()
    return 0

if __name__ == "__main__":
    sys.exit(run())