# Copyright (C) 2017 Julie Marchant <onpon4@riseup.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
xSGE is a collection of extensions for the SGE licensed under the GNU
General Public License. They are designed to give additional features
to free/libre software games which aren't necessary, but are nice to
have.
xSGE extensions are not dependent on any particular SGE implementation.
They should work with any implementation that follows the specification.
This extension provides particle effects for the SGE.
"""
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
__version__ = "0.1a0"
import random
import six
import sge
__all__ = ["Particle", "AnimationParticle", "TimedParticle", "BubbleParticle",
"AnimationBubbleParticle", "TimedBubbleParticle", "Emitter"]
[docs]class Particle(sge.dsp.Object):
"""
Base class for particles. It is identical to
:class:`sge.dsp.Object`, except that it is intangible by default.
"""
[docs] def __init__(self, x, y, z=0, tangible=False, **kwargs):
"""
``x``, ``y``, ``z``, ``tangible``, and all arguments passed to
``kwargs`` are passed as the corresponding arguments to the
constructor method of the parent class.
"""
super(Particle, self).__init__(x, y, z=z, tangible=tangible, **kwargs)
[docs]class AnimationParticle(Particle):
"""
Class for particle objects which animate once and are then
destroyed. It is otherwise identical to :class:`Particle`.
.. note::
:meth:`event_animation_end` is used to control the destruction.
"""
def event_animation_end(self):
super(AnimationParticle, self).event_animation_end()
self.destroy()
[docs]class TimedParticle(Particle):
"""
Class for particle objects which are destroyed after a designated
amount of time. It is otherwise identical to :class:`Particle`.
.. note::
An alarm with the name ``"__life"`` in :meth:`event_alarm` is
used to control the timing. It is initially set by
:meth:`event_create`.
.. attribute:: life
The number of frames (adjusted for delta timing) after which the
particle is destroyed. Setting this attribute resets the
``"__life"`` alarm to the given value. Set to :const:`None` to
disable timed destruction.
"""
@property
def life(self):
return self.__life
@life.setter
def life(self, value):
self.__life = value
self.alarms["__life"] = value
[docs] def __init__(self, x, y, z=0, life=None, tangible=False, **kwargs):
"""
Arguments set the respective initial attributes of the object.
See the documentation for :class:`TimedParticle` for more
information.
``x``, ``y``, ``z``, ``tangible``, and all arguments passed to
``kwargs`` are passed as the corresponding arguments to the
constructor method of the parent class.
"""
super(TimedParticle, self).__init__(x, y, z=z, tangible=tangible,
**kwargs)
self.__life = life
def event_create(self):
super(TimedParticle, self).event_create()
self.alarms["__life"] = self.life
def event_alarm(self, alarm_id):
super(TimedParticle, self).event_alarm(alarm_id)
if alarm_id == "__life":
self.destroy()
[docs]class BubbleParticle(Particle):
"""
Class for particle objects which randomly change their move
directions.
.. note::
:meth:`event_step` is used to control this behavior.
:attr:`move_direction` is manipulated.
.. attribute:: turn_factor
The largest amount of rotation possible.
.. attribute:: min_angle
The lowest possible angle permitted.
.. attribute:: max_angle
The highest possible angle permitted.
"""
[docs] def __init__(self, x, y, z=0, turn_factor=1, min_angle=180, max_angle=0,
tangible=False, **kwargs):
"""
Arguments set the respective initial attributes of the object.
See the documentation for :class:`TimedParticle` for more
information.
``x``, ``y``, ``z``, ``tangible``, and all arguments passed to
``kwargs`` are passed as the corresponding arguments to the
constructor method of the parent class.
"""
super(BubbleParticle, self).__init__(x, y, z=z, tangible=tangible,
**kwargs)
self.turn_factor = turn_factor
self.min_angle = min_angle
self.max_angle = max_angle
def event_step(self, time_passed, delta_mult):
super(BubbleParticle, self).event_step(time_passed, delta_mult)
f = self.turn_factor * delta_mult
self.move_direction += f * random.uniform(-1, 1)
min_angle = self.min_angle % 360
max_angle = self.max_angle % 360
while max_angle < min_angle:
max_angle += 360
md = self.move_direction % 360
while md < min_angle:
md += 360
if md > max_angle:
if md - max_angle > (360 - (max_angle - min_angle)) / 2:
self.move_direction = min_angle
else:
self.move_direction = max_angle
[docs]class AnimationBubbleParticle(AnimationParticle, BubbleParticle):
"""
Inherits the features of both :class:`AnimationParticle` and
:class:`BubbleParticle`.
"""
[docs]class TimedBubbleParticle(TimedParticle, BubbleParticle):
"""
Inherits the features of both :class:`TimedParticle` and
:class:`BubbleParticle`.
"""
[docs]class Emitter(sge.dsp.Object):
"""
Class for object emitters. These are :class:`sge.dsp.Object`
objects which create other :class:`sge.dsp.Object` objects of a
specified class at a specified interval.
To randomize the way particles are created, extend
:meth:`event_create_particle` in a derived class.
.. note::
An alarm with the name ``"__emitter"`` in :meth:`event_alarm` is
used to control the timing. It is initially set by
:meth:`event_create`.
.. attribute:: interval
The number of frames to wait in between the creation of each
particle (adjusted for delta timing).
.. attribute:: chance
The chance (out of 1) of a particle actually being created at
each iteration. This can be used to make particle generation
uneven.
.. attribute:: particle_cls
The class to use for the particles created. Any class derived
from :class:`sge.dsp.Object` will work.
.. attribute:: particle_args
The ordered arguments to pass to created particles' constructor
methods. If set to :const:`None`, an empty list is used.
.. attribute:: particle_kwargs
The keyword arguments to pass to created particles' constructor
methods. If set to :const:`None`, an empty dictionary is used.
.. attribute:: particle_lambda_args
A list of functions which, when a particle is about to be
created, are called and have the returned values passed to the
particle's constructor method instead of the corresponding index
of :attr:`particle_args`. This emitter is passed to each of
these functions as the first argument.
Values in the list set to :const:`None` are ignored. If this
list is longer than :attr:`particle_args`, any arguments not set
by either of these lists are set to :const:`None`.
If set to :const:`None`, an empty list is used.
.. attribute:: particle_lambda_kwargs
A dictionary of functions which, when a particle is about to be
created, are called and have the returned values passed to the
particle's constructor method instead of the corresponding key of
:attr:`particle_kwargs`. This emitter is passed to each of these
functions as the first argument.
If set to :const:`None`, an empty dictionary is used.
"""
@property
def interval(self):
return self.__interval
@interval.setter
def interval(self, value):
self.__interval = value
self.alarms["__emitter"] = min(value, self.alarms["__emitter"])
[docs] def __init__(self, x, y, z=0, interval=1, chance=1, particle_cls=Particle,
particle_args=None, particle_kwargs=None,
particle_lambda_args=None, particle_lambda_kwargs=None,
tangible=False, **kwargs):
"""
Arguments set the respective initial attributes of the object.
See the documentation for :class:`Emitter` for more information.
``x``, ``y``, ``z``, ``tangible``, and all arguments passed to
``kwargs`` are passed as the corresponding arguments to the
constructor method of :class:`sge.dsp.Object`.
"""
super(Emitter, self).__init__(x, y, z=z, tangible=tangible, **kwargs)
self.__interval = interval
self.chance = chance
self.particle_cls = particle_cls
self.particle_args = particle_args
self.particle_kwargs = particle_kwargs
self.particle_lambda_args = particle_lambda_args
self.particle_lambda_kwargs = particle_lambda_kwargs
def event_create(self):
super(Emitter, self).event_create()
self.alarms["__emitter"] = self.interval
def event_alarm(self, alarm_id):
if alarm_id == "__emitter":
if random.random() < self.chance:
args = (self.particle_args or [])[:]
kwargs = (self.particle_kwargs or {}).copy()
if self.particle_lambda_args:
while len(self.particle_lambda_args) > len(args):
args.append(None)
for i in six.moves.range(len(self.particle_lambda_args)):
f = self.particle_lambda_args[i]
if f is not None:
args[i] = f(self)
if self.particle_lambda_kwargs:
for i in self.particle_lambda_kwargs:
f = self.particle_lambda_kwargs[i]
kwargs[i] = f(self)
particle = self.particle_cls.create(*args, **kwargs)
self.event_create_particle(particle)
self.alarms["__emitter"] = self.interval
[docs] def event_create_particle(self, particle):
"""
Called immediately after the emitter creates a particle.
Arguments:
- ``particle`` -- The particle object just created.
"""
pass