Helpful Information
 
 
Category: Python
Particle System Physics

I've been working on a particle system in JavaScript for a while, and just couldn't get the physics to work properly.

Today, I downloaded Slingshot (http://www.slingshot-game.org/), a great game. I noticed that its particle physics seem to work fairly well, and downloaded the source code. Upon inspection, I found that it does not use a time based animation, as I was, but instead uses a more "step-based" system (see the update method):



# This file is part of Slingshot.
#
# Slingshot is a two-dimensional strategy game where two players attempt to shoot one
# another through a section of space populated by planets. The main feature of the
# game is that the shots, once fired, are affected by the gravity of the planets.

# Slingshot is Copyright 2007 Jonathan Musther and Bart Mak. It is released under the
# terms of the GNU General Public License version 2, or later if applicable.

# Slingshot 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 2 of the License, or any later version.

# Slingshot 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 Slingshot;
# if not, write to
# the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA


from settings import *
from general import *
import pygame
import math
from math import sqrt
from random import randint

class Particle(pygame.sprite.Sprite):

def __init__(self, pos = (0.0, 0.0), size = 10):
pygame.sprite.Sprite.__init__(self)
if size == 5:
self.image = Settings.particle_image5
else:
self.image = Settings.particle_image10
self.rect = self.image.get_rect()
# self.image, self.rect = load_image("explosion-10.png", (0,0,0))
self.pos = pos
self.impact_pos = pos
self.size = size
angle = randint(0, 359)
if size == 5:
speed = randint(Settings.PARTICLE_5_MINSPEED,Settings.PARTICLE_5_MAXSPEED)
else:
speed = randint(Settings.PARTICLE_10_MINSPEED,Settings.PARTICLE_10_MAXSPEED)
self.v = (0.1 * speed * math.sin(angle), -0.1 * speed * math.cos(angle))
self.flight = Settings.MAX_FLIGHT

def max_flight(self):
if self.flight < 0:
return True
else:
return False

def update(self, planets):
self.flight = self.flight - 1

self.last_pos = self.pos

for p in planets:
p_pos = p.get_pos()
mass = p.get_mass()
d = (self.pos[0] - p_pos[0])**2 + (self.pos[1] - p_pos[1])**2
a = (Settings.g * mass * (self.pos[0] - p_pos[0]) / (d * math.sqrt(d)), Settings.g * mass * (self.pos[1] - p_pos[1]) / (d * math.sqrt(d)))
self.v = (self.v[0] - a[0], self.v[1] - a[1])

self.pos = (self.pos[0] + self.v[0], self.pos[1] + self.v[1])

# END OF THE ALGORITHM I QUESTION

if not self.in_range():
return 0

for p in planets:
p_pos = p.get_pos()
r = p.get_radius()
d = (self.pos[0] - p_pos[0])**2 + (self.pos[1] - p_pos[1])**2
if d <= (r)**2:
self.impact_pos = get_intersect(p_pos, r, self.last_pos, self.pos)
self.pos = self.impact_pos
return 0

if Settings.BOUNCE:
if self.pos[0] > 799:
d = self.pos[0] - self.last_pos[0]
self.pos = (799, self.last_pos[1] + (self.pos[1] - self.last_pos[1]) * (799 - self.last_pos[0]) / d)
self.v = (-self.v[0], self.v[1])
if self.pos[0] < 0:
d = self.last_pos[0] - self.pos[0]
self.pos = (0,self.last_pos[1] + (self.pos[1] - self.last_pos[1]) * self.last_pos[0] / d)
self.v = (-self.v[0], self.v[1])
if self.pos[1] > 599:
d = self.pos[1] - self.last_pos[1]
self.pos = (self.last_pos[0] + (self.pos[0] - self.last_pos[0]) * (599 - self.last_pos[1]) / d, 599)
self.v = (self.v[0], -self.v[1])
if self.pos[1] < 0:
d = self.last_pos[1] - self.pos[1]
self.pos = (self.last_pos[0] + (self.pos[0] - self.last_pos[0]) * self.last_pos[1] / d, 0)
self.v = (self.v[0], -self.v[1])
# print self.pos
# print self.last_pos

self.rect.center = (round(self.pos[0]), round(self.pos[1]))
return 1

def in_range(self):
if pygame.Rect(-800, -600, 2400, 1800).collidepoint(self.pos):
return True
else:
return False

def visible(self):
if pygame.Rect(0, 0, 800, 600).collidepoint(self.pos):
return True
else:
return False

def get_pos(self):
return self.pos

def get_impact_pos(self):
return self.impact_pos

def get_size(self):
return self.size


I think I know enough about Python to port this to JavaScript. However, as I was doing so, I realized that the algorithms seem to be different. Slingshot's physics is (are?) great, though. Could someone explain exactly how/why update method (up to my comment) works?

Thanks,
1212jtraceur

Hi! The next block of code updates the positions (as you noticed):


self.last_pos = self.pos

for p in planets:
p_pos = p.get_pos()
mass = p.get_mass()
d = (self.pos[0] - p_pos[0])**2 + (self.pos[1] - p_pos[1])**2
a = (Settings.g * mass * (self.pos[0] - p_pos[0]) / (d * math.sqrt(d)), Settings.g * mass * (self.pos[1] - p_pos[1]) / (d * math.sqrt(d)))
self.v = (self.v[0] - a[0], self.v[1] - a[1])

self.pos = (self.pos[0] + self.v[0], self.pos[1] + self.v[1])

Each step, the velocity of the particles is updated with the acceleration due to gravitational force. This can be split into updates for all separate planets. For each planet, the acceleration is g*mass/(distance^2) in the direction of the planet (g can be used for tuning you system). This is where the additional 1/distance factor comes in, as the total acceleration is devided proportionally over the two elements of this vector (this is the (self.pos - p.pos)/sqrt(d) factor (note that d is distance^2)).
With this acceleration, the velocity is updated. With the updated velocity, the position is updated.

If you want a timebased method, you can put an extra factor before the position update to make the particle travel a bit longer or shorter into that direction. (That is, if you're keeping track of the framerate, you can adjust the distance the particles go each frame accordingly.) The reason that it works fairly well with slingshot is that we have limited the framerate to only 30. The framerate doesn't drop below 30 too easily (well, it still can...).
EDIT: put the same factor before the velocity update as well.

I hope it's clear. If not, I can make some graphics to illustrate where all factors come from.

Glad you like the game!

Bart










privacy (GDPR)