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]
Related
I started playing a little with pygame and so far I'm not doing badly. I encountered a problem and managed to solve it. The solution is possible. It is not 100% correct. I want to implement an eat method when the head of the subject meets the food.
It is probably a relatively simple method when the rest of the value of X and Y are equal to the head of the snake being eaten.
For some reason I was not able to fully understand the overlap of pixels and I am not really correct in the method.
The problem at the moment is that there is no overlap between the pixels and "eating is not done".
BACKGROUND = pygame.image.load("background.jpg")
WIDTH, HEIGHT = BACKGROUND.get_width(), BACKGROUND.get_height()
pygame.font.init()
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
def checkForEat(self):
head = self.body[-1]
x = self.food.getPos()[0]
y= self.food.getPos()[1]
# if abs(head[0] - x ) < 9 and abs(head[1] - y ) < 9: -- This is my temporary solution
if head[0] == x and head[1] == y:
self.food = Food()
self.eat()
I try not to add too much unnecessary code.
class Food:
def __init__(self):
self.color = (5, 5, 255)
self.pos = (random.randint(10,WIDTH-50),random.randint(10,HEIGHT-50))
def draw(self,win):
pygame.draw.circle(win,self.color, self.pos, 5)
def getPos(self):
return self.pos
class Snake:
START_POS = (85, 85)
def __init__(self):
self.food = Food()
self.block_size = 11
self.x , self.y = self.START_POS
self.body = self.create_body()
def create_body(self):
body = []
for i in range(self.length):
body.append((85,85+i*self.block_size))
return body
def draw(self,win):
WIN.blit(BACKGROUND, (0, 0))
self.food.draw(win)
for i in range(self.length):
pygame.draw.circle(win, (255, 0, 0), self.body[i], 5)
I'm not adding the rest of the program.
Just saying that apart from the problem I wrote above everything works fine.
Use pygame.Rect/pygame.Rect.colliderect to check if the bounding rectangle of the food overlaps with the head of the snake:
class Food:
def __init__(self):
self.color = (5, 5, 255)
self.pos = (random.randint(10,WIDTH-50),random.randint(10,HEIGHT-50))
def draw(self,win):
pygame.draw.circle(win,self.color, self.pos, 5)
def getPos(self):
return self.pos
def getRect(self):
return pygame.Rect(self.pos[0]-5, self.pos[1]-5, 10, 10)
class Snake:
START_POS = (85, 85)
def __init__(self):
self.food = Food()
self.block_size = 11
self.x , self.y = self.START_POS
self.body = self.create_body()
# [...]
def checkForEat(self):
head = self.body[-1]
head_rect = pygame.Rect(head[0]-5, head[1]-5, self.block_size, self.block_size)
food_rect = self.food.getRect()
if food_rect.colliderect(head_rect):
self.food = Food()
self.eat()
Also see How do I detect collision in pygame?.
Alternatively you can compute the Euclidean distance between the circle center of the circles and compare the distance to the sum of the radii:
class Snake:
# [...]
def checkForEat(self):
dx = self.food.getPos()[0] - self.body[-1][0]
dy = self.food.getPos()[1] - self.body[-1][1]
dist_center = math.hypot(dx, dy)
if dist_center <= 20:
self.food = Food()
self.eat()
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
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]
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.
I have a ball object that waits one second in the middle of the screen before moving. This is the update method:
def update(self, dt):
now = pygame.time.get_ticks() / 1000
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
else:
step = 255 / FPS
value = int(self._frame * step)
rgb = (value, value, value)
self._draw_ball(rgb)
self._frame += 1
That one second happens below the else clause. My goal is to have the ball image go from black (8, 8, 8) to white (255, 255, 255) in that time but as it is _draw_ball doesn't do anything.
def _draw_ball(self, rgb):
pygame.draw.circle(self.image, rgb, self.rect.center, BALL_RADIUS)
The funny things is, it works the first time when it's called in __init__. I've tried taking lines out of update and testing this code on its own in another module but can't figure out what's the problem. Why is pygame.draw.circle not drawing the the circles in the colors passed by the update method?
Here is the whole class:
#!python3
class Ball(pygame.sprite.Sprite):
def __init__(self, game, velocity):
super(Ball, self).__init__()
self.image = pygame.Surface((BALL_RADIUS*2, BALL_RADIUS*2))
self.image.fill(BLACK)
self.image.set_colorkey(BLACK, RLEACCEL)
self.rect = self.image.get_rect()
screen = pygame.display.get_surface()
self.area = screen.get_rect().inflate(-GAP*2, 0)
self.velocity = velocity
self.game = game
self.start_to_the = random.choice(['left', 'right'])
self._draw_ball(BALL_COLOR)
self.reinit()
def _draw_ball(self, rgb):
pygame.draw.circle(self.image, rgb, self.rect.center, BALL_RADIUS)
def _hit_topbottom(self):
return self.rect.top < self.area.top or self.rect.bottom > self.area.bottom
def _hit_leftright(self):
if self.rect.left < self.area.left: return 'left'
elif self.rect.right > self.area.right: return 'right'
else: return 0
def reinit(self):
self._spawn_time = pygame.time.get_ticks() / 1000
self._frame = 1
if self.start_to_the == 'left':
self.velocity = Vec2D(-BALL_SPEED, 0)
else:
self.velocity = Vec2D(BALL_SPEED, 0)
self.rect.center = self.area.center
def update(self, dt):
now = pygame.time.get_ticks() / 1000
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
else:
step = 255 / FPS
value = int(self._frame * step)
rgb = (value, value, value)
self.image.fill(rgb)
self._frame += 1
def calcnewpos(self, dt):
(dx, dy) = self.velocity.x, self.velocity.y
return self.rect.move(dx, dy)
def handle_collision(self):
(dx, dy) = self.velocity.x, self.velocity.y
if self._hit_topbottom():
dy = -dy
elif self._hit_leftright():
side = self._hit_leftright()
self.game.enemy.update_hitpos()
self.game.increase_score(side)
if side == 'left': self.start_to_the = 'right'
elif side == 'right': self.start_to_the = 'left'
self.reinit()
return
else:
if self.hit_paddle():
paddle = self.hit_paddle()
paddle.handle_collision()
if paddle == self.game.paddles['left']:
self.rect.left = GAP + PADDLE_WIDTH
elif paddle == self.game.paddles['right']:
self.rect.right = SCREEN_WIDTH - (GAP + PADDLE_WIDTH)
dx = -dx
dy = (self.rect.centery - paddle.rect.centery)
dy = (math.copysign(min(abs(dy) // 16 * 16, 32), dy)) / 4
paddle.handle_collision()
self.velocity = Vec2D(dx, dy)
def hit_paddle(self):
paddles = self.game.paddles.values()
for paddle in paddles:
if self.rect.colliderect(paddle.rect): return paddle
I don't see any calls to pygame.display.flip. This is the function responsible for updating the screen with the current state of your display surface. It also doesn't look like you are redrawing your ball on the display surface. Somewhere, probably in update or _draw_ball there should be calls like the following:
self.screen.draw(self.image, self.rect)
pygame.display.flip()
The first line draws the image of the ball to the surface representing the screen, and the second call updates the screen to reflect the new surface.
My second theory is that you are drawing new frames of the ball outside of the bounds of self.image. This theory comes from seeing that are moving the ball's rect according to velocity, but always drawing a circle on self.image at self.rect's center. The size of self.image is only BALL_RADIUS*2, which makes it easy to draw outside of it if self.rect's topleft becomes something that's not (0,0). Even if this isn't your problem now, it will be later.
in pygame the draw circle statement is :
pygame.draw.circle (SURFACE, COLOUR, (X, Y), SIZE, 0)
if you put your screen.fill statement after the circle statement then it will draw the circle and immediately cover it up with the colour of the screen, making your circle disappear a 10000th of a second after its drawn.