Pygame collisions is wierd [duplicate] - python
This question already has answers here:
Pygame: Collision by Sides of Sprite
(5 answers)
How can I rewrite my collision logic in my pygame platformer?
(1 answer)
Closed 16 days ago.
So Ive been at this one problem for more than a week now and i feel like Ive tried everything. Nothing works, Ive looked it up, but at the same time I don't want to just copy code i would like to actually understand it.
def x_collision(self):
for tile in tiles:
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
if tile.colliderect(self.rect):
if self.x_vel > 0:
self.x = tile.right
if self.x_vel < 0:
self.x = tile.left - self.size
def y_collision(self):
for tile in tiles:
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
if tile.colliderect(self.rect):
if self.y_vel > 0:
self.y = tile.bottom
if self.y_vel < 0:
self.y = tile.top
This is just the two main functions for the collision located inside the player class.
Ive gotten the collisions to work on each axis but when i merge the two together it just goes insane, I'm guessing this has something to do with the position of the player being set in the wrong place but that shouldn't be the problem considering im not checking for the collision in one function but instead in two different for each the x and y.
Just, it's probably because of some stupid reason and it shouldn't be that hard if you're not dumb like me but i just can't seem to figure it out
Also if you're testing the code just delete everything that has something to do with sound!
Here is the entire code for reference (i know it's messy but im very new to this so good luck!)
import pygame
import random
import math
import time
# Initialize Pygame
pygame.init()
pygame.mixer.init()
# Set the size of the window
size = (900, 600)
screen = pygame.display.set_mode(size)
# Set the title of the window
pygame.display.set_caption("Classes testing")
clock = pygame.time.Clock()
fps = 60
pygame.mixer.music.load('PewPewProject/Retro Platform.wav')
pygame.mixer.music.set_volume(0.1)
pygame.mixer.music.play(-1)
pop_sound = pygame.mixer.Sound("PewPewProject/vine-boom.wav")
pop_sound.set_volume(0.5)
level = [
['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'],
['1','E','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','E','0','0','0','1'],
['1','0','0','0','1','1','1','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','1','0','1','0','0','0','0','0','0','0','0','E','0','0','0','0','1'],
['1','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','E','1','1','1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','E','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','1','1','0','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','0','0','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','1','1','1','0','E','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','1','S','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','1','0','0','0','1','0','0','0','0','0','1'],
['1','0','0','E','0','0','0','0','0','1','1','1','1','1','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'],
]
tilex, tiley = [0, 0]
tilex_offset, tiley_offset = [0, 0]
tile_size = 50
tilemap = pygame.image.load('PewPewProject/tilemap.png')
tiles = []
collision_tiles = []
enemy_spawns = []
def build_level():
for tiley, rows in enumerate(level):
for tilex, tile in enumerate(rows):
if tile == '1':
tile_rect = pygame.Rect(tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size)
tiles.append(tile_rect)
elif tile == '0':
pass
elif tile == 'E':
enemy_spawns.append([tilex, tiley])
elif tile == 'S':
pass
elif tile == 'R':
pass
else:
print("Wrong Map Input")
def rebuild_level():
global tiles, enemy_spawns
tiles = []
enemy_spawns = []
build_level()
def draw_level():
for tiley, rows in enumerate(level):
for tilex, tile in enumerate(rows):
color = None
if tile == '1':
color = (100, 100, 100)
elif tile == 'S':
color = (50, 255, 100)
elif tile == 'R':
color = (255, 50, 50)
if color:
pygame.draw.rect(screen, color, (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
def random_except(start, end, exception_start, exception_end):
num = random.randint(start, end)
while num >= exception_start and num <= exception_end:
num = random.randint(start, end)
return num
class Player:
def __init__(self, x, y):
global projectiles
self.speed = 5
self.x = x
self.y = y
self.x_vel = 0
self.y_vel = 0
self.px_vel = 0
self.py_vel = 0
self.size = tile_size
self.gunsurface = pygame.Surface((90, 25), pygame.SRCALPHA)
self.healthboost = 1
self.health = 100 * 1
self.rotated_gunsurface = self.gunsurface
self.gunsurface.fill((150, 150, 150))
self.gunrect = self.gunsurface.get_rect()
projectiles = [Projectile(-100, 0, 0)]
self.last_shot_time = 0
self.cooldown = 0.5
self.is_slowed = False
dx = 0
dy = 0
self.angle = math.atan2(dy, dx)
self.force = 72
self.forceCounterCuzIAmBadAtPython = self.force
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
self.alive = True
self.colliding = False
def x_collision(self):
for tile in tiles:
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
if tile.colliderect(self.rect):
if self.x_vel > 0:
self.x = tile.right
if self.x_vel < 0:
self.x = tile.left - self.size
def y_collision(self):
for tile in tiles:
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
if tile.colliderect(self.rect):
if self.y_vel > 0:
self.y = tile.bottom
if self.y_vel < 0:
self.y = tile.top
def Movement(self):
global tiley_offset, tilex_offset
self.keys = pygame.key.get_pressed()
tilex_offset += self.x_vel
self.x_collision()
if self.x_vel != 0:
self.x_vel -= self.px_vel
self.forceCounterCuzIAmBadAtPython -= 1
if self.forceCounterCuzIAmBadAtPython <= 0:
self.x_vel = 0
tiley_offset += self.y_vel
self.y_collision()
if self.y_vel != 0:
self.y_vel -= self.py_vel
if self.forceCounterCuzIAmBadAtPython <= 0:
self.y_vel = 0
rebuild_level()
def Alive(self):
if self.health <= 0:
self.alive = False
def GUI(self):
self.empty_health_bar = pygame.draw.rect(screen, (200, 20, 20), (screen.get_width()/4, 10,466, 25))
self.health_bar = pygame.draw.rect(screen, (20, 200, 20), (screen.get_width()/4, 10, self.health*4.66/self.healthboost, 25))
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 10),(screen.get_width()/4+466, 10), 2)
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 35),(screen.get_width()/4+466, 35), 2)
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 10), (screen.get_width()/4, 35), 2)
pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4+466, 10), (screen.get_width()/4+466, 35), 2)
def Gun(self):
cursor_x, cursor_y = pygame.mouse.get_pos()
dx = cursor_x - (self.x + self.size)
dy = cursor_y - (self.y + self.size)
angle = math.atan2(dy, dx)
self.rotated_gunsurface = pygame.transform.rotate(self.gunsurface, math.degrees(angle * -1))
self.gunrect = self.rotated_gunsurface.get_rect(center = (self.x+self.size/2, self.y+self.size/2))
screen.blit(self.rotated_gunsurface, (self.gunrect.x, self.gunrect.y))
def Shoot(self):
global dx, dy
if pygame.key.get_pressed()[pygame.K_e]:
current_time = time.time()
if current_time - self.last_shot_time > self.cooldown:
self.forceCounterCuzIAmBadAtPython = self.force
pop_sound.play()
cursor_x, cursor_y = pygame.mouse.get_pos()
dx = cursor_x - (self.x + self.size/2)
dy = cursor_y - (self.y + self.size/2)
self.angle = math.atan2(dy, dx)
projectiles.append(Projectile(self.x+self.size/2, self.y+self.size/2, self.angle))
self.last_shot_time = current_time
self.x_vel = math.cos(self.angle) * self.speed
self.y_vel = math.sin(self.angle) * self.speed
self.px_vel = self.x_vel / self.force
self.py_vel = self.y_vel / self.force
def Render(self):
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
pygame.draw.rect(screen, (230, 100, 100), self.rect)
self.Gun()
class Projectile:
def __init__(self, x, y, angle):
self.x = x
self.y = y
self.angle = angle
self.speed = 8
self.hitbox = pygame.Rect(self.x, self.y, 5,5)
def Move(self):
self.x += math.cos(self.angle) * self.speed
self.y += math.sin(self.angle) * self.speed
def Render(self):
self.hitbox = pygame.draw.circle(screen, (255, 255, 0), (int(self.x), int(self.y)), 7)
class Enemy:
def __init__(self, health, main_speed, tag, size, player, x, y):
self.health = health
#self.main_speed = 3
self.tag = tag
self.size = size
self.player = player
self.speed = 3
self.x = x
self.y = y
self.alive = True
def Destroy(self):
self.alive = False
def Spawning(self):
self.hitbox = pygame.draw.rect(screen, (100, 240, 100), (self.x + tilex_offset, self.y + tiley_offset, self.size, self.size))
def Movement(self):
dx = self.player.x - self.x
dy = self.player.y - self.y
angle = math.atan2(dy - tiley_offset, dx - tilex_offset)
self.x += self.speed * math.cos(angle)
self.y += self.speed * math.sin(angle)
projectiles = [Projectile(-100, 0, 0)]
def main():
player = Player(screen.get_width()/2-tile_size/2, screen.get_height()/2-tile_size/2)
enemies = []
def Spawn_enemies(num_enemies, player):
for i in range(num_enemies):
x, y = random.choice(enemy_spawns)
enemy = Enemy(1, 1, i, 25, player, x*tile_size, y*tile_size)
enemies.append(enemy)
build_level()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Main Loop
screen.fill((100, 100, 200))
# usage
spawn_enemies_chance = random.randint(0, 250)
number_of_enemies = len(enemies)
if spawn_enemies_chance == 1 and number_of_enemies <= 4:
Spawn_enemies(1,player)
draw_level()
# enemy
for enemy in enemies:
if enemy.alive:
enemy.Movement()
enemy.Spawning()
for projectile in projectiles:
if enemy.hitbox.colliderect(projectile.hitbox):
enemy.health -= 1
pop_sound.play()
if enemy.health <= 0:
try:
enemy.alive = False
enemies.remove(enemy)
except:
print("list.remove(x): x not in list... Enemy moment")
projectiles.remove(projectile)
# Get Hurt
for enemy in enemies:
if enemy.hitbox.colliderect(player):
player.health -= 10
enemies.remove(enemy)
# Projectiles
for i, projectile in enumerate(projectiles):
projectile.Move()
projectile.Render()
if projectiles != []:
if projectile.x < 0 or projectile.x > screen.get_width() or projectile.y < 0 or projectile.y > screen.get_height():
projectiles.pop(i)
for tile in tiles:
if projectiles != []:
if tile.colliderect(projectile.hitbox):
try:
projectiles.pop(i)
except:
print(i," pop index out of range, don't mind it :)")
# player
player.Alive()
player.Shoot()
player.Movement()
player.Render()
# Renders the UI ontop of everything
player.GUI()
pygame.display.update()
clock.tick(fps)
pygame.quit()
# DO NOT DISTURB!
if __name__ == "__main__":
main()
Related
Why is my enemybullets shooting from wrong area in Pygame
I am currently creating a top-down shooter in Pygame and currently need my enemies to shoot bullets at my player. The problem is that when I move my character the position where the bullets are shooting from moves as well when they are meant to be shot from the enemies at all times. I have watched many different tutorials but non of which have proven helpful. Hoping someone can help. import pygame import sys import math import random import time import multiprocessing from pygame import mixer pygame.init() displayWidth = 100 displayHeight = 200 enemytime = time.time() + 10 enemyshoottime = time.time() + 1 #Enemy class Enemy1(object): def __init__(self, x, y): self.x = x self.y = y self.hit_box = (self.x-10, self.y -10, 70, 70) self.animation_images = [pygame.image.load("Enemy1_animation_0.png"), pygame.image.load("Enemy1_animation_1.png"), pygame.image.load("Enemy1_animation_2.png"), pygame.image.load("Enemy1_animation_3.png")] self.animation_count = 0 self.reset_offset = 0 self.offset_x = random.randrange(-150, 150) self.offset_y = random.randrange(-150, 150) self.health = 4 def main(self, display): if self.animation_count + 1 == 16: self.animation_count = 0 self.animation_count += 1 if self.reset_offset == 0: self.offset_x = random.randrange(-150, 150) self.offset_y = random.randrange(-150, 150) self.reset_offset = random.randrange(120, 150) else: self.reset_offset -= 1 if player.x + self.offset_x > self.x-display_scroll[0]: self.x += 1 elif player.x + self.offset_x < self.x-display_scroll[0]: self.x -= 1 if player.y + self.offset_y > self.y-display_scroll[1]: self.y += 1 elif player.y + self.offset_y < self.y-display_scroll[1]: self.y -= 1 display.blit(pygame.transform.scale(self.animation_images[self.animation_count//4], (50, 50)), (self.x-display_scroll[0], self.y-display_scroll[1])) self.hit_box = (self.x-display_scroll[0]-10, self.y-display_scroll[1]-10, 70, 70) pygame.draw.rect(display, (255, 0, 0), self.hit_box, 2) #Enemy Bullet class EnemyBullet: def __init__(self, x, y, playerx, playery): self.x = x self.y = y self.playerx = 300 self.playery = 300 self.speed = 7 self.angle = math.atan2(y-playerx, x-playery) self.x_vel = math.cos(self.angle) * self.speed self.y_vel = math.sin(self.angle) * self.speed def main(self, display): self.x -= int(self.x_vel) self.y -= int(self.y_vel) EnemyBulletRect = pygame.draw.circle(display, (255,0,0), (self.x, self.y), 5) #list's enemies = [ Enemy2(800, -200), Enemy3(-300, 500), Enemy4(1000, 400)] enemy = Enemy1(600, 400) enemy_bullets = [] sounds = ['explosion1.mp3', 'explosion2.mp3', 'explosion3.mp3'] player = Player(400, 300, 32, 32) display_scroll = [0,0] player_bullets = [] while True: display.fill((0, 0, 0)) display.blit(displayImage, (0, 0)) #display.blit(ImageBackground, (0, 0)) display.blit(Earth, (700, 100)) show_score(textX, textY) for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() pygame.quit() #Enemy shoots if time.time() >= enemyshoottime: enemy_bullets.append(EnemyBullet(enemy.x, enemy.y, playerx, playery)) from playsound import playsound playsound('lazer.mp3', block=False) enemyshoottime = time.time() + 1 for bullets in enemy_bullets: bullets.main(display) #spawn enemies if time.time() >= enemytime: # Time to spawn a new enemy. enemies.append( Enemy3( 100, 500 ) ) enemies.append( Enemy3( 600, 400 ) ) # Reset the alarm. enemytime = time.time() + 10
The motion vector of the bullet can be calculated from the normalized direction vector (Unit vector) multiplied by the velocity. The unit vector is obtained by dividing the components of the vector by the Euclidean length of the vector. Do not round the velocity before adding it to the position, but round the position when using it to draw the bullet. If you round the component vectors before adding them or round the position attributes themselves, there will be an inaccuracy that accumulates over time. Round only the values that are used in pygame.draw.circle Use round instead of int. class EnemyBullet: def __init__(self, x, y, playerx, playery): self.x = x self.y = y self.speed = 7 dx = playerx - x dy = playery - y d = math.sqrt(dx*dx, dy*dy) # or d = math.hypot(dx, dy) self.x_vel = self.speed * dx / d self.y_vel = self.speed * dy / d def main(self, display): self.x += self.x_vel self.y += self.y_vel EnemyBulletRect = pygame.draw.circle( display, (255,0,0), (round(self.x), round(self.y)), 5)
win variable not being set to False
I am making a pong game with pygame. The check_win() function checks if one side has won and for some reason, it the point counter keeps going up on the right side when detected, but not on the left side. So when it detects the ball hit the left wall, it works as intended, but when it hits the right side, it keeps incrementing the point. here the code import pygame pygame.init() class Pad: def __init__(self, x, win, score_x): self.win = win self.x = x self.y = 250 self.speed = 5 self.score = 0 self.score_x = score_x self.color = (255, 255, 255) self.font = pygame.font.Font("font.ttf", 25) self.base = pygame.Rect(0, 0, 30, 100) self.base.center = (self.x, self.y) self.collide_base = pygame.Rect(0, 0, 10, self.base.height) def render(self, type_): if type_ == 0: self.collide_base.right = self.base.right if type_ == 1: self.collide_base.left = self.base.left self.collide_base.y = self.base.y pygame.draw.rect(self.win, self.color, self.base) def move(self, keys, type_): if type_ == 0: if keys[pygame.K_w]: self.base.centery -= self.speed if keys[pygame.K_s]: self.base.centery += self.speed elif type_ == 1: if keys[pygame.K_UP]: self.base.centery -= self.speed if keys[pygame.K_DOWN]: self.base.centery += self.speed def collide_edge(self): if self.base.bottom >= self.win.get_height(): self.base.y -= self.speed if self.base.top <= 0: self.base.y += self.speed def draw_score(self): score = self.font.render(str(self.score), False, self.color) self.win.blit(score, (self.score_x, self.win.get_height() - (self.win.get_height() - 20))) class Ball: def __init__(self, win): self.win = win self.color = (255, 255, 255) self.leftWin = False self.rightWin = False self.speedx = 5 self.speedy = 5 self.x = self.win.get_width() / 2 self.y = self.win.get_height() / 2 self.base = pygame.Rect(0, 0, 20, 20) self.base.center = (self.x, self.y) def render(self): self.base.center = (self.x, self.y) pygame.draw.ellipse(self.win, self.color, self.base) def move(self): if self.base.bottom >= self.win.get_height(): self.speedy *= -1 if self.base.right >= self.win.get_width(): self.rightWin = True if self.base.top <= 0: self.speedy *= -1 if self.base.left <= 0: self.leftWin = True self.x += self.speedx self.y += self.speedy class Game: def __init__(self): self.width = 750 self.height = 500 self.running = True self.color = (0, 0, 0) self.fps = 60 self.win = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE) self.clock = pygame.time.Clock() self.pad1 = Pad(50, self.win, self.width / 2 + 25) self.pad2 = Pad(700, self.win, self.width / 2 - 40) self.ball = Ball(self.win) self.line = [] def draw_line(self): for x in range(20): if x % 2 == 0: rect = pygame.Rect(0, 0, 20, 40) rect.center = (self.width / 2, x * 40) self.line.append(rect) for rect in self.line: pygame.draw.rect(self.win, (255, 255, 255), rect) def check_pad(self): if self.ball.base.colliderect(self.pad1.base): self.ball.x += 10 self.ball.speedx *= -1 if self.ball.base.colliderect(self.pad2.base): self.ball.x -= 10 self.ball.speedx *= -1 def check_win(self): if self.ball.leftWin: self.ball.base.center = (self.width / 2, self.height / 2) self.ball.leftWin = False self.ball.speedx = 5 self.ball.speedy = 5 self.pad1.score += 1 if self.ball.rightWin: self.ball.base.center = (self.width / 2, self.height / 2) self.ball.rightWin = False self.ball.speedx = 5 self.ball.speedy = 5 self.pad2.score += 1 def run(self): while self.running: self.clock.tick(self.fps) self.win.fill(self.color) for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False keys = pygame.key.get_pressed() self.draw_line() self.check_pad() self.check_win() self.ball.render() self.ball.move() self.pad1.render(0) self.pad1.move(keys, 0) self.pad1.collide_edge() self.pad1.draw_score() self.pad2.render(1) self.pad2.move(keys, 1) self.pad2.collide_edge() self.pad2.draw_score() pygame.display.update() pygame.quit() if __name__ == '__main__': Game().run()
You have forgotten to update self.ball.x and self.ball.y. Note, self.base.center is set from self.x, self.y in the function render. class Game: # [...] def check_win(self): if self.ball.leftWin: self.ball.base.center = (self.width / 2, self.height / 2) self.ball.x, self.ball.y = self.ball.base.center # <--- self.ball.leftWin = False self.ball.speedx = 5 self.ball.speedy = 5 self.pad1.score += 1 if self.ball.rightWin: self.ball.base.center = (self.width / 2, self.height / 2) self.ball.x, self.ball.y = self.ball.base.center # <--- self.ball.rightWin = False self.ball.speedx = 5 self.ball.speedy = 5 self.pad2.score += 1
Pygame is a bit laggy. Is it because of the many calculations?
import pygame import math import random import time pygame.init() clock = pygame.time.Clock() width = 800 height = 600 screen = pygame.display.set_mode((width, height)) pygame.display.set_caption("Shadow Warrior") #its like that because this reduses lag a lot and help for better gameplay def loadify(imgname): return pygame.image.load(imgname).convert_alpha(screen) #Models: logo = loadify("textures/logo.png") pygame.display.set_icon(logo) wallModel = loadify("textures/wall.png") groundModel = loadify("textures/ground.png") weaponModel = loadify("textures/weapon.png") enemyBullet = loadify("textures/bullet.png") playerBullet = loadify("textures/enemyBullet.png") crosshair = loadify("textures/crosshair.png") playerWalkAnimR = [loadify("textures\walk0.png"), loadify("textures\walk1.png"), loadify("textures\walk2.png")] playerWalkAnimL = [loadify("textures\walk3.png"), loadify("textures\walk4.png"), loadify("textures\walk5.png")] pygame.mouse.set_visible(False) tileSize = 64 level = 0 hitParticles = [] weapons = [] removed_bullets = [] enemies = [] backgrounds = [] walls = [] all_bullets = [] # Classes class Particle: def __init__(self, x, y, velocityX, velocityY, radius, color): self.x = x self.y = y self.velocityX = velocityX self.velocityY = velocityY self.radius = radius self.color = color self.lifetime = random.randrange(50, 100) def draw(self, screen, camx, camy): self.lifetime -= 1 self.x += self.velocityX self.y += self.velocityY pygame.draw.circle(screen, self.color, (self.x - camx, self.y - camy), self.radius) class Background: def __init__(self, x, y): self.x = x self.y = y def draw(self, camx, camy): screen.blit(groundModel, (self.x - camx - tileSize / 2, self.y - camy - tileSize / 2)) class Wall: def __init__(self, x, y): self.x = x self.y = y #Check if wall collides with smth def collidesWith(self, other): return pygame.Rect(self.x, self.y, tileSize, tileSize).colliderect(other.x, other.y, tileSize-16, tileSize) def draw(self, camx, camy): screen.blit(wallModel, (self.x - camx - tileSize / 2, self.y - camy - tileSize / 2)) class Bullet: def __init__(self, x, y, velx, vely, isPlayer): self.x = x self.y = y self.velx = velx self.vely = vely self.isPlayer = isPlayer def collidesWith(self, other): return pygame.Rect(self.x, self.y, tileSize - 16, tileSize - 16).colliderect(other.x, other.y, tileSize-36, tileSize-36) def update(self): self.x += self.velx self.y += self.vely for bullet in all_bullets: for wall in walls: if bullet.collidesWith(wall): removed_bullets.append(bullet) for i in removed_bullets: if i in all_bullets: all_bullets.remove(i) def draw(self, camx, camy): if (self.isPlayer): screen.blit(pygame.transform.smoothscale(playerBullet, (16, 16)), (self.x - camx + 5, self.y - camy + 7)) else: screen.blit(pygame.transform.smoothscale(enemyBullet, (16, 16)), (self.x - camx + 5, self.y - camy + 7)) class slimeEnemy: def __init__(self, x, y): self.x = x self.y = y self.bulletSpeed = 1 self.aliveAnimations = [loadify("textures\slime_animation_0.png"), loadify("textures\slime_animation_1.png"), loadify("textures\slime_animation_2.png"), loadify("textures\slime_animation_3.png"),] self.animationsCount = 0 self.attackRate = 60 self.resetOffset = 0 self.SlimeHp = 7 + level * 2 self.slimeCollisionDmg = 1 + level/2 self.dmg = 1 + level / 2 self.isAlive = True # self.isBoss = isBoss #adding offset so the enemy doesnt move directly towards the player self.offsetX = random.randrange(-150, 150) self.offsetY = random.randrange(-150, 150) def collidesWithAnything(self): for wall in walls: if wall.collidesWith(self): return True return False def healthBar(self): pygame.draw.rect(screen, (0, 255, 0), (self.x - camx - 20, self.y + 40 - camy, self.SlimeHp*10 + level*2, 7)) def collidesWith(self, other): return pygame.Rect(self.x, self.y, tileSize-36, tileSize-36).colliderect(other.x, other.y, tileSize-36, tileSize-36) def update(self): # move enemy accordingly if self.resetOffset == 0: self.offsetX = random.randrange(-400, 400) self.offsetY = random.randrange(-400, 400) self.resetOffset = random.randrange(120, 150) else: self.resetOffset -= 1 if player.x + self.offsetX > self.x: self.x += 1 if self.collidesWithAnything(): self.x -= 1 elif player.x + self.offsetX < self.x: self.x -= 1 if self.collidesWithAnything(): self.x += 1 if player.y + self.offsetY > self.y: self.y += 1 if self.collidesWithAnything(): self.y -= 1 elif player.y + self.offsetY < self.y: self.y -= 1 if self.collidesWithAnything(): self.y += 1 self.healthBar() def attack(self): for i in range(3): angle = random.randrange(0, 360) bulletSpeed_x = self.bulletSpeed * math.cos(angle) + random.uniform(-5, 5) bulletSpeed_y = self.bulletSpeed * math.sin(angle) + random.uniform(-5, 5) all_bullets.append(Bullet(self.x, self.y, bulletSpeed_x, bulletSpeed_y, False)) def drawAlive(self, camx, camy): if self.animationsCount + 1 == 32: self.animationsCount = 0 self.animationsCount += 1 if self.attackRate == 0: self.attackRate = 60 self.attack() self.attackRate -= 1 screen.blit(pygame.transform.scale(self.aliveAnimations[self.animationsCount// 8], (32, 30)), (self.x - camx, self.y - camy)) class Weapon: def __init__(self, bulletSpeed, fireRate, bulletDmg): self.shooting = False self.bulletSpeed = bulletSpeed self.fireRate = fireRate self.bulletDmg = bulletDmg self.energy = 100 self.i = 0 def handle_weapons(self, screen): mouse_x, mouse_y = pygame.mouse.get_pos() rel_x, rel_y = mouse_x - width/2, mouse_y - height/2 angle = (180/math.pi) * - math.atan2(rel_y, rel_x) #draw crosshair screen.blit(crosshair, (mouse_x, mouse_y+5)) #rotate and draw weapon accordingly if angle > 90 or angle < -90: player_weapon_copy = pygame.transform.rotate(pygame.transform.flip(weaponModel, True, False), angle - 180) else: player_weapon_copy = pygame.transform.rotate(weaponModel, angle) screen.blit(player_weapon_copy, (width/2 +5 - int(player_weapon_copy.get_width()/2), height/2 + 15 - int(player_weapon_copy.get_height()/2))) def Shooting(self): if self.shooting: #calculates the angles the bullet should travel :/ if self.i % self.fireRate == 0: if (self.energy == 0): pass else: mouse_x, mouse_y = pygame.mouse.get_pos() distance_x = mouse_x - width/2 distance_y = mouse_y - height/2 angle = math.atan2(distance_y, distance_x) bulletSpeed_x = self.bulletSpeed * math.cos(angle) bulletSpeed_y = self.bulletSpeed * math.sin(angle) all_bullets.append(Bullet(player.x, player.y, bulletSpeed_x, bulletSpeed_y, True)) self.energy -= 1 self.i += 1 class Player: def __init__(self, x, y): self.x = int(x) self.y = int(y) self.leftPr = False self.rightPr = False self.downPr = False self.upPr = False self.speed = 4 self.health = 5 self.maxHp = 5 self.animationsCount = 0 def healthBar(self): pygame.draw.rect(screen, (255, 0, 0), (20, 25, 200, 10)) pygame.draw.rect(screen, (0, 255, 0), (20, 25, self.health*40, 10)) def energyBar(self): pygame.draw.rect(screen, (255, 0, 0), (20, 55, 200, 10)) pygame.draw.rect(screen, (48, 117, 255), (20, 55, weapon.energy*2, 10)) def collidesWithAnything(self): for wall in walls: if wall.collidesWith(self): return True return False def update(self): if self.leftPr and not self.rightPr: self.x -= self.speed if self.collidesWithAnything(): self.x += self.speed if self.rightPr and not self.leftPr: self.x += self.speed if self.collidesWithAnything(): self.x -= self.speed if self.upPr and not self.downPr: self.y -= self.speed if self.collidesWithAnything(): self.y += self.speed # revert if collision if self.downPr and not self.upPr: self.y += self.speed if self.collidesWithAnything(): self.y -= self.speed # check for enemy colisions def animations(self): if self.animationsCount + 1 >= 36: self.animationsCount = 0 if self.rightPr: screen.blit(pygame.transform.scale(playerWalkAnimR[self.animationsCount//12],(64, 64)), (width/2 - 40, height/2 - 30)) elif self.leftPr: screen.blit(pygame.transform.scale(playerWalkAnimL[self.animationsCount//12],(64, 64)), (width/2 - 40, height/2 - 30)) else: screen.blit(pygame.transform.scale(playerWalkAnimR[0],(64, 64)), (width/2 - 40, height/2 - 30)) self.animationsCount += 1 weapon = Weapon(10, 20, 2) player = Player(1, 0) #Loads map from file def loadMapFromFile(path): walls.clear() backgrounds.clear() enemies.clear() with open(path, "r") as f: y = 0 enemyLocations = [] # bossLocations = [] for line in f.readlines(): x = 0 for char in line: if char != ' ' and char != "\n": backgrounds.append(Background(x * tileSize, y * tileSize)) if char == '#': walls.append(Wall(x * tileSize, y * tileSize)) elif char == 'p' or char == 'P': player.x = x * tileSize player.y = y * tileSize elif char == 'e' or char == 'E': if random.randint(1, 100) <= 40: enemyLocations.append((x * tileSize, y * tileSize)) # elif char == 'b' or char == 'B': # if random.randint(1, 100) <= 10: # bossLocations.append((x * tileSize, y * tileSize, "True")) x += 1 y += 1 for enemyL in enemyLocations: enemies.append(slimeEnemy(*enemyL)) # for enemyL in bossLocations: # enemies.append(slimeEnemy(*enemyL, "True")) def gameOverScreen(): pygame.font.init() font = pygame.font.Font('arial.ttf', 32) text = font.render('GAME OVER', True, (255, 0, 0), (0, 0, 0)) text.set_colorkey((0, 0, 0)) screen.blit(text, (width/2-80,height/2-40)) pygame.display.flip() for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() loadMapFromFile('maps/Map0.txt') hitCooldown = 75 cooldown = 600 #running loop gameOver = False running = True while running: if gameOver: gameOverScreen() pass else: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_w: player.upPr = True if event.key == pygame.K_s: player.downPr = True if event.key == pygame.K_d: player.rightPr = True if event.key == pygame.K_a: player.leftPr = True if event.type == pygame.KEYUP: if event.key == pygame.K_w: player.upPr = False if event.key == pygame.K_s: player.downPr = False if event.key == pygame.K_d: player.rightPr = False if event.key == pygame.K_a: player.leftPr = False if event.type == pygame.MOUSEBUTTONDOWN: weapon.shooting = True if event.type == pygame.MOUSEBUTTONUP: weapon.shooting = False # updating camx = player.x - width / 2 camy = player.y - height / 2 screen.fill((0, 0, 0)) player.update() #check if slime got hit for bullet in all_bullets: bullet.update() for enemy in enemies: if enemy.collidesWith(player): if hitCooldown < 0: hitCooldown = 75 player.health -= enemy.slimeCollisionDmg if player.health <= 0: gameOver = True elif enemy.collidesWith(bullet) and bullet.isPlayer: removed_bullets.append(bullet) for i in range(10): hitParticles.append(Particle(enemy.x, enemy.y, random.randrange(-5, 5)/10, random.randrange(-5, 5)/10, 4, (108, 216, 32))) enemy.SlimeHp -= weapon.bulletDmg if enemy.SlimeHp <= 0: enemy.isAlive = False if player.health >= player.maxHp: player.health = player.maxHp else: player.health += 1 if weapon.energy >= 100: weapon.energy = 100 else: weapon.energy += 2 if bullet.collidesWith(player) and not bullet.isPlayer: if hitCooldown < 0: hitCooldown = 75 player.health -= enemy.dmg if player.health <= 0: gameOver = True removed_bullets.append(bullet) for bg in backgrounds: bg.draw(camx, camy) for wall in walls: wall.draw(camx, camy) # update all other entities #draw bullet for bullet in all_bullets: bullet.draw(camx, camy) # slime animations and remove dead slimes for enemy in enemies: enemy.update() if enemy.isAlive: enemy.drawAlive(camx, camy) if not enemy.isAlive: enemies.remove(enemy) # draw particles for particle in hitParticles: if particle.lifetime > 0: particle.draw(screen, camx, camy) else: hitParticles.pop(hitParticles.index(particle)) player.animations() weapon.handle_weapons(screen) weapon.Shooting() player.healthBar() player.energyBar() if len(enemies) == 0: if cooldown <= 0: cooldown = 600 loadMapFromFile("maps\Map{0}.txt".format(level+1)) level+=1 hitCooldown -= 1 cooldown -= 1 # finally update screen pygame.display.update() clock.tick(60) I know it is written really badly but I'm learning. The game is really laggy and I was wondering how I can make it run faster and smoother. Also is the lag created from the many calculations or am I just dumb? The lag comes when there are a lot of enemies on the map and they shoot at the same time. Can I fix the lag by making all the enemies shoot in different times? The game is for a school project.
I think your biggest issue is this: class Bullet: [ ... ] def update(self): self.x += self.velx self.y += self.vely for bullet in all_bullets: # <-- HERE for wall in walls: if bullet.collidesWith(wall): removed_bullets.append(bullet) for i in removed_bullets: if i in all_bullets: all_bullets.remove(i) Then in the main loop: #check if slime got hit for bullet in all_bullets: # <-- AGAIN bullet.update() The code is iterating through the bullets many more times than is necessary. By the time all your bullet objects are updated, they have been processed /N/ * /N/ times (i.e.: N-squared). So as you get more any more bullets, this square grows catastrophically. Try changing your Bullet.update() to only check itself. This will probably fix your lag issues - or at least help a lot. It will also "correct" your object encapsulation. A single Bullet has no business knowing about all the others, let alone checking collisions on their behalf. class Bullet: [ ... ] def update(self): self.x += self.velx self.y += self.vely for wall in walls: if self.collidesWith(wall): all_bullets.remove( self ) break If you have any more N-squared loops like this, it will give the best speedup reworking them first.
How do I get the enemy to chase the player? I would also like to know how to jump onto the moving object when I jump and not just go through it
moving object. It currently moves left and right across the screen and I would like the user to be able to jump on it. def moving_object(): global x_speed, y_speed pygame.draw.rect(win, (200, 140, 150), rectangle) rectangle.x += x_speed if rectangle.right >= 500 or rectangle.left <= 0: x_speed *= -1 class for my enemy. They currently move left and right across the screen but I would like them to follow the user. They can also not jump class enemy(): walkRight = [pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')] walkLeft = [pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')] def __init__(self, x, y, width, height, end): self.x = x self.y = y self.width = width self.height = height self.end = end self.WalkCount = 0 self.vel = 3 self.path = [self.x, self.end] self.hitbox = (self.x + 17, self.y + 2, 31, 57) self.health = 10 self.visible = True def draw(self, win): self.move() if self.visible: if self.WalkCount + 1 >= 33: self.WalkCount = 0 if self.vel > 0: win.blit(self.walkRight[self.WalkCount //3], (self.x, self.y)) self.WalkCount += 1 else: win.blit(self.walkLeft[self.WalkCount //3], (self.x, self.y)) self.WalkCount += 1 pygame.draw.rect(win, (255, 0, 0), (self.hitbox[0], self.hitbox[1] - 20, 50, 10)) pygame.draw.rect(win, (0, 255, 0), (self.hitbox[0], self.hitbox[1] - 20, 50, 10)) self.hitbox = (self.x + 17, self.y + 2, 31, 57) def move(self): if self.vel > 0: if self.x + self.vel < self.path[1]: self.x += self.vel else: self.vel = self.vel * -1 self.WalkCount = 0 else: if self.x - self.vel > self.path[0]: self.x += self.vel else: self.vel = self.vel * -1 self.WalkCount = 0 def hit(self): if self.health > 0: self.health -= 1 else: self.visible = False pass
So here is one way to make a pathfinding algorithm: import pygame import time pygame.init() screen = pygame.display.set_mode((700, 500)) clock = pygame.time.Clock() to_coords = [550, 100] from_coords = (150, 400) def go_to_koeff(from_, to): dx = to[0] - from_[0] dy = to[1] - from_[1] if dx != 0: dx /= abs(dx) if dy != 0: dy /= abs(dy) return dx, dy follow_coords_x = from_coords[0] follow_coords_y = from_coords[1] velocity = 1 run = True while run: clock.tick(60) screen.fill((0, 0, 0)) pygame.draw.circle(screen, (0, 255, 0), to_coords, 5) pygame.draw.circle(screen, (255, 0, 0), from_coords, 5) pygame.draw.circle(screen, (255, 255, 255), (follow_coords_x, follow_coords_y), 5) koeff = go_to_koeff((follow_coords_x, follow_coords_y), to_coords) follow_coords_x += velocity * koeff[0] follow_coords_y += velocity * koeff[1] keys = pygame.key.get_pressed() if keys[pygame.K_UP]: to_coords[1] -= 3 if keys[pygame.K_DOWN]: to_coords[1] += 3 if keys[pygame.K_LEFT]: to_coords[0] -= 3 if keys[pygame.K_RIGHT]: to_coords[0] += 3 for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pygame.display.update() pygame.quit() so basically the whole algorithm is that go_to_koeff() function: that function returns the koefficent of direction that can be used to change where the white dot moves that can be seen where the coefficient is multiplied by velocity. This algorithm isn't perfect but it finds the position so there is that. (You can play around a bit by moving the green dot with arrow keys to see how it moves) Another idea that came to mind was tracking player position in a list and making the enemy follow all the coords in the list which could be even worse but it will feel like it is actually following You. Also I just noticed that there is some useless change of variable names, but it doesn't affect anything much.
How to code bounce movement in pong using pygame
I'm a noob in python and I'm trying to recreate the Pong game, and I'm trying to do it by myself as much as possible. These are the issues I currently have with my code: I basically coded every possible movement of the ball when it bounces on the edge. I spent hours working on it, and I got it to work, I was just wondering if there was a more efficient way to produce a similar output with my code (coz I know this is supposed to be a beginner project)? Every time the ball bounces past the edge, the score increments for a split second, and it goes back to 0 every time the ball respawns. How can I make the ball spawn at random directions? at the start (and restart) of every round? Here's my code for the class and the main function of the program: import pygame import random from rect_button import Button pygame.init() WIDTH = 800 HEIGHT = 500 window = pygame.display.set_mode((WIDTH, HEIGHT)) TITLE = "Pong" pygame.display.set_caption(TITLE) # COLORS black = (0, 0, 0) white = (255, 255, 255) red = (255, 0, 0) green = (0, 255, 0) # FONTS small_font = pygame.font.SysFont("courier", 20) large_font = pygame.font.SysFont("courier", 60, True) class Paddle: def __init__(self, x, y, width, height, vel, color): self.x = x self.y = y self.width = width self.height = height self.vel = vel self.color = color def draw(self, window): pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height), 0) class Ball: def __init__(self, x, y, side, vel, color): self.x = x self.y = y self.side = side self.vel = vel self.color = color self.lower_right = False self.lower_left = True self.upper_right = False self.upper_left = False self.ball_bag = [] self.last_movement = 'ball.lower_right' def draw(self, window): pygame.draw.rect(window, self.color, (self.x, self.y, self.side, self.side), 0) def move_lower_right(self): self.x += self.vel self.y += self.vel def move_upper_right(self): self.x += self.vel self.y -= self.vel def move_upper_left(self): self.x -= self.vel self.y -= self.vel def move_lower_left(self): self.x -= self.vel self.y += self.vel def start(self): self.lower_right = True self.lower_left = False self.upper_right = False self.upper_left = False self.last_movement = 'ball.lower_left' # return random.choice([self.lower_right, self.lower_left, self.upper_left, self.upper_right]) is True def main(): run = True fps = 60 clock = pygame.time.Clock() # Initializing Paddles left_paddle = Paddle(20, 100, 10, 50, 5, white) right_paddle = Paddle(770, 350, 10, 50, 5, white) balls = [] def redraw_window(): window.fill(black) left_paddle.draw(window) right_paddle.draw(window) for ball in balls: ball.draw(window) player_A_text = small_font.render("Player A: " + str(score_A), 1, white) player_B_text = small_font.render("Player B: " + str(score_B), 1, white) window.blit(player_A_text, (320 - int(player_A_text.get_width() / 2), 10)) window.blit(player_B_text, (480 - int(player_B_text.get_width() / 2), 10)) pygame.draw.rect(window, white, (20, 450, 760, 1), 0) pygame.draw.rect(window, white, (20, 49, 760, 1), 0) pygame.draw.rect(window, white, (19, 50, 1, 400), 0) pygame.draw.rect(window, white, (780, 50, 1, 400), 0) pygame.display.update() while run: score_A = 0 score_B = 0 clock.tick(fps) if len(balls) == 0: ball = Ball(random.randrange(320, 465), random.randrange(200, 285), 15, 3, white) balls.append(ball) if ball.lower_left: ball.move_lower_left() if ball.last_movement == 'ball.lower_right': if ball.y + ball.side > HEIGHT - 50: ball.lower_left = False ball.last_movement = 'ball.lower_left' ball.upper_left = True if ball.last_movement == 'ball.upper_left': if ball.x < 30: if left_paddle.x < ball.x < left_paddle.x + left_paddle.width: if left_paddle.y < ball.y + ball.side < left_paddle.y + left_paddle.height: ball.lower_left = False ball.last_movement = 'ball.lower_left' ball.lower_right = True else: score_B += 1 balls.remove(ball) #ball.start() if ball.y + ball.side > HEIGHT - 50: ball.lower_left = False ball.last_movement = 'ball.lower_left' ball.upper_left = True if ball.upper_left: ball.move_upper_left() if ball.last_movement == 'ball.lower_left': if ball.x < 30: if left_paddle.x < ball.x < left_paddle.x + left_paddle.width: if left_paddle.y < ball.y + ball.side < left_paddle.y + left_paddle.height: ball.upper_left = False ball.last_movement = 'ball.upper_left' ball.upper_right = True else: score_B += 1 balls.remove(ball) #ball.start() if ball.y < 50: ball.upper_left = False ball.last_movement = 'ball.upper_left' ball.lower_left = True if ball.last_movement == 'ball.upper_right': if ball.y < 50: ball.upper_left = False ball.last_movement = 'ball.upper_left' ball.lower_left = True if ball.upper_right: ball.move_upper_right() if ball.last_movement == 'ball.upper_left': if ball.y < 50: ball.upper_right = False ball.last_movement = 'ball.upper_right' ball.lower_right = True if ball.last_movement == 'ball.lower_right': if ball.x + ball.side > WIDTH - 30: if right_paddle.x + right_paddle.width > ball.x + ball.side > right_paddle.x: if right_paddle.y < ball.y + ball.side < right_paddle.y + right_paddle.height: ball.upper_right = False ball.last_movement = 'ball.upper_right' ball.upper_left = True else: score_A += 1 balls.remove(ball) #ball.start() if ball.y < 50: ball.upper_right = False ball.last_movement = 'ball.upper_right' ball.lower_right = True if ball.lower_right: ball.move_lower_right() if ball.last_movement == 'ball.upper_right': if ball.y + ball.side > HEIGHT - 50: ball.lower_right = False ball.last_movement = 'ball.lower_right' ball.upper_right = True if ball.x + ball.side > WIDTH - 30: if right_paddle.x + right_paddle.width > ball.x + ball.side > right_paddle.x: if right_paddle.y < ball.y + ball.side < right_paddle.y + right_paddle.height: ball.lower_right = False ball.last_movement = 'ball.lower_right' ball.lower_left = True else: score_A += 1 balls.remove(ball) #ball.start() if ball.last_movement == 'ball.lower_left': if ball.y + ball.side > HEIGHT - 50: ball.lower_right = False ball.last_movement = 'ball.lower_right' ball.upper_right = True for event in pygame.event.get(): if event.type == pygame.QUIT: run = False quit() keys = pygame.key.get_pressed() if keys[pygame.K_UP] and right_paddle.y > 50: right_paddle.y -= right_paddle.vel if keys[pygame.K_w] and left_paddle.y > 50: left_paddle.y -= left_paddle.vel if keys[pygame.K_DOWN] and right_paddle.y + right_paddle.height < HEIGHT - 50: right_paddle.y += right_paddle.vel if keys[pygame.K_s] and left_paddle.y + left_paddle.height < HEIGHT - 50: left_paddle.y += left_paddle.vel if keys[pygame.K_SPACE]: pass redraw_window() quit() def main_menu(): run = True play_button = Button(green, 100, 350, 150, 75, "Play Pong") quit_button = Button(red, 550, 350, 150, 75, "Quit") pong_text = large_font.render("Let's Play Pong!!!", 1, black) while run: window.fill(white) play_button.draw(window, black) quit_button.draw(window, black) window.blit(pong_text, (int(WIDTH / 2 - pong_text.get_width() / 2), 100)) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.QUIT: run = False quit() if event.type == pygame.MOUSEMOTION: if play_button.hover(pygame.mouse.get_pos()): play_button.color = (0, 200, 0) else: play_button.color = green if quit_button.hover(pygame.mouse.get_pos()): quit_button.color = (200, 0, 0) else: quit_button.color = red if event.type == pygame.MOUSEBUTTONDOWN: if play_button.hover(pygame.mouse.get_pos()): main() if quit_button.hover(pygame.mouse.get_pos()): run = False quit() main_menu() Thanks!!!
Answer to Every time the ball bounces past the edge, the score increments for a split second, and it goes back to 0 every time the ball respawns. The score is continuously initialized in the main loop. You have to initialize the score before the loop: def main(): # [...] score_A = 0 # <--- INSERT score_B = 0 while run: # score_A = 0 <--- DELETE # score_B = 0
Answer to question 1: Yes. There is definitely a lot more effective way to do things. There is no need to make so many variables for direction. Simply say direction = [True, False], where direction[0] represents left and not direction[0] represents right in x-axis. Similarly, direction[1] represents y axis. This also solves your problem of randomizing the direction at start. You can simply do direction = [random.randint(0, 1), random.randint(0, 1)] in your init method to randomize the direction. The same way, make a list for speed as well. self.speed = [0.5, random.uniform(0.1, 1)]. This way, speed in left and right will always be same, but y will vary depending on the random number chosen, so there is proper randomness and you also don't have to hardcode random directions in. With this, movement becomes really simple. class Ball: def __init__(self, x, y, color, size): self.x = x self.y = y self.color = color self.size = size self.direction = [random.randint(0, 1), random.randint(0, 1)] self.speed = [0.5, random.uniform(0.1, 1)] def draw(self, display): pygame.draw.rect(display, self.color, (self.x, self.y, self.size, self.size)) def move(self): if self.direction[0]: self.x += self.speed[0] else: self.x -= self.speed[0] if self.direction[1]: self.y += self.speed[0] else: self.y -= self.speed[0] Because we have defined direction directions are Booleans, changing the state also becomes really easy. If the ball hits the paddle, you can simply toggle the bool direction[0] = not direction[0] in x and pick a new random number for y, instead of manually assigning bools. def switchDirection(self): self.direction = not self.direction self.speed[1] = random.uniform(0.1, 1) Paddle can be improved slightly as well, by giving Paddle class a move function instead of moving in the main loop. It just means you have to write less code. def move(self, vel, up=pygame.K_UP, down=pygame.K_DOWN): keys = pygame.key.get_perssed() if keys[up]: self.y -= vel if keys[down]: self.y += vel For collisions, I recommend using pygame.Rect() and colliderect since it's a lot more robust and probably more efficient as well. Example: import random import pygame WIN = pygame.display D = WIN.set_mode((800, 500)) class Paddle: def __init__(self, x, y, width, height, vel, color): self.x = x self.y = y self.width = width self.height = height self.vel = vel self.color = color def move(self, vel, up=pygame.K_UP, down=pygame.K_DOWN): keys = pygame.key.get_pressed() if keys[up]: self.y -= vel if keys[down]: self.y += vel def draw(self, window): pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height), 0) def getRect(self): return pygame.Rect(self.x, self.y, self.width, self.height) left_paddle = Paddle(20, 100, 10, 50, 5, (0, 0, 0)) right_paddle = Paddle(770, 350, 10, 50, 5, (0, 0, 0)) class Ball: def __init__(self, x, y, color, size): self.x = x self.y = y self.color = color self.size = size self.direction = [random.randint(0, 1), random.randint(0, 1)] self.speed = [0.3, random.uniform(0.2, 0.2)] def draw(self, window): pygame.draw.rect(window, self.color, (self.x, self.y, self.size, self.size)) def switchDirection(self): self.direction[0] = not self.direction[0] self.direction[1] = not self.direction[1] self.speed = [0.2, random.uniform(0.1, 0.5)] def bounce(self): self.direction[1] = not self.direction[1] self.speed = [0.2, random.uniform(0.01, 0.2)] def move(self): if self.direction[0]: self.x += self.speed[0] else: self.x -= self.speed[0] if self.direction[1]: self.y += self.speed[1] else: self.y -= self.speed[1] def getRect(self): return pygame.Rect(self.x, self.y, self.size, self.size) def boundaries(self): if ball.x <= 10: self.switchDirection() if ball.x + self.size >= 800: self.switchDirection() if ball.y + self.size >= 490: self.bounce() if ball.y <= 0: self.bounce() ball = Ball(400, 250, (255, 0, 0), 20) while True: pygame.event.get() D.fill((255, 255, 255)) ball.draw(D) ball.boundaries() ball.move() #print(ball.x, ball.y) left_paddle.draw(D) right_paddle.draw(D) right_paddle.move(0.4) left_paddle.move(0.4, down=pygame.K_s, up=pygame.K_w) if left_paddle.getRect().colliderect(ball.getRect()): ball.switchDirection() if right_paddle.getRect().colliderect(ball.getRect()): ball.switchDirection() WIN.flip()
I basically coded every possible movement of the ball when it bounces on the edge. I spent hours working on it, and I got it to work, I was just wondering if there was a more efficient way to produce a similar output with my code (coz I know this is supposed to be a beginner project)? and How can I make the ball spawn at random directions? at the start (and restart) of every round? Rather than implementing "every" direction of the ball you can use float coordinates. these variables are usualy called dx and dy. This way getting a random or reversed direction for your ball is simple, just use random or inverse values for dx and dy. Th update for your ball should then look like: def update(self. dt): self.x += self.dx * self.speed * time_elapsed self.y += self.dy * self.speed * time_elapsed # Time elasped is often called dt. Every time the ball bounces past the edge, the score increments for a split second, and it goes back to 0 every time the ball respawns. See Rabid76's answer. Ideally you should have a GameState object with scores, lives and other stuff as attributes.
Some food for thought on how to reduce all that ball movement/collision code, and make things a bit more reusable: import pygame class Ball: def __init__(self, bounds, color): from random import randint, choice self.bounds = bounds self.position = pygame.math.Vector2( randint(self.bounds.left, self.bounds.left+self.bounds.width), randint(self.bounds.top, self.bounds.top+self.bounds.height) ) self.velocity = pygame.math.Vector2(choice((-1, 1)), choice((-1, 1))) self.color = color self.size = 8 def draw(self, window): pygame.draw.rect( window, self.color, ( self.position.x-self.size, self.position.y-self.size, self.size*2, self.size*2 ), 0 ) def update(self): self.position.x += self.velocity.x self.position.y += self.velocity.y if not self.bounds.left+self.size < self.position.x < self.bounds.left+self.bounds.width-self.size: self.velocity.x *= -1 if not self.bounds.top+self.size < self.position.y < self.bounds.top+self.bounds.height-self.size: self.velocity.y *= -1 def main(): from random import randint window_width, window_height = 800, 500 window = pygame.display.set_mode((window_width, window_height)) pygame.display.set_caption("Pong") clock = pygame.time.Clock() black = (0, 0, 0) white = (255, 255, 255) padding = 20 bounds = pygame.Rect(padding, padding, window_width-(padding*2), window_height-(padding*2)) ball = Ball(bounds, white) def redraw_window(): window.fill(black) pygame.draw.rect( window, white, ( padding, padding, window_width-(padding*2), window_height-(padding*2) ), 1 ) ball.draw(window) pygame.display.update() while True: clock.tick(60) ball.update() for event in pygame.event.get(): if event.type == pygame.QUIT: break else: redraw_window() continue break pygame.quit() return 0 if __name__ == "__main__": import sys sys.exit(main()) This isn't a complete drop-in replacement, I've just reimplemented the Ball class. There are no paddles. Essentially, when instantiating a ball, you pass in a pygame.Rect, which describes the bounds in which the ball is allowed to bounce around in. You also pass in a color tuple. The ball then picks a random position within the bounds (the position is a pygame.math.Vector2, as opposed to storing x and y as separate instance variables). A ball also has a velocity, which is also a pygame.math.Vector2, so that you may have independent velocity components - one for x (horizontal velocity) and one for y (vertical velocity). The size of a ball simply describes the dimensions of the ball. If the size is set to 8, for example, then a ball will be 16x16 pixels. The Ball class also has an update method, which is invoked once per game-loop iteration. It moves the ball to the next position dictated by the velocity, and checks to see if the ball is colliding with the bounds. If it is, reverse the corresponding velocity component.