EDIT:
After countless hours, I'm still unable to fix the horizontal collision issue. I've used this link I was suggested to implement into my code:pygame platformer collision causes weird spacing issue
I found that the issue most likely stems from the collision
function. I've honestly tried everything and time is running out to submit my code to my teachers. I would be forever grateful if someone could help me pinpoint what is exactly wrong with the code that doesn't fix the horizontal collision.
Here is the newly revised code I made recently where horizontal collision still doesn't work:
#Neccessary libraries importedimport osimport randomimport mathimport pygamefrom os import listdirfrom os.path import isfile, join#Initialises necessary pygame functionspygame.init()pygame.display.set_caption("Gameplay")BG_COLOR = (255, 255, 255)width, height = 1920, 1080FPS = 60#Speed at which sprite movesPLAYER_VEL = 10window = pygame.display.set_mode((width, height))background_image = pygame.image.load("assets/background/BG.png").convert()background_image = pygame.transform.scale(background_image, (width, height))#Function sets how the block (platforms in this case) will look like.def load_block(size): COLOUR = (0, 0, 0) surface = pygame.Surface((size, size), pygame.SRCALPHA, 32) rect = pygame.Rect(96, 0, size, size) surface.fill(COLOUR, rect) return pygame.transform.scale2x(surface)#drawing the object and player onto the windowdef draw(player, objects, win, offset_x): window.blit(background_image, (0, 0)) # Draw the player sprite at its current position considering the offset win.blit(player.sprite, (player.rect.x - offset_x, player.rect.y)) # Draw each object in the objects list, adjusting their positions… # …relative to the offset. for obj in objects: obj.draw(win, offset_x) pygame.display.update()#function deals with collisions with platforms and ceiling.def vertical_collision(player, objects, dy): collided_objects = [] for obj in objects: # all possible objects sprite could collide with if pygame.sprite.collide_rect(player, obj): # Check for collision if dy >= 0: player.rect.bottom = obj.rect.top player.landed() else: player.rect.top = obj.rect.bottom player.hit_head() collided_objects.append(obj) return collided_objects#handles horizontal collisionsdef collide(player, objects, dx): player.move(dx, 0) # moves the player horizontally player.update() # updates the player's rectangle collided_object = None for obj in objects: for sprite in player.tiles.sprites(): if sprite.rect.colliderect(player.rect): if player.direction.x < 0: player.rect.left = sprite.rect.right elif player.direction.x > 0: player.rect.right = sprite.rect.left player.move(-dx, 0) # moves the player back to its original position player.update() # updates the player's rectangle return collided_object#class created to design a template of how the sprite will look like and how it will operateclass Player(pygame.sprite.Sprite): #sets colour of sprite COLOUR = (0, 0, 0) #sets rate of speed at which the sprite falls GRAVITY = 1 #initialises def __init__(self, x, y, width, height, image_path): super().__init__() self.image = pygame.transform.scale(pygame.image.load(image_path).convert_alpha(), (width * 4, height * 2)) self.rect = self.image.get_rect(topleft=(x, y)) self.x_vel = 0 self.y_vel = 0 self.direction = "right" self.animation_count = 0 self.fall_count = 0 self.jump_count = 0 self.sprite = self.image self.tiles = pygame.sprite.Group() def jump(self): self.y_vel = -self.GRAVITY*8 #When jumping, sprite will jump into air. self.animation_count=0 self.jump_count += 1 if self.jump_count==1: self.count=0 #As soon as sprite jumps, the gravity is removed. #then reapplies gravity when sprite is falling / has landed. #movement def move(self, dx, dy): self.rect.x += dx self.rect.y += dy #movement to left direction def move_left(self, vel): self.x_vel = -vel self.direction = "left" # Update direction self.animation_count = 0 #movement to right direction def move_right(self, vel): self.x_vel = vel self.direction = "right" # Update direction self.animation_count = 0 #keeps movement in loop def loop(self, fps): self.y_vel += min(1, (self.fall_count / fps) * self.GRAVITY) self.move(self.x_vel, self.y_vel) self.fall_count += 1 #function to determine if sprite has landed on a platform def landed(self): self.fall_count=0 self.y_vel=0 self.jump_count=0 #function to determine if sprite has collided with the ceiling def hit_head(self): self.count=0 self.y_vel*=-1 def update_sprite(self): sprite_sheet = "idle" #jump animations, will write this up later if self.y_vel != 0: if self.jump_count == 1: sprite_sheet = "jump" elif self.jump_count==2: sprite_sheet = "double_jump" elif self.y_vel> self.GRAVITY*2: sprite_sheet = "fall" elif self.x_vel != 0: sprite_sheet = "run" if self.x_vel != 0: sprite_sheet = "run" sprite_sheet_name = sprite_sheet +"_" + self.direction sprites = self.SPRITES[sprite_sheet_name] sprite_index = (self.animation_count // self. ANIMATION_DELAY) % len(sprites) self.sprite = sprites[sprite_index] self.animation_count += 1 self.update() def update(self): self.rect = self.sprite.get_rect(topleft=(self.rect.x, self.rect.y)) self.mask = pygame.mask.from_surface(self.sprite)class Object(pygame.sprite.Sprite): def __init__(self, x, y, width, height, name=None): super().__init__() self.rect = pygame.Rect(x, y, width, height) self.image = pygame.Surface((width, height)) self.image.fill(self.COLOUR) self.width = width self.height = height self.name = name def draw(self, win, offset_x): win.blit(self.image, (self.rect.x - offset_x, self.rect.y))class Block(Object): COLOUR = (0, 0, 0) def __init__(self, x, y, size): super().__init__(x, y, size, size) block = load_block(size) self.image.blit(block, (0, 0)) self.mask = pygame.mask.from_surface(self.image)#Function addresses any keys pressed to move the spritedef move(player, objects): keys = pygame.key.get_pressed() player.x_vel = 0 #handling horizontal collision collide_left = collide(player,objects, -PLAYER_VEL) collide_right = collide(player,objects, PLAYER_VEL) #only allows sprite to move if there is no block next to it if keys[pygame.K_d] and not collide_right: player.move_right(PLAYER_VEL) if keys[pygame.K_a] and not collide_left: player.move_left(PLAYER_VEL) #up key removed here, explained later in the code. #calls the vertical collision function vertical_collision(player,objects,player.y_vel) def gameplay(window): clock = pygame.time.Clock() block_size = 96 player = Player(400, 800, 25, 50, "assets/Sprite.png",) blocks = [Block(0, height - block_size, block_size)] #fills the bottom of the screen with platforms. floor = [Block (i * block_size, height - block_size, block_size) for i in range (-width // block_size, width * 2 // block_size)] #used to keep track of how much the player has moved horizontally relative to the window's view. offset_x = 0 #when sprite reaches x amount of pixels in either direction, the game starts to scroll. scroll_area_width = 200 #creating a block above platform objects = [*floor, Block(0,height - block_size*2, block_size)] run = True while run: clock.tick(FPS) # Event handling for event in pygame.event.get(): if event.type == pygame.QUIT: run = False break if event.type == pygame.KEYDOWN: if event.key == pygame.K_w and player.jump_count < 2: player.jump() # Update player and objects player.loop(FPS) move(player, objects + player.tiles.sprites()) # Check if player has reached the scrolling area and update offset accordingly if ((player.rect.right - offset_x >= width - scroll_area_width) and player.x_vel > 0) \ or ((player.rect.left - offset_x <= scroll_area_width) and player.x_vel < 0): offset_x += player.x_vel # Draw everything with the updated offset draw(player, objects, window, offset_x) pygame.quit() quit()if __name__ == "__main__": gameplay(window)
ORIGINAL POST:
When the sprite is moving to a block along the horizontal axis, the sprite should collide with the block. Instead, it just teleports the sprite to the top of the block.
#Neccessary libraries importedimport osimport randomimport mathimport pygamefrom os import listdirfrom os.path import isfile, join#Initialises necessary pygame functionspygame.init()pygame.display.set_caption("Gameplay")BG_COLOR = (255, 255, 255)width, height = 1920, 1080FPS = 60#Speed at which sprite movesPLAYER_VEL = 10window = pygame.display.set_mode((width, height))#Function sets how the block (platforms in this case) will look like.def load_block(size): COLOUR = (0, 0, 0) surface = pygame.Surface((size, size), pygame.SRCALPHA, 32) rect = pygame.Rect(96, 0, size, size) surface.fill(COLOUR, rect) return pygame.transform.scale2x(surface)#drawing the object and player onto the windowdef draw(player, objects, win, offset_x): win.fill(BG_COLOR) # Clear the window # Draw the player sprite at its current position considering the offset win.blit(player.sprite, (player.rect.x - offset_x, player.rect.y)) # Draw each object in the objects list, adjusting their positions… # …relative to the offset. for obj in objects: obj.draw(win, offset_x) pygame.display.update()#function deals with collisions with platforms and ceiling.def vertical_collision(player, objects, dy): collided_objects = [] for obj in objects: # all possible objects sprite could collide with if pygame.sprite.collide_rect(player, obj): # Check for collision if dy > 0: player.rect.bottom = obj.rect.top player.landed() else: #################THIS WAS CAUSING THE BLOODY PROBLEM player.rect.top = obj.rect.bottom player.hit_head() collided_objects.append(obj) return collided_objects#handles horizontal collisionsdef collide(player,objects,dx): player.move(dx,0) #checks if sprite moves to the right player.update() #calls update to update rectangle collided_object = None for obj in objects: if pygame.sprite.collide_mask(player,obj): collided_object = obj break player.move(-dx,0) player.update() return collided_object#class created to design a template of how the sprite will look like and how it will operateclass Player(pygame.sprite.Sprite): #sets colour of sprite COLOUR = (0, 0, 0) #sets rate of speed at which the sprite falls GRAVITY = 1 #initialises def __init__(self, x, y, width, height): super().__init__() self.sprite = pygame.Surface((width, height)) self.sprite.fill(self.COLOUR) self.rect = self.sprite.get_rect(topleft=(x, y)) self.x_vel = 0 self.y_vel = 0 self.direction = "right" # Set initial direction self.animation_count = 0 self.fall_count = 0 self.jump_count = 0 #declares count for jump. def jump(self): self.y_vel = -self.GRAVITY*8 #When jumping, sprite will jump into air. self.animation_count=0 self.jump_count += 1 if self.jump_count==1: self.count=0 #As soon as sprite jumps, the gravity is removed. #then reapplies gravity when sprite is falling / has landed. #movement def move(self, dx, dy): self.rect.x += dx self.rect.y += dy #movement to left direction def move_left(self, vel): self.x_vel = -vel self.direction = "left" # Update direction self.animation_count = 0 #movement to right direction def move_right(self, vel): self.x_vel = vel self.direction = "right" # Update direction self.animation_count = 0 #keeps movement in loop def loop(self, fps): self.y_vel += min(1, (self.fall_count / fps) * self.GRAVITY) self.move(self.x_vel, self.y_vel) self.fall_count += 1 #function to determine if sprite has landed on a platform def landed(self): self.fall_count=0 self.y_vel=0 self.jump_count=0 #function to determine if sprite has collided with the ceiling def hit_head(self): self.count=0 self.y_vel*=-1 def update_sprite(self): sprite_sheet = "idle" #jump animations, will write this up later if self.y_vel != 0: if self.jump_count == 1: sprite_sheet = "jump" elif self.jump_count==2: sprite_sheet = "double_jump" elif self.y_vel> self.GRAVITY*2: sprite_sheet = "fall" elif self.x_vel != 0: sprite_sheet = "run" if self.x_vel != 0: sprite_sheet = "run" sprite_sheet_name = sprite_sheet +"_" + self.direction sprites = self.SPRITES[sprite_sheet_name] sprite_index = (self.animation_count // self. ANIMATION_DELAY) % len(sprites) self.sprite = sprites[sprite_index] self.animation_count += 1 self.update() def update(self): self.rect = self.sprite.get_rect(topleft=(self.rect.x, self.rect.y)) self.mask = pygame.mask.from_surface(self.sprite)class Object(pygame.sprite.Sprite): def __init__(self, x, y, width, height, name=None): super().__init__() self.rect = pygame.Rect(x, y, width, height) self.image = pygame.Surface((width, height)) self.image.fill(self.COLOUR) self.width = width self.height = height self.name = name def draw(self, win, offset_x): win.blit(self.image, (self.rect.x - offset_x, self.rect.y))class Block(Object): COLOUR = (0, 0, 0) def __init__(self, x, y, size): super().__init__(x, y, size, size) block = load_block(size) self.image.blit(block, (0, 0)) self.mask = pygame.mask.from_surface(self.image)#Function addresses any keys pressed to move the spritedef move(player, objects): keys = pygame.key.get_pressed() player.x_vel = 0 #handling horizontal collision collide_left = collide(player,objects, -PLAYER_VEL) collide_right = collide(player,objects, PLAYER_VEL) #only allows sprite to move if there is no block next to it if keys[pygame.K_d] and not collide_right: player.move_right(PLAYER_VEL) if keys[pygame.K_a] and not collide_left: player.move_left(PLAYER_VEL) #up key removed here, explained later in the code. #calls the vertical collision function vertical_collision(player,objects,player.y_vel) def gameplay(window): clock = pygame.time.Clock() block_size = 96 player = Player (400, 800, 25, 50) blocks = [Block(0, height - block_size, block_size)] #fills the bottom of the screen with platforms. floor = [Block (i * block_size, height - block_size, block_size) for i in range (-width // block_size, width * 2 // block_size)] #used to keep track of how much the player has moved horizontally relative to the window's view. offset_x = 0 #when sprite reaches x amount of pixels in either direction, the game starts to scroll. scroll_area_width = 200 #creating a block above platform objects = [*floor, Block(0,height - block_size*4, block_size)] run = True while run: clock.tick(FPS) # Event handling for event in pygame.event.get(): if event.type == pygame.QUIT: run = False break if event.type == pygame.KEYDOWN: if event.key == pygame.K_w and player.jump_count < 2: player.jump() # Update player and objects player.loop(FPS) move(player, objects) # Check if player has reached the scrolling area and update offset accordingly if ((player.rect.right - offset_x >= width - scroll_area_width) and player.x_vel > 0) \ or ((player.rect.left - offset_x <= scroll_area_width) and player.x_vel < 0): offset_x += player.x_vel # Draw everything with the updated offset draw(player, objects, window, offset_x) pygame.quit() quit()if __name__ == "__main__": gameplay(window)
I adjusted the move() function and added collision() function for horizontal collisions.
Move() function was adjusted to add and not collide_right
and and not collide_left
Declared new variables within the move() function collide_left
and collide_right
Collision() function was added where player.move(dx,0)
and player.update
moves the object and updates the mask.
When I added these two functions, I initially had an error that mentioned AttributeError: 'Player' object has no attribute 'sprite'
, but I fixed that by changing self.sprite
to self.image
, but I'm not sure if that correlates with the problem I am having.
Any help would be greatly appreciated, and for context this is a platformer game I'm trying to code that is part of a school coursework I have to do.
Thanks in advance.