pygame bullet physics messed up by scrolling - python

the code below is the bullet class for my shooter game in pygame. as you can see if you run the full game (https://github.com/hailfire006/economy_game/blob/master/shooter_game.py) the code works great to fire bullets at the cursor as long as the player isn't moving. However, I recently added scrolling, where I change a global offsetx and offsety every time the player gets close to an edge. These offsets are then used to draw each object in their respective draw functions.
unfortunately, my bullet physics in the bullet's init function no longer work as soon as the player scrolls and the offsets are added. Why are the offsets messing up my math and how can I change the code to get the bullets to fire in the right direction?
class Bullet:
def __init__(self,mouse,player):
self.exists = True
centerx = (player.x + player.width/2)
centery = (player.y + player.height/2)
self.x = centerx
self.y = centery
self.launch_point = (self.x,self.y)
self.width = 20
self.height = 20
self.name = "bullet"
self.speed = 5
self.rect = None
self.mouse = mouse
self.dx,self.dy = self.mouse
distance = [self.dx - self.x, self.dy - self.y]
norm = math.sqrt(distance[0] ** 2 + distance[1] ** 2)
direction = [distance[0] / norm, distance[1] / norm]
self.bullet_vector = [direction[0] * self.speed, direction[1] * self.speed]
def move(self):
self.x += self.bullet_vector[0]
self.y += self.bullet_vector[1]
def draw(self):
make_bullet_trail(self,self.launch_point)
self.rect = pygame.Rect((self.x + offsetx,self.y + offsety),(self.width,self.height))
pygame.draw.rect(screen,(255,0,40),self.rect)

You don't take the offset into account when calculating the angle between the player and the mouse. You can fix this by changing the distance like this:
distance = [self.dx - self.x - offsetx, self.dy - self.y - offsety]

Related

How to calculate distance from a player to a dynamic collision point

I'm trying to create sensors for a car to keep track of the distances from the car to the borders of the track. My goal is to have 5 sensors (see image below) and use them to train a machine learning algorithm.
But I can't figure out a way to calculate these distances. For now, I just need a sample of code and a logical explanation of how to implement this with PyGame. But a mathematical and geometrical explanation would be really nice as well for further reading. I'm using this code from a YouTuber tutorial series.
My biggest issue is how to get the points in blue. (last picture) I need them to create the red lines from the car to the points and to calculate the length of these lines. These points are taking the car's position and rotation into account and they have a specific angle at which they get out of the car. I've managed to create the lines, but could not get the point the line would collide with the track.
What I want to accomplish:
I've tried different approaches to this problem, but for now, my biggest problem is how to get the position of the blue dots:
--- Edit from the feedback ------
I added a new paragraph to better explain the problem. This way I hope it is clearer why this problem is different from those said to be related to it. The other problem we have the desired final position (mouse or enemy) in this one we have to figure out which point is the one we are going to use to create the line, and this is my issue.
My GitHub repo of the project
https://github.com/pedromello/ml-pygame/blob/main/main.py
The part of the code where I'm trying to implement this:
class AbstractCar:
def __init__(self, max_vel, rotation_vel):
self.img = self.IMG
self.max_vel = max_vel
self.vel = 0
self.rotation_vel = rotation_vel
self.angle = 0
self.x, self.y = self.START_POS
self.acceleration = 0.1
def rotate(self, left=False, right=False):
if left:
self.angle += self.rotation_vel
elif right:
self.angle -= self.rotation_vel
def draw(self, win):
blit_rotate_center(win, self.img, (self.x, self.y), self.angle)
def move_forward(self):
self.vel = min(self.vel + self.acceleration, self.max_vel)
self.move()
def move_backward(self):
self.vel = max(self.vel - self.acceleration, -self.max_vel/2)
self.move()
def move(self):
radians = math.radians(self.angle)
vertical = math.cos(radians) * self.vel
horizontal = math.sin(radians) * self.vel
self.y -= vertical
self.x -= horizontal
def collide(self, mask, x=0, y=0):
car_mask = pygame.mask.from_surface(self.img)
offset = (int(self.x - x), int(self.y - y))
poi = mask.overlap(car_mask, offset)
return poi
def reset(self):
self.x, self.y = self.START_POS
self.angle = 0
self.vel = 0
class PlayerCar(AbstractCar):
IMG = RED_CAR
START_POS = (180, 200)
def reduce_speed(self):
self.vel = max(self.vel - self.acceleration / 2, 0)
self.move()
def bounce(self):
self.vel = -self.vel
self.move()
def drawSensors(self):
radians = math.radians(self.angle)
vertical = -math.cos(radians)
horizontal = math.sin(radians)
car_center = pygame.math.Vector2(self.x + CAR_WIDTH/2, self.y + CAR_HEIGHT/2)
pivot_sensor = pygame.math.Vector2(car_center.x + horizontal * -100, car_center.y - vertical * -100)
#sensor1 = Vector(30, 0).rotate(self.angle) #+ self.pos # atualiza a posição do sensor 1
#sensor2 = Vector(30, 0).rotate((self.angle+30)%360) #+ self.pos # atualiza a posição do sensor 2
#sensor3 = Vector(30, 0).rotate((self.angle-30)%360) #+ self.pos # atualiza a posição do sensor 3
#rotate pivot sensor around car center
sensor_2 = pivot_sensor.rotate((self.angle+30)%360)
# Sensor 1
pygame.draw.line(WIN, (255, 0, 0), car_center, pivot_sensor, 2)
# Sensor 2
pygame.draw.line(WIN, (255, 0, 0), car_center, sensor_2, 2)
# Sensor 3
#pygame.draw.line(WIN, (255, 0, 0), (self.x, self.y), (self.x + horizontal * 100, self.y - vertical * 100), 2)
Thank you for the comments, I solved my problem using the idea of firing sensors so I can get the point on the wall when the "bullet" hits it.
As we can see when the bullet hits the wall we can create a line that connects the point to the car. This is not the best solution, as it takes time for the bullet to hit the wall and in the meantime, the car is "blind".
As Rabbid76 commented, using raycasting may be the solution I was looking for.
Code for reference:
Sensor Bullet class
class SensorBullet:
def __init__(self, car, base_angle, vel, color):
self.x = car.x + CAR_WIDTH/2
self.y = car.y + CAR_HEIGHT/2
self.angle = car.angle
self.base_angle = base_angle
self.vel = vel
self.color = color
self.img = pygame.Surface((4, 4))
self.fired = False
self.hit = False
self.last_poi = None
def draw(self, win):
pygame.draw.circle(win, self.color, (self.x, self.y), 2)
def fire(self, car):
self.angle = car.angle + self.base_angle
self.x = car.x + CAR_WIDTH/2
self.y = car.y + CAR_HEIGHT/2
self.fired = True
self.hit = False
def move(self):
if(self.fired):
radians = math.radians(self.angle)
vertical = math.cos(radians) * self.vel
horizontal = math.sin(radians) * self.vel
self.y -= vertical
self.x -= horizontal
def collide(self, x=0, y=0):
bullet_mask = pygame.mask.from_surface(self.img)
offset = (int(self.x - x), int(self.y - y))
poi = TRACK_BORDER_MASK.overlap(bullet_mask, offset)
if poi:
self.fired = False
self.hit = True
self.last_poi = poi
return poi
def draw_line(self, win, car):
if self.hit:
pygame.draw.line(win, self.color, (car.x + CAR_WIDTH/2, car.y + CAR_HEIGHT/2), (self.x, self.y), 1)
pygame.display.update()
def get_distance_from_poi(self, car):
if self.last_poi is None:
return -1
return math.sqrt((car.x - self.last_poi[0])**2 + (car.y - self.last_poi[1])**2)
Methods the car must perform to use the sensor
# Inside car's __init__ method
self.sensors = [SensorBullet(self, 25, 12, (100, 0, 255)), SensorBullet(self, 10, 12, (200, 0, 255)), SensorBullet(self, 0, 12, (0, 255, 0)), SensorBullet(self, -10, 12, (0, 0, 255)), SensorBullet(self, -25, 12, (0, 0, 255))]
# ------
# Cars methods
def fireSensors(self):
for bullet in self.sensors:
bullet.fire(self)
def sensorControl(self):
#print(contains(self.sensors, lambda x: x.hit))
for bullet in self.sensors:
if not bullet.fired:
bullet.fire(self)
for bullet in self.sensors:
bullet.move()
def get_distance_array(self):
return [bullet.get_distance_from_poi(self) for bullet in self.sensors]

Is there any way I can make my code easier to understand and simpler? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
I am wondering if by any chance there is a way to optimise this portion of my code. Regarding the pathing system and the redraw section. I find it really difficult to follow through it. Even changing the whole thing into a more understanding format will suit me. Thank you very much.
class wolf(object): #creating the wolf class
wolf_right = ['pics/WR.png'] + ['pics/WR' + str(i) + '.png' for i in range(2, 18)] #identifies the right wolf pics
wolf_left = ['pics/WL.png'] + ['pics/WL' + str(i) + '.png' for i in range(2, 18)] #identifies the left wolf pics
run_right = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_right] #loads the right wolf pics
run_left = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_left] #load the left wolf pics
def __init__(self, x, y, width, height, finish): #initialising the object wolf
self.x = x #x coord of wolf
self.y = y #y coord of wolf
self.width = width #width of wolf
self.height = height #height of wolf
self.path = [x, finish] # This part here determines the movement limits of the wolf, back and forth
self.run_distance = 0 #wolf is initially did not move
self.velocity = 9 #speed of wolf moving
self.collision_box = (self.x + 60, self.y, 280, 160)#the arguments inside are the coordinates designating the sides of the box
#and then the width and the height of the box
def pathing(self): #pathing system
if self.velocity > 0: # If wolf moving to the right
if self.x < self.path[1] + self.velocity: #ensures that it keeps moving if wolf is not at finish
self.x += self.velocity #allows wolf to move
else: #if the finish is reached then go backwards
self.velocity = self.velocity * -1 #where velocity goes negative
#according to displacement, a particle with -ve velocity goes backwards
self.x += self.velocity #allows wolf to move
else: # If wold is going to the left
if self.x > self.path[0] - self.velocity: #ensures that it keeps moving if the wolf is not at finish
self.x += self.velocity #allows wolf to move
else: #if the finish is reached then go backwards
self.velocity = self.velocity * -1 #where velocity goes negative
#according to displacement, a particle with -ve velocity goes backwards
self.x += self.velocity #allows wolf to move
def got_hit(self): #function if the wolf takes damage from the ninja
print("Congrats, you have hit the wolf!")
def redraw(self, win):#just like for the ninja we do the same steps
self.pathing()
if self.run_distance + 1 >= 51: #This time I am running 17 sprites thus, 17 * 3 (where 3 sprites per second)
self.run_distance = 0
if self.velocity < 0: #if velocity is increasing meaning movement, links left images with left movement
win.blit(self.run_left[self.run_distance//3], (self.x,self.y))
self.run_distance += 1
else: #else linking right images with right movement
win.blit(self.run_right[self.run_distance//3], (self.x,self.y))
self.run_distance += 1
#pg.draw.rect(win, (0,200,0), self.collision_box,2) #this will draw a green box around the wolf of lines thickness 2
self.collision_box = (self.x + 50 , self.y, 200, 150) # ensures the box is drawn and is updated alongside motion
The code is not that complicated, but that's my opinion. I read it once through and it was easy to follow. Just one thing, self.x += self.velocity is done at the end of each of the 4 cases in pathing. It is sufficient to do it once at the end of pathing, instead of separately in each case. Something similar can be done for self.run_distance += 1 in redraw:
class wolf(object): #creating the wolf class
# [...]
def pathing(self): #pathing system
if self.velocity > 0 and self.x >= self.path[1] + self.velocity or \
self.velocity < 0 and self.x <= self.path[0] - self.velocity:
self.velocity = self.velocity * -1
self.x += self.velocity
# [...]
def redraw(self, win):#just like for the ninja we do the same steps
self.pathing()
if self.run_distance + 1 >= 51: #This time I am running 17 sprites thus, 17 * 3 (where 3 sprites per second)
self.run_distance = 0
run_surf = self.run_left if self.velocity < 0 else self.run_right
win.blit(run_surf[self.run_distance//3], (self.x,self.y))
self.run_distance += 1
#pg.draw.rect(win, (0,200,0), self.collision_box,2) #this will draw a green box around the wolf of lines thickness 2
self.collision_box = (self.x + 50 , self.y, 200, 150) # ensures the box is drawn and is updated alongside motion
Anyway in pygame it is intended to use pygame.Rect, pygame.sprite.Sprite and pygame.sprite.Group.
Each Sprite should have the attributes .rect and .image and the method update(). The Sprites should be contained in Groups. The Groups can be drawn (draw()) and updated (update()).
That makes the code easy to read, short, comprehensible and extendable. e.g.:
(Class Names should normally use the CapWords convention.)
class Wolf(pygame.sprite.Sprite):
wolf_right = ['pics/WR.png'] + ['pics/WR' + str(i) + '.png' for i in range(2, 18)] #identifies the right wolf pics
wolf_left = ['pics/WL.png'] + ['pics/WL' + str(i) + '.png' for i in range(2, 18)] #identifies the left wolf pics
run_right = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_right] #loads the right wolf pics
run_left = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_left] #load the left wolf pics
def __init__(self, x, y, finish):
super().__init__():
self.image = run_left[0]
self.rect = pygame.Rect(x, y, 280, 160)
self.path = [x, finish] # This part here determines the movement limits of the wolf, back and forth
self.run_distance = 0 #wolf is initially did not move
self.velocity = 9 #speed of wolf moving
self.collision_box = (self.rect.x + 60, self.rect.y, 280, 160) #the arguments inside are the coordinates designating the sides of the box
#and then the width and the height of the box
def update(self, angle):
if self.velocity > 0 and self.rect.x >= self.path[1] + self.velocity or \
self.velocity < 0 and self.rect.x <= self.path[0] - self.velocity:
self.velocity = self.velocity * -1
self.rect.x += self.velocity
if self.run_distance + 1 >= 51: #This time I am running 17 sprites thus, 17 * 3 (where 3 sprites per second)
self.run_distance = 0
run_surf = self.run_left if self.velocity < 0 else self.run_right
if self.run_distance//3 > len(run_surf):
self.run_distance = 0
self.image = run_surf[self.run_distance//3]
self.run_distance += 1
self.collision_box = (self.rect.x + 50 , self.rect.y, 200, 150) # ensures the box is drawn and is updated alongside motion
wolf = Wolf(........)
all_sprites = pygame.sprite.Group()
all_sprites.add(wolf)
while True:
# [...]
all_sprites.update(win)
# [...]
all_sprites.draw(win)
pygame.display.flip()

Generate enemies around the player from all directions randomly

The enemy are being generated from above the screen and then move toward player in the middle, I want to generate enemies randomly around the screen from all directions but not inside the screen directly and proceed to move towards the player and also enemy sprites are sometimes joining combining and moving together how to repel the enemy sprites.
I have tried changing x,y coordinates of enemy objects using a random range but sometimes they generate objects inside the play screen, I want enemies to generate outside the playing window.
class Mob(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load('enemy.png').convert_alpha()
self.image = pg.transform.smoothscale(pg.image.load('enemy.png'), (33, 33))
self.image_orig = self.image.copy()
self.radius = int(29 * .80 / 2)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speed = 4
self.rot = 0
self.rot_speed = 5
self.last_update = pg.time.get_ticks()
def rotate(self):
now = pg.time.get_ticks()
if now - self.last_update > 50:
self.last_update = now
self.rot = (self.rot + self.rot_speed) % 360
new_image = pg.transform.rotozoom(self.image_orig, self.rot, 1)
old_center = self.rect.center
self.image = new_image
self.rect = self.image.get_rect()
self.rect.center = old_center
def update(self):
self.rotate()
dirvect = pg.math.Vector2(rotator.rect.x - self.rect.x,
rotator.rect.y- self.rect.y)
if dirvect.length_squared() > 0:
dirvect = dirvect.normalize()
# Move along this normalized vector towards the player at current speed.
if dirvect.length_squared() > 0:
dirvect.scale_to_length(self.speed)
self.rect.move_ip(dirvect)
if self.rect.top > height + 10 or self.rect.left < -25 or self.rect.right > width + 20:
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speed = random.randrange(1, 4)
[UPDATE]
This the remaining code:
import math
import random
import os
import pygame as pg
import sys
pg.init()
height = 650
width = 1200
os_x = 100
os_y = 45
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (os_x, os_y)
screen = pg.display.set_mode((width, height), pg.NOFRAME)
screen_rect = screen.get_rect()
background = pg.image.load('background.png').convert()
background = pg.transform.smoothscale(pg.image.load('background.png'), (width, height))
clock = pg.time.Clock()
running = True
font_name = pg.font.match_font('Bahnschrift', bold=True)
def draw_text(surf, text, size, x, y, color):
[...]
class Mob(pg.sprite.Sprite):
[...]
class Rotator(pg.sprite.Sprite):
def __init__(self, screen_rect):
pg.sprite.Sprite.__init__(self)
self.screen_rect = screen_rect
self.master_image = pg.image.load('spaceship.png').convert_alpha()
self.master_image = pg.transform.smoothscale(pg.image.load('spaceship.png'), (33, 33))
self.radius = 12
self.image = self.master_image.copy()
self.rect = self.image.get_rect(center=[width / 2, height / 2])
self.delay = 10
self.timer = 0.0
self.angle = 0
self.distance = 0
self.angle_offset = 0
def get_angle(self):
mouse = pg.mouse.get_pos()
offset = (self.rect.centerx - mouse[0], self.rect.centery - mouse[1])
self.angle = math.degrees(math.atan2(*offset)) - self.angle_offset
old_center = self.rect.center
self.image = pg.transform.rotozoom(self.master_image, self.angle, 1)
self.rect = self.image.get_rect(center=old_center)
self.distance = math.sqrt((offset[0] * offset[0]) + (offset[1] * offset[1]))
def update(self):
self.get_angle()
self.display = 'angle:{:.2f} distance:{:.2f}'.format(self.angle, self.distance)
self.dx = 1
self.dy = 1
self.rect.clamp_ip(self.screen_rect)
def draw(self, surf):
surf.blit(self.image, self.rect)
def shoot(self, mousepos):
dx = mousepos[0] - self.rect.centerx
dy = mousepos[1] - self.rect.centery
if abs(dx) > 0 or abs(dy) > 0:
bullet = Bullet(self.rect.centerx, self.rect.centery, dx, dy)
all_sprites.add(bullet)
bullets.add(bullet)
There's not much informations to go by here, but you probably need to check the x and y range your play window has and make sure the random spawn coordinates you generate are outside of it:
In your init:
# These are just example min/max values. Maybe pass these as arguments to your __init__ method.
min_x = min_y = -1000
max_x = max_y = 1000
min_playwindow_x = min_playwindow_y = 500
max_playwindow_x = max_playwindow_y = 600
self.x = (random.randrange(min_x, min_playwindow_x), random.randrange(max_playwindow_x, max_x))[random.randrange(0,2)]
self.y = (random.randrange(min_y, min_playwindow_y), random.randrange(max_playwindow_y, max_y))[random.randrange(0,2)]
This solution should work in basically any setup. For x and y it generates a tuple of values outside the playing window. Then a coinflip decides on the value. This will only spawn mobs that are diagonally outside the playing field, but it will always generate valid random coordinates.
Another approach would be just generating as many random variables as needed to get a valid pair like this:
while min_playingwindow_x <= self.x <= max_playingwindow_x and
min_playingwindow_y <= self.y <= max_playingwindow_y:
# While within screen(undesired) calculate new random positions
self.x = random.randrange(min_x, max_x)
self.y = random.randrange(min_y, max_y)
This can be really slow however if your valid amount of positions is (for example) only 1% of the total positions.
IF you need something really fleshed out, you need to know the corners of both your map and the rectangle that is actually displayed, which is I assume smaller than the entire map(otherwise you cannot spawn enemies outside your view.
(0,0)
+----------------------+
| A |
|-----+-----------+----|
| D | W | B |
|-----+-----------+----|
| C |
+----------------------+(max_x, max_y)
In this diagram W is the window that is acutally visible to the player, and A,B,C,D together are the part of your map that is not currently visible. Since you only want to spawn mobs outside the player's view, you'll need to make sure that the coordinates you generate are inside your map and outside your view:
def generate_coordinates_outside_of_view(map_width=1000, map_height=1000, view_window_top_left=(100, 100),
view_width=600, view_height=400):
"""
A very over the top way to generate coordinates outside surrounding a rectangle within a map almost without bias
:param map_width: width of map in pixels (note that 0,0 on the map is top left)
:param map_height: height of map in pixels
:param view_window_top_left: top left point(2-tuple of ints) of visible part of map
:param view_width: width of view in pixels
:param view_height: height of view in pixels
"""
from random import randrange
# generate 2 samples for each x and y, one guaranteed to be random, and one outside the view for sure.
x = (randrange(0, map_width), (randrange(0, view_window_top_left[0]),
randrange(view_window_top_left[0] + view_width, map_width))[randrange(0, 2)])
y = (randrange(0, map_height), (randrange(0, view_window_top_left[1]),
randrange(view_window_top_left[1] + view_height, map_height))[randrange(0, 2)])
# now we have 4 values. To get a point outside our view we have to return a point where at least 1 of the
# values x/y is guaranteed to be outside the view.
if randrange(0, 2) == 1: # to be almost completely unbiased we randomize the check
selection_x = randrange(0, 2)
selection_y = randrange(0, 2) if selection_x == 1 else 1
else:
selection_y = randrange(0, 2)
selection_x = randrange(0, 2) if selection_y == 1 else 1
return x[selection_x], y[selection_y]
HTH

Sprite not moving in the same direction it is facing

I have an issue with my sprite not moving in the same direction as it is facing. It is top view of a beetle, 10 images animating its leg movements. The sprite animation works fine, choosing new 'random' direction by rotating the image and re-centering to previous center is working too.
What I can't get to work is the sprite to move 'forward', that is to move in the new direction it chooses/faces every second or so. The new direction is simply a small 10-15 degrees rotation left or right from previous position. Instead it moves in what seem like random movements every time the sprite chooses a new direction. For example it will move southwest while facing east, or move north while facing south etc.
I suspect the problem is in the move() method where the movement isn't properly translated via trig values. I am adding to the rect.x values because it is the same as the cartesian coords system while subtracting from rect.y values because it is inverted in pygame compared to cartesian. Going down is increasing positively, up - decreasing y values, increasing negatively.
The class for the beetle sprite is below:
class Foe():
def __init__(self, location):
self.sprites = []
for i in range(1, 11):
file = pg.image.load("beetleprac1/000"+str(i)+"a.png").convert() # 10 sprite files named "0001a-10a.png" last 1 is idle stance
#file.set_colorkey((0, 255, 255))
self.sprites.append(file)
self.move_anim_index = 9
self.image = self.sprites[self.move_anim_index]
self.rotated_image = self.image.copy()
self.rect = self.image.get_rect(center=location)
self.move_rect_coords = [self.rect.x, self.rect.y]
self.angle = 90
self.speed = 3
self.time = 0
self.frames_counter = 0
def move_anim(self, rate=2): # rate is in frames
if self.frames_counter % rate == 0:
self.move_anim_index += 1
if self.move_anim_index >= 9:
self.move_anim_index = 0
self.image = self.sprites[self.move_anim_index]
#self.rect = self.image.get_rect(center=self.rect.center)
self.rotated_image = self.image.copy()
self.rotated_image = pg.transform.rotate(self.image, self.angle - 90)
self.rect = self.rotated_image.get_rect(center=self.rect.center)
def move(self, last_tick):
self.move_anim()
self.rect.x += int(self.speed * math.cos(self.angle))
self.rect.y -= int(self.speed * math.sin(self.angle))
self.move_rect_coords = [self.rect.x, self.rect.y]
def change_direction(self, change_time): # change_time in frames
if self.frames_counter % change_time == 0:
a = random.choice([-1, 1])
self.angle += 15
if self.angle < 0: self.angle += 360
if self.angle > 360: self.angle -=360
#if self.rect.x <
def track_time(self, last_tick, amount):
self.time += last_tick
self.frames_counter += 1
if self.time >= last_tick * amount:
self.time = 0
self.counter = 0
def think(self, last_tick):
self.track_time(last_tick, FPS*2)
self.change_direction(FPS)
def update(self, last_tick, screen_rect):
self.think(last_tick)
self.move(last_tick)
def draw(self, screen):
screen.blit(self.rotated_image, self.move_rect_coords)
And here is the full code and repository at github: https://github.com/fn88/buganimprac2
sin(), cos() and other trigonometric functions use radians -> cos(math.radians(angle))

Lennard Jones interaction between particles. Particles moving to one point

import numpy as np
import random
import pygame
background_colour = (255,255,255)
width, height = 300, 325
eps = 1
sigma = 1
dt = 0.05
class Particle():
def __init__(self):
self.x = random.uniform(0,400)
self.y = random.uniform(0,500)
self.vx = random.uniform(-.1,.1)
self.vy = random.uniform(-.1,.1)
self.fx = 0
self.fy = 0
self.m = 1
self.size = 10
self.colour = (0, 0, 255)
self.thickness = 0
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
elif self.x < self.size:
self.x = 2*self.size - self.x
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
elif self.y < self.size:
self.y = 2*self.size - self.y
def getForce(self, p2):
dx = self.x - p2.x
dy = self.y - p2.y
self.fx = 500*(-8*eps*((3*sigma**6*dx/(dx**2+dy**2)**4 - 6*sigma**12*dx/(dx**2+dy**2)**7)))
self.fy = 500*(-8*eps*((3*sigma**6*dy/(dx**2+dy**2)**4 - 6*sigma**12*dy/(dx**2+dy**2)**7)))
return self.fx, self.fy
def verletUpdate(self,dt):
self.x = self.x + dt*self.vx+0.5*dt**2*self.fx/self.m
self.y = self.y + dt*self.vy+0.5*dt**2*self.fy/self.m
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
screen = pygame.display.set_mode((width, height))
screen.fill(background_colour)
partList = []
for k in range(10):
partList.append(Particle())
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background_colour)
for k, particle in enumerate(partList):
for p2 in partList[k+1:]:
particle.getForce(p2)
particle.verletUpdate(dt)
particle.bounce()
particle.display()
pygame.display.flip()
pygame.quit()
Is my code correct? I tried to simulate particles in 2D move with Lennard Jones forces. I think calculating forces works okay but why my particles are moving to one point? Ocasionally I also get error OverflowError: Python int too large to convert to C long Any advice would be useful.
I can not comment on the physics of the simulation, but as far as the display is concerned following are my observations:
Your particles move to one point because the update condition for the x and y parameter in your code in verletUpdate are slowly moving to values beyond the display area. Also to values out of the range of the int() function which is causing your error. You can see this with the statement:
def verletUpdate(self,dt):
self.x = self.x + dt*self.vx+0.5*dt**2*self.fx/self.m
self.y = self.y + dt*self.vy+0.5*dt**2*self.fy/self.m
print self.x
print self.y
Sample Output:
290.034892392
9.98686293664
290.028208837
9.99352484332
-2.55451579742e+19
1.12437640586e+19
Also they saturate and with iterations, the update gets smaller and smaller:
def display(self):
print ' %s + %s '%(self.x,self.y)
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
Output:
10.0009120033 + 10.0042647307
10.0009163718 + 10.0000322065
10.0009120033 + 10.0042647307
10.0009163718 + 10.0000322065
...
10.0009163718 + 10.0000322065
10.0009120033 + 10.0042647307
10.0009163718 + 10.0000322065
This is also why your bounce functions and your limit checking is not working. And after a lot of iterations on occasion your self.x and self.y are far exceeding the limits of int().
The code seems fine, but you can get rid of the overflow error by adding some checks above the draw line. For instance I initialized them randomly again to simulate a particle going off screen and us tracking a new one. Feel free to change it.
def display(self):
if(self.x<0 or self.x>height):
self.__init__()
print "reset"
if(self.y<0 or self.y>width):
self.__init__()
print "reset"
print ' %s + %s '%(self.x,self.y)
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
Also at one point you adress the array as [k+1:], and addressing the zero element caused a divide by zero error. You might want to look at that.

Categories