summaryrefslogtreecommitdiff
path: root/unrpyc/renpy/display/particle.py
diff options
context:
space:
mode:
Diffstat (limited to 'unrpyc/renpy/display/particle.py')
-rw-r--r--unrpyc/renpy/display/particle.py615
1 files changed, 615 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/particle.py b/unrpyc/renpy/display/particle.py
new file mode 100644
index 0000000..cf52417
--- /dev/null
+++ b/unrpyc/renpy/display/particle.py
@@ -0,0 +1,615 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This code supports sprite and particle animation.
+
+from renpy.display.render import render, BLIT
+
+import renpy.display
+import random
+
+
+class SpriteCache(renpy.object.Object):
+ """
+ This stores information about a displayble, including the identity
+ of the displayable, and when it was first displayed. It is also
+ responsible for caching the displayable surface, so it doesn't
+ need to be re-rendered.
+ """
+
+ # Private Fields:
+ #
+ # child - The child displayable.
+ #
+ # st - The shown time when this was first displayed, or None if it hasn't
+ # been rendered.
+ #
+ # render - The render of child.
+ #
+ # If true, then the render is simple enough it can just be appended to
+ # the manager's render's children list.
+
+class Sprite(renpy.object.Object):
+ """
+ :doc: sprites class
+
+ This represents a sprite that is managed by the SpriteManager. It contains
+ fields that control the placement of the sprite on the screen. Sprites
+ should not be created directly. Instead, they should be created by
+ calling :meth:`SpriteManager.create`.
+
+ The fields of a sprite object are:
+
+ `x`, `y`
+ The x and y coordinates of the upper-left corner of the sprite,
+ relative to the SpriteManager.
+
+ `zorder`
+ An integer that's used to control the order of this sprite in the
+ relative to the other sprites in the SpriteManager. The larger the
+ number is, the closer to the viewer the sprite is.
+
+ `events`
+ If True, then events are passed to child. If False, the default,
+ the children igore events (and hence don't spend time processing
+ them).
+
+ The methods of a Sprite object are:
+ """
+
+ # Fields:
+ #
+ # child - the displayable that is the child of this sprite.
+ # cache - the SpriteCache of child.
+ # live - True if this sprite is still alive.
+ # manager - A reference to the SpriteManager.
+
+ def set_child(self, d):
+ """
+ :doc: sprites method
+
+ Changes the Displayable associated with this sprite to `d`.
+ """
+
+ id_d = id(d)
+
+ sc = self.manager.displayable_map.get(id_d, None)
+ if sc is None:
+ d = renpy.easy.displayable(d)
+
+ sc = SpriteCache()
+ sc.render = None
+ sc.child = d
+ sc.st = None
+
+ self.manager.displayable_map[id_d] = sc
+
+ self.cache = sc
+
+ def destroy(self):
+ """
+ :doc: sprites method
+
+ Destroys this sprite, preventing it from being displayed and
+ removing it from the SpriteManager.
+ """
+
+ self.manager.dead_child = True
+ self.live = False
+ self.events = False
+
+
+
+class SpriteManager(renpy.display.core.Displayable):
+ """
+ :doc: sprites class
+
+ This displayable manages a collection of sprites, and displays
+ them at the fastest speed possible.
+ """
+
+ def __init__(self, update=None, event=None, predict=None, ignore_time=False, **properties):
+ """
+ `update`
+ If not None, a function that is called each time a sprite
+ is rendered by this sprite manager. It is called with one
+ argument, the time in seconds since this sprite manager
+ was first displayed. It is expected to return the number
+ of seconds until the function is called again, and the
+ SpriteManager is rendered again.
+
+ `event`
+ If not None, a function that is called when an event occurs.
+ It takes as arguments:
+ * A pygame event object.
+ * The x coordinate of the event.
+ * The y coordinate of the event.
+ * The time since the sprite manager was first shown.
+ If it returns a non-None value, the interaction ends, and
+ that value is returned.
+
+ `predict`
+ If not None, a function that returns a list of
+ displayables. These displayables are predicted when the
+ sprite manager is.
+
+ `ignore_time`
+ If True, then time is ignored when rendering displayables. This
+ should be used when the sprite manager is used with a relatively
+ small pool of images, and those images do not change over time.
+ This should only be used with a small number of displayables, as
+ it will keep all displayables used in memory for the life of the
+ SpriteManager.
+
+ After being rendered once (before the `update` function is called),
+ SpriteManagers have the following fields:
+
+ `width`, `height`
+
+ The width and height of this SpriteManager, in pixels.
+
+
+ SpriteManagers have the following methods:
+ """
+
+ super(SpriteManager, self).__init__(self, **properties)
+
+ self.update_function = update
+ self.event_function = event
+ self.predict_function = predict
+ self.ignore_time = ignore_time
+
+ # A map from a displayable to the SpriteDisplayable object
+ # representing that displayable.
+ self.displayable_map = { }
+
+ # A list of children of this displayable, in zorder. (When sorted.)
+ # This is a list of Sprites.
+ self.children = [ ]
+
+ # True if at least one child has been killed.
+ self.dead_child = False
+
+ # True if at least one child responds to events.
+ self.events = False
+
+ # The width and height.
+ self.width = None
+ self.height = None
+
+ def create(self, d):
+ """
+ :doc: sprites method
+
+ Creates a new Sprite for the displayable `d`, and adds it to this
+ SpriteManager.
+ """
+
+ id_d = id(d)
+
+ sc = self.displayable_map.get(id_d, None)
+ if sc is None:
+ d = renpy.easy.displayable(d)
+
+ sc = SpriteCache()
+ sc.render = None
+ sc.child = d
+ sc.st = None
+ self.displayable_map[id_d] = sc
+
+ s = Sprite()
+ s.x = 0
+ s.y = 0
+ s.zorder = 0
+ s.cache = sc
+ s.live = True
+ s.manager = self
+ s.events = False
+
+ self.children.append(s)
+
+ return s
+
+ def predict_one(self):
+ if self.predict_function is not None:
+ for i in self.predict_function():
+ renpy.display.predict.displayable(i)
+
+
+ def redraw(self, delay=0):
+ """
+ :doc: sprite method
+
+ Causes this SpriteManager to be redrawn in `delay` seconds.
+ """
+
+ renpy.display.render.redraw(self, delay)
+
+ def render(self, width, height, st, at):
+
+ self.width = width
+ self.height = height
+
+ if self.update_function is not None:
+
+ redraw = self.update_function(st)
+
+ if redraw is not None:
+ renpy.display.render.redraw(self, redraw)
+
+ if not self.ignore_time:
+ self.displayable_map.clear()
+
+ if self.dead_child:
+ self.children = [ i for i in self.children if i.live ]
+
+ self.children.sort(key=lambda sc:sc.zorder)
+
+ caches = [ ]
+
+ rv = renpy.display.render.Render(width, height)
+
+ events = False
+
+ for i in self.children:
+
+ events |= i.events
+
+ cache = i.cache
+ r = i.cache.render
+ if cache.render is None:
+ if cache.st is None:
+ cache.st = st
+
+ cst = st - cache.st
+
+ cache.render = r = render(cache.child, width, height, cst, cst)
+ cache.fast = (r.operation == BLIT) and (r.forward is None) and (r.alpha == 1.0)
+ rv.depends_on(r)
+
+ caches.append(cache)
+
+
+ if cache.fast:
+ for child, xo, yo, _focus, _main in r.children:
+ rv.children.append((child,
+ xo + i.x,
+ yo + i.y,
+ False,
+ False))
+
+ else:
+ rv.subpixel_blit(r, (i.x, i.y))
+
+ for i in caches:
+ i.render = None
+
+ return rv
+
+ def event(self, ev, x, y, st):
+ for i in range(len(self.children) -1, -1, -1):
+ s = self.children[i]
+
+ if s.events:
+ rv = s.cache.child.event(ev, x - s.x, y - s.y, st - s.cache.st)
+ if rv is not None:
+ return rv
+
+ if self.event_function is not None:
+ return self.event_function(ev, x, y, st)
+ else:
+ return None
+
+ def visit(self):
+ rv = [ ]
+
+ try:
+ if self.predict_function:
+ pl = self.predict_function()
+ for i in pl:
+ i = renpy.easy.displayable(i)
+ rv.append(i)
+ except:
+ pass
+
+ return rv
+
+ def destroy_all(self):
+ self.children = [ ]
+
+
+class Particles(renpy.display.core.Displayable, renpy.python.NoRollback):
+ """
+ Supports particle motion, using the old API.
+ """
+
+ __version__ = 1
+
+ nosave = [ 'particles' ]
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.sm = SpriteManager(update=self.update_callback, predict=self.predict_callback)
+
+ def after_setstate(self):
+ self.particles = None
+
+ def __init__(self, factory, **properties):
+ """
+ @param factory: A factory object.
+ """
+
+ super(Particles, self).__init__(**properties)
+
+ self.sm = SpriteManager(update=self.update_callback, predict=self.predict_callback)
+
+ self.factory = factory
+ self.particles = None
+
+ def update_callback(self, st):
+
+ particles = self.particles
+
+ if st == 0 or particles is None:
+ self.sm.destroy_all()
+ particles = [ ]
+
+ add_parts = self.factory.create(particles, st)
+
+ new_particles = [ ]
+
+ for sprite, p in particles:
+ update = p.update(st)
+
+ if update is None:
+ sprite.destroy()
+ continue
+
+ x, y, _t, d = update
+
+ if d is not sprite.cache.child:
+ sprite.set_child(d)
+
+ sprite.x = x
+ sprite.y = y
+
+ new_particles.append((sprite, p))
+
+ if add_parts:
+ for p in add_parts:
+ update = p.update(st)
+
+ if update is None:
+ continue
+
+ x, y, _t, d = update
+
+ if d is None:
+ continue
+
+ sprite = self.sm.create(d)
+ sprite.x = x
+ sprite.y = y
+
+ new_particles.append((sprite, p))
+
+ self.particles = new_particles
+
+ return 0
+
+ def predict_callback(self):
+ return self.factory.predict()
+
+ def render(self, w, h, st, at):
+ return renpy.display.render.render(self.sm, w, h, st, at)
+
+class SnowBlossomFactory(renpy.python.NoRollback):
+
+ rotate = False
+
+ def __setstate__(self, state):
+ self.start = 0
+ vars(self).update(state)
+ self.init()
+
+ def __init__(self, image, count, xspeed, yspeed, border, start, fast, rotate=False):
+ self.image = renpy.easy.displayable(image)
+ self.count = count
+ self.xspeed = xspeed
+ self.yspeed = yspeed
+ self.border = border
+ self.start = start
+ self.fast = fast
+ self.rotate = rotate
+ self.init()
+
+ def init(self):
+ self.starts = [ random.uniform(0, self.start) for _i in range(0, self.count) ] # W0201
+ self.starts.append(self.start)
+ self.starts.sort()
+
+ def create(self, particles, st):
+
+ def ranged(n):
+ if isinstance(n, tuple):
+ return random.uniform(n[0], n[1])
+ else:
+ return n
+
+ if not particles and self.fast:
+ rv = [ ]
+
+ for _i in range(0, self.count):
+ rv.append(SnowBlossomParticle(self.image,
+ ranged(self.xspeed),
+ ranged(self.yspeed),
+ self.border,
+ st,
+ random.uniform(0, 100),
+ fast=True,
+ rotate=self.rotate))
+ return rv
+
+
+ if particles is None or len(particles) < self.count:
+
+ # Check to see if we have a particle ready to start. If not,
+ # don't start it.
+ if particles and st < self.starts[len(particles)]:
+ return None
+
+ return [ SnowBlossomParticle(self.image,
+ ranged(self.xspeed),
+ ranged(self.yspeed),
+ self.border,
+ st,
+ random.uniform(0, 100),
+ fast=False,
+ rotate=self.rotate) ]
+
+ def predict(self):
+ return [ self.image ]
+
+
+class SnowBlossomParticle(renpy.python.NoRollback):
+
+ def __init__(self, image, xspeed, yspeed, border, start, offset, fast, rotate):
+
+ # safety.
+ if yspeed == 0:
+ yspeed = 1
+
+ self.image = image
+ self.xspeed = xspeed
+ self.yspeed = yspeed
+ self.border = border
+ self.start = start
+ self.offset = offset
+ self.rotate = rotate
+
+
+ if not rotate:
+ sh = renpy.config.screen_height
+ sw = renpy.config.screen_width
+ else:
+ sw = renpy.config.screen_height
+ sh = renpy.config.screen_width
+
+
+ if self.yspeed > 0:
+ self.ystart = -border
+ else:
+ self.ystart = sh + border
+
+
+ travel_time = (2.0 * border + sh) / abs(yspeed)
+
+ xdist = xspeed * travel_time
+
+ x0 = min(-xdist, 0)
+ x1 = max(sw + xdist, sw)
+
+ self.xstart = random.uniform(x0, x1)
+
+ if fast:
+ self.ystart = random.uniform(-border, sh + border)
+ self.xstart = random.uniform(0, sw)
+
+ def update(self, st):
+ to = st - self.start
+
+ xpos = self.xstart + to * self.xspeed
+ ypos = self.ystart + to * self.yspeed
+
+ if not self.rotate:
+ sh = renpy.config.screen_height
+ else:
+ sh = renpy.config.screen_width
+
+ if ypos > sh + self.border:
+ return None
+
+ if ypos < -self.border:
+ return None
+
+ if not self.rotate:
+ return int(xpos), int(ypos), to + self.offset, self.image
+ else:
+ return int(ypos), int(xpos), to + self.offset, self.image
+
+def SnowBlossom(d,
+ count=10,
+ border=50,
+ xspeed=(20, 50),
+ yspeed=(100, 200),
+ start=0,
+ fast=False,
+ horizontal=False):
+
+ """
+ :doc: sprites_extra
+
+ The snowblossom effect moves multiple instances of a sprite up,
+ down, left or right on the screen. When a sprite leaves the screen, it
+ is returned to the start.
+
+ `d`
+ The displayable to use for the sprites.
+
+ `border`
+ The size of the border of the screen. The sprite is considered to be
+ on the screen until it clears the border, ensuring that sprites do
+ not disappear abruptly.
+
+ `xspeed`, `yspeed`
+ The speed at which the sprites move, in the horizontal and vertical
+ directions, respectively. These can be a single number or a tuple of
+ two numbers. In the latter case, each particle is assigned a random
+ speed between the two numbers. The speeds can be positive or negative,
+ as long as the second number in a tuple is larger than the first.
+
+ `start`
+ The delay, in seconds, before each particle is added. This can be
+ allows the particles to start at the top of the screen, while not
+ looking like a "wave" effect.
+
+ `fast`
+ If true, particles start in the center of the screen, rather than
+ only at the edges.
+
+ `horizontal`
+ If true, particles appear on the left or right side of the screen,
+ rather than the top or bottom.
+ """
+
+ # If going horizontal, swap the xspeed and the yspeed.
+ if horizontal:
+ xspeed, yspeed = yspeed, xspeed
+
+ return Particles(SnowBlossomFactory(image=d,
+ count=count,
+ border=border,
+ xspeed=xspeed,
+ yspeed=yspeed,
+ start=start,
+ fast=fast,
+ rotate=horizontal))
+