I am trying to understand how to use deltatime in pygame, especially when it comes to collisions. I basically made a square bouncing around a window with some blocks.
Here is the version that doesn't use dt, and that works fine:
import pygame,sys
class Block(pygame.sprite.Sprite):
def __init__(self,pos,size,groups):
super().__init__(groups)
self.image = pygame.Surface(size)
self.image.fill('yellow')
self.rect = self.image.get_rect(topleft = pos)
class Ball(pygame.sprite.Sprite):
def __init__(self,groups,obstacles):
super().__init__(groups)
self.image = pygame.Surface((40,40))
self.image.fill('red')
self.rect = self.image.get_rect(center = (400,400))
# attributes for dt influenced movement
self.direction = pygame.math.Vector2((0.8,1))
self.speed = 6
self.obstacles = obstacles
def vertical_collision(self):
for sprite in self.obstacles.sprites():
if sprite.rect.colliderect(self.rect):
if self.direction.y < 0: # moving up
self.rect.top = sprite.rect.bottom
else: # moving down
self.rect.bottom = sprite.rect.top
self.direction.y *= -1
def horizontal_collision(self):
for sprite in self.obstacles.sprites():
if sprite.rect.colliderect(self.rect):
if self.direction.x < 0: # left
self.rect.left = sprite.rect.right
else: # right
self.rect.right = sprite.rect.left
self.direction.x *= -1
def wall_constraint(self):
if self.rect.right >= 800 or self.rect.left <= 0:
self.direction.x *= -1
if self.rect.bottom >= 800 or self.rect.top <= 0:
self.direction.y *= -1
def update(self):
self.wall_constraint()
self.rect.y += self.direction.y * self.speed
self.vertical_collision()
self.rect.x += self.direction.x * self.speed
self.horizontal_collision()
# setup
pygame.init()
screen = pygame.display.set_mode((800,800))
clock = pygame.time.Clock()
# sprite groups
all_sprites = pygame.sprite.Group()
collision_sprites = pygame.sprite.Group()
# sprite creation
Block((100,400),(60,200),[all_sprites,collision_sprites])
Block((700,600),(100,200),[all_sprites,collision_sprites])
Block((400,200),(200,100),[all_sprites,collision_sprites])
Block((600,300),(10,200),[all_sprites,collision_sprites])
Block((100,200),(100,100),[all_sprites,collision_sprites])
ball = Ball(all_sprites,collision_sprites)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill('black')
all_sprites.update()
all_sprites.draw(screen)
pygame.display.update()
clock.tick(60)
The problem I have is how to convert this to a format that works with deltatime, here is what I have so far:
import pygame,sys,time
class Block(pygame.sprite.Sprite):
def __init__(self,pos,size,groups):
super().__init__(groups)
self.image = pygame.Surface(size)
self.image.fill('yellow')
self.rect = self.image.get_rect(topleft = pos)
class Ball(pygame.sprite.Sprite):
def __init__(self,groups,obstacles):
super().__init__(groups)
self.image = pygame.Surface((40,40))
self.image.fill('red')
self.rect = self.image.get_rect(center = (400,400))
# attributes for dt influenced movement
self.pos = pygame.math.Vector2(self.rect.topleft)
self.direction = pygame.math.Vector2((0.8,1))
self.speed = 200
self.obstacles = obstacles
def vertical_collision(self):
for sprite in self.obstacles.sprites():
if sprite.rect.colliderect(self.rect):
if self.direction.y < 0: # moving up
self.pos.y = sprite.rect.bottom + 0.1
else: # moving down
self.pos.y = sprite.rect.top + self.rect.height - 0.1
self.rect.y = self.pos.y
self.direction.y *= -1
def horizontal_collision(self):
for sprite in self.obstacles.sprites():
if sprite.rect.colliderect(self.rect):
if self.direction.x < 0: # left
self.pos.x = sprite.rect.right + 0.1
else: # right
self.pos.x = sprite.rect.left - self.rect.width - 0.1
self.rect.x = self.pos.x
self.direction.x *= -1
def wall_constraint(self):
if self.rect.right >= 800 or self.rect.left <= 0:
self.direction.x *= -1
if self.rect.bottom >= 800 or self.rect.top <= 0:
self.direction.y *= -1
def update(self,dt):
self.wall_constraint()
self.pos.y += self.direction.y * self.speed * dt
self.vertical_collision()
self.pos.x += self.direction.x * self.speed * dt
self.horizontal_collision()
self.rect.x = round(self.pos.x)
self.rect.y = round(self.pos.y)
# setup
pygame.init()
screen = pygame.display.set_mode((800,800))
clock = pygame.time.Clock()
# sprite groups
all_sprites = pygame.sprite.Group()
collision_sprites = pygame.sprite.Group()
# sprite creation
Block((100,400),(60,200),[all_sprites,collision_sprites])
Block((700,600),(100,200),[all_sprites,collision_sprites])
Block((400,200),(200,100),[all_sprites,collision_sprites])
Block((600,300),(10,200),[all_sprites,collision_sprites])
Block((100,200),(100,100),[all_sprites,collision_sprites])
ball = Ball(all_sprites,collision_sprites)
last_time = time.time()
while True:
# delta time
dt = time.time() - last_time
last_time = time.time()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill('black')
all_sprites.update(dt)
all_sprites.draw(screen)
pygame.display.update()
clock.tick(120)
I am storing the position of the ball inside of a pos attribute (as a vector) and at the end of the update loop I am setting the rect position of the sprite to the x and y position of that pos vector. This is to account for pygame placing rects on integers.
The vertical collision seems to be working but the horizontal one really does not. I suspect it has to do with the position of the pos attribute and the resulting collision, I tried to give it an offset after it collided ( hence the + 0.1 or - 0.1) but that doesn't seem to make a difference.
In case someone is looking for the answer, my problem was the update method. It should have looked like this:
def update(self,dt):
self.wall_constraint()
# vertical movement + collision
self.pos.y += self.direction.y * self.speed * dt
self.rect.y = round(self.pos.y)
self.vertical_collision()
# horizontal movement + collision
self.pos.x += self.direction.x * self.speed * dt
self.rect.x = round(self.pos.x)
self.horizontal_collision()
Related
I'm making a simple topdown shooter game. The problem is with bullet starting point. I have player image where rifle barrel is between topmid and topright of rectangle. How to make bullet start always from the rifle barrel regardless image rotation?
# key input
def get_input(self, dt):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.direction.rotate_ip(dt * -360)
if keys[pygame.K_RIGHT]:
self.direction.rotate_ip(dt * 360)
self.angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
if keys[pygame.K_UP]:
self.movement = 1
self.status = 'move'
else:
self.movement = 0
self.status = 'idle'
if keys[pygame.K_DOWN]:
self.movement = -1
self.status = 'move'
if keys[pygame.K_SPACE] and not self.bullet.sprites():
self.create_bullet(dt)
shoot = pygame.mixer.Sound('audio/rumble.flac')
shoot.play()
# movement
def move(self, speed, dt):
movement_v = self.direction * self.movement
if movement_v.length() > 0:
movement_v.normalize_ip()
self.pos += movement_v * dt * speed
self.rect = self.image.get_rect(center=self.pos)
# boundary
if self.rect.x <= 0:
self.rect.x = 0
if self.rect.x >= 1216:
self.rect.x = 1216
if self.rect.y <= 0:
self.rect.y = 0
if self.rect.y >= 704:
self.rect.y = 704
# create bullet instance
def create_bullet(self, dt):
self.bullet.add(Bullet(self.rect.center, self.direction.normalize(), self.bullet_speed, self.angle))
You have rotate the offset of the bullet starting point with pygame.math.Vector2.rotate. e.g.:
offset = pygame.math.Vecotr2(15, 10) # (15, 10) is just an example
rotated_offset = offset.rotate(-self.angle)
pos = pygame.math.Vector2(self.rect.center) + rotated_offset
bullet = Bullet((pos.x, pos.y), ...)
I don't know if there is a better way to implement ramps.
First i calculate the points that belong to the hipotenuse and use collidepoint to see if there is a collision between the rectangle and any point that belongs to the hipotenuse, then i update the rectangle based on the point where there was a collision.
Being careful when the rectangle is at the top of the ramp.
The rectangle ascends the ramp perfectly, but when the rectangle descends the ramp, the rectangle shakes.
import sys
import pygame
from pygame.locals import *
pygame.init()
fps = 60
fpsClock = pygame.time.Clock()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
def draw_grid():
for y in range(0,height,32):
pygame.draw.line(screen,'red',(0,y),(width,y))
for x in range(0,width,32):
pygame.draw.line(screen,'red',(x,0),(x,height))
class Ramp(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
#self.image.fill('green')
pygame.draw.polygon(self.image, color,
points=[(0, 0), (0, height), (width, height)])
self.rect = self.image.get_rect(topleft=(x, y))
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill('blue')
self.rect = self.image.get_rect(topleft=(x,y))
self.speed = 5
self.direction = pygame.math.Vector2(0,0)
self.gravity = 0.9
self.initial_jump = -20
self.on_ground = True
def apply_gravity(self):
self.direction.y += self.gravity
self.rect.y += self.direction.y
def move(self):
keys=pygame.key.get_pressed()
if keys[K_LEFT]:
self.direction.x = -self.speed
elif keys[K_RIGHT]:
self.direction.x = self.speed
else:
self.direction.x = 0
if keys[K_UP] and self.on_ground:
self.direction.y = self.initial_jump
self.on_ground = False
self.rect.x += self.direction.x
def check_borders(self):
if self.rect.x <= 0:
self.rect.x = 0
if self.rect.right >= width:
self.rect.right = width
if self.rect.bottom >= height:
self.rect.bottom = height
self.direction.y = 0
self.on_ground = True
if self.rect.colliderect(ramp_rect):
if self.direction.x > 0 and abs(self.rect.right-ramp_rect.left) <= 5:
self.rect.right = ramp_rect.left
# ramp stuff
for p in hypotenuse_points:
if self.rect.collidepoint(p):
if self.rect.left >= ramp_rect.left:
self.rect.bottomleft = p
else:
self.rect.bottom = ramp_rect.top
self.on_ground = True
self.direction.y = 0
def update(self):
self.move()
self.apply_gravity()
self.check_borders()
player = pygame.sprite.GroupSingle(Player(12*32,10*32))
ramp = pygame.sprite.GroupSingle(Ramp(5*32,10*32,7*32,5*32,'red'))
ramp_rect = ramp.sprite.rect
m = (ramp_rect.height)/( ramp_rect.width)
x1,y1 = ramp_rect.topleft
hypotenuse_points = []
for x in range(ramp_rect.left,ramp_rect.right):
hypotenuse_points.append((x,m*(x-x1)+y1)) # Point-slope equation
while True:
screen.fill('white')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ramp.draw(screen)
player.update()
player.draw(screen)
#draw_grid()
pygame.draw.lines(screen,'black',False,hypotenuse_points,3)
pygame.display.update()
fpsClock.tick(fps)
There is no problem with your code. Only gravity is too weak. The movement is so fast that gravity is acting too late. Note that instead of moving down the slope, you move to the right and then fall.
Of course there is one problem with your code. 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. [...]
The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If this is done every frame, the position error will accumulate over time.
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 of the rectangle.
Instead of the list of points I suggest to compute the height of the ramp under the palyer:
if self.rect.colliderect(ramp_rect):
ratio = ramp_rect.height / ramp_rect.width
self.rect.bottom = ramp_rect.bottom - (ramp_rect.right - max(self.rect.left, ramp_rect.left)) * ratio
self.y = self.rect.y
Complete example:
import sys
import pygame
from pygame.locals import *
pygame.init()
fps = 60
fpsClock = pygame.time.Clock()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
def draw_grid():
for y in range(0,height,32):
pygame.draw.line(screen,'red',(0,y),(width,y))
for x in range(0,width,32):
pygame.draw.line(screen,'red',(x,0),(x,height))
class Ramp(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
super().__init__()
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
#self.image.fill('green')
pygame.draw.polygon(self.image, color,
points=[(0, 0), (0, height), (width, height)])
self.rect = self.image.get_rect(topleft=(x, y))
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill('blue')
self.rect = self.image.get_rect(topleft=(x,y))
self.x, self.y = self.rect.topleft
self.speed = 5
self.direction = pygame.math.Vector2(0,0)
self.gravity = 0.9
self.initial_jump = -20
self.on_ground = True
def apply_gravity(self):
self.direction.y += self.gravity
self.y += self.direction.y
self.rect.y = round(self.y)
def move(self):
keys = pygame.key.get_pressed()
self.direction.x = (keys[K_RIGHT] - keys[K_LEFT]) * self.speed
if keys[K_UP] and self.on_ground:
self.direction.y = self.initial_jump
self.on_ground = False
self.x += self.direction.x
self.rect.x = round(self.x)
def check_borders(self):
if self.rect.x <= 0:
self.rect.x = 0
self.x = self.rect.x
if self.rect.right >= width:
self.rect.right = width
self.x = self.rect.x
if self.rect.bottom >= height:
self.rect.bottom = height
self.direction.y = 0
self.on_ground = True
self.y = self.rect.y
if self.rect.colliderect(ramp_rect):
if self.old_rect.right-1 <= ramp_rect.left:
self.rect.right = ramp_rect.left
self.x = self.rect.x
else:
ratio = ramp_rect.height / ramp_rect.width
bottom = ramp_rect.bottom - (ramp_rect.right - max(self.rect.left, ramp_rect.left)) * ratio
if self.on_ground or self.rect.bottom > bottom:
self.rect.bottom = bottom
self.y = self.rect.y
self.direction.y = 0
self.on_ground = True
def update(self):
self.old_rect = self.rect.copy()
self.move()
self.apply_gravity()
self.check_borders()
player = pygame.sprite.GroupSingle(Player(12*32,10*32))
ramp = pygame.sprite.GroupSingle(Ramp(5*32,10*32,7*32,5*32,'red'))
ramp_rect = ramp.sprite.rect
m = (ramp_rect.height)/( ramp_rect.width)
x1,y1 = ramp_rect.topleft
while True:
screen.fill('white')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ramp.draw(screen)
player.update()
player.draw(screen)
pygame.display.update()
fpsClock.tick(fps)
I've been working on this project about tanks (based on game Tank Trouble) and I'm wondering how I can move forward after I change angle of the sprite. Also if you know how I can make my bullets ricochet from the walls. I will really appreciate any help given. Thank you!
Here is my tank and bullet:
Here is code of the game:
import sys
import pygame
class Game:
def __init__(self):
self.run = True
self.screen_width = 1060
self.screen_height = 798
self.image = pygame.image.load("sprites/background/background1.png")
self.image = pygame.transform.scale(self.image, (self.screen_width, self.screen_height))
self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
# all_sprites is used to update and draw all sprites together.
self.all_sprites = pygame.sprite.Group()
# for collision detection with enemies.
self.bullet_group = pygame.sprite.Group()
self.tank = Tank()
self.all_sprites.add(self.tank)
bullet = Bullet(self.tank)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
def handle_events(self):
keys = pygame.key.get_pressed()
self.tank.handle_events()
if keys[pygame.K_UP]:
self.tank.rect.centery -= 5
if keys[pygame.K_DOWN]:
self.tank.rect.centery += 5
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.run = False
if event.key == pygame.K_SPACE:
bullet = Bullet(self.tank)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
def update(self):
# Calls `update` methods of all contained sprites.
self.all_sprites.update()
def draw(self):
self.screen.blit(self.image, (0, 0))
self.all_sprites.draw(self.screen) # Draw the contained sprites.
pygame.display.update()
class Tank(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("sprites/player/player_tank.png")
self.org_image = self.image.copy()
# A nicer way to set the start pos with `get_rect`.
self.rect = self.image.get_rect(center=(70, 600))
self.angle = 0
self.direction = pygame.Vector2(1, 0)
self.pos = pygame.Vector2(self.rect.center)
def handle_events(self):
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]:
self.angle += 3
if pressed[pygame.K_RIGHT]:
self.angle -= 3
self.direction = pygame.Vector2(1, 0).rotate(-self.angle)
self.image = pygame.transform.rotate(self.org_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Bullet(pygame.sprite.Sprite):
def __init__(self, tank):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("sprites/bullet/bullet.png")
self.image = pygame.transform.scale(self.image, (16, 16))
self.rect = self.image.get_rect()
self.rect.centerx = tank.rect.centerx + 3 # How much pixels from tank turret on x axis
self.rect.centery = tank.rect.centery - 25 # How much pixels from tank turret on y axis
def update(self):
self.rect.y -= 10 # Move up 10 pixels per frame.
def main():
pygame.init()
pygame.display.set_caption('Tank Game')
clock = pygame.time.Clock()
game = Game()
while game.run:
game.handle_events()
game.update()
game.draw()
clock.tick(60)
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()
To move the tank in the direction which is faced, wirte a method, that computes a direction vector, dependent on an velocity argument and the self.angle attribute. The nagle and the velocity define a vector by Polar coordinats.
Add the vector to it's current position (self.pos). Finally update the self.rect attribute by the position:
class Tank(pygame.sprite.Sprite):
# [...]
def move(self, velocity):
direction = pygame.Vector2(0, velocity).rotate(-self.angle)
self.pos += direction
self.rect.center = round(self.pos[0]), round(self.pos[1])
Invoke the method when up or down is pressed:
class Game:
# [...]
def handle_events(self):
# [...]
if keys[pygame.K_UP]:
self.tank.move(-5)
if keys[pygame.K_DOWN]:
self.tank.move(5)
The movement direction of the bullets can be computed similar:
class Bullet(pygame.sprite.Sprite):
def __init__(self, tank):
#[...]
self.rect = self.image.get_rect()
self.angle = tank.angle
self.pos = pygame.Vector2(tank.pos)
self.rect.center = round(self.pos.x), round(self.pos.y)
self.direction = pygame.Vector2(0, -10).rotate(-self.angle)
def update(self):
self.pos += self.direction
self.rect.center = round(self.pos[0]), round(self.pos[1])
To make the bullet bounce, you have to reflect the direction when the bullet hits a border:
class Bullet(pygame.sprite.Sprite):
# [...]
def update(self):
self.pos += self.direction
self.rect.center = round(self.pos.x), round(self.pos.y)
if self.rect.left < 0:
self.direction.x *= -1
self.rect.left = 0
self.pos.x = self.rect.centerx
if self.rect.right > 1060:
self.direction.x *= -1
self.rect.right = 1060
self.pos.x = self.rect.centerx
if self.rect.top < 0:
self.direction.y *= -1
self.rect.top = 0
self.pos.y = self.rect.centery
if self.rect.bottom > 798:
self.direction.y *= -1
self.rect.right = 798
self.pos.y = self.rect.centery
In my game the problem is that bullets are coming only from one place i.e, from the center. As my player rotates in direction of cursor, I want the bullets to be shot from top of the player even if the player is rotated and travel in a straight line in the direction player is facing towards, As the player rotates in the direction of cursor.
As you can view here the the bullets are always in same direction and always come out of same place.
I tried to use getpos() method to get cursor position and tried to subtract from the player coordinates but failed to get the result.
I think the problem is within the def shoot(self) method of Rotator class, I need to get the coordinates spaceship's tip even when it is rotating all time.
import math
import random
import os
import pygame as pg
import sys
pg.init()
height=650
width=1200
os_x = 100
os_y = 45
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (os_x,os_y)
screen = pg.display.set_mode((width,height),pg.NOFRAME)
screen_rect = screen.get_rect()
background=pg.image.load('background.png').convert()
background = pg.transform.smoothscale(pg.image.load('background.png'), (width,height))
clock = pg.time.Clock()
running = True
class Mob(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load('enemy.png').convert_alpha()
self.image = pg.transform.smoothscale(pg.image.load('enemy.png'), (33,33))
self.rect = self.image.get_rect()
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(1, 8)
self.speedx = random.randrange(-3, 3)
def update(self):
self.rect.x += self.speedx
self.rect.y += self.speedy
if self.rect.top > height + 10 or self.rect.left < -25 or self.rect.right > width + 20:
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(1, 8)
class Rotator(pg.sprite.Sprite):
def __init__(self, screen_rect):
pg.sprite.Sprite.__init__(self)
self.screen_rect = screen_rect
self.master_image = pg.image.load('spaceship.png').convert_alpha()
self.master_image = pg.transform.smoothscale(pg.image.load('spaceship.png'), (33,33))
self.image = self.master_image.copy()
self.rect = self.image.get_rect(center=[width/2,height/2])
self.delay = 10
self.timer = 0.0
self.angle = 0
self.distance = 0
self.angle_offset = 0
def get_angle(self):
mouse = pg.mouse.get_pos()
offset = (self.rect.centerx - mouse[0], self.rect.centery - mouse[1])
self.angle = math.degrees(math.atan2(*offset)) - self.angle_offset
old_center = self.rect.center
self.image = pg.transform.rotozoom(self.master_image, self.angle,1)
self.rect = self.image.get_rect(center=old_center)
self.distance = math.sqrt((offset[0] * offset[0]) + (offset[1] * offset[1]))
def update(self):
self.get_angle()
self.display = 'angle:{:.2f} distance:{:.2f}'.format(self.angle, self.distance)
self.dx = 1
self.dy = 1
self.rect.clamp_ip(self.screen_rect)
def draw(self, surf):
surf.blit(self.image, self.rect)
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.centery)
all_sprites.add(bullet)
bullets.add(bullet)
class Bullet(pg.sprite.Sprite):
def __init__(self, x, y):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load('bullet.png').convert_alpha()
self.image = pg.transform.smoothscale(pg.image.load('bullet.png'), (10,10))
self.rect = self.image.get_rect()
self.rect.y = y
self.rect.x = x
self.speedy = -8
def update(self):
self.rect.y += self.speedy
# kill if it moves off the top of the screen
if self.rect.bottom < 0:
self.kill()
all_sprites = pg.sprite.Group()
bullets = pg.sprite.Group()
mobs = pg.sprite.Group()
rotator = Rotator(screen_rect)
all_sprites.add(rotator)
for i in range(5):
m = Mob()
all_sprites.add(m)
mobs.add(m)
while running:
keys = pg.key.get_pressed()
for event in pg.event.get():
if event.type == pg.QUIT:
sys.exit()
pygame.quit()
if event.type == pg.MOUSEBUTTONDOWN:
rotator.shoot()
screen.blit(background, [0, 0])
all_sprites.update()
hits = pg.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
m = Mob()
all_sprites.add(m)
mobs.add(m)
hits = pg.sprite.spritecollide(rotator, mobs, False)
if hits:
running = False
all_sprites.draw(screen)
clock.tick(60)
pg.display.update()
See Shooting a bullet in pygame in the direction of mouse and calculating direction of the player to shoot pygame.
Pass the mouse position to rotator.shoot(), when the mouse button is pressed:
if event.type == pg.MOUSEBUTTONDOWN:
rotator.shoot(event.pos)
Calculate the direction of from the rotator to the mouse position and pass it the constructor of the new bullet object:
def shoot(self, mousepos):
dx = mousepos[0] - self.rect.centerx
dy = mousepos[1] - self.rect.centery
if abs(dx) > 0 or abs(dy) > 0:
bullet = Bullet(self.rect.centerx, self.rect.centery, dx, dy)
all_sprites.add(bullet)
bullets.add(bullet)
Use pygame.math.Vector2 to store the current positon of the bullet and the normalized direction of the bullet (Unit vector):
class Bullet(pg.sprite.Sprite):
def __init__(self, x, y, dx, dy):
pg.sprite.Sprite.__init__(self)
self.image = pg.transform.smoothscale(pg.image.load('bullet.png').convert_alpha(), (10,10))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.speed = 8
self.pos = pg.math.Vector2(x, y)
self.dir = pg.math.Vector2(dx, dy).normalize()
Calcualate the new position of the bullet in update() (self.pos += self.dir * self.speed) and update the .rect attribute by the new position.
.kill() the bullet when it leaves the screen. This can be checked by self.rect.colliderect():
class Bullet(pg.sprite.Sprite):
# [...]
def update(self):
self.pos += self.dir * self.speed
self.rect.center = (round(self.pos.x), round(self.pos.y))
if not self.rect.colliderect(0, 0, width, height):
self.kill()
I am new to python and am trying to write a game that launches a character and when he interacts with a sprite on the ground, something will change, for example speed. My apologies for the disorganization in my code. I have taken samples from a few tutorials and I can't make them work together.
How do I make the player's collision with the bomb detectable?
import pygame
import random
import math
drag = 1
gravity = (math.pi, .4)
elasticity = .75
# Colors
BLACK = ( 0, 0, 0)
WHITE = ( 255, 255, 255)
BLUE = ( 0, 0, 255)
RED = ( 255, 0, 0)
GREEN = ( 0, 255, 0)
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
def addVectors((angle1, length1), (angle2, length2)):
x = math.sin(angle1) * length1 + math.sin(angle2) * length2
y = math.cos(angle1) * length1 + math.cos(angle2) * length2
angle = 0.5 * math.pi - math.atan2(y, x)
length = math.hypot(x, y)
return (angle, length)
class Player(pygame.sprite.Sprite):
change_x = 0
change_y = 0
level = None
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('player.png')
image_rect = self.image.get_rect()
self.rect = pygame.rect.Rect(x, y, image_rect.width, image_rect.height)
def update(self):
pass
def move(self):
(self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)
self.rect.x += math.sin(self.angle) * self.speed
self.rect.y -= math.cos(self.angle) * self.speed
self.speed *= drag
def bounce(self):
if self.rect.x > 800 - self.rect.width:
self.rect.x = 2*(800 - self.rect.width) - self.rect.x
self.angle = - self.angle
self.speed *= elasticity
elif self.rect.x < 0:
self.rect.x = 2*self.rect.width - self.rect.x
self.angle = - self.angle
self.speed *= elasticity
if self.rect.y > SCREEN_HEIGHT - self.rect.height:
self.rect.y = 2*(SCREEN_HEIGHT - self.rect.height) - self.rect.y
self.angle = math.pi - self.angle
self.speed *= elasticity
class Bomb(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('cherry.png')
image_rect = self.image.get_rect()
self.rect = pygame.rect.Rect(x, y, image_rect.width, image_rect.height)
class Level():
bomb_list = None
world_shift = 0
def __init__(self, player):
self.bomb_list = pygame.sprite.Group()
self.player = player
def update(self):
self.bomb_list.update()
def draw(self, screen):
screen.fill(BLACK)
self.bomb_list.draw(screen)
def shift_world(self, shift_x):
self.world_shift += shift_x
for bomb in self.bomb_list:
bomb.rect.x += shift_x
if bomb.rect.x < 0:
self.bomb_list.remove(bomb)
self.bomb_list.add(Bomb(random.randint(SCREEN_WIDTH, 2*SCREEN_WIDTH), 580))
I am not sure if this Level_01 class is even necessary:
class Level_01(Level):
def __init__(self, player):
Level.__init__(self, player)
bombs = 0
for n in range(10):
self.bomb_list.add(Bomb(random.randint(0, SCREEN_WIDTH), 580))
This was my attempt at a collision detection method. I'm not sure if it should be in a class, in main, or seperate. I can't seem to get the list of bombs, and the player at the same time.
def detectCollisions(sprite1, sprite_group):
if pygame.sprite.spritecollideany(sprite1, sprite_group):
sprite_group.remove(pygame.sprite.spritecollideany(sprite1, sprite_group))
print True
else:
print False
def main():
pygame.init()
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
active_sprite_list = pygame.sprite.Group()
enemy_list = pygame.sprite.Group()
player = Player(0, 0)
player.speed = 30
player.angle = math.radians(45)
player.rect.x = 500
player.rect.y = SCREEN_HEIGHT - player.rect.height
active_sprite_list.add(player)
done = False
clock = pygame.time.Clock()
current_level = Level_01(player)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.go_left()
if event.key == pygame.K_RIGHT:
player.go_right()
active_sprite_list.update()
enemy_list.update()
player.level = current_level
player.move()
player.bounce()
if player.rect.x >= 500:
diff = player.rect.x - 500
player.rect.x = 500
current_level.shift_world(-diff)
if player.rect.x <= 120:
diff = 120 - player.rect.x
player.rect.x = 120
current_level.shift_world(diff)
current_level.draw(screen)
active_sprite_list.draw(screen)
enemy_list.draw(screen)
clock.tick(60)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
Thanks for helping me out!
Probably the easiest thing to do is to draw out pixels (or virtual pixels) and if in drawing bomb/person pixels you find an overlap then you know a collision occurred.
You can however get way more complicated (and efficient) in your collision detection if you need a higher performance solution. See Wikipedia Collision Detection for a reference.
I suggest creating sprite groups using pygame.sprite.Group() for each class; Bomb and Player. Then, use pygame.sprite.spritecollide().
For example:
def Main()
...
player_list = pygame.sprite.Group()
bomb_list = pygame.sprite.Group()
...
Then in your logic handling loop, after creating a Player and Bomb instance, you could do something like this:
for bomb in bomb_list:
# See if the bombs has collided with the player.
bomb_hit_list = pygame.sprite.spritecollide(bomb, player_list, True)
# For each bomb hit, remove bomb
for bomb in bomb_hit_list:
bomb_list.remove(bomb)
# --- Put code here that will trigger with each collision ---