2D Elastic Collision, momentum not conserved in the y direction - python

I am a high school student trying to model a 2D Elastic Collision between two billiard balls. I used vector rotation as shown in this video and I also referenced this website as well. Despite that, the collisions weren't working specifically when the two balls collide having the same x value (a vertical collision).
As I tried to fix that edge case issue, I noticed that the change in momentum in the y direction gets larger and larger as the collision vector becomes more vertical, when it should be zero. This issue is only present with momentum in the y direction, not in the x direction.
My code (apologies in advance if it's too convoluted):
#main.py
import pygame
import numpy as np
from math import sin, cos, atan2, degrees as deg
g = 9.81
m = .17
r = 10
timeInterval = 1/60
def getValue(value):
absValue = np.abs(value)/value
return absValue
def checkCollison(Ball):
global prior
collided = []
instances = Ball.instances
for a in range(len(instances)):
for b in range(len(instances) - 1, a, -1):
ballA, ballB = instances[a], instances[b]
dist = ((ballA.x - ballB.x)**2 + (ballA.y - ballB.y)**2 + (ballA.z - ballB.z)**2)**0.5
if dist <= 2*r and not(f"{a}{b}" in collided) and dist != 0:
BallCollision(ballA, ballB, (ballA.vx, ballA.vy), (ballB.vx, ballB.vy))
collided.append(f"{a}{b}")
def BallCollision(b1, b2, u1, u2):
deltaX = b2.x - b1.x
deltaY = b2.y - b1.y
angle = atan2(deltaY, deltaX)
print(deg(angle))
# Rotate it to where the collision line is parallel to the horizontal
u1x = b1.vx * cos(angle) + b1.vy * sin(angle)
u1y = b1.vy * cos(angle) - b1.vx * sin(angle)
u2x = b2.vx * cos(angle) + b2.vy * sin(angle)
u2y = b2.vy * cos(angle) - b2.vx * sin(angle)
v1x = ((b1.m - b2.m) / (b1.m + b2.m)) * u1x + ((2 * b2.m) / (b1.m + b2.m)) * u2x
v1y = u1y
v2x = ((2 * b1.m) / (b1.m + b2.m)) * u1x + ((b2.m - b1.m) / (b1.m + b2.m)) * u2x
v2y = u2y
midpointX = (b1.x + b2.x) / 2
midpointY = (b1.y + b2.y) / 2
b1.x += (b1.x - midpointX)/2
b1.y += (b1.y - midpointY)/2
b2.x += (b2.x - midpointX)/2
b2.y += (b2.y - midpointY)/2
#Rotate back
v1x = v1x * cos(angle) - v1y * sin(angle)
v1y = v1y * cos(angle) + v1x * sin(angle)
v2x = v2x * cos(angle) - v2y * sin(angle)
v2y = v2y * cos(angle) + v2x * sin(angle)
print("change in x momentum: ", b1.vx + b2.vx - v1x - v2x)
print("change in y momentum: ", b1.vy + b2.vy - v1y - v2y)
b1.vx, b1.vy = v1x, v1y
b2.vx, b2.vy = v2x, v2y
class Ball(pygame.sprite.Sprite):
instances = []
def __init__(self, x, y, z, vx, vy, vz, ax, ay, az, m):
super(Ball, self).__init__()
self.__class__.instances.append(self)
self.x, self.y, self.z = x, y, z
self.vx, self.vy, self.vz = vx, vy, vz
self.ax, self.ay, self.az = ax, ay, az
self.m = m
def motion(self):
Ax = 0
Ay = 0
self.vx = self.vx + Ax * timeInterval
self.vy = self.vy + Ay * timeInterval
self.vz = self.vy #+ Az * timeInterval
self.x = self.x + self.vx * timeInterval + 1 / 2 * (Ax) * (timeInterval ** 2)
self.y = self.y + self.vy * timeInterval + 1 / 2 * (Ay) * (timeInterval ** 2)
self.z = self.y + self.vy * timeInterval #+ 1 / 2 * (Ay) * (timeInterval ** 2)
checkCollison(Ball)
#game.py
from main import *
WIDTH, HEIGHT = 1000, 500
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
FPS = 60
WHITE = (255, 255, 255)
ball1 = Ball(*(100,100,0,0,100,0,0,0,0, m))
ball2 = Ball(*(115,200,0,0,0,0,0,0,0, m))
def draw_window():
WIN.fill(WHITE)
ball1.motion()
ball2.motion()
pygame.draw.circle(WIN, (0,0,0), (ball1.x,ball1.y), r)
pygame.draw.circle(WIN, (0,0,0), (ball2.x,ball2.y), r)
pygame.display.update()
def main():
clock = pygame.time.Clock()
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
draw_window()
pygame.quit()
if __name__ == "__main__":
main()

The problem with your code lies in the way you rotate back your velocities in your BallCollision function:
#Rotate back
v1x = v1x * cos(angle) - v1y * sin(angle)
v1y = v1y * cos(angle) + v1x * sin(angle)
v2x = v2x * cos(angle) - v2y * sin(angle)
v2y = v2y * cos(angle) + v2x * sin(angle)
In these lines you overwrite your variables for v1x and v2x but still try to use them in the next line for your rotation. However the fix is of course not difficult, you just need to use different variables names. For example the following code fixed "vertical" collisions for me:
#Rotate back
newv1x = v1x * cos(angle) - v1y * sin(angle)
newv1y = v1y * cos(angle) + v1x * sin(angle)
newv2x = v2x * cos(angle) - v2y * sin(angle)
newv2y = v2y * cos(angle) + v2x * sin(angle)
print("change in x momentum: ", b1.vx + b2.vx - newv1x - newv2x)
print("change in y momentum: ", b1.vy + b2.vy - newv1y - newv2y)
b1.vx, b1.vy = newv1x, newv1y
b2.vx, b2.vy = newv2x, newv2y

Related

matplotlib AttributeError: 'NoneType' object has no attribute '_get_view'

I'm trying to animate the collision physics of N number of balls in matplotlib, but when I try to run the program, I get an AttributeError: 'NoneType' object has no attribute '_get_view' error. How can this be corrected in this case?
import numpy as np
from numpy import sqrt
from random import randint
import math
from matplotlib import pyplot as plt
from matplotlib import collections
plt.rcParams["figure.figsize"] = 4, 4
from matplotlib.animation import FuncAnimation
np_rnd = np.random.default_rng()
class Ball:
x0 = 0
y0 = 0
x = 0
y = 0
r = 0
t = 0
m = 0
v_speed = np.array([0, 0])
v_accel = np.array([0, 0])
def __init__(self, x_, y_, r_, m_, v_speed_, v_accel_):
self.m = m_
self.x = x_
self.y = y_
self.x0 = x_
self.y0 = y_
self.r = r_
self.v_speed = v_speed_
self.v_accel = v_accel_
self.t = 0
def get_cmap(n, name='hsv'):
return plt.cm.get_cmap(name, n)
FPS = 77
X_MIN = -1
X_MAX = 100
Y_MIN = -1
Y_MAX = 100
RADIUS_MIN = 1
RADIUS_MAX = 3
K_RADIUS = 50
MASS_MAX = 6
SPEED_MAX = 0.5
ACCEL_MAX = 0
N = 5
T = 1
ball_list = []
xy_balls = np.c_[np_rnd.integers(X_MIN+RADIUS_MAX, X_MAX-RADIUS_MAX,(N)),
np_rnd.integers(Y_MIN+RADIUS_MAX, Y_MAX-RADIUS_MAX,(N))]
r_balls = np_rnd.integers(RADIUS_MIN, RADIUS_MAX, N)
color_balls = np.arange(0, N)
fig, ax = plt.subplots()
ax.plot([X_MIN, X_MAX], [0, 0], color='black', linewidth=0.5)
plt.annotate("x", xy=(X_MAX - 2, 0))
ax.plot([0, 0], [Y_MIN, Y_MAX], color='black', linewidth=0.5)
plt.annotate("y", xy=(0, Y_MAX - 2))
plt.annotate("0", xy=(2, 2))
cmap = get_cmap(N)
for i in range(N):
ball_list.append(Ball(xy_balls[i][0],
xy_balls[i][1],
r_balls[i],
randint(1, MASS_MAX),
np_rnd.uniform(0, SPEED_MAX, size=2),
np_rnd.uniform(0, ACCEL_MAX, size=2)
)
)
balls_plt = [plt.Circle(center, radius=radius, color=cmap(color)) for center, radius, color in zip(xy_balls, r_balls, color_balls)]
balls_plt_updater = balls_plt
def checkBorder(ball):
if (ball.x > X_MAX) or (ball.x < X_MIN):
ball.x = (X_MAX - 1) if ball.x > X_MAX else X_MIN + 1
ball.x0 = ball.x
ball.y0 = ball.y
ball.v_speed[0] = -(ball.v_speed[0] + ball.t * ball.v_accel[0])
ball.v_speed[1] = ball.v_speed[1] + ball.t * ball.v_accel[1]
ball.v_accel[0] = -ball.v_accel[0]
ball.t = 0
if (ball.y > Y_MAX) or (ball.y < Y_MIN):
ball.y = (Y_MAX - 1) if ball.y > Y_MAX else Y_MIN + 1
ball.x0 = ball.x
ball.y0 = ball.y
ball.v_speed[0] = ball.v_speed[0] + ball.t * ball.v_accel[0]
ball.v_speed[1] = -(ball.v_speed[1] + ball.t * ball.v_accel[1])
ball.v_accel[1] = -ball.v_accel[1]
ball.t = 0
def checkBite(ball1, ball2):
D = math.sqrt(((ball1.x - ball2.x) ** 2) + ((ball1.y - ball2.y) ** 2))
if D < (ball1.r + ball2.r):
ball1.v_speed[0] = ((ball1.m - ball2.m) * ball1.v_speed[0] + 2 * ball2.m * ball2.v_speed[0]) / (
ball1.m + ball2.m)
ball1.v_speed[1] = ((ball1.m - ball2.m) * ball1.v_speed[1] + 2 * ball2.m * ball2.v_speed[1]) / (
ball1.m + ball2.m)
ball2.v_speed[0] = ((ball2.m - ball1.m) * ball2.v_speed[0] + 2 * ball1.m * ball1.v_speed[0]) / (
ball1.m + ball2.m)
ball2.v_speed[1] = ((ball2.m - ball1.m) * ball2.v_speed[1] + 2 * ball1.m * ball1.v_speed[1]) / (
ball1.m + ball2.m)
ball1.x0 = ball1.x + (ball1.v_speed[0] * ball1.r)
ball1.y0 = ball1.y + (ball1.v_speed[1] * ball1.r)
ball2.x0 = ball2.x + (ball2.v_speed[0] * ball2.r)
ball2.y0 = ball2.y + (ball2.v_speed[1] * ball2.r)
ball1.t = T
ball2.t = T
def setCoord(ball):
ball.x = ball.x0 + ball.v_speed[0] * ball.t + (ball.v_accel[0] * (ball.t ** 2)) / 2
ball.y = ball.y0 + ball.v_speed[1] * ball.t + (ball.v_accel[1] * (ball.t ** 2)) / 2
ball.t += T
def animate(i, balls):
for j in range(len(balls)):
#for k in range(j+1, len(balls)):
# checkBite(balls[j], balls[k])
checkBorder(balls[j])
setCoord(balls[j])
balls_plt[j].center = balls[j].x, balls[j].y
return balls_plt_updater
anim = FuncAnimation(fig=fig, func=animate, fargs=(ball_list,),
frames=np.linspace(0, FPS * 2, num=1000, dtype=np.intc, endpoint=False),
interval=1000 / (FPS), blit=True, repeat=True)
plt.show()
I tried to trace the error and ended up with plt.show(). What could be the problem?

How to get vertices of rotated rectangle?

I try smth like this:
def main_rec():
width = random.randint(150, 250)
height = random.randint(150, 250)
angle = rand_angle()
c, s = np.cos(angle), np.sin(angle)
R = np.array(((c, -s), (s, c)))
center = (random.randint(0, 640), random.randint(0, 480))
x1y10 = (center[0] - width / 2, center[1] + height / 2)
x2y20 = (x1y10[0] + width, x1y10[1])
x3y30 = (x2y20[0], x2y20[1] - height)
x4y40 = (x3y30[0] - width, x3y30[1])
x1y1 = (x1y10[0] * R[0][0] + x1y10[1] * R[0][1], x1y10[0] * R[1][0] + x1y10[1] * R[1][1])
x2y2 = (x2y20[0] * R[0][0] + x2y20[1] * R[0][1], x1y10[1] * R[0][1] + x2y20[1] * R[1][1])
x3y3 = (x3y30[0] * R[0][0] + x3y30[1] * R[0][1], x3y30[0] * R[1][0] + x3y30[1] * R[1][1])
x4y4 = (x4y40[0] * R[0][0] + x4y40[1] * R[0][1], x4y40[1] * R[0][1] + x4y40[1] * R[1][1])
points = [x1y1, x2y2, x3y3, x4y4]
return points, angle / 3.14159 * 180
but I don't know how to set a condition for the corners to be right. I try to use rotation matrix. It makes normal rectangles only for angle = 0
using numpy and rotation matrix code would be:
import numpy as np
import matplotlib.pyplot as plt
def create_rect(width,height,center):
x,y = center
w,h = width,height
return np.array([[x-w/2,y-h/2],
[x+w/2,y-h/2],
[x+w/2,y+h/2],
[x-w/2,y+h/2],
[x-w/2,y-h/2]]).T
width = np.random.randint(150,250)
height = np.random.randint(150,250)
center = (np.random.randint(0, 640), np.random.randint(0, 480))
angle = np.random.randint(0,360)/360.0*2*np.pi
rotmat = np.array([[np.cos(angle),-np.sin(angle)],
[np.sin(angle),np.cos(angle)]])
rect = create_rect(width,height,center)
rot_rect = rotmat # rect
plt.imshow(*rot_rect)

Particles overlapping when colliding in python py-game

]When running the simulation the particles can overlap and freak out a bit. I think because it's a discrete simulation when the collision is detected the particles have already overlapped so the sections which calculates the final velocity doesn't work right.
I solved the same problem for the particles and the screen boundary but I can't figure out how do to it for a particle-particle collision. Any help would be greatly appreciated
import pygame
import random
import math
import numpy as np
width, height = 700, 450
screen = pygame.display.set_mode((width, height))
particles = []
no_particles = 10
tick_speed = 200
class Particle:
def __init__(self, x, y, r):
self.r = r
self.pos = np.array([x, y])
self.vel = np.array([random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)])
self.acc = np.array([0, 0]) #tick_speed/(tick_speed * 10)
def display(self):
pygame.draw.circle(screen, (227, 53, 15), (int(self.pos[0]), int(self.pos[1])), self.r, 1)
def move(self):
self.vel = self.vel + self.acc
self.pos = self.pos + self.vel
def bounce(self):
if self.pos[1] > height - self.r:
self.pos[1] = 2*(height - self.r) - self.pos[1]
self.vel[1] = -self.vel[1]
elif self.pos[1] < self.r:
self.pos[1] = 2*self.r - self.pos[1]
self.vel[1] = -self.vel[1]
if self.pos[0] + self.r > width:
self.pos[0] = 2*(width - self.r) - self.pos[0]
self.vel[0] = -self.vel[0]
elif self.pos[0] < self.r:
self.pos[0] = 2*self.r - self.pos[0]
self.vel[0] = -self.vel[0]
#classmethod
def collision(cls, p1, p2):
dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
if dc <= p1.r + p2.r:
x1, y1 = p1.pos[0], p1.pos[1]
x2, y2 = p2.pos[0], p2.pos[1]
m1, m2 = p1.r**2, p2.r**2
n = np.array([x2-x1, y2-y1])
un = n / np.linalg.norm(n)
ut = np.array([-un[1], un[0]])
v1 = p1.vel
v2 = p2.vel
v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)
v2n = np.dot(un, v2)
v2t = np.dot(ut, v2)
v1n_prime_s = (v1n * (m1 - m2) + 2*m2*v2n) / (m1 + m2)
v2n_prime_s = (v2n * (m2 - m1) + 2*m1*v1n) / (m1 + m2)
v1n_prime = v1n_prime_s * un
v1t_prime = v1t * ut
v2n_prime = v2n_prime_s * un
v2t_prime = v2t * ut
u1 = v1n_prime + v1t_prime
u2 = v2n_prime + v2t_prime
p1.vel = u1
p2.vel = u2
while len(particles) < no_particles:
r = random.randint(10, 20)
x = random.randint(r, width-r)
y = random.randint(r, height-r)
collide = False
for particle in particles:
d = (particle.pos[0] - x)**2 + (particle.pos[1] - y)**2
if d < (r + particle.r)**2:
collide = True
break
if not collide:
particles.append(Particle(x, y, random.randint(10, 20)))
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255))
for particle in particles:
particle.move()
particle.bounce()
for particle2 in [p for p in particles if p!= particle]:
particle.collision(particle, particle2)
particle.display()
pygame.display.flip()
clock.tick(tick_speed)
pygame.quit()
quit()
The problem is that you are checking and resolving collision multiple times for the same balls in a single iteration.
Since the direction has already once been "reflected" once, these dot products,
v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)
v2n = np.dot(un, v2)
v2t = np.dot(ut, v2)
yield the opposite direction in the next collision resolution, which causes the balls to move towards each other.
"Energy" is being added to the system every time the balls collide, because of these,
p1.r**2, p2.r**2
2*m2*v2n, 2*m1*v1n
which causes the freak out that you mention.
Solution is to simply check and resolve the collisions once:
for i in range(len(particles)):
particles[i].move()
particles[i].bounce()
for j in range(i + 1, len(particles)):
particles[i].collision(particles[i], particles[j])
particles[i].display()
As Rabbid76 pointed out, this fails as the number of particles increases. If you statically resolve the collision first, everything will work since the collision will have already been resolved and your dynamic resolution code will set the velocities accordingly.
#classmethod
def resolveStatically(cls, n, dc, p1, p2):
displacement = (dc - p1.r - p2.r) * 0.5
p1.pos[0] += displacement * (n[0] / dc)
p1.pos[1] += displacement * (n[1] / dc)
p2.pos[0] -= displacement * (n[0] / dc)
p2.pos[1] -= displacement * (n[1] / dc)
#classmethod
def collision(cls, p1, p2):
dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
if dc <= p1.r + p2.r:
#...
n = np.array([x2-x1, y2-y1])
cls.resolveStatically(n, dc, p1, p2)
Full code:
import pygame
import random
import math
import numpy as np
width, height = 700, 450
screen = pygame.display.set_mode((width, height))
particles = []
no_particles = 100
tick_speed = 200
class Particle:
def __init__(self, x, y, r):
self.r = r
self.pos = np.array([x, y])
self.vel = np.array([random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)])
self.acc = np.array([0, 0]) #tick_speed/(tick_speed * 10)
def display(self):
pygame.draw.circle(screen, (227, 53, 15), (int(self.pos[0]), int(self.pos[1])), self.r, 1)
def move(self):
self.vel = self.vel + self.acc
self.pos = self.pos + self.vel
def bounce(self):
if self.pos[1] > height - self.r:
self.pos[1] = 2*(height - self.r) - self.pos[1]
self.vel[1] = -self.vel[1]
elif self.pos[1] < self.r:
self.pos[1] = 2*self.r - self.pos[1]
self.vel[1] = -self.vel[1]
if self.pos[0] + self.r > width:
self.pos[0] = 2*(width - self.r) - self.pos[0]
self.vel[0] = -self.vel[0]
elif self.pos[0] < self.r:
self.pos[0] = 2*self.r - self.pos[0]
self.vel[0] = -self.vel[0]
#classmethod
def resolveStatically(cls, n, dc, p1, p2):
displacement = (dc - p1.r - p2.r) * 0.5
p1.pos[0] += displacement * (n[0] / dc)
p1.pos[1] += displacement * (n[1] / dc)
p2.pos[0] -= displacement * (n[0] / dc)
p2.pos[1] -= displacement * (n[1] / dc)
#classmethod
def collision(cls, p1, p2):
dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
if dc <= p1.r + p2.r:
x1, y1 = p1.pos[0], p1.pos[1]
x2, y2 = p2.pos[0], p2.pos[1]
m1, m2 = p1.r**2, p2.r**2
n = np.array([x2-x1, y2-y1])
cls.resolveStatically(n, dc, p1, p2)
un = n / np.linalg.norm(n)
ut = np.array([-un[1], un[0]])
v1 = p1.vel
v2 = p2.vel
v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)
v2n = np.dot(un, v2)
v2t = np.dot(ut, v2)
v1n_prime_s = (v1n * (m1 - m2) + 2*m2*v2n) / (m1 + m2)
v2n_prime_s = (v2n * (m2 - m1) + 2*m1*v1n) / (m1 + m2)
v1n_prime = v1n_prime_s * un
v1t_prime = v1t * ut
v2n_prime = v2n_prime_s * un
v2t_prime = v2t * ut
u1 = v1n_prime + v1t_prime
u2 = v2n_prime + v2t_prime
p1.vel = u1
p2.vel = u2
while len(particles) < no_particles:
r = random.randint(10, 20)
x = random.randint(r, width-r)
y = random.randint(r, height-r)
collide = False
for particle in particles:
d = (particle.pos[0] - x)**2 + (particle.pos[1] - y)**2
if d < (r + particle.r)**2:
collide = True
break
if not collide:
particles.append(Particle(x, y, random.randint(10, 20)))
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255))
for i in range(len(particles)):
particles[i].move()
particles[i].bounce()
for j in range(i + 1, len(particles)):
particles[i].collision(particles[i], particles[j])
particles[i].display()
pygame.display.flip()
clock.tick(tick_speed)
pygame.quit()
quit()

Real time graph plotting with pygame [duplicate]

I am trying to generate and draw a sin waves. I am using this formulae that I found online y = Amp * sin(2 * PI * frequency * time + shift)
import pygame
import math
import time
window = pygame.display.set_mode((600, 600))
class Point:
def __init__(self):
self.x = 0
self.y = 0
class Line:
def __init__(self):
self.points = []
def generateLine(startX, nPoints, length, y):
line = Line()
for i in range(nPoints):
p = Point()
p.x = startX + ((i / nPoints) * length)
p.y = y
line.points.append(p)
return line;
nPoints = 100
line = generateLine(10, nPoints, 590, 300)
start = time.time()
accNPoints = 0
frequency = 100
amplitude = 30
overallY = 300
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
window.fill((255, 255, 255))
keys = pygame.key.get_pressed()
if (keys[pygame.K_a]): frequency -= 0.002
if (keys[pygame.K_d]): frequency += 0.002
if (keys[pygame.K_s]): amplitude -= 0.05
if (keys[pygame.K_w]): amplitude += 0.05
if (keys[pygame.K_q]): overallY += 0.5
if (keys[pygame.K_e]): overallY -= 0.5
if (keys[pygame.K_p]): accNPoints += 0.5
if (keys[pygame.K_o]): accNPoints -= 0.5
if accNPoints > 50:
line = generateLine(10, nPoints, 590, 300)
accNPoints = 0
nPoints += 1
elif accNPoints < -50:
line = generateLine(10, nPoints, 590, 300)
accNPoints = 0
nPoints -= 1
for i in range(1, len(line.points)):
#calculate y based on x
#y = A * sin(2 * PI * f * t + shift)
#yStart = (amplitude * math.sin(2 * math.pi * frequency * ((time.time() - start) * 0.01) + line.points[i].x)) + overallY
#yEnd = (amplitude * math.sin(2 * math.pi * frequency * ((time.time() - start) * 0.01) + line.points[i - 1].x)) + overallY
yStart = (amplitude * math.sin(2 * math.pi * frequency + line.points[i].x)) + overallY
yEnd = (amplitude * math.sin(2 * math.pi * frequency + line.points[i - 1].x)) + overallY
pygame.draw.circle(window, (255, 0, 0), (line.points[i].x, yStart), 1)
pygame.draw.circle(window, (255, 0, 0), (line.points[i - 1].x, yEnd), 1)
pygame.draw.aaline(
window,
(0, 0, 0),
(line.points[i].x, yStart),
(line.points[i - 1].x, yEnd)
)
pygame.display.flip()
There seems to be two problems. Changing frequency value does not really seem to change the frequency of the wave. Frequency seems to be dependent on the nPoints variable which is in the function that generates line i.e. def generateLine(startX, nPoints, length, y):.
The formula is wrong. The x-coordinate depends on the control variable of the loop (i). The y-coordinate needs to depend on the x-coordinate:
e.g.: Frequency 5 (5 waves)
frequency = 5
amplitude = 50
overallY = 300
while True:
# [...]
no_pts = window.get_width()
for i in range(no_pts):
x = i/no_pts * 2 * math.pi
y = (amplitude * math.cos(x * frequency)) + overallY
if i > 0:
pygame.draw.aaline(window, (0, 0, 0), prev_pt, (i, y))
prev_pt = (i, y)
# [...]

Moving a comet in pygame

I want a commet in pygame to shoot across the screen. Here is my commet class
class Commet:
def __init__(self):
self.x = -10
self.y = 10
self.radius = 20
self.commet = pygame.image.load(r"C:\Users\me\OneDrive\Documents\A level python codes\final game\commet.png")
self.commet = pygame.transform.scale(self.commet, (self.radius, self.radius))
self.drop = 0.0000009
self.speed = 2
self.pos = 0
self.commets = []
Then i added 20 commets to the self.commets list.
def tail(self, n): # n is a variable used to denote the length of the self.commets
for i in range(n):
if len(self.commets) <= n - 1:
self.commets.append(Commet())
I am having two problems. The first problem being moving the commet. To move it i did this
def move_tail(self):
for c in self.commets:
c.x += self.speed
for i in range(len(self.commets) - 1):
self.commets[i].y += ((self.commets[i + 1].x) ** 2) * self.drop
For x- coordinate i just added 2 to its value every frame. However, for the yvalue of the commet, i want it to produce a tail-like following effect. I tried assigning the y value of the commet to the square of x value of the commet in the index position one above the commet we are referring to in the list self.commets.I expected the commets to follow each other along a general x = y **2 quadradic curve. They do follow the curve but all at the same rate(i expected them to follow at different rate because all the commets have different x values), which dosent give me the tail-like effect. How would i be able to produce this tail-like effect?
The second part of my question is that i want the commets following the first one get smaller and smaller. I tried decreasing the radius value, which is used to scale the image i imported. The code looks like this
# Decrease radius
for i in range(n):
self.commets[i].radius = i + 1
When i print out the values of radius of the commets on the console, they range from 1 to 20, as i expect them to, but the size of the image that appears on the screen is the same for all the commets in the list.The following code is how i blit the commet
for i in range(n):
self.commets[i].pos = i * 10 # This line maintains a certain x- distance between commets
for c in self.tails:
D.blit(c.commet, (c.x - c.pos, c.y))
if self.pos >= n:
self.pos = n
Given you want your comet to fly from left to right on a FullHD screen.
The comet shall start at the left side at a y coordinate of 900, then reach its highest point at x=1400 and y = 100 and then fall to 600 at the right side of the screen.
A parabola is generally y = ax²+bx+c.
To be independent of the screen resolution, you would of course calculate those values from some percentage, say 900 ~ screen height * 83%, 600 ~ screen height * 55%, 1400 ~ screen width * 73%, 100 ~ screen height * 9%
With three points given, you can calculate a parabola:
class ParabolaFrom3Points:
def __init__(self, points: list):
self.a = (points[0][0] * (points[1][1] - points[2][1]) + points[1][0] * (
points[2][1] - points[0][1]) + points[2][0] * (points[0][1] - points[1][1])) / (
(points[0][0] - points[1][0]) * (points[0][0] - points[2][0]) * (
points[2][0] - points[1][0]))
self.b = (points[0][0] ** 2 * (points[1][1] - points[2][1]) + points[1][0] ** 2 * (
points[2][1] - points[0][1]) + points[2][0] ** 2 * (points[0][1] - points[1][1])) / (
(points[0][0] - points[1][0]) * (points[0][0] - points[2][0]) * (
points[1][0] - points[2][0]))
self.c = (points[0][0] ** 2 * (points[1][0] * points[2][1] - points[2][0] * points[1][1]) +
points[0][0] * (points[2][0] ** 2 * points[1][1] - points[1][0] ** 2 * points[2][1]) +
points[1][0] * points[2][0] * points[0][1] * (points[1][0] - points[2][0])) / (
(points[0][0] - points[1][0]) * (points[0][0] - points[2][0]) * (
points[1][0] - points[2][0]))
def y(self, x: int) -> int:
return int(self.a * x ** 2 + self.b * x + self.c)
The Comet is quite simple then. It just needs to know its parabola function and can then calculate the y from the x.
class Comet:
def __init__(self, radius: int, para: ParabolaFrom3Points):
self.x = -radius # Be invisible at the beginning
self.radius = radius
self.para = para
def move(self, x):
self.x = x
def paint(self, screen):
x = self.x
radius = self.radius
for tail in range(20):
pygame.draw.circle(screen, [255, 255, 255], (int(x), self.para.y(x)), radius)
x = x - radius / 2
radius -= 1
Test code:
import pygame
pygame.init()
pygame.fastevent.init()
clock = pygame.time.Clock()
window = pygame.display.set_mode((1920, 1080))
pygame.display.set_caption('Comet example')
comet = Comet(20, ParabolaFrom3Points([(0, 1080 * 0.83), (1920 * 0.73, 1080 * 0.12), (1920, 1080 * 0.55)]))
for x in range(-20, 1920 + 200, 3):
comet.move(x)
comet.paint(window)
clock.tick(90)
pygame.display.flip()
window.fill([0, 0, 0])
pygame.quit()

Categories

Resources