How do you program collision in classes? - python

I've been having a hard time getting collision to work. I'm new and don't know the in and outs with coding so please can you explain your reasoning.
Here's a simple of some code:
def __init__(self, x, y, width, height, sprite):
self.x = player_x
self.y = player_y
self.width = player.width
self.height = player.height
self.col = pygame.sprite.collide_rect(self, sprite)
pygame.sprite.Sprite.__init__(self)
def velocity_player(self):
player_vertical_velocity = 0
player_horizontal_velocity = 0
player_y = player_y + player_vertical_velocity
player_x = player_x + player_horizontal_velocity
player_fr = 5
def render(self):
player_surface = pygame.image.load("Bunny.png")
player_surface.set_colorkey(TRANSPARENT_GREEN)
player_left = pygame.image.load("Bunny_left.png")
player_left.set_colorkey(TRANSPARENT_GREEN)
player_left = pygame.image.load("Bunny_left.png")
player_left.set_colorkey(TRANSPARENT_GREEN)
player_fall = pygame.image.load("Bunny_Fall.png")
player_fall.set_colorkey(TRANSPARENT_GREEN)
player_jump = pygame.image.load("Bunny_r_Jump.png")
screen.blit(player_surface, [player_x, player_y])

First, start by learning the concept of Classes (see also Python - Classes and OOP Basics). The collision must be detected in every frame. Usually you don't want to do it in the constrictor of a class. See How do I detect collision in pygame?.
When using pygame.sprite.Sprites and pygame.sprite.Groups, collision is detected by functions like pygame.sprite.spritecollide() and pygame.sprite.groupcollide(). However, collision detection is usually not performed in the Sprite class. This is done in the outer code that manages the Sprites and the interaction between the Sprites.
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
class RectSprite(pygame.sprite.Sprite):
def __init__(self, color, x, y, w, h):
super().__init__()
self.image = pygame.Surface((w, h))
self.image.fill(color)
self.rect = pygame.Rect(x, y, w, h)
player = RectSprite((255, 0, 0), 0, 0, 50, 50)
obstacle1 = RectSprite((128, 128, 128), 50, 150, 50, 50)
obstacle2 = RectSprite((128, 128, 128), 150, 50, 50, 50)
all_group = pygame.sprite.Group([obstacle1, obstacle2, player])
obstacle_group = pygame.sprite.Group([obstacle1, obstacle2])
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
player.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(player, obstacle_group, False)
window.fill(0)
all_group.draw(window)
for s in collide:
pygame.draw.rect(window, (255, 255, 255), s.rect, 5, 1)
pygame.display.flip()
pygame.quit()

Related

How do I make a sprite rotate around another moving sprite?

I am currently making a game and I want to add a power up. I am essentially trying to make something similar to the spell book from Castlevania. I found a code to help me get the initial position and rotation. The problem is, the cross doesn't move from it's initial location. It just circles in place while the other one leaves it. I am still new to pygame and the math that goes with game dev. It is still pretty confusing.
import pygame
class Crucifix(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.sprite = pygame.image.load('image'). convert_alpha()
self.image = self.sprite
self.rect = self.image.get_rect()
self.rect.x =500
self.rect.y = 500
self.mask = pygame.mask.from_surface(self.image)
def update(self):
self.rect.x -= 2
if self.rect.x == -100:
self.kill()
class EnemiesStrong(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.original_image = pygame.image.load('image').convert_alpha()
self.image = self.original_image
self.rect = self.image.get_rect()
self.angle = 0
def initLoc(self, pos, radius):
self.pos = pos
self.radius = radius
def update(self):
center = pygame.math.Vector2(self.pos) + pygame.math.Vector2(0, -self.radius).rotate(-self.angle)
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = (round(center.x), round(center.y)))
def turnLeft(self):
self.angle = (self.angle + 4) % 360
pygame.init()
window = pygame.display.set_mode((800, 800))
clock = pygame.time.Clock()
cross1= Crucifix()
enemy_s = EnemiesStrong()
pos = cross1.rect.x, cross1.rect.y
enemy_s.initLoc(cross1.rect.center, 100)
all_sprites = pygame.sprite.Group(enemy_s)
all_sprites.add(cross1)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
enemy_s.turnLeft()
all_sprites.update()
window.fill(0)
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
exit()
I am not exactly sure what to try at this point. Any help would be appreciated.
You have to update the position of enemy_s in every frame depending on the position of cross1. Add a updatePos method to the EnemiesStrong class:
class EnemiesStrong(pygame.sprite.Sprite):
# [...]
def updatePos(self, new_pos):
self.pos = new_pos
Call updatePos in the application loop:
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
enemy_s.turnLeft()
enemy_s.updatePos(cross1.rect.center) # <---
all_sprites.update()
window.fill(0)
all_sprites.draw(window)
pygame.display.flip()
Complete and minimal example based on your original code:
import pygame, math
class Crucifix(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((40, 40), pygame.SRCALPHA)
pygame.draw.circle(self.image, "white", (20, 20), 20)
pygame.draw.line(self.image, "magenta", (20, 0), (20, 40), 5)
pygame.draw.line(self.image, "magenta", (0, 20), (40, 20), 5)
self.rect = self.image.get_rect(center = (400, 400))
self.mask = pygame.mask.from_surface(self.image)
self.px = 400
self.a = 0
def update(self):
offset_x = math.sin(self.a) * 100
self.a += 0.05
self.rect.x = round(self.px + offset_x)
class EnemiesStrong(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.original_image = pygame.Surface((40, 40), pygame.SRCALPHA)
pygame.draw.circle(self.original_image, "red", (20, 20), 20)
pygame.draw.polygon(self.original_image, "yellow", ((20, 0), (40, 20), (20, 40), (0, 20)))
self.image = self.original_image
self.rect = self.image.get_rect()
self.angle = 0
def initLoc(self, pos, radius):
self.pos = pos
self.radius = radius
def update(self):
center = pygame.math.Vector2(self.pos) + pygame.math.Vector2(0, -self.radius).rotate(-self.angle)
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = (round(center.x), round(center.y)))
def turnLeft(self):
self.angle = (self.angle + 4) % 360
def updatePos(self, new_pos):
self.pos = new_pos
pygame.init()
window = pygame.display.set_mode((800, 800))
clock = pygame.time.Clock()
cross1 = Crucifix()
enemy_s = EnemiesStrong()
pos = cross1.rect.x, cross1.rect.y
enemy_s.initLoc(cross1.rect.center, 100)
all_sprites = pygame.sprite.Group(enemy_s)
all_sprites.add(cross1)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *background.get_size(), (32, 32, 32), (48, 48, 48)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
[pygame.draw.rect(background, color, rect) for rect, color in tiles]
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
enemy_s.turnLeft()
enemy_s.updatePos(cross1.rect.center)
all_sprites.update()
window.blit(background, (0, 0))
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
exit()

How do you point the barrel towards mouse in pygame?

I have fixed the problem pointed out in this post, but now I am confused on how to point the gray rectangle (barrel) towards my mouse. Could somebody please help? Thanks!
Here is my code:
import pygame
import math
pygame.init()
screen = pygame.display.set_mode((400, 400))
pygame.display.set_caption("diep.io")
screen.fill((255,255,255))
auto_shoot = False
class Bullet:
def __init__(self, x_move, y_move, x_loc, y_loc):
self.image = pygame.image.load("C:\\Users\\ender\\OneDrive\\Documents\\repos\\Bullet.png")
self.x_move = x_move
self.y_move = y_move
self.x_loc = x_loc
self.y_loc = y_loc
self.bullet_rect = self.image.get_rect()
def update(self):
self.x_loc += self.x_move
self.y_loc += self.y_move
self.bullet_rect.center = round(self.x_loc), round(self.y_loc)
rect = screen.blit(self.image, self.bullet_rect)
if not screen.get_rect().contains(rect):
bullets.remove(self)
if self.x_loc > 400 or self.y_loc > 400:
bullets.remove(self)
bullet = None
bullets = []
while True:
screen.fill((255, 255, 255))
pygame.draw.rect(screen, (100, 100, 100), (205, 193, 25, 15)) # This is the rectangle I want to point at my mouse
pygame.draw.circle(screen, (82, 219, 255), (200, 200), 15)
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONUP:
x = pygame.mouse.get_pos()[0] - 200
y = pygame.mouse.get_pos()[1] - 200
pythag = float(math.sqrt(x**2 + y**2))
bullets.append(Bullet(x/pythag, y/pythag, 200, 200))
for bullet in bullets:
bullet.update()
pygame.display.update()
pygame.time.delay(10)
Note: This is different from other problems because I am trying to point a rectangle at my mouse, and other posts are pointing an image at the mouse. That is not what I want to do. Secondly, pygame rectangles are defined by their bottom left corner, so this further complicates things.
For the rotation of the bullets see How to move a sprite according to an angle in Pygame and calculating direction of the player to shoot PyGmae.
Compute the angle of the direction vector grees(math.atan2(-y_move, x_move)) and rotate the bullet image:
class Bullet:
def __init__(self, x_move, y_move, x_loc, y_loc):
self.image = pygame.image.load("C:\\Users\\ender\\OneDrive\\Documents\\repos\\Bullet.png")
self.image = pygame.transform.rotate(self.image, math.degrees(math.atan2(-y_move, x_move)))
Rotating the rectangle is a little trickier. Draw the cannon on a pygame.Surface:
cannon = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.rect(cannon, (100, 100, 100), (30, 17, 25, 15))
pygame.draw.circle(cannon, (82, 219, 255), (25, 25), 15)
See How do I rotate an image around its center using PyGame?.
Write a function to rotate and blit a Surface:
def blitRotateCenter(surf, image, center, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = image.get_rect(center = center).center)
surf.blit(rotated_image, new_rect)
Use the function in the application loop to draw the cannon:
while True:
# [...]
x = pygame.mouse.get_pos()[0] - 200
y = pygame.mouse.get_pos()[1] - 200
angle = math.degrees(math.atan2(-y, x))
blitRotateCenter(screen, cannon, (200, 200), angle)
# [...]
See also How to rotate an image(player) to the mouse direction? and How can you rotate the sprite and shoot the bullets towards the mouse position?.
Complete example:
import pygame
import math
pygame.init()
screen = pygame.display.set_mode((400, 400))
pygame.display.set_caption("diep.io")
screen.fill((255,255,255))
clock = pygame.time.Clock()
class Bullet:
def __init__(self, x_move, y_move, x_loc, y_loc):
#self.image = pygame.image.load("C:\\Users\\ender\\OneDrive\\Documents\\repos\\Bullet.png")
self.image = pygame.Surface((20, 5), pygame.SRCALPHA)
self.image.fill((255, 0, 0))
self.image = pygame.transform.rotate(self.image, math.degrees(math.atan2(-y_move, x_move)))
self.x_move = x_move
self.y_move = y_move
self.x_loc = x_loc
self.y_loc = y_loc
self.bullet_rect = self.image.get_rect()
def update(self):
self.x_loc += self.x_move
self.y_loc += self.y_move
self.bullet_rect.center = round(self.x_loc), round(self.y_loc)
rect = screen.blit(self.image, self.bullet_rect)
if not screen.get_rect().contains(rect):
bullets.remove(self)
if self.x_loc > 400 or self.y_loc > 400:
bullets.remove(self)
cannon = pygame.Surface((50, 50), pygame.SRCALPHA)
pygame.draw.rect(cannon, (100, 100, 100), (30, 17, 25, 15))
pygame.draw.circle(cannon, (82, 219, 255), (25, 25), 15)
def blitRotateCenter(surf, image, center, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = image.get_rect(center = center).center)
surf.blit(rotated_image, new_rect)
bullet = None
bullets = []
auto_shoot = False
run = True
while run:
x = pygame.mouse.get_pos()[0] - 200
y = pygame.mouse.get_pos()[1] - 200
angle = math.degrees(math.atan2(-y, x))
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONUP:
pythag = float(math.sqrt(x**2 + y**2))
bullets.append(Bullet(x/pythag, y/pythag, 200, 200))
screen.fill((255, 255, 255))
for bullet in bullets:
bullet.update()
blitRotateCenter(screen, cannon, (200, 200), angle)
pygame.display.update()
clock.tick(100)
pygame.quit()
exit()

How to draw shapes using the grouping thing

I just learned how to group stuff in pygame but when I try to use it to draw a sprite it says: " ExternalError: TypeError: Cannot read property 'left' of undefined on line 32"
Here is my code:
import pygame
from pygame import *
screen = pygame.display.set_mode((600, 600))
blocks = pygame.sprite.Group()
class Block():
def __init__(self, x, y, w, h, typ):
super().__init__()
self.x = x
self.y = y
self.w = w
self.h = h
self.typ = typ
def draw(self):
pygame.draw.rect(screen, (0, 0, 0), (self.x, self.y, self.w, self.h))
class Player():
def __init__(self, x, y):
super().__init__()
self.x = x
self.y = y
self.accX = 0
self.accY = 0
def draw(self):
pygame.draw.rect(screen, (0, 0, 0), (self.x, self.y, 20, 20))
player = Player(0, 0)
while True:
Blocks = Block(0, 0, 100, 50, 1)
blocks.add(Blocks)
blocks.draw(screen)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
pygame.display.update()
pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.
The former delegates the to the update method of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():
Calls the update() method on all Sprites in the Group [...]
The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]
This means Block and Player must to be derived from pygame.sprite.Sprite and has to have the attributes rect and image. Add the Block and Player instances to a Group and us the darw() method to draw them.
Complete example:
import pygame
from pygame import *
screen = pygame.display.set_mode((600, 600))
class Block(pygame.sprite.Sprite):
def __init__(self, x, y, w, h, typ):
super().__init__()
self.image = pygame.Surface((w, h))
self.image.fill((127, 127, 127))
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((20, 20))
self.image.fill((255, 255, 0))
self.rect = self.image.get_rect(topleft = (x, y))
blocks = pygame.sprite.Group()
objects = pygame.sprite.Group()
block = Block(0, 0, 100, 50, 1)
blocks.add(block)
player = Player(0, 0)
objects.add(player)
clock = pygame.time.Clock()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((0, 0, 0))
blocks.draw(screen)
objects.draw(screen)
pygame.display.update()
pygame.quit()

Pygame mask collision

I'm trying to get proper collision detection with rotating surfaces in pygame. I decided to try using masks. It somewhat works, but it is not as precise as I'd liked/thought. I tried updating the mask at the end of the cycle to get a "fresh" hitbox for the next frame, but it didn't work as expected. What is my mistake?
import pygame
import random
WHITE = [255, 255, 255]
RED = [255, 0, 0]
pygame.init()
FPS = pygame.time.Clock()
fps = 6
winW = 1000
winH = 500
BGCOLOR = WHITE
win = pygame.display.set_mode((winW, winH))
win.fill(WHITE)
pygame.display.set_caption('')
pygame.display.set_icon(win)
class Box(pygame.sprite.Sprite):
def __init__(self, x, y, w, h):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([w, h], pygame.SRCALPHA)
self.image.fill(random_color())
self.mask = pygame.mask.from_surface(self.image)
self.rect = pygame.Rect(x, y, w, h)
self.angle = 0
def move(self):
self.rect.center = pygame.mouse.get_pos()
def draw(self):
blits = self.rotate()
win.blit(blits[0], blits[1])
self.mask = pygame.mask.from_surface(blits[0])
def rotate(self):
self.angle += 3
new_img = pygame.transform.rotate(self.image, self.angle)
new_rect = new_img.get_rect(center=self.rect.center)
return new_img, new_rect
def update_display():
win.fill(BGCOLOR)
player.draw()
for p in platforms:
p.draw()
pygame.display.update()
def collision():
return pygame.sprite.spritecollide(player, plat_collide, False, pygame.sprite.collide_mask)
def random_color():
return [random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)]
player = Box(100, 400, 50, 50)
platforms = [Box(300, 400, 100, 50)]
plat_collide = pygame.sprite.Group(platforms)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
hits = collision()
if hits:
BGCOLOR = RED
else:
BGCOLOR = WHITE
player.move()
update_display()
FPS.tick(fps)
pygame.quit()
Your application works fine. But note, pygame.sprite.collide_mask() use the .rect and .mask attribute of the sprite object for the collision detection.
You have to update self.rect after rotating the image:
class Box(pygame.sprite.Sprite):
# [...]
def rotate(self):
self.angle += 3
new_img = pygame.transform.rotate(self.image, self.angle)
new_rect = new_img.get_rect(center=self.rect.center)
# update .rect attribute
self.rect = new_rect # <------
return new_img, new_rect
See also Sprite mask
Minimal example: repl.it/#Rabbid76/PyGame-SpriteMask
import pygame
class SpriteObject(pygame.sprite.Sprite):
def __init__(self, x, y, w, h, color):
pygame.sprite.Sprite.__init__(self)
self.angle = 0
self.original_image = pygame.Surface([w, h], pygame.SRCALPHA)
self.original_image.fill(color)
self.image = self.original_image
self.rect = self.image.get_rect(center = (x, y))
self.mask = pygame.mask.from_surface(self.image )
def update(self):
self.rotate()
def rotate(self):
self.angle += 0.3
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = self.rect.center)
self.mask = pygame.mask.from_surface(self.image )
pygame.init()
clock = pygame.time.Clock()
window = pygame.display.set_mode((400, 400))
size = window.get_size()
moving_object = SpriteObject(0, 0, 50, 50, (128, 0, 255))
static_objects = [
SpriteObject(size[0] // 2, size[1] // 3, 100, 50, (128, 128, 128)),
SpriteObject(size[0] // 4, size[1] * 2 // 3, 100, 50, (128, 128, 128)),
SpriteObject(size[0] * 3 // 4, size[1] * 2 // 3, 100, 50, (128, 128, 128))
]
all_sprites = pygame.sprite.Group([moving_object] + static_objects)
static_sprites = pygame.sprite.Group(static_objects)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
moving_object.rect.center = pygame.mouse.get_pos()
all_sprites.update()
collide = pygame.sprite.spritecollide(moving_object, static_sprites, False, pygame.sprite.collide_mask)
window.fill((255, 0, 0) if collide else (255, 255, 255))
all_sprites.draw(window)
pygame.display.update()
pygame.quit()
exit()

How to fix the occurrence of Black Screen when running the program

I am new to Python Game Development and I am trying to learn by following the tutorial on Youtube by FreeCodeCamp but not getting the expected output. When I run the program the window opens but displays a black screen with no output.
Tried to include pygame.init() and pygame.display.init() but that didn't work either.
import pygame
width = 500
height = 500
win = pygame.display.set_mode((width, height))
pygame.display.set_caption("Client")
client_number = 0
class Player():
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.height = height
self.width = width
self.color = color
self.rect = (x, y, width, height)
self.vel = 3
def draw(self, win):
pygame.draw.rect(win, self.color, self.rect)
def move(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.x -= self.vel
if keys[pygame.K_RIGHT]:
self.x += self.vel
if keys[pygame.K_DOWN]:
self.y += self.vel
if keys[pygame.K_UP]:
self.y -= self.vel
self.rect = (self.x, self.y, self.width, self.height)
def redraw_Window(win, player):
win.fill((255, 255, 255))
player.draw(win)
pygame.display.update()
def main():
run = True
p = Player(50, 50, 100, 100, (0, 255, 0))
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
p.move()
redraw_Window(win, p)
main()
You've to respect the Indentation.
p.move() and redraw_Window(win, p) have to be in the scope of the main loop (in scope of while run:) rather than in scope of the function main main:
def main():
run = True
p = Player(50, 50, 100, 100, (0, 255, 0))
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
# ->>>
p.move()
redraw_Window(win, p)
main()

Categories