I am rewriting a simple game. Before I just used e.g. pygame.draw.circle() to create and interact with objects.
However, there was a need to use masks for more complex tasks (like collision with polygons).
This is how I let the ball bounce on walls before:
def bounce(self):
# Right boundary
if self.x > width - self.radius:
self.x = 2 * (width - self.radius) - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Left boundary
elif self.x < self.radius:
self.x = 2 * self.radius - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Top boundary
if self.y > height - self.radius:
self.y = 2 * (height - self.radius) - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Bottom boundary
elif self.y < self.radius:
self.y = 2 * self.radius - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
But now with masks implemented, it only stops when it hits the wall but doesn't bounce off anymore.
Full code:
import pygame
from pygame.math import Vector2
import math
width = 1150
height = 800
# Colors
GOLD = (255, 215, 0)
drag = 0.999 # Between 0 and 1
elasticity = 0.75 # Between 0 and 1
pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Define game object class
class Circle:
def __init__(self, coordinates, velocity, angle, radius, objectColor):
self.x = coordinates[0]
self.y = coordinates[1]
self.velocity = velocity
self.angle = angle
self.radius = radius
self.objectColor = objectColor
self.initSurface = pygame.Surface((self.radius*2, self.radius*2), pygame.SRCALPHA)
self.rectangle = self.initSurface.get_rect(center=Vector2(self.x, self.y))
self.surface = self.initSurface
self.mask = None
self.draw()
def draw(self):
pygame.draw.circle(self.initSurface, self.objectColor, [self.radius, self.radius], self.radius)
self.mask = pygame.mask.from_surface(self.initSurface)
def move(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.velocity = Vector2(self.velocity) * drag
self.rectangle.center = Vector2(self.x, self.y)
def bounce(self):
# Right boundary
if self.x > width - self.radius:
self.x = 2 * (width - self.radius) - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Left boundary
elif self.x < self.radius:
self.x = 2 * self.radius - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Top boundary
if self.y > height - self.radius:
self.y = 2 * (height - self.radius) - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Bottom boundary
elif self.y < self.radius:
self.y = 2 * self.radius - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
class Polygon:
def __init__(self, coordinates, velocity, angle, pointList, objectColor):
self.x = coordinates[0]
self.y = coordinates[1]
self.velocity = velocity
self.angle = angle
self.pointList = pointList
self.objectColor = objectColor
self.initSurface = pygame.Surface((max(self.pointList, key=lambda item: item[0])[0],
max(self.pointList, key=lambda item: item[1])[1]), pygame.SRCALPHA)
self.rectangle = self.initSurface.get_rect(center=Vector2(self.x, self.y))
self.surface = self.initSurface
self.mask = None
self.draw()
def draw(self):
pygame.draw.polygon(self.initSurface, self.objectColor, self.pointList)
self.mask = pygame.mask.from_surface(self.initSurface)
def move(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.rectangle.center = Vector2(self.x, self.y)
def rotate(self, angle):
self.angle += angle
self.velocity.rotate_ip(-angle)
surface = pygame.transform.rotate(self.initSurface, self.angle)
self.rectangle = surface.get_rect(center=self.rectangle.center)
# We need a new mask after the rotation.
self.mask = pygame.mask.from_surface(surface)
self.surface = surface
# Colliding game object particles
def collide(p1, p2):
offset = p1.rectangle[0] - p2.rectangle[0], p1.rectangle[1] - p2.rectangle[1]
overlap = myBall.mask.overlap(p1.mask, offset)
if overlap:
p2.velocity = Vector2(p1.velocity) * 1.4
# Images
BG_IMG = pygame.Surface((1150, 800))
BG_IMG.fill((30, 120, 30))
# Init Ball and car (input)
myBall = Circle(Vector2(575, 400), Vector2(0, 0), 0, 60, GOLD)
myInput = Polygon(Vector2(470, 370), Vector2(3, 0), 0, [(0, 0), (50, 10), (50, 20), (0, 30)], GOLD)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
myInput.rotate(5)
elif keys[pygame.K_RIGHT]:
myInput.rotate(-5)
# Move the car
myInput.move()
# Move the ball
myBall.velocity *= .99 # Friction
myBall.move()
myBall.bounce()
# Car collision.
collide(myInput, myBall)
# Drawing
screen.blit(BG_IMG, (0, 0))
screen.blit(myBall.surface, myBall.rectangle)
screen.blit(myInput.surface, myInput.rectangle)
pygame.display.flip()
clock.tick(60)
pygame.quit()
What am I missing here? I can't see through anymore.
In the bounce method you make the the velocity smaller, you never flip it so, just add a minus sign and it should bounce back instead of moving slower and slower into the boundry
self.velocity = Vector2(-self.velocity) * elasticity
Related
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)
I have a character in PyGame which I want to move forward depending on its rotation. Currently it's very strange and wonky.
Here's all my code: https://www.toptal.com/developers/hastebin/enapuravet.lua
Here's the relevent code:
def rotate(self, amount):
self.rotation += amount
if self.rotation == 181:
self.rotation = -180
elif self.rotation == -181:
self.rotation = 180
print(self.rotation)
self.capybara = pygame.transform.rotate(self.base_capybara, self.rotation)
self.rect = self.capybara.get_rect(center = self.rect.center)
def move_forward(self, speed):
print(self.rotation)
new_coordinates = calculate_new_xy((self.x, self.y), 2, self.rotation*(math.pi/180))
self.x = new_coordinates[0]
self.y = new_coordinates[1]
self.rect = self.capybara.get_rect(center = (self.x, self.y))
def calculate_new_xy(old_xy,speed,angle_in_radians):
print(angle_in_radians)
new_x = old_xy[0] + (speed*math.cos(angle_in_radians))
new_y = old_xy[1] + (speed*math.sin(angle_in_radians))
return new_x, new_y
Here's what it does:
https://gyazo.com/3b7bf1c5b2e760a53913b6d3acae6e67
I also tried some other x_y calculating functions like this:
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
but they had the same/very similar results.
This is the capybara:
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .center) of the rectangle.
The unit of the angle of math.sin and math.cos is Randian. You needs to convert the angle from degrees to radians with math.radians:
class Capybara:
def __init__(self, size_multiplier):
# [...]
self.x, self.y = 300, 300
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def rotate(self, amount):
self.rotation += amount
ifv self.rotation > 360:
self.rotation -= 360
elif self.rotation < 0:
self.rotation += 360
self.capybara = pygame.transform.rotate(self.base_capybara, self.rotation)
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def move(self, move):
self.x += move * math.cos(math.radians(self.rotation + 90))
self.y -= move * math.sin(math.radians(self.rotation + 90))
See also Move Character with Vector, Image rotation while moving or How to turn the sprite in pygame while moving with the keys.
Minimal example:
import pygame, math
pygame.init()
size = width, height = 600, 600
green = 50, 168, 82
screen = pygame.display.set_mode(size)
class Capybara:
def __init__(self, size_multiplier):
self.capybara = pygame.image.load('capybara.png')
self.o_size = 92, 206
self.new_size = (self.o_size[0] * size_multiplier,self.o_size[1] * size_multiplier)
self.capybara = pygame.transform.scale(self.capybara, self.new_size)
self.base_capybara = self.capybara
self.rotation = 0
self.x, self.y = 300, 300
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def rotate(self, amount):
self.rotation += amount
if self.rotation > 360:
self.rotation -= 360
elif self.rotation < 0:
self.rotation += 360
self.capybara = pygame.transform.rotate(self.base_capybara, self.rotation)
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def move(self, move):
self.x += move * math.cos(math.radians(self.rotation + 90))
self.y -= move * math.sin(math.radians(self.rotation + 90))
capybara = Capybara(0.8)
fpsClock = pygame.time.Clock()
rotate_l = False
rotate_r = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
keys = pygame.key.get_pressed()
rotate = keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]
move = keys[pygame.K_DOWN] - keys[pygame.K_UP]
capybara.move(-move * 5)
capybara.rotate(-rotate)
screen.fill(green)
screen.blit(capybara.capybara, capybara.rect)
pygame.display.update()
fpsClock.tick(60)
I have implemented a rotation for the player sprite so it faces the mouse, but when I move outside of the original window and the 'camera' starts moving, the rotation stops working.
Here is my code for player rotation:
class Player(pg.sprite.Sprite):
def __init__(self, x, y):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((TILESIZE, TILESIZE))
self.image.fill(WHITE)
self.orig_img = self.image
self.rect = self.image.get_rect(center=(x, y))
self.vel = vec(0, 0)
self.pos = vec(x, y) * TILESIZE
self.last_shot = 0
self.health = 100
def update(self):
self.rotate()
self.get_keys()
self.pos += self.vel * dt
self.rect.center = self.pos
def rotate(self):
_, self.angle = (pg.mouse.get_pos() - self.pos).as_polar()
self.image = pg.transform.rotozoom(self.orig_img, self.angle, 1)
self.rect = self.image.get_rect(center=self.rect.center)
def get_keys(self):
self.vel = vec(0, 0)
keys = pg.key.get_pressed()
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.vel.x = -PLAYERSPEED
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.vel.x = PLAYERSPEED
if keys[pg.K_UP] or keys[pg.K_w]:
self.vel.y = -PLAYERSPEED
if keys[pg.K_DOWN] or keys[pg.K_s]:
self.vel.y = PLAYERSPEED
if self.vel.x != 0 and self.vel.y != 0:
self.vel *= 0.7071
if keys[pg.K_SPACE]:
now = pg.time.get_ticks()
if now - self.last_shot > BULLET_RATE:
self.last_shot = now
self.shoot()
def shoot(self):
dir = vec(1, 0).rotate(self.angle)
bullet = Bullet(self.pos, dir)
all_sprites.add(bullet)
bullets.add(bullet)
And here for camera and implementing camera in my game loop:
class Camera:
def __init__(self, width, height):
self.camera = pg.Rect(0, 0, width, height)
self.width = width
self.height = height
def apply(self, entity):
return entity.rect.move(self.camera.topleft)
def update(self, target):
x = -target.rect.x + int(WIDTH / 2)
y = -target.rect.y + int(HEIGHT / 2)
# limit scrolling to map size
x = min(0, x) # left
y = min(0, y) # top
x = max(-(self.width - WIDTH), x) # right
y = max(-(self.height - HEIGHT), y) # bottom
self.camera = pg.Rect(x, y, self.width, self.height)
Inside game loop:
for sprite in all_sprites:
screen.blit(sprite.image, camera.apply(sprite))
all_sprites.update()
camera.update(player)
I have read from other posts about making the mouse pos from screen cords to world cords, but I can't figure out how I would do that in my case...
The angle of the image is calculated with the following line of code:
_, self.angle = (pg.mouse.get_pos() - self.pos).as_polar()
pg.mouse.get_pos() is a screen coordinate. So the code will only work if self.pos is also a screen coordinate.
If self.pos is a coordinate in the world, you need to calculate the position relative to the screen.
Pseudo code:
screen_pos = self.pos - camera.camera.topleft
_, self.angle = (pg.mouse.get_pos() - screen_pos).as_polar()
So I been having this problem with my rectangle/projectile rotation, I want it so that my rectangle/projectile will rotate with my rotating sprite but the code I'm trying for it is not working for me, The code that I'm trying is giving me this error. 'pygame.Surface' object has no attribute 'x' I have tried moving the code around, I have also tried to change the code so I wont get the error no more, and I have tried using a hitbox but I still keep getting the error. This is my two sprites
The code I'm trying
self.dist = 100
dx = self.pin.x + self.dist*math.cos(-self.pin.angle*(math.pi/180)) -65 # why offset needed ?
dy = self.pin.y + self.dist*math.sin(-self.pin.angle*(math.pi/180)) -50 # why offset needed ?
self.rect.topleft = (dx,dy)
pygame.draw.rect(window,self.color,self.rect)
My full code
import pygame,math,random
pygame.init()
# Windowing screen width and height
width = 500
height = 500
window = pygame.display.set_mode((width,height))
# Name of window
pygame.display.set_caption("Game")
# The Background
background = pygame.image.load("img/BG.png")
def blitRotate(surf, image, pos, originPos, angle):
# calcaulate the axis aligned bounding box of the rotated image
w, h = image.get_size()
sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle))
min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
# calculate the translation of the pivot
pivot = pygame.math.Vector2(originPos[0], -originPos[1])
pivot_rotate = pivot.rotate(angle)
pivot_move = pivot_rotate - pivot
# calculate the upper left origin of the rotated image
origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
# get a rotated image
rotated_image = pygame.transform.rotate(image, angle)
# rotate and blit the image
surf.blit(rotated_image, origin)
# Player class
class Player:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.speed = 4
self.cannon = pygame.image.load("img/Cannon.png")
self.cannon = pygame.transform.scale(self.cannon,(self.cannon.get_width()//2, self.cannon.get_height()//2))
self.rect = pygame.Rect(x,y,width,height)
self.hitbox = (self.x,self.y,30,30)
self.image = self.cannon
self.rect = self.image.get_rect(center = (self.x, self.y))
self.look_at_pos = (self.x, self.y)
self.isLookingAtPlayer = False
self.look_at_pos = (x,y)
self.angle = 0
def get_rect(self):
self.rect.topleft = (self.x,self.y)
return self.rect
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.hitbox)
player_rect = self.cannon.get_rect(center = self.get_rect().center)
player_rect.centerx -= 0
player_rect.centery += 90
# Another part of cannon rotating
dx = self.look_at_pos[0] - self.rect.centerx
dy = self.look_at_pos[1] - self.rect.centery
angle = (180/math.pi) * math.atan2(-dy, dx) - 90
gun_size = self.image.get_size()
pivot_abs = player_rect.centerx, player_rect.top + 13
pivot_rel = (gun_size[0] // 2, 105)
pygame.draw.rect(window,self.color,self.rect)
blitRotate(window, self.image,pivot_abs, pivot_rel, angle)
def lookAt( self, coordinate ):
self.look_at_pos = coordinate
# Players gun
class projectile(object):
def __init__(self,x,y,dirx,diry,color):
self.x = x
self.y = y
self.dirx = dirx
self.diry = diry
self.pin = pygame.image.load("img/Pin.png")
self.pin = pygame.transform.scale(self.pin,(self.pin.get_width()//6, self.pin.get_height()//6))
self.rect = self.pin.get_rect()
self.topleft = ( self.x, self.y )
self.speed = 10
self.color = color
self.hitbox = (self.x + 20, self.y, 30,40)
def move(self):
self.x += self.dirx * self.speed
self.y += self.diry * self.speed
def draw(self):
self.rect.topleft = (round(self.x), round(self.y))
window.blit(self.pin,self.rect)
self.hitbox = (self.x + 20, self.y,30,30)
# For rotating the the projectile
self.dist = 100
dx = self.pin.x + self.dist*math.cos(-self.pin.angle*(math.pi/180)) -65 # why offset needed ?
dy = self.pin.y + self.dist*math.sin(-self.pin.angle*(math.pi/180)) -50 # why offset needed ?
self.rect.topleft = (dx,dy)
pygame.draw.rect(window,self.color,self.rect)
# The color white
white = (255,255,255)
# The xy cords, width, height and color of my classes[]
playerman = Player(350,385,34,75,white)
# This is where my balloons get hit by the bullet and disappers
# redrawing window
def redrawwindow():
window.fill((0,0,0))
# Drawing the window in
window.blit(background,(0,0))
# drawing the player in window
playerman.draw()
# Drawing the players bullet
for bullet in bullets:
bullet.draw()
# Frames for game
fps = 30
clock = pygame.time.Clock()
#projectile empty list
bullets = []
# main loop
run = True
while run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
if len(bullets) < 6700:
mousex , mousey = pygame.mouse.get_pos()
start_x , start_y = playerman.rect.x + 12, playerman.rect.y - 3
mouse_x , mouse_y = event.pos
dir_x , dir_y = mouse_x - start_x , mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance , dir_y/distance, (0,0,0))
bullets.append(new_bullet)
for bullet in bullets[:]:
bullet.move()
if bullet.x < 0 or bullet.x > 900 or bullet.y < 0 or bullet.y > 900:
bullets.pop(bullets.index(bullet))
# gun rotation
mousex, mousey = pygame.mouse.get_pos()
if not playerman.isLookingAtPlayer:
playerman.lookAt((mousex, mousey))
# telling game that key means when a key get pressed
keys = pygame.key.get_pressed()
# The player moving when the key a is pressed
if keys[pygame.K_a] and playerman.x > playerman.speed:
playerman.x -= playerman.speed
# The player moving when the key d is pressed
if keys[pygame.K_d] and playerman.x < 500 - playerman.width - playerman.speed:
playerman.x += playerman.speed
# Calling the redraw function
redrawwindow()
# updating game
pygame.display.update()
# quiting the game
pygame.quit()
You are already moving the pin in the projectile.move method. In the projectile.draw method you just have to rotate the pin. See How do I rotate an image around its center using PyGame? and How to rotate an image(player) to the mouse direction?:
class projectile(object):
# [...]
def draw(self):
self.rect.center = (round(self.x), round(self.y))
angle = math.degrees(math.atan2(-self.diry, self.dirx)) - 90
rotated_pin = pygame.transform.rotate(self.pin, angle)
rotated_rect = rotated_pin.get_rect(center = self.rect.center)
pygame.draw.rect(window,self.color, rotated_rect)
window.blit(rotated_pin, rotated_rect)
self.hitbox = (self.x + 20, self.y,30,30)
You have to find a starting position of the pin. The pin should start somewhere on top of the blowpipe. Add a get_pivot method to the Player class:
class Player:
# [...]
def get_pivot(self):
player_rect = self.cannon.get_rect(center = self.get_rect().center)
return player_rect.centerx, player_rect.top + 103
Add a get_angle method that calculates the angle of the blowpipe:
class Player:
# [...]
def get_angle(self):
pivot_abs = self.get_pivot()
dx = self.look_at_pos[0] - pivot_abs[0]
dy = self.look_at_pos[1] - pivot_abs[1]
return math.degrees(math.atan2(-dy, dx))
Use this methods to compute the top of the blow pipes:
class Player:
# [...]
def get_top(self):
pivot_x, pivot_y = self.get_pivot()
angle = self.get_angle()
length = 100
top_x = pivot_x + length * math.cos(math.radians(angle))
top_y = pivot_y - length * math.sin(math.radians(angle))
return top_x, top_y
You can also use the get_pivot and get_angle methods in the draw method:
class Player:
# [...]
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.hitbox)
gun_size = self.image.get_size()
pivot_abs = self.get_pivot()
pivot_rel = (gun_size[0] // 2, 105)
angle = self.get_angle() - 90
pygame.draw.rect(window,self.color,self.rect)
blitRotate(window, self.image,pivot_abs, pivot_rel, angle)
Use get_top to set the starting position of the pin:
mousex, mousey = pygame.mouse.get_pos()
start_x, start_y = playerman.get_top()
mouse_x, mouse_y = event.pos
dir_x, dir_y = mouse_x - start_x , mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance, dir_y/distance, (0,0,0))
bullets.append(new_bullet)
Draw the pins in before the blowpipe so it looks like the pins are coming out of the blowpipe:
def redrawwindow():
window.fill((0,0,0))
# Drawing the window in
window.blit(background,(0,0))
# Drawing the players bullet
for bullet in bullets:
bullet.draw()
# drawing the player in window
playerman.draw()
Complete examplee:
import pygame,math,random
pygame.init()
# Windowing screen width and height
width = 500
height = 500
window = pygame.display.set_mode((width,height))
# Name of window
pygame.display.set_caption("Game")
# The Background
background = pygame.image.load("img/BG.png")
def blitRotate(surf, image, pos, originPos, angle):
# calcaulate the axis aligned bounding box of the rotated image
w, h = image.get_size()
sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle))
min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
# calculate the translation of the pivot
pivot = pygame.math.Vector2(originPos[0], -originPos[1])
pivot_rotate = pivot.rotate(angle)
pivot_move = pivot_rotate - pivot
# calculate the upper left origin of the rotated image
origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
# get a rotated image
rotated_image = pygame.transform.rotate(image, angle)
# rotate and blit the image
surf.blit(rotated_image, origin)
# Player class
class Player:
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.speed = 4
self.cannon = pygame.image.load("img/Cannon.png")
self.cannon = pygame.transform.scale(self.cannon,(self.cannon.get_width()//2, self.cannon.get_height()//2))
self.rect = pygame.Rect(x,y,width,height)
self.hitbox = (self.x,self.y,30,30)
self.image = self.cannon
self.rect = self.image.get_rect(center = (self.x, self.y))
self.look_at_pos = (self.x, self.y)
self.isLookingAtPlayer = False
self.look_at_pos = (x,y)
self.angle = 0
def get_rect(self):
self.rect.topleft = (self.x,self.y)
return self.rect
def get_pivot(self):
player_rect = self.cannon.get_rect(center = self.get_rect().center)
return player_rect.centerx, player_rect.top + 103
def get_angle(self):
pivot_abs = self.get_pivot()
dx = self.look_at_pos[0] - pivot_abs[0]
dy = self.look_at_pos[1] - pivot_abs[1]
return math.degrees(math.atan2(-dy, dx))
def get_top(self):
pivot_x, pivot_y = self.get_pivot()
angle = self.get_angle()
length = 100
top_x = pivot_x + length * math.cos(math.radians(angle))
top_y = pivot_y - length * math.sin(math.radians(angle))
return top_x, top_y
def draw(self):
self.rect.topleft = (self.x,self.y)
pygame.draw.rect(window,self.color,self.hitbox)
gun_size = self.image.get_size()
pivot_abs = self.get_pivot()
pivot_rel = (gun_size[0] // 2, 105)
angle = self.get_angle() - 90
pygame.draw.rect(window,self.color,self.rect)
blitRotate(window, self.image,pivot_abs, pivot_rel, angle)
def lookAt( self, coordinate ):
self.look_at_pos = coordinate
# Players gun
class projectile(object):
def __init__(self,x,y,dirx,diry,color):
self.x = x
self.y = y
self.dirx = dirx
self.diry = diry
self.pin = pygame.image.load("img/Pin.png")
self.pin = pygame.transform.scale(self.pin,(self.pin.get_width()//6, self.pin.get_height()//6))
self.rect = self.pin.get_rect()
self.center = ( self.x, self.y )
self.speed = 10
self.color = color
self.hitbox = (self.x + 20, self.y, 30,40)
def move(self):
self.x += self.dirx * self.speed
self.y += self.diry * self.speed
def draw(self):
self.rect.center = (round(self.x), round(self.y))
angle = math.degrees(math.atan2(-self.diry, self.dirx)) - 90
rotated_pin = pygame.transform.rotate(self.pin, angle)
rotated_rect = rotated_pin.get_rect(center = self.rect.center)
pygame.draw.rect(window,self.color, rotated_rect)
window.blit(rotated_pin, rotated_rect)
self.hitbox = (self.x + 20, self.y,30,30)
# The color white
white = (255,255,255)
# The xy cords, width, height and color of my classes[]
playerman = Player(350,385,34,75,white)
# This is where my balloons get hit by the bullet and disappers
# redrawing window
def redrawwindow():
window.fill((0,0,0))
# Drawing the window in
window.blit(background,(0,0))
# Drawing the players bullet
for bullet in bullets:
bullet.draw()
# drawing the player in window
playerman.draw()
# Frames for game
fps = 30
clock = pygame.time.Clock()
#projectile empty list
bullets = []
# main loop
run = True
while run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
if len(bullets) < 6700:
mousex, mousey = pygame.mouse.get_pos()
start_x, start_y = playerman.get_top()
mouse_x, mouse_y = event.pos
dir_x, dir_y = mouse_x - start_x , mouse_y - start_y
distance = math.sqrt(dir_x**2 + dir_y**2)
if distance > 0:
new_bullet = projectile(start_x, start_y, dir_x/distance, dir_y/distance, (0,0,0))
bullets.append(new_bullet)
for bullet in bullets[:]:
bullet.move()
if bullet.x < 0 or bullet.x > 900 or bullet.y < 0 or bullet.y > 900:
bullets.pop(bullets.index(bullet))
# gun rotation
mousex, mousey = pygame.mouse.get_pos()
if not playerman.isLookingAtPlayer:
playerman.lookAt((mousex, mousey))
# telling game that key means when a key get pressed
keys = pygame.key.get_pressed()
# The player moving when the key a is pressed
if keys[pygame.K_a] and playerman.x > playerman.speed:
playerman.x -= playerman.speed
# The player moving when the key d is pressed
if keys[pygame.K_d] and playerman.x < 500 - playerman.width - playerman.speed:
playerman.x += playerman.speed
# Calling the redraw function
redrawwindow()
# updating game
pygame.display.update()
# quiting the game
pygame.quit()
I don't know why but collisions aren't being detected in my game. Its an asteroids style game and I want the bullets to destroy the asteroids, and the game to end when the ship gets hit by one, but they're passing through each other without doing any of that.
Here's my code:
import pygame
from math import sin, cos, pi
from random import randint
scr_width = 800
scr_height = 600
window = pygame.display.set_mode((scr_width, scr_height))
pygame.display.set_caption("Asteroids")
clock = pygame.time.Clock()
space_img = pygame.image.load("sprites/space.jpg")
red = (255, 0, 0)
class Ship:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 0
self.vel = 0
self.vel_max = 12
self.angle = 0
self.hitbox = (self.x, self.y, 10, 10)
self.ship_img = pygame.image.load("sprites/ship_off.png")
self.ship_img_copy = pygame.transform.rotate(self.ship_img, self.angle)
def draw(self):
self.ship_img = pygame.image.load("sprites/ship_off.png")
self.ship_img_copy = pygame.transform.rotate(self.ship_img, self.angle)
window.blit(self.ship_img_copy,
(self.x - (self.ship_img_copy.get_width()) / 2, self.y - (self.ship_img_copy.get_height()) / 2))
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.ship_img = pygame.image.load("sprites/ship_on.png")
self.ship_img_copy = pygame.transform.rotate(self.ship_img, self.angle)
window.blit(self.ship_img_copy,
(self.x - (self.ship_img_copy.get_width()) / 2, self.y - (self.ship_img_copy.get_height()) / 2))
# collision stuff
self.mask = pygame.mask.from_surface(self.ship_img_copy)
self.rect = pygame.Rect(self.x - (self.ship_img_copy.get_width()) / 2,
self.y - (self.ship_img_copy.get_height()) / 2,
self.ship_img_copy.get_width(), self.ship_img_copy.get_height())
def move(self):
keys = pygame.key.get_pressed()
# todo acceleration and thrust mechanics
if keys[pygame.K_w]:
self.vel = min(self.vel + 1, self.vel_max)
elif self.vel > 0:
self.vel = self.vel - 0.4
if keys[pygame.K_a]:
self.angle += 7
if keys[pygame.K_d]:
self.angle -= 7
self.x += self.vel * cos(self.angle * (pi / 180) + (90 * pi / 180))
self.y -= self.vel * sin(self.angle * (pi / 180) + 90 * (pi / 180))
# So that if it leaves one side it comes from the other
if self.y < 0:
self.y = (self.y - self.vel) % 600
elif self.y > 600:
self.y = (self.y + self.vel) % 600
elif self.x < 0:
self.x = (self.x - self.vel) % 800
elif self.x > 800:
self.x = (self.x + self.vel) % 800
class Asteroid:
def __init__(self):
self.ang_change = randint(1, 5)
self.ang = randint(0, 90) * (pi / 180)
y_values = [1, 599]
self.sx = randint(0, 800)
self.sy = y_values[randint(0, 1)]
# If object spawns from the top, it moves down instead of moving up and de-spawning immediately
if self.sy == y_values[0]:
self.neg = -1
else:
self.neg = 1
self.speed = randint(5, 10)
self.ang += self.ang_change
self.asteroid_angle = randint(0, 80)
self.asteroid_img = pygame.image.load("sprites/asteroid.png")
self.asteroid_copy = pygame.transform.rotate(self.asteroid_img, self.ang)
def generate(self):
self.ang += self.ang_change
self.asteroid_img = pygame.image.load("sprites/asteroid.png")
self.asteroid_copy = pygame.transform.rotate(self.asteroid_img, self.ang)
window.blit(self.asteroid_copy,
(self.sx - (self.asteroid_copy.get_width()) / 2, self.sy - (self.asteroid_copy.get_height()) / 2))
# collision stuff
self.mask = pygame.mask.from_surface(self.asteroid_copy)
self.rect = pygame.Rect(self.sx - (self.asteroid_copy.get_width()) / 2,
self.sy - (self.asteroid_copy.get_height()) / 2,
self.asteroid_copy.get_width(), self.asteroid_copy.get_height())
class Projectiles:
def __init__(self, x, y, angle):
self.x = x
self.y = y
self.angle = angle
self.vel = 20
self.bullet_body = pygame.image.load("sprites/bullet.png")
def draw(self):
self.bullet_body = pygame.image.load("sprites/bullet.png")
# collision stuff
self.rect = pygame.Rect(self.x - (self.bullet_body.get_width()) / 2,
self.y - (self.bullet_body.get_height()) / 2, 5, 5)
window.blit(self.bullet_body, (self.x - 2, self.y))
self.mask = pygame.mask.from_surface(self.bullet_body)
def redraw():
window.blit(space_img, (0, 0))
ship.draw()
for asteroid in asteroids:
asteroid.generate()
for bullet in bullets:
bullet.draw()
pygame.display.update()
# collision stuff
def collisions():
asteroids = pygame.sprite.Group()
bullets = pygame.sprite.Group()
if pygame.sprite.spritecollide(ship, asteroids, True, pygame.sprite.collide_mask):
print("hit")
pygame.quit()
pygame.sprite.groupcollide(asteroids, bullets, True, True, pygame.sprite.collide_mask)
# main loop
run = True
ship = Ship(400, 300)
next_fire = pygame.time.get_ticks() + 400
bullets = []
asteroids = []
while run:
clock.tick(60)
keys = pygame.key.get_pressed()
pygame.time.delay(35)
# collision stuff
collisions()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if keys[pygame.K_SPACE]:
if len(bullets) < 11 and pygame.time.get_ticks() >= next_fire:
bullets.append(
Projectiles(round(ship.x + ship.width - 6.5 // 2), round(ship.y + ship.width - 6.5 // 2), ship.angle))
next_fire = pygame.time.get_ticks() + 400
for bullet in bullets:
if 800 > bullet.x > 0 and 600 > bullet.y > 0:
bullet.x += bullet.vel * cos(bullet.angle * (pi / 180) + 90 * (pi / 180))
bullet.y -= bullet.vel * sin(bullet.angle * (pi / 180) + 90 * (pi / 180))
else:
bullets.pop(bullets.index(bullet))
# To limit the number of asteroids on screen
if len(asteroids) < 5:
asteroids.append(Asteroid())
for asteroid in asteroids:
if 805 > asteroid.sx > 0 and 605 > asteroid.sy > 0:
asteroid.sx += asteroid.speed * cos(asteroid.asteroid_angle * (pi / 180) + 90 * (pi / 180))
asteroid.sy -= asteroid.speed * sin(asteroid.asteroid_angle * (pi / 180) + 90 * (pi / 180)) * asteroid.neg
if asteroid.sx < 0:
asteroid.sx = (asteroid.sx - asteroid.speed) % 805
elif asteroid.sx > 805:
asteroid.sx = (asteroid.sx + asteroid.speed) % 805
else:
asteroids.pop(asteroids.index(asteroid))
window.fill((0, 0, 0))
ship.move()
redraw()
pygame.quit()
Is it that I can't make a seperate function for collisions, or have I missed something else?
I couldn't test code but usually problem is that collision's function use values from self.rect but you don't use them but self.x and self.y to keep position.
You should do in __init__
self.rect = self.image.get_rect()
self.rect.center = (x, y)
and later you can add to self.rect.x and self.rect.y and it will automatically calculate position for
blit(self.image, self.rect)
and for collision with other object
self.rect.colliderect(other.rect)
or with mouse in event.MOUSEBUTTONDOWN
self.rect.collidepoint(event.pos)
to test if object was clicked (ie. button).
If you want to use pygame Group
asteroids = pygame.sprite.Group()
bullets = pygame.sprite.Group()
then you shouldn't create them inside collisions() but at start instead of lists
asteroids = []
bullets = []
and you should add objects to groups instead of appending to lists.
asteroids.add(Asteroid())
and you can even run function for all objects without using for-loop
asteroids.draw()