Converting pygame 2d water ripple to pyOpenGL - python

I have a 2d pygame water simulation thingy that I followed a tutorial to make. I also found the answer to this question to fix issues with the tutorial: Pygame water physics not working as intended
I have since been trying to convert this program over to using pyopengl to render things. However, I have been struggling to:
A: Draw the water polygon
B: texture the water polygon with a tiled texture
Here is my (rather poor) attempt at converting this code to pyopengl.
import pygame, random
import math as m
from pygame import *
from OpenGL import *
from OpenGL.GLU import *
from OpenGL.GL import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32,DOUBLEBUF|OPENGL) # initiate the window
clock = pygame.time.Clock()
def draw_polygon(polygon_points):
glBegin(GL_POLYGON);
for i in polygon_points:
glVertex3fv(i)
#glEnd()
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.04
self.d = 0.08
self.time = 1
def update(self):
x = self.y_pos - self.target_y
a = -(self.k * x + self.d * self.velocity)
if self.y_pos > self.target_y:
self.y_pos -= 0.1
if self.y_pos < self.target_y:
self.y_pos += 0.1
self.velocity = round(self.velocity)
self.y_pos += self.velocity
self.velocity += a
self.time += 1
class water_tile():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
passes = 4 # more passes = more splash spreading
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for p in range(passes):
for i in range(0, len(self.springs)):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs):
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[(i + 1)%len(self.springs)].y_pos)
self.springs[(i + 1)%len(self.springs)].velocity += rightDeltas[i]
for i in range(0, len(self.springs)):
if round (leftDeltas[i],12) == 0 or round (rightDeltas[i],12) == 0:
self.springs[i - 1].y_pos = self.y_end+10
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i] # you were updating velocity here!
if i < len(self.springs):
self.springs[(i + 1)%len(self.springs)].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index >= 0 and index < len(self.springs):
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_end-self.x_start), abs(self.y_start - self.y_end)), depth=8).convert_alpha()
polygon_points = []
polygon_points.append((self.x_start, self.y_start,0))
for spring in range(len(self.springs)):
polygon_points.append((self.springs[spring].x_pos, self.springs[spring].y_pos,0))
polygon_points.append((self.springs[len(self.springs) - 1].x_pos, self.y_start,0))
draw_polygon(polygon_points)
return water_surface
class water_object:
def __init__(self, x_start, x_end, y_start, y_end, segment_length, x_pos, y_pos):
self.water = water_tile(x_start,x_end,y_start,y_end,segment_length)
self.image = self.water.draw()
self.rect = self.image.get_rect()
self.rect.x = x_pos
self.rect.y = y_pos
def update(self):
self.water.update(0.1)
self.image = self.water.draw()
water_list = [water_object(0,276+16,64,0,16,0,20)]
while True:
screen.fill((0,0,0))
for water in water_list:
gluPerspective(45, (WINDOW_SIZE[0]/WINDOW_SIZE[1]), 0.1, 50.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
water.update()
#screen.blit(water.image, (water.rect.x,water.rect.y))
#water_test.x_start = water_test.x_start + 1
#if random.randint(0,8) == 1:
#water_test.splash(random.randint(0, len(water_test.springs) - 1),2)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
print (len(water.water.springs))
water.water.splash(random.randint(0, len(water.water.springs) - 1),50)
pygame.display.update()
clock.tick(60)
However, despite my attempt, I couldnt get anything to display on screen at all. How can I fix this/how can I attain the 2 things I have been struggling with?

You cannot draw an OpenGL primitive to a pygame.Surface. Anyway there is no need to do so.
For the best performance, directly draw to the default framebuffer (window).
Since you want to draw a line, you have to use a Line primitive type. GL_POLYGON would draw a filed convex polygon. Use the primitive type GL_LINE_STRIP:
def draw_polygon(polygon_points):
glBegin(GL_LINE_STRIP)
for pt in polygon_points:
glVertex2f(*pt)
glEnd()
Before you draw the line, ser the current color by glColor:
glColor3f(0, 0, 1)
draw_polygon(polygon_points)
The vertex coordinates of the lie are specified in window space. Hence you have to setup an Orthographic projection rather than a Perspective projection. Specify the current matrix by [glMatrixMode] and set the projection matrix by glOrtho. Since the matrix operations do not set a matrix, but multiply the current matrix by the specified matrix, I recommend to load the identity matrix before (glLoadIdentity):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, WINDOW_SIZE[0], WINDOW_SIZE[1], 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
Before you draw the line you have to clear the framebuffer by glClear. The clear color can be defined by glClearColor:
glClearColor(1, 1, 1, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
Complete example:
import pygame
from OpenGL import *
from OpenGL.GL import *
def draw_polygon(surf_rect, polygon_points):
glBegin(GL_LINE_STRIP)
#glBegin(GL_TRIANGLE_STRIP)
for pt in polygon_points:
glVertex2f(*pt)
glVertex2f(pt[0], surf_rect.height)
glEnd()
class WaterParticle():
def __init__(self, x, y):
self.x, self.y = x, y
self.target_y = y
self.velocity = 0
self.k = 0.04
self.d = 0.08
def update(self):
x = self.y - self.target_y
a = -(self.k * x + self.d * self.velocity)
#self.p[1] += -0.1 if x > 0 else 0.1 if x < 0 else 0
self.y += self.velocity
self.velocity += a
class Water():
def __init__(self, x_start, x_end, y_start, segment_length, passes, spread):
n = abs(x_end - x_start + segment_length - 1) // segment_length + 1
self.particles = [WaterParticle(i * segment_length + x_start, y_start) for i in range(n)]
self.passes = passes
self.spread = spread
def update(self):
for particle in self.particles:
particle.update()
left_deltas = [0] * len(self.particles)
right_deltas = [0] * len(self.particles)
for _ in range(self.passes):
for i in range(len(self.particles)):
if i > 0:
left_deltas[i] = self.spread * (self.particles[i].y - self.particles[i - 1].y)
self.particles[i - 1].velocity += left_deltas[i]
if i < len(self.particles)-1:
right_deltas[i] = self.spread * (self.particles[i].y - self.particles[i + 1].y)
self.particles[i + 1].velocity += right_deltas[i]
for i in range(len(self.particles)):
if i > 0:
self.particles[i-1].y += left_deltas[i]
if i < len(self.particles) - 1:
self.particles[i+1].y += right_deltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.particles):
self.particles[index].velocity += speed
def draw(self, surf_rect):
polygon_points = []
for spring in range(len(self.particles)):
polygon_points.append((self.particles[spring].x, self.particles[spring].y))
glColor3f(0, 0, 1)
draw_polygon(surf_rect, polygon_points)
pygame.init()
window = pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL)
clock = pygame.time.Clock()
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, *window.get_size(), 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glClearColor(1, 1, 1, 1)
water_line_y = window.get_height() // 2
water = Water(0, window.get_width(), window.get_height() // 2, 3, 8, 0.025)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.MOUSEBUTTONDOWN:
velocity = water_line_y - event.pos[1]
if velocity > 0:
index = int(len(water.particles) * event.pos[0] / window.get_width())
water.splash(index, velocity)
water.update()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
water.draw(window.get_rect())
pygame.display.flip()
clock.tick(50)

Related

How can I connect two points with a series of circles?

I am trying to make realistic water in pygame:
This is till now my code:
from random import randint
import pygame
WIDTH = 700
HEIGHT = 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5
run = True
class Molecule:
def __init__(self, x, y, radius, force, k):
self.x = x
self.y = y
self.radius = radius
self.force = force
self.k = k
self.max_amplitude = y + force/k
self.min_amplitude = y - force/k
self.up = False
self.down = True
self.restore = False
def draw(self, win):
pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)
def oscillate(self):
if self.y <= self.max_amplitude and self.down == True:
self.y += VELOCITY
if self.y == self.max_amplitude or self.up:
self.up = True
self.down = False
self.y -= VELOCITY
if self.y == self.min_amplitude:
self.up = False
self.down = True
molecules = []
for i in range(100):
FORCE = randint(10, 20)
molecules.append(Molecule(x, y, RADIUS, FORCE, K))
x += 10
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
for molecule in molecules:
molecule.draw(win)
molecule.oscillate()
for i in range(len(molecules)):
try:
pygame.draw.line(win, BLACK, (molecules[i].x, molecules[i].y), (molecules[i+1].x, molecules[i+1].y))
pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i+1].x, HEIGHT))
except:
pass
pygame.display.flip()
pygame.quit()
But as may expected the water curve is not smooth:
Look at it:
Sample Img1
I want to connect the two randomly added wave points using a set of circles not line like in this one so that a smooth curve could occur.
And in this way i could add the water color to it such that it will draw aqua lines or my desired color line from the point to the end of screen and all this will end up with smooth water flowing simulation.
Now the question is how could i make the points connect together smoothly into a smooth curve by drawing point circles at relative points?
I suggest sticking the segments with a Bézier curves. Bézier curves can be drawn with pygame.gfxdraw.bezier
Calculate the slopes of the tangents to the points along the wavy waterline:
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
Use the the tangents to define 4 control points for each segment and draw the curve with pygame.gfxdraw.bezier:
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)
Complete example:
from random import randint
import pygame
import pygame.gfxdraw
WIDTH = 700
HEIGHT = 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5
class Molecule:
def __init__(self, x, y, radius, force, k):
self.x = x
self.y = y
self.radius = radius
self.force = force
self.k = k
self.max_amplitude = y + force/k
self.min_amplitude = y - force/k
self.up = False
self.down = True
self.restore = False
def draw(self, win):
pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)
def oscillate(self):
if self.y <= self.max_amplitude and self.down == True:
self.y += VELOCITY
if self.y == self.max_amplitude or self.up:
self.up = True
self.down = False
self.y -= VELOCITY
if self.y == self.min_amplitude:
self.up = False
self.down = True
molecules = []
for i in range(50):
FORCE = randint(10, 20)
molecules.append(Molecule(x, y, RADIUS, FORCE, K))
x += 20
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
for molecule in molecules:
molecule.draw(win)
molecule.oscillate()
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)
for i in range(len(molecules)-1):
pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i].x, HEIGHT))
pygame.display.flip()
pygame.quit()
If you want to "fill" the water, you must calculate the points along the Bézier line and draw a filled polygon. How to calculate a Bézier curve is explained in Trying to make a Bezier Curve on PyGame library How Can I Make a Thicker Bezier in Pygame? and "X". You can use the following function:
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(b, samples):
return [ptOnCurve(b, i/samples) for i in range(samples+1)]
Use the bezier to stitch the wavy water polygon:
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
pts = [(WIDTH, HEIGHT), (0, HEIGHT)]
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pts += bezier([p0, p1, p2, p3], 4)
Draw the polygon with pygame.draw.polygon():
pygame.draw.polygon(win, AQUA, pts)
Complete example:
from random import randint
import pygame
class Node:
def __init__(self, x, y, force, k, v):
self.x = x
self.y = y
self.y0 = y
self.force = force
self.k = k
self.v = v
self.direction = 1
def oscillate(self):
self.y += self.v * self.direction
if self.y0 - self.force / self.k > self.y or self.y0 + self.force / self.k < self.y:
self.direction *= -1
def draw(self, surf):
pygame.draw.circle(surf, "black", (self.x, self.y), 3)
window = pygame.display.set_mode((700, 500))
clock = pygame.time.Clock()
width, height = window.get_size()
no_of_nodes = 25
dx = width / no_of_nodes
nodes = [Node(i*dx, height//2, randint(15, 30), 1, 0.5) for i in range(no_of_nodes+1)]
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(b, samples):
return [ptOnCurve(b, i/samples) for i in range(samples+1)]
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for molecule in nodes:
molecule.oscillate()
ts = []
for i in range(len(nodes)):
pa = nodes[max(0, i-1)]
pb = nodes[min(len(nodes)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
pts = [(width, height), (0, height)]
for i in range(len(nodes)-1):
p0 = nodes[i].x, nodes[i].y
p3 = nodes[i+1].x, nodes[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pts += bezier([p0, p1, p2, p3], 4)
window.fill("white")
pygame.draw.polygon(window, 'aqua', pts)
for molecule in nodes:
molecule.draw(window)
pygame.display.flip()
pygame.quit()
exit()

Problem with speed unstability of enemies [duplicate]

This question already has answers here:
Pygame doesn't let me use float for rect.move, but I need it
(2 answers)
Problem with Pygame movement acceleration, platformer game
(1 answer)
Closed 1 year ago.
I tried to make the enemies(yellow box) follow the player(red box) with a constant velocity from all directions.
The problem of my code is the speed of the enemy from x+(Right) and y+(Bottom) axis to the player is slower than x-(Left) and y-(Above).
Image: https://i.stack.imgur.com/qJNp7.png
I think the problem is in here. I can't find it. It may be somewhere else.
for enemy_types in Global_Obj[2]:
zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)
print(enemy_types.rect.center)
x,y = enemy_types.rect.center
x += dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
y += dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas
enemy_types.rect.center = (x,y)
Here is all of my code.
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 8 21:56:06 2021
#author: Toon
"""
import pygame
import cv2
import numpy as np
import math
import random
import time
dt = 0.01
window = (1280, 720)
win = pygame.display.set_mode(window)
background = pygame.Surface(window)
pygame.display.set_caption("First Game")
run = True
class playerIO():
def __init__(self):
self.name = 'Player'
self.width = 30
self.height = 30
self.speed = 1000
self.color = (255,0,0)
self.HPwidth = 50
self.HPheight = 8
self.HPoffset = 10
self.MAXHP = 1000
self.HP = self.MAXHP
self.HPregeneration_per_sec = 5
self.MANAwidth = 50
self.MANAheight = 8
self.MANAoffset = 10
self.MAXMANA = 3000
self.MANA = self.MAXMANA
self.MANAregeneration_per_sec = 10
self.image = pygame.Surface([self.width, self.height])
self.image.fill(self.color)
self.rect = self.image.get_rect(topleft=(80, 80))
self.bullet_delay = [0, 0, 0]
def draw(self):
self.HPbar_high = self.rect.y-self.HPheight-self.HPoffset
self.MANAbar_high = self.HPbar_high-self.MANAheight-self.MANAoffset
pygame.draw.rect(win, self.color, self.rect)
pygame.draw.rect(win, (255,255,255), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth, self.HPheight))
pygame.draw.rect(win, (0,255,0), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth*self.HP/self.MAXHP, self.HPheight))
pygame.draw.rect(win, (255,255,255), (self.rect.x + self.width/2 - self.MANAwidth/2, self.MANAbar_high, self.MANAwidth, self.MANAheight))
pygame.draw.rect(win, (0,0,255), (self.rect.x + self.width/2 - self.MANAwidth/2, self.MANAbar_high, self.MANAwidth*self.MANA/self.MAXMANA, self.MANAheight))
class BulletIO():
def __init__(self):
self.MANA_usage = 5
self.zeta = 0
self.width = 15
self.height = 15
self.speed = 1600
self.damage = 8
self.color = (0,255,0)
self.image = pygame.Surface([self.width, self.height])
self.image.fill(self.color)
self.rect = self.image.get_rect()
self.reload_delay = 0.2 #shot delay
def variant(self,X,Y, Zeta):
self.rect.x = X
self.rect.y = Y
self.zeta = Zeta
def draw(self):
pygame.draw.rect(win, self.color, self.rect)
class LaserIO():
def __init__(self):
self.MANA_usage = 10
self.zeta = 0
self.width = 8
self.height = 8
self.speed = 500
self.damage = 20
self.color = (0,0,160)
self.image = pygame.Surface([self.width, self.height])
self.image.fill(self.color)
self.rect = self.image.get_rect()
self.reload_delay = 0 #shot delay
def variant(self,X,Y, Zeta):
self.rect.x = X
self.rect.y = Y
self.zeta = Zeta
def draw(self):
pygame.draw.rect(win, self.color, self.rect)
class LaserBeamIO():
def __init__(self):
self.MANA_usage_per_sec = 1000
self.zeta = 0
self.width = 8
self.height = 8
self.speed = 500
self.damage = 20
self.color = (0,160,160)
self.image = pygame.Surface([self.width, self.height])
self.image.fill(self.color)
self.rect = self.image.get_rect()
self.reload_delay = 5 #shot delay
self.charge_time = 0
self.beam_distance = 60
self.max_charge_time = 10
self.charge_size_per_sec = 20
self.charge_damage_per_sec = 100
def charge_beam(self, player, Zeta):
self.charge_time+=dt
self.width += self.charge_size_per_sec * dt
self.height += self.charge_size_per_sec * dt
self.damage += self.charge_damage_per_sec * dt
self.image = pygame.Surface([self.width, self.height])
self.image.fill(self.color)
self.rect = self.image.get_rect()
self.rect.center = (player[0] + self.beam_distance * math.cos(Zeta), player[1] + self.beam_distance * math.sin(Zeta))
self.zeta = Zeta
def draw(self):
pygame.draw.rect(win, self.color, self.rect)
class EnemyIO():
def __init__(self):
self.name = 'enemy'
self.width = 15
self.height = 15
self.zeta = 0
self.speed = 200
self.color = (255,255,0)
self.MAXHP = 16
self.regeneration = 0.2
self.HPwidth = 20
self.HPheight = 5
self.HPoffset = 8
self.HP = self.MAXHP
self.damage = 10
self.image = pygame.Surface([self.width, self.height])
self.image.fill(self.color)
self.rect = self.image.get_rect(topleft=(20, 20))
def randompos(self, spawn_offset = 200):
while 1 :
try:
rand_pole = random.randint(1,4)
if rand_pole == 1 or rand_pole == 2:
self.rect.x = random.randint(FirstPlayer.rect.x+1+spawn_offset, window[0]-self.width)
else:
self.rect.x = random.randint(1,FirstPlayer.rect.x-spawn_offset)
if rand_pole == 1 or rand_pole == 4:
self.rect.y = random.randint(FirstPlayer.rect.y+1+spawn_offset, window[1]-self.height)
else:
self.rect.x = random.randint(1,FirstPlayer.rect.y-spawn_offset)
return 0
except:
pass
def draw(self):
self.HPbar_high = self.rect.y-self.HPheight-self.HPoffset
pygame.draw.rect(win, self.color, self.rect)
pygame.draw.rect(win, (255,255,255), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth, self.HPheight))
pygame.draw.rect(win, (0,255,0), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth*self.HP/self.MAXHP, self.HPheight))
def direction(mouse, obj):
#y = 1 clockwise
zeta = math.atan2((mouse[1]-obj[1]),(mouse[0]-obj[0]))
return zeta
def enemy_spawn(Enemy_max = 30, slow_rate = 2):
num = len(Global_Obj[2])
if num < Enemy_max:
# random spawn enemy 1/100
rand = random.randint(1, int((num+1)*slow_rate))
if rand == 1:
ene = EnemyIO()
ene.randompos()
Global_Obj[2].append(ene)
#Global_Obj[0] are player obj type, Global_Obj[1] are bullet obj type, Global_Obj[2] are enemy obj type, Global_Obj[3] are beam obj type
Global_Obj = [[],[],[],[]]
FirstPlayer = playerIO()
Global_Obj[0].append(FirstPlayer)
win.blit(background,(0, 0))
mouse_pos = (-1,-1)
beam_load = False
pygame.init()
# define the RGB value for white,
# green, blue colour .
text_color = (0, 0, 0)
white = (255, 255, 255)
# set the pygame window name
pygame.display.set_caption('Game')
# create a font object.
# 1st parameter is the font file
# which is present in pygame.
# 2nd parameter is size of the font
font = pygame.font.Font('freesansbold.ttf', 32)
# create a text surface object,
# on which text is drawn on it.
text = font.render('Game start', True, text_color)
# create a rectangular object for the
# text surface object
textRect = text.get_rect()
# set the center of the rectangular object.
textRect.center = (window[0] // 2, window[1] // 2)
inteface_run = True
# infinite loop
while inteface_run:
# completely fill the surface object
# with white color
win.fill(white)
# copying the text surface object
# to the display surface object
# at the center coordinate.
win.blit(text, textRect)
# iterate over the list of Event objects
# that was returned by pygame.event.get() method.
for event in pygame.event.get():
if pygame.mouse.get_pressed()[0] or event.type == pygame.QUIT:
inteface_run = False
# Draws the surface object to the screen.
pygame.display.update()
pygame.time.delay(1000)
font = pygame.font.Font('freesansbold.ttf', 16)
end_time = 300 # sec
t0= time.time()
while run:
pygame.display.flip()
pygame.time.delay(int(dt*1000))
# player move event
keys = pygame.key.get_pressed()
if keys[pygame.K_ESCAPE]:
run = False
if keys[pygame.K_SPACE] :
FirstPlayer.rect.x, FirstPlayer.rect.y = (80,80)
if (keys[pygame.K_a] or keys[pygame.K_LEFT]):
FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x - FirstPlayer.speed * dt, FirstPlayer.rect.y)
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x + FirstPlayer.speed * dt, FirstPlayer.rect.y)
if keys[pygame.K_w] or keys[pygame.K_UP]:
FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x, FirstPlayer.rect.y - FirstPlayer.speed * dt)
if keys[pygame.K_s] or keys[pygame.K_DOWN]:
FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x, FirstPlayer.rect.y + FirstPlayer.speed * dt)
if FirstPlayer.rect.x < 0:
FirstPlayer.rect.x = 0
if FirstPlayer.rect.x+FirstPlayer.width > window[0]:
FirstPlayer.rect.x = window[0] - FirstPlayer.width
if FirstPlayer.rect.y < 0:
FirstPlayer.rect.y = 0
if FirstPlayer.rect.y+FirstPlayer.height > window[1]:
FirstPlayer.rect.y = window[1] - FirstPlayer.height
#player HP regen
if FirstPlayer.HP < FirstPlayer.MAXHP:
FirstPlayer.HP +=FirstPlayer.HPregeneration_per_sec*dt
if FirstPlayer.HP > FirstPlayer.MAXHP:
FirstPlayer.HP = FirstPlayer.MAXHP
#player MANA regen
if FirstPlayer.MANA < FirstPlayer.MAXMANA:
FirstPlayer.MANA +=FirstPlayer.MANAregeneration_per_sec*dt
if FirstPlayer.MANA > FirstPlayer.MAXMANA:
FirstPlayer.MANA = FirstPlayer.MAXMANA
#bullet reload time
for i in range(len(FirstPlayer.bullet_delay)):
if FirstPlayer.bullet_delay[i]>0:
FirstPlayer.bullet_delay[i]-= dt
if FirstPlayer.bullet_delay[i]<0:
FirstPlayer.bullet_delay[i] =0
# bullet click event
mouse_pos = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0] and FirstPlayer.MANA >= BulletIO().MANA_usage and FirstPlayer.bullet_delay[0]==0:
bullet = BulletIO()
FirstPlayer.MANA -= bullet.MANA_usage
zeta = direction(mouse_pos, FirstPlayer.rect.center)
bullet.variant(*FirstPlayer.rect.center, zeta)
FirstPlayer.bullet_delay[0] = bullet.reload_delay
Global_Obj[1].append(bullet)
if pygame.mouse.get_pressed()[2] and FirstPlayer.MANA >= LaserIO().MANA_usage and FirstPlayer.bullet_delay[1]==0:
laser = LaserIO()
FirstPlayer.MANA -= laser.MANA_usage
zeta = direction(mouse_pos, FirstPlayer.rect.center)
laser.variant( *FirstPlayer.rect.center, zeta)
FirstPlayer.bullet_delay[1] = laser.reload_delay
Global_Obj[1].append(laser)
# Global_Obj[1] bullet obj type movement
for bullet_type in Global_Obj[1]:
bullet_type.rect.x += dt * bullet_type.speed * math.cos(bullet_type.zeta)
bullet_type.rect.y += dt * bullet_type.speed * math.sin(bullet_type.zeta)
if bullet_type.rect.x > window[0] or bullet_type.rect.x < 0 or bullet_type.rect.y > window[1] or bullet_type.rect.y < 0:
Global_Obj[1].remove(bullet_type)
# Global_Obj[3] beam obj type movement
for bullet_type in Global_Obj[3]:
bullet_type.rect.x += dt * bullet_type.speed * math.cos(bullet_type.zeta)
bullet_type.rect.y += dt * bullet_type.speed * math.sin(bullet_type.zeta)
if bullet_type.rect.x > window[0] or bullet_type.rect.x < 0 or bullet_type.rect.y > window[1] or bullet_type.rect.y < 0:
Global_Obj[3].remove(bullet_type)
# enemy
enemy_spawn()
# Global_Obj[2] enemy obj type movement
for enemy_types in Global_Obj[2]:
zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)
print(enemy_types.rect.center)
x,y = enemy_types.rect.center
x += dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
y += dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas
enemy_types.rect.center = (x,y)
# enemy-player damage check
for enemy_type in Global_Obj[2]:
if FirstPlayer.rect.colliderect(enemy_type.rect):
FirstPlayer.HP -= enemy_type.damage
Global_Obj[2].remove(enemy_type)
# bullet - enemy damage check
for bullet_type in Global_Obj[1]:
for enemy_type in Global_Obj[2]:
if bullet_type.rect.colliderect(enemy_type.rect):
enemy_type.HP -= bullet_type.damage
if enemy_type.HP<=0:
Global_Obj[2].remove(enemy_type)
Global_Obj[1].remove(bullet_type)
break
# beam - enemy damage check
for bullet_type in Global_Obj[3]:
for enemy_type in Global_Obj[2]:
if bullet_type.rect.colliderect(enemy_type.rect):
enemy_type.HP -= bullet_type.damage
if enemy_type.HP<=0:
Global_Obj[2].remove(enemy_type)
#end game
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 2 and FirstPlayer.bullet_delay[2]==0: # left mouse button
beam = LaserBeamIO()
Global_Obj[3].append(beam)
beam_load = True
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 2 and beam_load:
zeta = direction(mouse_pos, FirstPlayer.rect.center)
beam.charge_beam( FirstPlayer.rect.center, zeta)
FirstPlayer.bullet_delay[2] = beam.reload_delay
beam_load = False
if beam_load:
if FirstPlayer.MANA <= beam.MANA_usage_per_sec * dt:
zeta = direction(mouse_pos, FirstPlayer.rect.center)
beam.charge_beam( FirstPlayer.rect.center, zeta)
FirstPlayer.bullet_delay[2] = beam.reload_delay
beam_load = False
else:
FirstPlayer.MANA -= beam.MANA_usage_per_sec * dt
zeta = direction(mouse_pos, FirstPlayer.rect.center)
beam.charge_beam( FirstPlayer.rect.center, zeta)
if FirstPlayer.HP <= 0:
run = False
te = int(time.time() - t0)
if te >= end_time:
run = False
# draw obj
win.blit(background,(0, 0))
for obj_types in Global_Obj:
for obj in obj_types:
obj.draw()
text = font.render(f'timer:{te}/{end_time} sec', True, white)
# create a rectangular object for the
# text surface object
textRect = text.get_rect()
# set the center of the rectangular object.
textRect.center = (window[0] // 2, window[1]-40)
win.blit(text, textRect)
pygame.quit()
Feel free to give me any suggestions to improve my code. I'm very new to Pygame.
Your enemies do move at the same speed in all directions, which can be measured by adding a few lines:
# Global_Obj[2] enemy obj type movement
for enemy_types in Global_Obj[2]:
zetas = math.dist(enemy_types.rect.center, FirstPlayer.rect.center)
x, y = enemy_types.rect.center
dx = dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
dy = dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas
x += dx
y += dy
print((dx ** 2 + dy ** 2) ** 0.5)
enemy_types.rect.center = (x, y)
The code consistently prints out 2.
I suspect that it has something to do with the amount of time it takes to evaluate negative/positive calculations, which can be measured using the time.perf_counter() method.
Tip: Your
zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)
can be replaced with
zetas = math.dist(enemy_types.rect.center, FirstPlayer.rect.center)
a much more efficient method.
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data:
The coordinates for Rect objects are all integers. [...]
When you do
enemy_types.rect.center = (x,y)
it is the same as you would do:
enemy_types.rect.center = (int(x), int(y))
The fraction component of the coordinate get lost. This causes that the movement to the left and to the top is faster than to the right and to the bottom.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:
class EnemyIO():
def __init__(self):
# [...]
self.x, self.y = self.rect.center
for enemy_types in Global_Obj[2]:
zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)
enemy_types.x += dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
enemy_types.y += dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas
enemy_types.rect.center = round(enemy_types.x), round(enemy_types.y)

Python treating all instances of an object as the same

I'm making a game with pygame and pymunk as a physics engine. I'm trying to kill a bullet whenever it hits a player or goes past its lifetime.
When I tried to space.remove(self.shape) and the second bullet hits the player, it gives me an "AssertionError: shape not in space, already removed. I simply changed it to teleport the bullets away, and then learned of the real error.
When I have more than one bullet in the space and a bullet hits the enemy player, all the current bullets teleport away, which means that when I tried to remove one bullet, it called the remove on all the bullets and thats why I had the initial error.
However the problem still remains that one bullet is being treated as every bullet.
Why is something that should be a non-static variable being called as a static variable?
I even tried to use deepcopy to see if that fixed it, but to no avail
This is my chunk of code, apologies since I don't know what is needed to understand it.
The key parts are most likely the Bullet class, the shoot() function in the Player class, and the drawBulletCollision() function
# PyGame template.
# Import modules.
import sys, random, math, time, copy
from typing import List
import pygame
from pygame.locals import *
from pygame import mixer
import pymunk
import pymunk.pygame_util
from pymunk.shapes import Segment
from pymunk.vec2d import Vec2d
pygame.mixer.pre_init(44110, -16, 2, 512)
mixer.init()
# Set up the window.
width, height = 1440, 640
screen = pygame.display.set_mode((width, height))
bg = pygame.image.load("space.png")
def draw_bg():
screen.blit(bg, (0, 0))
#load sounds
#death_fx = pygame.mixer.Sound("")
#death_fx.set_volume(0.25)
shoot_fx = mixer.Sound("shot.wav")
shoot_fx.set_volume(0.25)
#mixer.music.load("video.mp3")
#mixer.music.play()
#time.sleep(2)
#mixer.music.stop()
#gun_mode_fx = pygame.mixer.Sound("")
#gun_mode_fx.set_volume(0.25)
#thrust_mode_fx = pygame.mixer.Sound("")
#thrust_mode_fx.set_volume(0.25)
collision_fx = mixer.Sound("thump.wav")
collision_fx.set_volume(0.25)
ship_group = pygame.sprite.Group()
space = pymunk.Space()
space.gravity = 0, 0
space.damping = 0.6
draw_options = pymunk.pygame_util.DrawOptions(screen)
bulletList = []
playerList = []
environmentList = []
arbiterList = []
b0 = space.static_body
segmentBot = pymunk.Segment(b0, (0,height), (width, height), 4)
segmentTop = pymunk.Segment(b0, (0,0), (width, 0), 4)
segmentLef = pymunk.Segment(b0, (width,0), (width, height), 4)
segmentRit = pymunk.Segment(b0, (0,0), (0, height), 4)
walls = [segmentBot,segmentLef,segmentRit,segmentTop]
for i in walls:
i.elasticity = 1
i.friction = 0.5
i.color = (255,255,255,255)
environmentList.append(i)
class Player(object):
radius = 30
def __init__(self, position, space, color):
self.body = pymunk.Body(mass=5,moment=10)
self.mode = 0 # 0 is gun, 1 is thrust, ? 2 is shield
self.body.position = position
self.shape = pymunk.Circle(self.body, radius = self.radius)
#self.image
#self.shape.friction = 0.9
self.shape.elasticity= 0.2
space.add(self.body,self.shape)
self.angleGun = 0
self.angleThrust = 0
self.health = 100
self.speed = 500
self.gearAngle = 0
self.turningSpeed = 5
self.shape.body.damping = 1000
self.cooldown = 0
self.fireRate = 30
self.shape.collision_type = 1
self.shape.color = color
playerList.append(self)
def force(self,force):
self.shape.body.apply_force_at_local_point(force,(0,0))
def rocketForce(self):
radians = self.angleThrust * math.pi/180
self.shape.body.apply_force_at_local_point((-self.speed * math.cos(radians),-self.speed * math.sin(radians)),(0,0))
def draw(self):
gear = pygame.image.load("gear.png")
gearBox = gear.get_rect(center=self.shape.body.position)
gearRotated = pygame.transform.rotate(gear, self.gearAngle)
#gearRotated.rect.center=self.shape.body.position
x,y = self.shape.body.position
radianGun = self.angleGun * math.pi/180
radianThrust = self.angleThrust * math.pi/180
radiyus = 30 *(100-self.health)/100
screen.blit(gearRotated,gearBox)
self.gearAngle += 1
if radiyus == 30:
radiyus = 32
pygame.draw.circle(screen,self.shape.color,self.shape.body.position,radiyus,0)
pygame.draw.circle(screen,(0,0,0),self.shape.body.position,radiyus,0)
pygame.draw.line(
screen,(0,255,0),
(self.radius * math.cos(radianGun) * 1.5 + x,self.radius * math.sin(radianGun) * 1.5 + y),
(x,y), 5
)
pygame.draw.line(
screen,(200,200,0),
(self.radius * math.cos(radianThrust) * 1.5 + x,self.radius * math.sin(radianThrust) * 1.5 + y),
(x,y), 5
)
#more
def targetAngleGun(self,tAngle):
tempTAngle = tAngle - self.angleGun
tempTAngle = tempTAngle % 360
if(tempTAngle < 180 and not tempTAngle == 0):
self.angleGun -= self.turningSpeed
elif(tempTAngle >= 180 and not tempTAngle == 0):
self.angleGun += self.turningSpeed
self.angleGun = self.angleGun % 360
#print(tAngle, "target Angle")
#print(self.angleGun, "selfangleGun")
#print(tempTAngle, "tempTAngle")
def targetAngleThrust(self,tAngle):
tempTAngle = tAngle - self.angleThrust
tempTAngle = tempTAngle % 360
if(tempTAngle < 180 and not tempTAngle == 0):
self.angleThrust -= self.turningSpeed
elif(tempTAngle >= 180 and not tempTAngle == 0):
self.angleThrust += self.turningSpeed
self.angleThrust = self.angleThrust % 360
#print(tAngle, "target Angle")
#print(self.angleThrust, "selfangleGun")
#print(tempTAngle, "tempTAngle")
def targetAngle(self,tAngle):
if(self.mode == 0):
self.targetAngleGun(tAngle)
elif(self.mode == 1):
self.targetAngleThrust(tAngle)
def shoot(self):
if(self.cooldown == self.fireRate):
x,y = self.shape.body.position
radianGun = self.angleGun * math.pi/180
spawnSpot = (self.radius * math.cos(radianGun) * 1.5 + x,self.radius * math.sin(radianGun)*1.5+y)
self.shape.body.apply_impulse_at_local_point((-20 * math.cos(radianGun),-20 * math.sin(radianGun)),(0,0))
print(spawnSpot)
bT = Bullet(spawnSpot, 5, 50,self.shape.color)
b = copy.deepcopy(bT)
bulletList.append(b)
space.add(b.shape,b.shape.body)
b.getShot(self.angleGun)
self.cooldown = 0
print('pew')
shoot_fx.play()
# HEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEREEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
def tick(self):
self.draw()
if(self.cooldown < self.fireRate):
self.cooldown += 1
#for o in playerList:
# c = self.shape.shapes_collide(o.shape)
# if(len(c.points)>0):
# self.damage(c.points[0].distance/10)
for o in bulletList:
c = self.shape.shapes_collide(o.shape)
#print(c)
for o in walls:
c = self.shape.shapes_collide(o)
if(len(c.points)>0):
self.damage(c.points[0].distance * 3)
def damage(self, damage):
self.health -= abs(damage)
if self.health < 0:
self.health = 0
#maybe make it part of the player class
def drawWallCollision(arbiter, space, data):
for c in arbiter.contact_point_set.points:
r = max(3, abs(c.distance * 5))
r = int(r)
p = tuple(map(int, c.point_a))
pygame.draw.circle(data["surface"], pygame.Color("red"), p, r, 0)
print('magnitude', math.sqrt(arbiter.total_impulse[0]**2 + arbiter.total_impulse[1]**2))
#print('position', p)
#print(data)
print("its all arbitrary")
s1, s2 = arbiter.shapes
collision_fx.play()
def drawBulletCollision(arbiter, space, data):
s1, s2 = arbiter.shapes
for c in arbiter.contact_point_set.points:
magnitude = math.sqrt(arbiter.total_impulse[0]**2 + arbiter.total_impulse[1]**2)
for p in playerList:
avr = ((c.point_a[0] + c.point_b[0])/2, (c.point_a[1] + c.point_b[1])/2)
distance = (math.sqrt((avr[0] - p.shape.body.position[0]) **2 + (avr[1] - p.shape.body.position[1]) **2 ))
if(distance < Bullet.explosionRadius + Player.radius):
if not(s1.color == s2.color):
p.damage(magnitude)
for b in bulletList:
avr = ((c.point_a[0] + c.point_b[0])/2, (c.point_a[1] + c.point_b[1])/2)
distance = (math.sqrt((avr[0] - p.shape.body.position[0]) **2 + (avr[1] - p.shape.body.position[1]) **2 ))
if(distance < Bullet.explosionRadius + Player.radius):
if not(s1.color == s2.color):
b.damage(magnitude)
pygame.draw.circle(data["surface"], pygame.Color("red"), tuple(map(int, c.point_a)), 10, 0)
print('magnitude', magnitude)
#print('position', p)
#print(data)
print("its all arbitrary")
def drawArbitraryCollision(arbiter, space, data):
collision_fx.play()
class Ship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("gear.png")
self.rect = self.image.get_rect()
self.rect.center = [x, y]
def rotate(self):
self.image = pygame.transform.rotate(self.image,1)
class Bullet(object):
damage = 2
explosionRadius = 5
def __init__(self, position, size, speed,color):
pts = [(-size, -size), (size, -size), (size, size), (-size, size)]
self.body = copy.deepcopy(pymunk.Body(mass=0.1,moment=1))
self.shape = copy.deepcopy(pymunk.Poly(self.body, pts))
self.shape.body.position = position
self.shape.friction = 0.5
self.shape.elasticity = 1
self.shape.color = color
self.speed = speed
self.size = size
self.shape.collision_type = 2
#space.add(self.body,self.shape)
#bulletList.append(self)
self.lifetime = 0
def getShot(self,angle):
radians = angle * math.pi/180
self.shape.body.apply_impulse_at_local_point((self.speed * math.cos(radians),self.speed * math.sin(radians)),(0,0))
def tick(self):
self.lifetime += 1
if(self.lifetime > 300):
self.shape.body.position = (10000,30)
def damage(self, damage):
self.lifetime = 300
#VELOCITY OF BULLET STARTS WITH VELOCITY OF PLAYER
#MAKE VOLUME OF SOUND DEPEND ON THE IMPULSE FOR THE IMPACTS
#error on purpose so you notice this
#INSTANCES NOT WORKING????
def runPyGame():
# Initialise PyGame.
pygame.init()
# Set up the clock. This will tick every frame and thus maintain a relatively constant framerate. Hopefully.
fps = 60.0
fpsClock = pygame.time.Clock()
running = True
font = pygame.font.SysFont("Arial", 16)
p1 = Player((240,240),space,(132, 66, 245,255))
p2 = Player((1200,400),space,(47, 247, 184,255))
space.add(segmentBot,segmentTop,segmentLef,segmentRit)
# Main game loop.
ch = space.add_collision_handler(1, 0)
ch.data["surface"] = screen
ch.post_solve = drawWallCollision
ch = space.add_collision_handler(1, 2)
ch.data["surface"] = screen
ch.post_solve = drawBulletCollision
ch = space.add_collision_handler(0, 2)
ch.data["surface"] = screen
ch.post_solve = drawArbitraryCollision
dt = 1/fps # dt is the time since last frame.
while True: # Loop forever!
keys = pygame.key.get_pressed()
for event in pygame.event.get():
# We need to handle these events. Initially the only one you'll want to care
# about is the QUIT event, because if you don't handle it, your game will crash
# whenever someone tries to exit.
if event.type == QUIT:
pygame.quit() # Opposite of pygame.init
sys.exit() # Not including this line crashes the script on Windows.
if event.type == KEYDOWN:
if event.key == pygame.K_s:
p1.mode = -(p1.mode - 0.5) + 0.5
print(p1.mode)
if (event.key == pygame.K_k and p1.mode == 0):
p1.shoot()
if event.key == pygame.K_KP_5:
p2.mode = -(p2.mode - 0.5) + 0.5
print(p2.mode)
if (event.key == pygame.K_m and p2.mode == 0):
p2.shoot()
#b = Bullet((200,200),51,51)
if(keys[K_w]):
p1.targetAngle(90)
if(keys[K_q]):
p1.targetAngle(45)
if(keys[K_a]):
p1.targetAngle(0)
if(keys[K_z]):
p1.targetAngle(315)
if(keys[K_x]):
p1.targetAngle(270)
if(keys[K_c]):
p1.targetAngle(225)
if(keys[K_d]):
p1.targetAngle(180)
if(keys[K_e]):
p1.targetAngle(135)
if(keys[K_k] and p1.mode == 1):
p1.rocketForce()
if(keys[K_KP_8]):
p2.targetAngle(90)
if(keys[K_KP_7]):
p2.targetAngle(45)
if(keys[K_KP_4]):
p2.targetAngle(0)
if(keys[K_KP_1]):
p2.targetAngle(315)
if(keys[K_KP_2]):
p2.targetAngle(270)
if(keys[K_KP_3]):
p2.targetAngle(225)
if(keys[K_KP_6]):
p2.targetAngle(180)
if(keys[K_KP_9]):
p2.targetAngle(135)
if(keys[K_m] and p2.mode == 1):
p2.rocketForce()
# Handle other events as you wish.
screen.fill((250, 250, 250)) # Fill the screen with black.
# Redraw screen here.
### Draw stuff
draw_bg()
space.debug_draw(draw_options)
for i in playerList:
i.tick()
screen.blit(
font.render("P1 Health: " + str(p1.health), True, pygame.Color("white")),
(50, 10),
)
screen.blit(
font.render("P2 Health: " + str(p2.health), True, pygame.Color("white")),
(50, 30),
)
for i in bulletList:
i.tick()
ship_group.draw(screen)
# Flip the display so that the things we drew actually show up.
pygame.display.update()
dt = fpsClock.tick(fps)
space.step(0.01)
pygame.display.update()
runPyGame()
I cant point to the exact error since the code is quite long and depends on files I dont have. But here is a general advice for troubleshooting:
Try to give a name to each shape when you create them, and then print it out. Also print out the name of each shape that you add or remove from the space. This should show which shape you are actually removing and will probably make it easy to understand whats wrong.
For example:
...
self.shape = pymunk.Circle(self.body, radius = self.radius)
self.shape.name = "circle 1"
print("Created", self.shape.name)
...
print("Adding", self.shape.name)
space.add(self.body,self.shape)
...
(Note that you need to reset the name of shapes you copy, since otherwise the copy will have the same name.)

Python/ Pygame : 2d angular momentum / inertia

my first ever Python program has hit a block I don't think I have the knowledge to solve myself.
It's a controllable spaceship on a 2d surface, I want to add momentum / inertia
I have it so the ship keeps travelling on the vector it previously was, when the engine is stopped.
However I can only get it to 'snap' to the new vector it rotates to face instantly.
What I want to happen is that inertia vector slowly aligns with the new pointing vector as it accelerates- like rotational acceleration? ( I'm not too hot on the math ) - I can rotate the inertia vector , but I would need to compare it somehow with the new pointing vector , and modify it based upon their difference?
if anyone could advise as to how I might start to approach this, that would be great - I suspect I coming at this from completely the wrong way.
Heres some of the code ( be gentle please!)
the sprite used is this : - ship.png
import pygame
import sys
from math import sin, cos, pi, atan2
from pygame.locals import *
import random
from random import randint
from pygame.math import Vector2
import operator
"""solar system generator"""
"""set screen size and center and some global namespace colors for ease of use"""
globalalpha = 255
screenx = int(1200)
screeny = int(700)
centerx = int(screenx / 2)
centery = int(screeny / 2)
center = (centerx, centery)
black = ( 0, 0, 0)
white = (255, 255, 255)
red = (209, 2, 22)
TRANSPARENT = (255,0,255)
numstars = 150
DISPLAYSURF = pygame.display.set_mode((screenx, screeny), 0, 32)
clock = pygame.time.Clock()
globaltimefactor = 1
shipimage = pygame.image.load('ship.png').convert()
DISPLAYSURF.fill(black)
screen_rect = DISPLAYSURF.get_rect()
class Playership(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.imageorig = pygame.image.load('ship.png').convert_alpha()
self.startpos = (screen_rect.center)
self.image = self.imageorig.copy()
self.rect = self.imageorig.get_rect(center=self.startpos)
self.angle = 0
self.currentposx = 600
self.currentposy = 350
self.tuplepos = (self.currentposx, self.currentposy)
self.speed = 1
self.rotatespeed = 1.5
self.initialvec = (600, 0)
self.destination = 0
self.anglechange = 0
self.currentspeed = 0
self.maxspeed = 5
self.engineon = False
self.newvec = (600, 0)
self.newdestination = 0
self.acceleration = 0.015
self.inertiaspeed = 0
self.transitionalvec = self.initialvec
def get_angleafterstopping(self):
newvec = self.initialvec
self.newvec = newvec
def get_destinationafterstopping(self):
x_dist = self.newvec[0] - self.tuplepos[0]
y_dist = self.newvec[1] - self.tuplepos[1]
self.newdestination = atan2(-y_dist, x_dist) % (2 * pi)
def get_destination(self):
x_dist = self.initialvec[0] - self.tuplepos[0]
y_dist = self.initialvec[1] - self.tuplepos[1]
self.destination = atan2(-y_dist, x_dist) % (2 * pi)
def moveship(self):
if self.engineon is True:
self.currentspeed = self.currentspeed + self.acceleration
if self.currentspeed > self.maxspeed:
self.currentspeed = self.maxspeed
elif self.currentspeed < 0:
self.currentspeed = 0
self.inertiaspeed = self.currentspeed
elif self.engineon is False:
self.currentposx = self.currentposx + (cos(self.newdestination) * self.inertiaspeed * globaltimefactor)
self.currentposy = self.currentposy - (sin(self.newdestination) * self.inertiaspeed * globaltimefactor)
self.tuplepos = (self.currentposx, self.currentposy)
self.rect.center = self.tuplepos
return
self.get_destination()
self.currentposx = self.currentposx + (cos(self.destination) * self.currentspeed * globaltimefactor)
self.currentposy = self.currentposy - (sin(self.destination) * self.currentspeed * globaltimefactor)
self.tuplepos = (self.currentposx, self.currentposy)
self.rect.center = self.tuplepos
def rotateship(self, rotation):
self.anglechange = self.anglechange - (rotation * self.rotatespeed * globaltimefactor)
self.angle += (rotation * self.rotatespeed * globaltimefactor)
self.image = pygame.transform.rotate(self.imageorig, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
initialvec = self.tuplepos + Vector2(0, -600).rotate(self.anglechange * globaltimefactor)
initialvec = int(initialvec.x), int(initialvec.y)
self.initialvec = initialvec
myship = Playership()
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(myship)
firsttimedone = False
def main():
done = False
while not done:
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_LEFT]:
myship.rotateship(1)
if keys_pressed[pygame.K_RIGHT]:
myship.rotateship(-1)
if keys_pressed[pygame.K_UP]:
myship.engineon = True
myship.moveship()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit();
if event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
myship.engineon = False
myship.currentspeed = 0
myship.get_angleafterstopping()
myship.get_destinationafterstopping()
DISPLAYSURF.fill(black)
all_sprites_list.update()
all_sprites_list.draw(DISPLAYSURF)
pygame.draw.line(DISPLAYSURF, white, (myship.tuplepos), (myship.initialvec))
pygame.draw.line(DISPLAYSURF, red, (myship.tuplepos), (myship.newvec))
pygame.display.flip()
if myship.engineon is False:
myship.moveship()
clock.tick(50)
pygame.display.set_caption("fps: " + str(clock.get_fps()))
if __name__ == '__main__':
pygame.init()
main()
pygame.quit(); sys.exit();
EDIT :
I fixed it : just required a better understanding of vectors
ship starts off with acceleration and velocity both stated as vectors.
self.position = vec(screenx / 2, screeny / 2)
self.vel = vec(0, 0)
self.acceleration = vec(0, -0.2) # The acceleration vec points upwards from the starting ship position
rotating the ship rotates that vector in place
self.acceleration.rotate_ip(self.angle_speed)
self.angle += self.angle_speed
self.image = pygame.transform.rotate(self.imageorig, -self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
accelerating is this :
self.vel += self.acceleration * self.enginepower * globaltimefactor
updating position :
self.position += self.vel
self.rect.center = self.position
I was making it harder than it needed to be, velocity needed to be constant until acted upon by the rotated acceleration vector. I didn't know how to add vectors together etc.
I fixed it : just required a better understanding of vectors
ship starts off with acceleration and velocity both stated as vectors.
self.position = vec(screenx / 2, screeny / 2)
self.vel = vec(0, 0)
self.acceleration = vec(0, -0.2) # The acceleration vec points upwards from the starting ship position
rotating the ship rotates that vector in place
self.acceleration.rotate_ip(self.angle_speed)
self.angle += self.angle_speed
self.image = pygame.transform.rotate(self.imageorig, -self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
accelerating is this :
self.vel += self.acceleration * self.enginepower * globaltimefactor
updating position :
self.position += self.vel
self.rect.center = self.position
I was making it harder than it needed to be, velocity needed to be constant until acted upon by the rotated acceleration vector. I didn't know how to add vectors together etc.

Lennard-Jones potential simulation

import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
number_of_particles = 70
my_particles = []
background_colour = (255,255,255)
width, height = 500, 500
sigma = 1
e = 1
dt = 0.1
v = 0
a = 0
r = 1
def r(p1,p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
angle = 0.5 * math.pi - math.atan2(dy, dx)
dist = np.hypot(dx, dy)
return dist
def collide(p1, p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
dist = np.hypot(dx, dy)
if dist < (p1.size + p2.size):
tangent = math.atan2(dy, dx)
angle = 0.5 * np.pi + tangent
angle1 = 2*tangent - p1.angle
angle2 = 2*tangent - p2.angle
speed1 = p2.speed
speed2 = p1.speed
(p1.angle, p1.speed) = (angle1, speed1)
(p2.angle, p2.speed) = (angle2, speed2)
overlap = 0.5*(p1.size + p2.size - dist+1)
p1.x += np.sin(angle) * overlap
p1.y -= np.cos(angle) * overlap
p2.x -= np.sin(angle) * overlap
p2.y += np.cos(angle) * overlap
def LJ(r):
return -24*e*((2/r*(sigma/r)**12)-1/r*(sigma/r)**6)
def verlet():
a1 = -LJ(r(p1,p2))
r = r + dt*v+0.5*dt**2*a1
a2 = -LJ(r(p1,p2))
v = v + 0.5*dt*(a1+a2)
return r, v
class Particle():
def __init__(self, (x, y), size):
self.x = x
self.y = y
self.size = size
self.colour = (0, 0, 255)
self.thickness = 1
self.speed = 0
self.angle = 0
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
self.x += np.sin(self.angle)
self.y -= np.cos(self.angle)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.angle = - self.angle
elif self.x < self.size:
self.x = 2*self.size - self.x
self.angle = - self.angle
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.angle = np.pi - self.angle
elif self.y < self.size:
self.y = 2*self.size - self.y
self.angle = np.pi - self.angle
screen = pygame.display.set_mode((width, height))
for n in range(number_of_particles):
x = random.randint(15, width-15)
y = random.randint(15, height-15)
particle = Particle((x, y), 15)
particle.speed = random.random()
particle.angle = random.uniform(0, np.pi*2)
my_particles.append(particle)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background_colour)
for i, particle in enumerate(my_particles):
particle.move()
particle.bounce()
for particle2 in my_particles[i+1:]:
collide(particle, particle2)
particle.display()
pygame.display.flip()
pygame.quit()
I wanted to simulate particles by Lennard-Jones potential. My problem with this code is that I do not know how to use the Verlet algorithm.
I do not know where I should implement the Verlet algorithm; inside the class or outside?
How can I use velocity from the Verlet algorithm in the move method?
Is my implementation of the Verlet algorithm correct, or should I use arrays for saving results?
What else should I change to make it work?
You can keep the dynamical variables, position and velocity, inside the class instances, however then each class needs an acceleration vector to accumulate the force contributions. The Verlet integrator has the role of a controller, it acts from outside on the collection of all particles. Keep the angle out of the computations, the forth and back with trigonometric functions and their inverses is not necessary. Make position, velocity and acceleration all 2D vectors.
One way to implement the velocity Verlet variant is (see https://stackoverflow.com/tags/verlet-integration/info)
verlet_step:
v += a*0.5*dt;
x += v*dt; t += dt;
do_collisions(t,x,v,dt);
a = eval_a(x);
v += a*0.5*dt;
do_statistics(t,x,v);
which supposes a vectorized variant. In your framework, there would be some iterations over the particle collection to include,
verlet_step:
for p in particles:
p.v += p.a*0.5*dt; p.x += p.v*dt;
t += dt;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
collide(p1,p2);
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
apply_LJ_forces(p1,p2);
for p in particles:
p.v += p.a*0.5*dt;
do_statistics(t,x,v);
No, you could not have done nothing wrong since you did not actually call the Verlet function to update position and velocity. And no, a strict vectorization is not necessary, see above. The implicit vectorization via the particles array is sufficient. You would only need a full vectorization if you wanted to compare with the results of a standard integrator like those in scipy.integrate using the same model to provide the ODE function.
Code with some add-ons but without collisions, desingularized potential
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
background_colour = (255,255,255)
width, height = 500, 500
aafac = 2 # anti-aliasing factor screen to off-screen image
number_of_particles = 50
my_particles = []
sigma = 10
sigma2 = sigma*sigma
e = 5
dt = 0.1 # simulation time interval between frames
timesteps = 10 # intermediate invisible steps of length dt/timesteps
def LJ_force(p1,p2):
rx = p1.x - p2.x
ry = p1.y - p2.y
r2 = rx*rx+ry*ry
r2s = r2/sigma2+1
r6s = r2s*r2s*r2s
f = 24*e*( 2/(r6s*r6s) - 1/(r6s) )
p1.ax += f*(rx/r2)
p1.ay += f*(ry/r2)
p2.ax -= f*(rx/r2)
p2.ay -= f*(ry/r2)
def Verlet_step(particles, h):
for p in particles:
p.verlet1_update_vx(h);
p.bounce()
#t += h;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
LJ_force(p1,p2);
for p in particles:
p.verlet2_update_v(h);
class Particle():
def __init__(self, (x, y), (vx,vy), size):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.size = size
self.colour = (0, 0, 255)
self.thickness = 2
self.ax = 0
self.ay = 0
def verlet1_update_vx(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
self.x += self.vx*h
self.y += self.vy*h
self.ax = 0
self.ay = 0
def verlet2_update_v(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
def display(self,screen, aa):
pygame.draw.circle(screen, self.colour, (int(aa*self.x+0.5), int(aa*self.y+0.5)), aa*self.size, aa*self.thickness)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.vx = - self.vx
elif self.x < self.size:
self.x = 2*self.size - self.x
self.vx = - self.vx
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.vy = - self.vy
elif self.y < self.size:
self.y = 2*self.size - self.y
self.vy = - self.vy
#------------ end class particle ------------
#------------ start main program ------------
for n in range(number_of_particles):
x = 1.0*random.randint(15, width-15)
y = 1.0*random.randint(15, height-15)
vx, vy = 0., 0.
for k in range(6):
vx += random.randint(-10, 10)/2.
vy += random.randint(-10, 10)/2.
particle = Particle((x, y),(vx,vy), 10)
my_particles.append(particle)
#--------- pygame event loop ----------
screen = pygame.display.set_mode((width, height))
offscreen = pygame.Surface((aafac*width, aafac*height))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
offscreen.fill(background_colour)
for k in range(timesteps):
Verlet_step(my_particles, dt/timesteps)
for particle in my_particles:
particle.display(offscreen, aafac)
pygame.transform.smoothscale(offscreen, (width,height), screen)
pygame.display.flip()
pygame.quit()

Categories

Resources