Tweens, building a particle emitter

This is the place to post any code that you want to share with the community. Only completed scripts should be posted here.
Note: posts here are not necessarily endorsed by the community, and may represent amateur or even bad practices.

Tweens, building a particle emitter

Postby nilamo » Sat Sep 10, 2016 6:52 pm

First, the code:
Code: Select all
class Tween(object):
   def __init__(self, start=0, end=1, duration=10):
      self.startpoint = start
      self.endpoint = end
      self.duration = duration
      self.current = 0
   
   @classmethod
   def start(cls, point):
      tw = cls(point)
      return tw
   
   def end(self, point):
      self.endpoint = point
      return self
   
   def over(self, duration):
      self.duration = duration
      return self
   
   def tick(self, amount):
      self.current += amount
      percent_complete = self.current / self.duration
      if percent_complete >= 1.0:
         end = self.endpoint
         self.endpoint = None
         return end
      return ((self.endpoint - self.startpoint) * percent_complete) + self.startpoint
      
if __name__ == '__main__':
   t = Tween.start(0).end(10).over(1000)
   ticking = True
   while ticking:
      tick = t.tick(10)
      ticking = tick
      if tick:
         print(tick)

Tweening is used in animation to smoothly move something from one place to another. Here's a quick little script to help with that. A better example to help understand would be to think of an element moving across a webpage. The "start" param would be the element's original x-coordinate, and the "end" is what you want that x-coordinate to be when the animation is complete. "over", then, is the length of time it should take to get from start to end, probably in milliseconds. You then repeatedly tick the tweener, letting it know how much time has passed since the last time you ticked it, and it lets you know what the new value should be. Following our webpage example, you'd set the element's x-coordinate to whatever the return value of tick() was. The result, with a good 'over' and 'tick' setting, would appear to be an element that smoothly moves itself from one place to another.

In python, this sort of thing could very easily be used with a graphical package (such as pygame) to create animations. When I get some free time, I may do an example of it in pygame to demonstrate.
Last edited by nilamo on Tue Sep 13, 2016 6:19 pm, edited 1 time in total.
This forum is shutting down. Future discussion should take place at http://python-forum.io
User avatar
nilamo
 
Posts: 87
Joined: Thu Jul 21, 2016 8:00 pm
Location: Michigan

Re: Tweens, first look

Postby nilamo » Mon Sep 12, 2016 2:50 pm

Code: Select all
class Tween(object):
   def __init__(self, start=0, end=1, duration=10):
      self.startpoint = start
      self.endpoint = end
      self.duration = duration
      self.current = 0
      self.complete = False
   
   @classmethod
   def start(cls, point):
      tw = cls(point)
      return tw
   
   def end(self, point):
      self.endpoint = point
      return self
   
   def over(self, duration):
      self.duration = duration
      return self
   
   def tick(self, amount):
      self.current += amount
      percent_complete = self.current / self.duration
      if percent_complete >= 1.0:
         self.complete = True
         return self.endpoint
      return ((self.endpoint - self.startpoint) * percent_complete) + self.startpoint
      
if __name__ == '__main__':
   import pygame
   import time
   
   WHITE = (255, 255, 255)
   BLACK = (0, 0, 0)
   
   # initialize pygame
   pygame.init()
   screen_size = (700, 500)
   # create a window
   screen = pygame.display.set_mode(screen_size)
   pygame.display.set_caption("Tween Test")
   
   # let's create a tweener
   tween = Tween.start(100).end(screen_size[0]-100).over(1000)
   bouncing_ball = pygame.Surface((10, 10))
   bouncing_ball.fill(WHITE)
   
   # clock is used to set a max fps
   clock = pygame.time.Clock()
   last_update = time.time()
   
   running = True
   while running:
      for event in pygame.event.get():
         if event.type == pygame.QUIT:
            running = False
      
      #clear the screen
      screen.fill(BLACK)
      
      # draw to the screen
      tick = tween.tick(1000 * (time.time() - last_update))
      screen.blit(bouncing_ball, (tick, 125))
      if tween.complete:
         # create a new tweener to move the dot back where it came from
         # it'll go back and forth, like a bouncing ball
         tween = Tween.start(tween.endpoint).end(tween.startpoint).over(tween.duration)

      # flip() updates the screen to make our changes visible
      pygame.display.flip()
      
      last_update = time.time()
      clock.tick(60)
   
   pygame.quit()


Here's an example using pygame. If you run it (and have pygame installed), you'll see a small box bounce back and forth from left to right, smoothly sliding across the screen.

Currently, only linear easing is implemented. Next up, I'll add more easings. Probably the default should be ease-in-sine (view a ton of them here: http://easings.net/). ease in sine is nice, because the movement starts slow, then picks up the pace afterward... like a car that needs a little bit of time to start moving (very few things in nature actually move linearly).

...pay no attention to my poor mouse-drawing skills.
Attachments
tween.PNG
tween.PNG (5 KiB) Viewed 1167 times
This forum is shutting down. Future discussion should take place at http://python-forum.io
User avatar
nilamo
 
Posts: 87
Joined: Thu Jul 21, 2016 8:00 pm
Location: Michigan

Re: Tweens, first look

Postby nilamo » Mon Sep 12, 2016 8:38 pm

Math is hard, but here's some easings. Code is mostly stolen from here.
Code: Select all
class Easing(object):
   @staticmethod
   def linear(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      return start + ((end - start) * percent)

   @staticmethod
   def in_out_cubic(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      ts = percent * percent
      tc = ts * percent
      return start + (end - start) * (-2*ts + 3*ts)
      
   @staticmethod
   def in_quintic(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      return start + (end - start) * (percent * percent * percent * percent)
      
   @staticmethod
   def in_cubic(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      return start + (end - start) * (percent * percent * percent)
      
class Tween(object):
   def __init__(self, start=0, end=1, duration=10, easing=Easing.linear):
      self.startpoint = start
      self.endpoint = end
      self.duration = duration
      self.current = 0
      self.complete = False
      self.easing = easing
   
   @classmethod
   def start(cls, point):
      tw = cls(point)
      return tw
   
   def end(self, point):
      self.endpoint = point
      return self
   
   def over(self, duration):
      self.duration = duration
      return self
   
   def via(self, easing):
      self.easing = easing
      return self
   
   def tick(self, amount):
      self.current += amount
      if self.current >= self.duration:
         self.complete = True
         return self.endpoint
      return self.easing(self.startpoint, self.endpoint, self.current, self.duration)
      
      
if __name__ == '__main__':
   import pygame
   import time
   
   WHITE = (255, 255, 255)
   BLACK = (0, 0, 0)
   
   # initialize pygame
   pygame.init()
   screen_size = (700, 500)
   # create a window
   screen = pygame.display.set_mode(screen_size)
   pygame.display.set_caption("Tween Test")
   
   # let's create a tweener
   tween = Tween.start(100).end(screen_size[0]-100).over(1000).via(Easing.in_quintic)
   bouncing_ball = pygame.Surface((10, 10))
   bouncing_ball.fill(WHITE)
   
   # clock is used to set a max fps
   clock = pygame.time.Clock()
   last_update = time.time()
   
   running = True
   while running:
      for event in pygame.event.get():
         if event.type == pygame.QUIT:
            running = False
      
      #clear the screen
      screen.fill(BLACK)
      
      # draw to the screen
      tick = tween.tick(1000 * (time.time() - last_update))
      screen.blit(bouncing_ball, (tick, 125))
      if tween.complete:
         # create a new tweener to move the dot back where it came from
         # it'll go back and forth, like a bouncing ball
         easing = Easing.in_cubic if tween.easing == Easing.in_quintic else Easing.in_quintic
         tween = Tween.start(tween.endpoint).end(tween.startpoint).over(tween.duration).via(easing)

      # flip() updates the screen to make our changes visible
      pygame.display.flip()
      
      last_update = time.time()
      clock.tick(60)
   
   pygame.quit()
This forum is shutting down. Future discussion should take place at http://python-forum.io
User avatar
nilamo
 
Posts: 87
Joined: Thu Jul 21, 2016 8:00 pm
Location: Michigan

Re: Tweens, building a particle emitter

Postby nilamo » Tue Sep 13, 2016 6:35 pm

So now that we've got easings, let's wrap the tweener controls into a controller class which can handle multiple tweens at once. Let's call it a particle. With a little setup code, the main loop is getting nice and slim and easy to understand. The next step is building an emitter, which creates multiple particles, sets their effects, and keeps track of all active particles.

A particle emitter (since we're now talking about that), is something which is used to create various effects. Effects such as fog, fire, clouds, the flash+smoke of gun fire, or an explosion. Included here are the bare bones (the only thing a particle can do is update it's own x-position), but we'll add more later. This will start getting exciting once particles can have dynamic x/y positions, colors, alpha, and size properties.
Code: Select all
class Easing(object):
   @staticmethod
   def linear(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      return start + ((end - start) * percent)

   @staticmethod
   def in_out_cubic(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      ts = percent * percent
      tc = ts * percent
      return start + (end - start) * (-2*ts + 3*ts)
      
   @staticmethod
   def in_quintic(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      return start + (end - start) * (percent * percent * percent * percent)
      
   @staticmethod
   def in_cubic(start, end, timepoint, maxtime):
      percent = timepoint / maxtime
      return start + (end - start) * (percent * percent * percent)
      
class Tween(object):
   def __init__(self, start=0, end=1, duration=10, easing=Easing.in_cubic):
      self.startpoint = start
      self.endpoint = end
      self.duration = duration
      self.current = 0
      self.complete = False
      self.easing = easing
   
   @classmethod
   def start(cls, point):
      tw = cls(point)
      return tw
   
   def end(self, point):
      self.endpoint = point
      return self
   
   def over(self, duration):
      self.duration = duration
      return self
   
   def via(self, easing):
      self.easing = easing
      return self
   
   def tick(self, amount):
      self.current += amount
      if self.current >= self.duration:
         self.complete = True
         return self.endpoint
      return self.easing(self.startpoint, self.endpoint, self.current, self.duration)

class Particle(object):
   def __init__(self, screen, pos=(0,0), size=(1, 1), rgb=(0,0,0)):
      self.screen = screen
      self.surface = pygame.Surface(size)
      self.rgb = rgb
      self.surface.fill(self.rgb)
      self.start_time = None
      self.last_update = None
      self.complete = False
      self.pos = pos
      self.alpha = 255
      self.effects = []
   
   def add_effect(self, starttime, effect, tweener):
      self.effects.append([starttime, effect, tweener])
   
   def start(self):
      self.start_time = time.time()
      self.last_update = time.time()
      self.screen.blit(self.surface, self.pos)
      self.complete = False
      
   def update(self):
      last = self.last_update
      self.last_update = time.time()
      since_last = 1000 * (self.last_update - last)
      since_start = 1000 * (self.last_update - self.start_time)
      
      active_effects = []
      for effect in self.effects:
         if effect[0] <= since_start:
            effect[1](self, effect[2].tick(since_last))
            if not effect[2].complete:
               active_effects.append(effect)
         else:
            active_effects.append(effect)
      self.effects = active_effects
      
      self.surface.fill(self.rgb)
      self.screen.blit(self.surface, self.pos)
      self.complete = 0 == len(active_effects)
      
   def setXPos(self, new_x):
      self.pos = (new_x, self.pos[1])
   
   
if __name__ == '__main__':
   import pygame
   import time
   
   WHITE = (255, 255, 255)
   BLACK = (0, 0, 0)
   
   # initialize pygame
   pygame.init()
   screen_size = (700, 500)
   
   # create a window
   screen = pygame.display.set_mode(screen_size)
   pygame.display.set_caption("Particle Test")
   
   # clock is used to set a max fps
   clock = pygame.time.Clock()
   
   particle = Particle(screen, (200, 400), (10, 10), WHITE)
   particle.add_effect(2000, lambda p, val: p.setXPos(val), Tween.start(200).end(600).over(3000))
   particle.add_effect(5000, lambda p, val: p.setXPos(val), Tween.start(600).end(100).over(3000))
   particle.start()
   
   running = True
   while running:
      for event in pygame.event.get():
         if event.type == pygame.QUIT:
            running = False
      
      #clear the screen
      screen.fill(BLACK)
      
      # draw to the screen
      if particle:
         particle.update()
         if particle.complete:
            particle = None

      # flip() updates the screen to make our changes visible
      pygame.display.flip()
      
      last_update = time.time()
      clock.tick(60)
   
   pygame.quit()
This forum is shutting down. Future discussion should take place at http://python-forum.io
User avatar
nilamo
 
Posts: 87
Joined: Thu Jul 21, 2016 8:00 pm
Location: Michigan

Re: Tweens, building a particle emitter

Postby nilamo » Thu Sep 15, 2016 5:34 pm

This series will be continued on the new forums...
STAY TUNED, SPORTSFANS
This forum is shutting down. Future discussion should take place at http://python-forum.io
User avatar
nilamo
 
Posts: 87
Joined: Thu Jul 21, 2016 8:00 pm
Location: Michigan


Return to Completed Scripts

Who is online

Users browsing this forum: No registered users and 2 guests