So I'm creating a little pong game in python (pygame) and I'm trying to create a bot for it...
if you have the balls velocity:
velocity = [10, 5]
the balls x:
x = 300
and the y:
y = 300
is it possible the calculate where the ball is going to go (also knowing where the edges are where the ball bounces)
my code for the game till now:
https://pastebin.com/eQRZedqs
import pygame
import sys
SIZE = (1000, 600)
FRAMES = 60
win = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
class Player:
def __init__(self, x, y):
self.size = (15, 120)
self.x = x
self.y = y
def draw(self, surface):
pygame.draw.rect(surface, (255, 255, 255),
pygame.Rect((self.x, self.y), self.size))
class Ball:
def __init__(self):
self.x = SIZE[0] // 2
self.y = SIZE[1] // 2
self.vel = [-5, 0]
def move(self):
self.x += self.vel[0]
self.y += self.vel[1]
if self.y <= 20 or self.y >= SIZE[1] - 20:
self.vel[1] *= -1
if (self.x <= 45 and bot.y < self.y < bot.y + bot.size[1]) or (self.x >= SIZE[0] - 45 and p.y < self.y < p.y + p.size[1]):
self.vel[0] *= -1
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 255), (self.x, self.y), 10)
class Bot(Player):
def __init__(self, x, y):
super().__init__(x, y)
self.ball_vel = b.vel
self.ball_x = b.x
self.ball_y = b.y
def draw_screen(surface):
surface.fill((0, 0, 0))
p.draw(surface)
b.draw(surface)
bot.draw(surface)
b.move()
pygame.display.update()
def key_handler():
keys = pygame.key.get_pressed()
if (keys[pygame.K_UP] or keys[pygame.K_w]) and p.y >= 20:
p.y -= 5
elif (keys[pygame.K_DOWN] or keys[pygame.K_s]) and p.y + p.size[1] <= SIZE[1] - 20:
p.y += 5
def main_loop():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
key_handler()
draw_screen(win)
clock.tick(FRAMES)
if __name__ == "__main__":
pygame.init()
p = Player(SIZE[0] - 45, SIZE[1] // 2 - 60)
b = Ball()
bot = Bot(20, SIZE[1] // 2 - 60)
main_loop()
I'd say add the following function to the Ball class:
class Ball:
def __init__(self):
self.x = SIZE[0] // 2
self.y = SIZE[1] // 2
self.vel = [-5, 0]
def move(self):
self.x += self.vel[0]
self.y += self.vel[1]
if self.y <= 20 or self.y >= SIZE[1] - 20:
self.vel[1] *= -1
if (self.x <= 45 and bot.y < self.y < bot.y + bot.size[1]) or (self.x >= SIZE[0] - 45 and p.y < self.y < p.y + p.size[1]):
self.vel[0] *= -1
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 255), (self.x, self.y), 10)
# projecting to see in which y the ball will end up (returns the y coordinate)
def project(self):
# setting up local variables as to not change the actual position of the ball
x = self.x
y = self.y
vel = self.vel
# doing practically the same thing as move but with the local x and y
while x > 45 and x < SIZE[0] - 45:
x += vel[0]
y += vel[1]
if y <= 20 or y >= SIZE[1] - 20:
vel[1] *= -1
return y
Since you have the return value of y, then you can get your bot to move directly there, either in a slow manner or an instant one.
If you want to get into quicker solutions, you need to use some physics equations, but in my opinion, this is quick enough (it'll get there pretty quickly). Also, you could alternatively use trigonometry to get the answer, by imagining the velocities as one vector (then you could get the angle of motion relative to the y axis and then use trig to get the final length until the y reaches the border. Repeat that, until you get to the y-axis borders, and you can calculate your x more efficiently (though, once again, there is most probably no need for that, since it should run fast enough).
Related
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Pygame Drawing a Rectangle
(6 answers)
Closed 5 months ago.
I'm building a game with Sprites, using almost exactly the same code for all the sprites. But when it comes to my ship its telling me I have no rect or colliderect is invalid etc etc etc.
I tried to put a box around my 'rect' to make sure it was loaded correctly but I just get this error.
My code for the ship is
class Spaceship(pygame.sprite.Sprite):
def __init__(self, x, y, scale):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("ship.png")
self.image = pygame.transform.scale(self.image, (int(self.image.get_width() * scale), int(self.image.get_height() * scale)))
self.rect = self.image.get_rect() # isn't this correct to get a rect!?
self.rect.center = [x, y]
self.pos = pygame.mouse.get_pos()
self.alive = True
self.speed = 100
self.dx = 0
self.dy = 0
self.x = 0
self.y = 0
def update(self):
pygame.draw.rect(screen,(GREEN), self.rect, 2) # Trying to establish rect object
self.pos = pygame.mouse.get_pos()
self.dx = (self.pos[0])
self.dy = (self.pos[1])
if self.x +20 <= self.dx:
self.x += speed
if self.x +20 >= self.dx:
self.x -= speed
self.y = (self.pos[1])
if self.y +20 <= self.dy:
self.y += speed
if self.y +20 >= self.dy:
self.y -= speed
print(self.pos[0])
print(self.pos[1])
if self.x >= SW - 40:
self.x = SW - 40
if self.x <= 0:
self.x = 0
if self.y >= SH -40:
self.y = SH - 40
if self.y <= SH - 600:
self.y = SH - 600
self.blit = (self.x, self.y)
self.rect = (self.x, self.y)
If I can't get this rect right I can't check rect collisions.
Thanks for any thoughts.
VIDEO When my player distance is a little farther than the knife they stop shooting at the player I am not sure why? How could I fix it and make sure it shoots where ever the player distance is. Like I don't want it to stop shooting when the player is a little farther away from the projectile I am not sure if I have a range for it to shoot the player or maybe when my player scrolls
when it stops shooting at the player the shooting sound still plays but my projectiles aren't shooting and that causes my sound to keep playing rapidly.
This is how my enemies shoot:
for shootss in shootsright:
shootss.x += shootss.xspeed
shootss.y += shootss.yspeed
if shootss.x > 700 or shootss.x < 0 or shootss.y > 500 or shootss.y < 0:
shootsright.pop(shootsright.index(shootss))
shootss.lookAt((playerman.x,playerman.y))
if box1.health > 25:
if len(shootsright) < 1:
for enemyshoot in enemyshooting:
BULLET_SPEED = 10
start_x = round(enemyshoot.x+enemyshoot.width+-35)
start_y = round(enemyshoot.y + enemyshoot.height+-25)
target_x = playerman.x+playerman.width//2
target_y = playerman.y+playerman.width//2
delta_x, delta_y = target_x - start_x, target_y - start_y
distance = math.sqrt(delta_x ** 2 + delta_y ** 2)
dir_x = BULLET_SPEED * delta_x / distance
dir_y = BULLET_SPEED * delta_y / distance
distance = math.sqrt(dir_x**2 + dir_y**2)
knifesound.play()
if distance > 0:
shootsright.append(enemyboolss(start_x,start_y,(0,0,0),dir_x, dir_y))
This is the class for the projectile:
class projectile(object):
def __init__(self, x, y, dirx, diry, color):
self.x = x
self.y = y
self.dirx = dirx
self.diry = diry
self.isJump = False
self.slash = pygame.image.load("round.png")
self.slash = pygame.transform.scale(self.slash,(self.slash.get_width()//6,self.slash.get_height()//6))
self.rect = self.slash.get_rect()
self.rect.topleft = ( self.x, self.y )
self.speed = 18
self.color = color
self.hitbox = (self.x + -18, self.y, 46,60)
def move(self):
self.x += self.dirx * self.speed
self.y += self.diry * self.speed
def draw(self, window):
self.rect.topleft = (round(self.x), round(self.y))
window.blit(self.slash, self.rect)
self.hitbox = (self.x + -18, self.y, 30,30)
my full code: script
Your screen is 800x800, but the knife code is checking 700x500. The knife projectile is created but immediately removed from the knife list at the next loop:
if shootss.x > 700 or shootss.x < 0 or shootss.y > 500 or shootss.y < 0:
shootsright.pop(shootsright.index(shootss))
shootss.lookAt((playerman.x,playerman.y))
This causes the knife flash but no movement for knives past 700.
After setting the check to 800, the knives worked correctly including sound.
I have a problem with collisions in my game, spaceship's mask doesn't collide properly with a background and I believe that offset is a problem, however I'm not sure.
I've tried multiple collision techniques and checked a lot of answers to my problem, but none of them helped me.
import pygame as pg
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (2, 29)
pg.init()
def gameLoop():
display_width = 1800
display_height = 1000
pg.display.set_caption("Kerbal Space Landing Simulator")
clock = pg.time.Clock()
display = pg.display.set_mode((display_width, display_height))
sprsp = pg.image.load('C:/Users/PC/PycharmProjects/untitled/sprspaceship.png').convert_alpha()
cosbg = pg.image.load('C:/Users/PC/PycharmProjects/untitled/cosmos bg.png').convert_alpha()
done = False
class Spaceship:
def __init__(self, x, y, mass):
self.x = x
self.y = y
self.width = 139
self.height = 106
self.velx = 0
self.vely = 0
self.mass = mass
self.color = (255, 255, 255)
self.spr = sprsp
self.fuel = 500
self.mask = pg.mask.from_surface(self.spr)
self.angle = 0
self.changerot = 0
def check_controls(self):
if keys[pg.K_SPACE] and self.fuel > 0:
if self.angle > 0:
self.vely += 0.005 * (self.angle - 90)
self.velx += -0.005 * self.angle
else:
self.vely += -0.005 * (self.angle + 90)
self.velx += -0.005 * self.angle
self.fuel += -3
if keys[pg.K_LEFT] and self.angle < 90:
self.angle += 2
if keys[pg.K_RIGHT] and self.angle > -90:
self.angle += -2
def update_pos(self):
self.vely += 0.01
self.x += self.velx
self.y += self.vely
self.mask = pg.mask.from_surface(self.spr)
def update_rotation(self):
self.rspr = pg.transform.rotate(self.spr, self.angle)
self.changerot -= self.angle
def draw(self):
if self.fuel > 0:
pg.draw.rect(display, (255, 255, 255), (display_width - 100, 100 + 500 - self.fuel, 10, self.fuel), 0)
display.blit(self.rspr, (int(self.x), int(self.y)))
self.changerot = 0
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.mask = pg.mask.from_threshold(display, (160, 160, 160))
self.hitbox = pg.Rect(self.x, self.y, display_width, 500)
self.ox = display_width // 2 - self.x // 2
self.oy = display_height // 2 - self.y // 2
def draw(self):
pg.draw.rect(display, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
spaceship = (Spaceship(500, 100, 1))
terrain = (Terrain(0, 800))
def redrawGameWindow():
display.blit(cosbg, (0, 0))
spaceship.draw()
terrain.draw()
pg.display.update()
def check_for_collisions():
offset = (int(spaceship.x - terrain.ox), int(spaceship.y - terrain.oy))
print(offset)
print(spaceship.mask.overlap(terrain.mask, offset))
return spaceship.mask.overlap(terrain.mask, offset)
# return spaceship.hitbox.colliderect(terrain.hitbox)
# return pg.sprite.spritecollide(spaceship.spr, terrain.mask, False, pg.sprite.collide_mask)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
mouse_pressed = pg.mouse.get_pressed()
x, y = pg.mouse.get_pos()
spaceship.check_controls()
spaceship.update_pos()
spaceship.update_rotation()
if check_for_collisions() is not None:
print('Hit! You\'ve hit the ground with the speed:', spaceship.vely)
exit()
redrawGameWindow()
clock.tick(60)
gameLoop()
Spaceship doesn't collide with the surface. I know how to fix it using simpler code, but in future I want to use randomly generated terrain. Could you help me with these collisions?
The mask for the Terrain is never set. Crate a proper Terrain mask:
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
maskSurf = pg.Surface((display_width, display_height)).convert_alpha()
maskSurf.fill(0)
pg.draw.rect(maskSurf, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
self.mask = pg.mask.from_surface(maskSurf)
print(self.mask.count())
# [...]
When using pygame.mask.Mask.overlap(), then you've to check the overlapping of the Spaceship and the Terrain, rather than the Terrain and the Spaceship.
Since the Terrain mask is a mask of the entire screen, the offset for the overlap() test is the position of the Spaceship:
def check_for_collisions():
offset = (int(spaceship.x), int(spaceship.y))
collide = terrain.mask.overlap(spaceship.mask, offset)
print(offset, collide)
return collide
Minimal example: repl.it/#Rabbid76/PyGame-SurfaceMaskIntersect
See also: Mask
Let's consider following code (balls with random centers and velocities collide, with screen surface bounds, and with each other):
import pygame,sys,math
from pygame.locals import *
from random import randrange
WIDTH = 500
HEIGHT = 500
WHITE = (255,255,255)
BLUE = (0, 0, 255)
RADIUS = 10
FPS = 30
DISPLAYSURF = pygame.display.set_mode((WIDTH,HEIGHT),0,32)
TAB = []
fpsClock = pygame.time.Clock()
pygame.display.set_caption('Animation')
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, A):
return math.sqrt((A.x-self.x)**2 + (A.y-self.y)**2)
def getTouple(self):
return (self.x,self.y)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def norm(self):
return math.sqrt(self.x**2 + self.y**2)
class Ball:
def __init__(self, center, radius, velocity):
self.center = center
self.radius = radius
self.velocity = velocity
def __init__(self):
self.radius = RADIUS
self.center = Point(randrange(RADIUS,WIDTH-RADIUS), randrange(RADIUS,HEIGHT-RADIUS))
vx = randrange(-5,5)
vy = randrange(-5,5)
while vx == 0 or vy == 0:
vx = randrange(-5,5)
vy = randrange(-5,5)
self.velocity = Vector(vx,vy)
def draw(self):
self.center.x += self.velocity.x
self.center.y += self.velocity.y
for ball in TAB:
if ball != self:
if ball.center.distance(self.center) <= 2*RADIUS:
tmp = self.velocity
self.velocity = ball.velocity
ball.velocity = tmp
if self.center.x - self.radius <= 0:
self.velocity.x = -self.velocity.x
if self.center.x + self.radius >= WIDTH:
self.velocity.x = -self.velocity.x
if self.center.y - self.radius <= 0:
self.velocity.y = -self.velocity.y
if self.center.y + self.radius >= HEIGHT:
self.velocity.y = -self.velocity.y
pygame.draw.circle(DISPLAYSURF, BLUE, self.center.getTouple(), self.radius, 0)
BallNum = 30
for i in range(BallNum):
k = Ball()
TAB.append(k)
while True:
DISPLAYSURF.fill(WHITE)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
for ball in TAB:
ball.draw()
pygame.display.update()
fpsClock.tick(FPS)
This code works but not corectly: some balls are 'catched' by the bounds and others 'stick together'. I think the problem is in Ball class method draw. I will be grateful for any ideas on how to improve that code.
The main problem with the code you have posted which causes some of the balls to stick together, is that after they bounce off of each other, they still are in contact, so they bounce off of each other indefinitely which causes them to 'stick together'.
To fix this problem:
Add in an attribute/variable which holds a reference to the ball that it last hit. I called this attribute ball_last_hit and I initialized it to None.
Then, in the collision detection code, simply check to make sure the ball is not in contact with the ball it last hit, with an if statement like this: if ball != self.ball_last_hit:.
Then, if the balls should bounce off of each other, after swapping their velocities, set self.ball_last_hit to ball.
This will solve the 'sticking together' problem between the balls, but the ball could still get 'stuck' to the wall, although it is less common. To solve this you could add in an attribute called something like self.ignore_walls and after each collision with a wall, set it to true, then after a certain amount of time, set it back to false, so it can collide with walls again.
Here is the fixed code:
import pygame,sys,math
from pygame.locals import *
from random import randrange
WIDTH = 500
HEIGHT = 500
WHITE = (255,255,255)
BLUE = (0, 0, 255)
RADIUS = 10
FPS = 30
DISPLAYSURF = pygame.display.set_mode((WIDTH,HEIGHT),0,32)
TAB = []
fpsClock = pygame.time.Clock()
pygame.display.set_caption('Animation')
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, A):
return math.sqrt((A.x-self.x)**2 + (A.y-self.y)**2)
def getTouple(self):
return (self.x,self.y)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def norm(self):
return math.sqrt(self.x**2 + self.y**2)
class Ball:
def __init__(self):
self.radius = RADIUS
self.center = Point(randrange(RADIUS,WIDTH-RADIUS), randrange(RADIUS,HEIGHT-RADIUS))
vx = randrange(-5,5)
vy = randrange(-5,5)
while vx == 0 or vy == 0:
vx = randrange(-5,5)
vy = randrange(-5,5)
self.velocity = Vector(vx,vy)
self.ball_last_hit = None
def draw(self):
self.center.x += self.velocity.x
self.center.y += self.velocity.y
for ball in TAB:
if ball != self:
if ball.center.distance(self.center) <= 2*RADIUS:
if ball != self.ball_last_hit:
tmp = self.velocity
self.velocity = ball.velocity
ball.velocity = tmp
self.ball_last_hit = ball
if self.center.x - self.radius <= 0:
self.velocity.x = -self.velocity.x
if self.center.x + self.radius >= WIDTH:
self.velocity.x = -self.velocity.x
if self.center.y - self.radius <= 0:
self.velocity.y = -self.velocity.y
if self.center.y + self.radius >= HEIGHT:
self.velocity.y = -self.velocity.y
pygame.draw.circle(DISPLAYSURF, BLUE, self.center.getTouple(), self.radius, 0)
BallNum = 30
for i in range(BallNum):
k = Ball()
TAB.append(k)
while True:
DISPLAYSURF.fill(WHITE)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
for ball in TAB:
ball.draw()
pygame.display.update()
fpsClock.tick(FPS)
Note: instead of using a temp variable, and executing three lines of code, you can swap the balls' velocities with just one this line of code: self.velocity, ball.velocity = ball.velocity, self.velocity.
I hope this answer helped you, and if you have any further questions, please feel free to leave a comment below!
Edit: to get rid of the sticking to the walls, all we need to do is make sure that after a collision with the wall, the ball is not still in contact with the wall, because if it is, it will bounce against the wall indefinitely, and get 'stuck'
After changing the ball's velocity, when it collides with the wall, I just set the center of the ball to one pixel away from the wall, so it is definitely not touching the wall, and this fixed the problem for me.
Here is the new collision detection code, which goes at the end of the draw() method. There are just 4 new lines needed.
if self.center.x - self.radius <= 0:
self.velocity.x = -self.velocity.x
self.center.x = self.radius + 1
if self.center.x + self.radius >= WIDTH:
self.velocity.x = -self.velocity.x
self.center.x = WIDTH - self.radius - 1
if self.center.y - self.radius <= 0:
self.velocity.y = -self.velocity.y
self.center.y = self.radius + 1
if self.center.y + self.radius >= HEIGHT:
self.velocity.y = -self.velocity.y
self.center.y = HEIGHT - self.radius - 1
Note: this simulation gets very tricky if you increase the ball number even higher, because groups of 3 balls start to collide with each other at a time, and your current code is only capable of handling collision between two balls.
I'm making a simple pong game and I've written the majority of the code.
The problem is, once the ball falls, if you continue to move the paddle, it will bounce back up into the screen from the bottom. I need it to stay off the screen permanently once it misses the paddle.
Any feedback is appreciated! Thanks in advance!
L1_base.py (my base code):
import math
import random
import sys, pygame
from pygame.locals import *
import ball
import colors
import paddle
# draw the scene
def draw(screen, ball1, paddle1) :
screen.fill((128, 128, 128))
ball1.draw_ball(screen)
paddle1.draw_paddle(screen)
#function to start up the main drawing
def main():
pygame.init()
width = 600
height = 600
screen = pygame.display.set_mode((width, height))
ball1 = ball.Ball(300, 1, 40, colors.YELLOW, 0, 4)
paddle1 = paddle.Paddle(200, 575, colors.GREEN, 100, 20)
while 1:
for event in pygame.event.get():
if event.type == QUIT: sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
paddle1.update_paddle('right', 35)
if event.key == pygame.K_LEFT:
paddle1.update_paddle('left', 35)
ball1.test_collide_top_ball(0)
ball1.test_collide_bottom_ball(600, paddle1)
ball1.update_ball()
draw(screen, ball1, paddle1)
pygame.display.flip()
if __name__ == '__main__':
main()
ball.py (contains ball class/methods):
import pygame
class Ball:
def __init__(self, x, y, radius, color, dx, dy):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.dx = dx
self.dy = dy
def draw_ball(self, screen):
pygame.draw.ellipse(screen, self.color,
pygame.Rect(self.x, self.y, self.radius, self.radius))
def update_ball(self):
self.x += self.dx
self.y += self.dy
def test_collide_top_ball(self, top_height):
if (self.y <= top_height) and (self.dy < 0):
self.dy *= -1
def test_collide_bottom_ball(self, coll_height, paddle):
if (self.y >= coll_height - self.radius - (600 - paddle.y)) and (self.x >= paddle.x) and (self.x <= paddle.x + paddle.width) and (self.dy > 0):
self.dy *= -1
paddle.py (contains paddle class/methods):
import pygame
class Paddle:
def __init__(self, x, y, c, w, h):
self.x = x
self.y = y
self.color = c
self.width = w
self.height = h
def draw_paddle(self, screen):
pygame.draw.rect(screen, self.color,
pygame.Rect(self.x, self.y, self.width, self.height), 0)
def update_paddle(self, dir, dx):
if (dir == 'left'):
self.x = max(self.x - dx, 0)
else:
self.x = min(self.x + dx, 600 - self.width)
def get_left(self):
if (self.x < 300):
return self.x
def get_right(self):
if (self.x >= 300):
return self.x
You tried to cram too much stuff into one if statement in test_collide_bottom_ball and that made debugging much harder. Its also easier in this case to consider all the reasons not to self.dy *= -1 over the reasons you should. That switching of directions is a privilege paddle and should only happen on special occasions.
I have left my debugging code in to clarify the way i thought about the problem, also there is still another bug which can get the ball stuck in the paddle, but you should be able to fix that given a similar approach. Please see the following code:
def test_collide_bottom_ball(self, coll_height, paddle):
print paddle.x, paddle.width + paddle.x, self.x
# shoot ball back to top when it has gone off the bottom
if self.y > 600:
self.y = 300
return
# ignore when the ball is far from the y access of the paddle
if not self.y >= coll_height - self.radius - (600 - paddle.y):
return
# check that the current x plus raidus to find "too small" condition
if self.x + self.radius <= paddle.x:
print "self.x too small"
return
# for too big we have to consider the paddle width
if self.x >= paddle.x + paddle.width:
print "self.x too big"
return
# ok, looks like its time to switch directions!
self.dy *= -1
P.S. use the pep8 coding standard utility. Its not any harder to code following pep8 and will save you headache.