Real time graph plotting with pygame [duplicate] - python

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)
# [...]

Related

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

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

Threading in Tkinter and Python3

I was adjusting this code to my needs in PyCharm where it worked well, without any exceptions and errors. When I was trying it out in Jupyter Notebook it worked, but when I closed the Tkinter window, I get the exception Exception in thread Thread-: and the Error RuntimeError: main thread is not in main loop .
The traceback is: line 90, in run - line 51, in do action - line 30, in try_move
I tried to find the solution, but I only found mtTkinter for Python2.
Since I am new to threading, I don't know how to solve this problem and why it is only showing in Jupyter Notebook. Is it possible that Jupyter Notebook is the source of the problem?
The code is:
from tkinter import *
import threading
import time
def render_grid():
global specials, walls, WIDTH, x, y, player
for i in range(x):
for j in range(y):
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="misty rose", width=1)
temp = {}
cell_scores[(i, j)] = temp
for (i, j, c, w) in specials:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill=c, width=1)
for (i, j) in walls:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="wheat4", width=1)
def set_cell_score(state, action, val):
global cell_score_min, cell_score_max
def try_move(dx, dy):
global player, x, y, score, walk_reward, me, restart
if restart:
restart_game()
new_x = player[0] + dx
new_y = player[1] + dy
score += walk_reward
if (new_x >= 0) and (new_x < x) and (new_y >= 0) and (new_y < y) and not ((new_x, new_y) in walls):
board.coords(me, new_x * WIDTH + WIDTH * 2 / 10, new_y * WIDTH + WIDTH * 2 / 10, new_x * WIDTH + WIDTH * 8 / 10,
new_y * WIDTH + WIDTH * 8 / 10)
player = (new_x, new_y)
for (i, j, c, w) in specials:
if new_x == i and new_y == j:
score -= walk_reward
score += w
if score > 0:
print("Success! score: ", score)
else:
print("Fail! score: ", score)
restart = True
return
# print "score: ", score
def call_up(event):
try_move(0, -1)
def call_down(event):
try_move(0, 1)
def call_left(event):
try_move(-1, 0)
def call_right(event):
try_move(1, 0)
def restart_game():
global player, score, me, restart
player = (0, y - 1)
score = 1
restart = False
board.coords(me, player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10)
def has_restarted():
return restart
def start_game():
master.mainloop()
master = Tk()
master.resizable(False, False)
cell_score_min = -0.2
cell_score_max = 0.2
WIDTH = 100
(x, y) = (5, 5)
actions = ["up", "down", "left", "right"]
board = Canvas(master, width=x * WIDTH, height=y * WIDTH)
player = (0, y - 1)
score = 1
restart = False
walk_reward = -0.04
walls = [(1, 1), (1, 2), (2, 1), (2, 2)]
specials = [(4, 1, "salmon1", -1), (4, 0, "medium sea green", 1)]
cell_scores = {}
discount = 0.3
states = []
Q = {}
for i in range(x):
for j in range(y):
states.append((i, j))
for state in states:
temp = {}
for action in actions:
temp[action] = 0.1
set_cell_score(state, action, temp[action])
Q[state] = temp
for (i, j, c, w) in specials:
for action in actions:
Q[(i, j)][action] = w
set_cell_score((i, j), action, w)
def do_action(action):
s = player
r = -score
if action == actions[0]:
try_move(0, -1)
elif action == actions[1]:
try_move(0, 1)
elif action == actions[2]:
try_move(-1, 0)
elif action == actions[3]:
try_move(1, 0)
else:
return
s2 = player
r += score
return s, action, r, s2
def max_Q(s):
val = None
act = None
for a, q in Q[s].items():
if val is None or (q > val):
val = q
act = a
return act, val
def inc_Q(s, a, alpha, inc):
Q[s][a] *= 1 - alpha
Q[s][a] += alpha * inc
set_cell_score(s, a, Q[s][a])
def run():
global discount
time.sleep(1)
alpha = 1
t = 1
while True:
# Pick the right action
s = player
max_act, max_val = max_Q(s)
(s, a, r, s2) = do_action(max_act)
# Update Q
max_act, max_val = max_Q(s2)
inc_Q(s, a, alpha, r + discount * max_val)
t += 1.0
if has_restarted():
restart_game()
time.sleep(0.01)
t = 1.0
time.sleep(0.1)
render_grid()
master.bind("<Up>", call_up)
master.bind("<Down>", call_down)
master.bind("<Right>", call_right)
master.bind("<Left>", call_left)
me = board.create_rectangle(player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10, fill="sandy brown",
width=1, tag="me")
board.grid(row=0, column=0)
t = threading.Thread(target=run)
t.setDaemon(True)
t.start()
start_game()
Probably all GUIs don't like to run in threads and all changes in widgets should be in main thread (but calculations still can be in separated threads)
In tkinter you could use master.after(milliseconds, function_name) instead of thread and while-loop to run code periodically - and this will works like loop but in current thread.
def run():
global t
# Pick the right action
s = player
max_act, max_val = max_Q(s)
(s, a, r, s2) = do_action(max_act)
# Update Q
max_act, max_val = max_Q(s2)
inc_Q(s, a, alpha, r + discount * max_val)
t += 1.0
if has_restarted():
restart_game()
time.sleep(0.01)
t = 1.0
# run again after 100ms
master.after(100, run)
and later start it as normal function
#master.after(100, run) # run after 100ms
run() # run at once
start_game()
Full working code:
EDIT:
You may also use global variable - ie. running = True - to stop looping before destroying GUI.
It may need also master.protocol("WM_DELETE_WINDOW", on_delete) to execute function when you click closing button [X]
from tkinter import *
#import threading
import time
def render_grid():
global specials, walls, WIDTH, x, y, player
for i in range(x):
for j in range(y):
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="misty rose", width=1)
temp = {}
cell_scores[(i, j)] = temp
for (i, j, c, w) in specials:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill=c, width=1)
for (i, j) in walls:
board.create_rectangle(i * WIDTH, j * WIDTH, (i + 1) * WIDTH, (j + 1) * WIDTH, fill="wheat4", width=1)
def set_cell_score(state, action, val):
global cell_score_min, cell_score_max
def try_move(dx, dy):
global player, x, y, score, walk_reward, me, restart
if restart:
restart_game()
new_x = player[0] + dx
new_y = player[1] + dy
score += walk_reward
if (new_x >= 0) and (new_x < x) and (new_y >= 0) and (new_y < y) and not ((new_x, new_y) in walls):
board.coords(me, new_x * WIDTH + WIDTH * 2 / 10, new_y * WIDTH + WIDTH * 2 / 10, new_x * WIDTH + WIDTH * 8 / 10,
new_y * WIDTH + WIDTH * 8 / 10)
player = (new_x, new_y)
for (i, j, c, w) in specials:
if new_x == i and new_y == j:
score -= walk_reward
score += w
if score > 0:
print("Success! score: ", score)
else:
print("Fail! score: ", score)
restart = True
return
# print "score: ", score
def call_up(event):
try_move(0, -1)
def call_down(event):
try_move(0, 1)
def call_left(event):
try_move(-1, 0)
def call_right(event):
try_move(1, 0)
def restart_game():
global player, score, me, restart
player = (0, y - 1)
score = 1
restart = False
board.coords(me, player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10)
def has_restarted():
return restart
def start_game():
master.mainloop()
def do_action(action):
s = player
r = -score
if action == actions[0]:
try_move(0, -1)
elif action == actions[1]:
try_move(0, 1)
elif action == actions[2]:
try_move(-1, 0)
elif action == actions[3]:
try_move(1, 0)
else:
return
s2 = player
r += score
return s, action, r, s2
def max_Q(s):
val = None
act = None
for a, q in Q[s].items():
if val is None or (q > val):
val = q
act = a
return act, val
def inc_Q(s, a, alpha, inc):
Q[s][a] *= 1 - alpha
Q[s][a] += alpha * inc
set_cell_score(s, a, Q[s][a])
def run():
global t
# Pick the right action
s = player
max_act, max_val = max_Q(s)
(s, a, r, s2) = do_action(max_act)
# Update Q
max_act, max_val = max_Q(s2)
inc_Q(s, a, alpha, r + discount * max_val)
t += 1.0
if has_restarted():
restart_game()
time.sleep(0.01)
t = 1.0
# run again after 100ms
if running:
master.after(100, run)
def on_delete():
global running
running = False
master.destroy()
# --- main ---
master = Tk()
master.resizable(False, False)
cell_score_min = -0.2
cell_score_max = 0.2
WIDTH = 100
(x, y) = (5, 5)
actions = ["up", "down", "left", "right"]
board = Canvas(master, width=x * WIDTH, height=y * WIDTH)
player = (0, y - 1)
score = 1
restart = False
walk_reward = -0.04
walls = [(1, 1), (1, 2), (2, 1), (2, 2)]
specials = [(4, 1, "salmon1", -1), (4, 0, "medium sea green", 1)]
cell_scores = {}
discount = 0.3
states = []
Q = {}
for i in range(x):
for j in range(y):
states.append((i, j))
for state in states:
temp = {}
for action in actions:
temp[action] = 0.1
set_cell_score(state, action, temp[action])
Q[state] = temp
for (i, j, c, w) in specials:
for action in actions:
Q[(i, j)][action] = w
set_cell_score((i, j), action, w)
render_grid()
master.bind("<Up>", call_up)
master.bind("<Down>", call_down)
master.bind("<Right>", call_right)
master.bind("<Left>", call_left)
me = board.create_rectangle(player[0] * WIDTH + WIDTH * 2 / 10, player[1] * WIDTH + WIDTH * 2 / 10,
player[0] * WIDTH + WIDTH * 8 / 10, player[1] * WIDTH + WIDTH * 8 / 10, fill="sandy brown",
width=1, tag="me")
board.grid(row=0, column=0)
#t = threading.Thread(target=run)
#t.setDaemon(True)
#t.start()
running = True
alpha = 1
t = 1
run() # run at once
#master.after(100, run) # run after 100ms
master.protocol("WM_DELETE_WINDOW", on_delete)
start_game()

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()

Why does the text get scaled?

I'm trying to move a text alongside the player character but it gets scaled weird...
Here's a picture of the player character alongside the text "test":
It should be some kind of antialiasing since that is off in the code for the moving text:
import pygame, sys
from pygame.locals import *
class MovingText():
def __init__(self, text, font_size, color, surface, target, off_1, off_2):
self.font = pygame.font.SysFont(None, font_size)
self.textobj = self.font.render(text, 0, color)
self.textrect = self.textobj.get_rect()
self.surface = surface
self.target = target
self.offset = (off_1, off_2)
self.textrect.center = self.target_pos()
def update(self):
self.textrect.center = self.target_pos()
self.surface.blit(self.textobj, self.textrect.center)
def target_pos(self):
pos = self.target.rect.center
return pos[0] + self.offset[1], pos[1] + self.offset[0]
What I think is causing the problem is either when the text gets created, here:
self.follow_text = movingtext.MovingText('test', 10, (255, 255, 255), self.display, self.player, 10, 5)
If you think it's something else you're free to check the rest of the code out, don't really think that but i've been proven wrong once or twice:
import pygame, sys
from pygame.locals import *
from pygame.mouse import get_pos
import time
from utils import button, constants, movingtext
from entities import player, entity
import game
class Game():
def __init__(self, map_number):
pygame.init()
self.clock = pygame.time.Clock()
self.screen = pygame.display.set_mode((pygame.display.Info().current_w, pygame.display.Info().current_h), pygame.RESIZABLE)
self.display = pygame.Surface((300,300))
self.font_small = pygame.font.SysFont(None, 20)
self.font_medium = pygame.font.SysFont(None, 32)
self.test_bg = pygame.image.load('images/wp.png')
self.pause = False
self.timer = 0
self.game_called = time.time()
self.flag_mover = False
self.map_number = map_number
f = open('maps/map'+self.map_number+'.txt')
self.map_data = [[int(c) for c in row] for row in f.read().split('\n')]
f.close()
#Tile list -----
self.spawn_img = pygame.image.load('images/spawn.png').convert()
self.spawn_img.set_colorkey((0, 0, 0))
self.goal_img = pygame.image.load('images/goal.png').convert()
self.goal_img.set_colorkey((0, 0, 0))
self.key_img = pygame.image.load('images/key.png').convert()
self.key_img.set_colorkey((0, 0, 0))
self.lava_img = pygame.image.load('images/lava.png').convert()
self.lava_img.set_colorkey((0, 0, 0))
self.grass_img = pygame.image.load('images/grass2.png').convert()
self.grass_img.set_colorkey((0, 0, 0))
#Player
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 1:
self.player = player.Player(self.display, (150 + (x+1) * 10 - y * 10, 100 + x * 5 + (y-0.5) * 5), self.map_data)
#goal flag
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 2:
self.goal_flag = entity.Entity(self.display, (150 + (x+1) * 10 - y * 10, 100 + x * 5 + (y-1) * 5), 'images/goal_flag.png')
#points
self.point_list = []
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 3:
self.points = entity.Entity(self.display, (150 + (x+1) * 10 - y * 10, 100 + x * 5 + (y-0.5) * 5),'images/point.png')
self.point_list.append(self.points)
self.running = True
self.click = False
def drawText(self, text, font, color, surface, x, y):
textobj = font.render(text, 1, color)
textrect = textobj.get_rect()
textrect.topleft = (x, y)
surface.blit(textobj, textrect)
def gameLoop(self):
while self.running:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
if self.pause == False:
self.pause = True
else:
self.pause = False
if self.pause == False:
self.screen.blit(pygame.transform.scale(self.display, self.screen.get_size()), (0, 0))
self.display.fill(0) #clears the scree
self.drawText('game', self.font_small, (255, 255, 255), self.screen, 20, 20)
# self.drawText('time: '+str(int(self.timer/1000)), self.font_small, (255, 255, 255), self.screen, self.player.rect[0], self.player.rect[1])
self.follow_text = movingtext.MovingText('test', 10, (255, 255, 255), self.display, self.player, 10, 5)
# self.follow_text = movingtext.MovingText()
#Draws the map
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 0:
self.display.blit(self.lava_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 1:
self.display.blit(self.spawn_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 2:
self.display.blit(self.goal_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 3:
self.display.blit(self.key_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 4:
self.display.blit(self.grass_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
#collision detection between entities
if self.goal_flag.rect[0] == self.player.rect[0] and self.goal_flag.rect[1] == self.player.rect[1] - 2:
self.flag_mover = True
if self.flag_mover == True:
self.goal_flag.rect[1] += -0.1
#point collision
for point_collision in self.point_list:
if point_collision.rect[0] == self.player.rect[0] and point_collision.rect[1] == self.player.rect[1]:
self.point_list.remove(point_collision)
#update
for points in self.point_list:
points.update()
self.goal_flag.update()
self.player.update()
self.follow_text.update()
else:
self.screen.blit(pygame.transform.scale(self.display, self.screen.get_size()), (0, 0))
self.drawText('game', self.font_small, (255, 255, 255), self.screen, 20, 20)
self.drawText('PAUSED', self.font_medium, (255, 255, 255), self.screen, pygame.display.Info().current_w/2-50, pygame.display.Info().current_h/2)
for y, row in enumerate(self.map_data):
for x, tile in enumerate(row):
if tile == 0:
self.display.blit(self.lava_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 1:
self.display.blit(self.spawn_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 2:
self.display.blit(self.goal_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 3:
self.display.blit(self.key_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
if tile == 4:
self.display.blit(self.grass_img, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
pygame.display.update()
self.clock.tick(60)
The text is scaled as you draw the text on a surface and scale the surface to the size of your window. You have to draw the text on the display surface directly.

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