Pygame spritecollide hitboxes - python

I am working on a simple 2D platformer, but I am having some trouble with the hitboxes of my sprites. I use the pygame.sprite.spritecollide function to generate a list of blocks(platforms) touching my player sprite.
Here is my player class:
class Player( pygame.sprite.Sprite ):
def __init__(self,x,y,image):
super( Player, self ).__init__()
self.vel_x = 0
self.vel_y = 0
self.moveSpeed = 3
self.jumpPower = 7
self.direction = idle
self.grounded = False
self.falling = True
self.jumping = False
self.climbing = False
self.image = pygame.Surface((50,50))#pygame.transform.scale( image, (50,50))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.width = 50
self.height = 50
self.rect.bottom = self.rect.y + self.height
def set_position( self,x,y ):
self.rect.x = x
self.rect.y = y
def experience_gravity(self, gravity = 0.3):
if not self.grounded:
self.vel_y += gravity
self.falling = True
else:
self.vel_y = 0
self.grounded = True
self.falling = False
def jump(self):
self.grounded = False
self.vel_y -= self.jumpPower
def update(self, collidable = pygame.sprite.Group() ):
global collision
self.experience_gravity()
self.rect.x += self.vel_x
self.rect.y += self.vel_y
collision_list = pygame.sprite.spritecollide( self, collidable, False )
for p in collision_list:
if ( self.vel_y > 0 ):
self.vel_y = 0
self.grounded = True
In my the update method, I check for collisions between a sprite group holding all of my platforms (parameter collidable) and the player.
Here's my block class:
class Block( pygame.sprite.Sprite ):
def __init__( self, x, y, width, height):
super( Block, self ).__init__()
self.image = pygame.Surface( ( width, height ) )
self.image.fill( black )
self.rect = self.image.get_rect()
self.type = platform
self.rect.x = x
self.rect.y = y
self.width = width
self.height = height
Pretty self-explanatory block class. The rest of my code is to update and blit everything onto a white background. The problem I run into is that when the player lands on a platform, it only stops falling (becomes grounded) when it is already in the platform. Even stranger, the depth the block sinks intot he platform is not consistent. Sometimes it will fall in 10 pixels, other ties 20 pixels. Here's a screenshot of the player getting stuck:
The player block is stuck inside the platform block
So this is really baffling me, especially since the amount the block falls in is inconsistent. If anyone could give me an idea on how to fix this, I'll really appreciate it.
With kindest regards,
Derek

It's hard to diagnose without any debugging trace, but I have an idea or two.
First of all, dump a debugging trace: print the salient values at each time step: position of the player block, positions of colliding blocks, and applicable velocities. This should give you a table-like snapshot at each time step of the involved objects.
Now, look at the values in the last time step before the collision. I suspect that you might have something like a player velocity of -20, but landing on a platform only 10 pixels below. Your update logic needs to handle the landing between the two time steps, and stop the player at the platform height. It looks to me as if the player is allowed to fall for the entire time step, which will embed his tender lower extremities 10 pixels into the platform.
Second, check that you're comparing the nearer edges: in this case, the player's lower edge with the platform's upper edge. You will later need to handle the case where a player's lower-right corner contacts the upper-left corner of a block, and you have to determine whether the player first hits the block's top (landing) or side (change horizontal velocity and drop).
I wouldn't alter the computed velocity; I would think it's easier just to adjust the final position. The last block of code you posted is where you already deal with a collision as a special case. When you detect a collision, get the top surface (y-value) of the object. If that's above the current position of the bottom of the player, assign that top surface as the correct position. This will leave your player sitting on top of whatever is the highest object on the collision list.
Note that this works only as long as your entire collision list is from falling; for lateral collisions as well, you'll need to separate the lists and adjust each boundary as needed. This can get complicated if there are collisions from several sides on the same time step -- for instance, a goombah slams into the player from the left just as the player hits both a wall and a platform.

Related

Screen looks like it's sinking due to weird collision/jumping values that affect camera movement

I'm trying to implement vertical camera movement in my PyGame platformer (horizontal camera movement works fine, almost definitely irrelevant to this) but the screen always appears to constantly be sinking unless the player jumps (but when the player finishes his jump, it starts sinking again). Here's the code:
if self.onGround == False: # falling
self.vermo += 0.4 # player's vertical momentum increasing as he falls
if self.onGround == True:
self.jump = False
self.fall = False
if self.vermo > 10: # can't fall too fast
self.vermo = 10
if self.vermo > 0 and self.collideWithBlock == False: # checks if the player is not on the ground but has also past climax/zenith of jump
self.fall = True
if keys[pygame.K_SPACE] and self.onGround == True: # makes player jump but only if touching the ground
self.vermo = -12
self.jump = True
if self.fall == True and self.onGround == False: # if the player is not on the ground but has past the climax.zenith of his jump, the camera should follow the player downward
for sprite in self.game.all_sprites: # this moves every sprite on the screen to give the illusion of camera movement
sprite.rect.y -= self.vermo
if self.jump == True: # if the player is shooting upwards (jumping) then the camera should follow him upward
for sprite in self.game.all_sprites:
sprite.rect.y -= self.vermo # I thought this should be += but apparently not
The reason this is happening is due to self.vermo looping the values below. The problem is, I have no idea what causes this.
These are the values:
0
0.4
0.8
1.2000000000000002
0
Here is the basic code for a camera class:
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 + width//2
y = -target.rect.y + height//2
x = min(0, x)
y = min(0, y)
x = max(-(self.width - width), x)
y = max(-(self.height - height), y)
self.camera = pg.Rect(x,y,self.width,self.height)
Every time you update, you have to apply the update method, with target=player.
This will adjust the camera according the player's new position.
def update(self):
self.all_sprites.update()
self.camera.update(self.player)
When you blit the other objects to the screen, you create the illusion the player is moving.
for sprite in self.all_sprites:
self.screen.blit(sprite.image, self.camera.apply(sprite))
Keep in mind that the width and height parameters passed into the camera constructor are the width and height of the level, not the width and height of the screen.
I would try implementing this and see if it fixes your sinking problem.
You could also try adding this to your existing code:
if self.onGround == True:
self.vermo = 0
self.jump = False
self.fall = False
This will ensure that when the player is on the ground, no vertical momentum.
I really couldn't tell you why, but in this line of code:
if self.vermo > 0 and self.collideWithBlock == False:
self.fall = True
the 0 needs to be replaced with 1.6. Again, no idea why, but it works. I don't question the ways of the Python gods, but by all means if you know why this is I encourage you to comment it here.
The camera, as it is now, still has some issues, but I know why these issues are occurring and I'm currently working on fixing them.

Having problems creating an animation from a sprite sheet in pygame

I am relatively new to Python and started messing around with pygame a few days ago. I made a sprite sheet in photoshop and I am trying to use it to animate the player in the game. The sprite sheet has 8 images, 4 for walking left and 4 for walking right. I want to show these animations at the correct time. I have tried many different methods I've found online but I haven't managed to get any working and I am still quite confused about it.
This is the Char class that controls the character's movement and sprite. I tried indexing the sprite and changing the index in the update method but I understand this isn't possible but I've left it in to show what I was trying to do. Any help would be appreciated!
import pygame, sys
from pygame.sprite import Sprite
class Char(Sprite):
def __init__(self, pf_game):
"""initialise char and set its starting location"""
self.screen = pf_game.screen
self.settings = pf_game.settings
self.screen_rect = pf_game.screen.get_rect()
self.dog_sprite = pygame.image.load('images/dog_sprite_sheet.bmp').convert()
self.current_sprite = 0
self.dog_image = self.dog_sprite[self.current_sprite]
self.rect = self.dog_image.get_rect()
# start new char at the bottom of centre of the screen
self.rect.midbottom = self.screen_rect.midbottom
#store a decimal value for the ships horizontal position
self.x = float(self.rect.x)
self.y = float(self.rect.y)
#movement flags
self.moving_right = False
self.moving_left = False
self.is_jump = False
def update(self):
"""Update the chars position based on movement flags"""
#update the chars x value and create animation for moving right
if self.moving_right and self.rect.right<self.screen_rect.right:
if self.current_sprite == 7:
self.current_sprite = 4
self.dog_image = self.dog_sprite[self.current_sprite]
self.current_sprite+=1
self.x+= self.settings.char_speed
#update the chars x value and create animation for moving left
if self.moving_left and self.rect.left>0:
if self.current_sprite == 3:
self.current_sprite = 0
self.dog_image = self.dog_sprite[self.current_sprite]
self.current_sprite+=1
self.x-= self.settings.char_speed
#update rect object from self.x
self.rect.x = self.x
self.rect.y = self.y
def blitme(self):
"""Draw the char at its curretn loaction"""
self.screen.blit(self.dog_image, self.rect)
edit:
I found a spritesheet class that seems to do what I want on http://www.pygame.org/wiki/Spritesheet. I am running into an error though when I try and input the coordinates. I get the error images_at() got multiple values for argument 'colorkey'. When i remove the colorkey I get the error images_at() takes from 2 to 3 positional arguments but 5 were given.
This is the code I am using.
self.dog_sprite=spritesheet.spritesheet('dog_sprite_sheet.bmp')
self.left_images=[]
self.right_images=[]
self.left_images=self.dog_sprite.images_at((0,0,19,19),(20,0,19,19),(39,0,19,19),(58,0,19,19), colorkey=(255, 255, 255))
self.right_images=self.dog_sprite.images_at((77,0,19,19),(96,0,19,19),(115,0,19,19),(134,0,19,19), colorkey=(255, 255, 255))
this may help:
def anim(anim_count):
if anim_count < 6:
screen.blit('anim1.png', player_position)
if anim_count > 6 and anim_count < 12:
screen.blit('anim2.png', player_position)
you should call anim at every frame, and then apply 1 to anim_count

How do I implement random direction change on my pygame enemy?

So I currently have an enemy class that moves left, encounters the end of the screen, then flips direction 180 degrees and walks right. It does this constantly.
I wish to make it randomly change direction, so it's more unpredictable. The issue I'm encountering is when I implement a random number generator for the distance of direction, how would I then inherit it into a variable when I create the instance? Or is there a more efficient way to do this?
Entire enemy class:
class enemy(object):#need to use self to access these
walkRight = [pygame.image.load('Game/R1E.png'),pygame.image.load('Game/R2E.png'),pygame.image.load('Game/R3E.png'),pygame.image.load('Game/R4E.png'),pygame.image.load('Game/R5E.png'),pygame.image.load('Game/R6E.png'),pygame.image.load('Game/R7E.png'),pygame.image.load('Game/R8E.png'),pygame.image.load('Game/R9E.png'),pygame.image.load('Game/R10E.png'),pygame.image.load('Game/R11E.png')]
walkLeft = [pygame.image.load('Game/L1E.png'),pygame.image.load('Game/L2E.png'),pygame.image.load('Game/L3E.png'),pygame.image.load('Game/L4E.png'),pygame.image.load('Game/L5E.png'),pygame.image.load('Game/L6E.png'),pygame.image.load('Game/L7E.png'),pygame.image.load('Game/L8E.png'),pygame.image.load('Game/L9E.png'),pygame.image.load('Game/L10E.png'),pygame.image.load('Game/L11E.png')]
def __init__(self,x,y,width,height,end): #get R3E png
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x,self.end]
self.walkCount = 0
self.vel = 3
def draw(self,window): #enemy is gonna move from left, to right, left, to right etc between 2 co ordinate points
self.move()
if self.walkCount + 1 >= 33: #if sign is changed, as walkcount would always be less than 33, it wouldn't change
self.walkCount = 0
#rather than using self.left, sel.right, we can use out velocity
if self.vel > 0: #this means we're moving right, integer division 3 so it doesn't look like we going too fast
window.blit(self.walkRight[self.walkCount //3],(self.x,self.y))
self.walkCount += 1
else:
window.blit(self.walkLeft[self.walkCount //3],(self.x,self.y))
self.walkCount += 1 #if not moving right, we're moving left
#check if we're drawing an image to the left or right
def move(self): #move method #to change directions, he needs to change velocity (multiply by -1 etc)
if self.vel > 0:
if self.x < self.path[1] + self.vel:#check if he's about to move past the point on screen, we're accessing the self.end eleme
self.x += self.vel
else: #e.g. if it's greater than, we change direction
self.vel = self.vel * -1 #flipped 180 degrees, so goes other direction
self.x += self.vel
self.walkCount = 0
else: #if our vel is negative
if self.x > self.path[0] - self.vel:
self.x += self.vel #vel is gonna be negative already if we've changed directions
else:
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
Movement function:
def move(self): #move method #to change directions, he needs to change velocity (multiply by -1 etc)
if self.vel > 0:
if self.x < self.path[1] + self.vel:#check if he's about to move past the point on screen, we're accessing the self.end eleme
self.x += self.vel
else: #e.g. if it's greater than, we change direction
self.vel = self.vel * -1 #flipped 180 degrees, so goes other direction
self.x += self.vel
self.walkCount = 0
else: #if our vel is negative
if self.x > self.path[0] - self.vel:
self.x += self.vel #vel is gonna be negative already if we've changed directions
else:
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
Creating the instance just before the main loop(x,y,height,width,walk distance):
goblin = enemy(0,440,64,64,450)
My attempt - still only takes one random number & permanently uses it whilst the game runs.
def timerthing():
pathenemy = random.randint(0,450)
return pathenemy
#now to write main loop which checks for collisions, mouse events etc
#make path randomly change
man = player(200,444,64,66) #creates instance of the player / object
goblin = enemy(0,440,64,64,timerthing())#need to create instance of enemy so he appears on the screen, path ends at timerthing
run = True
bullets = []
while run:
clock.tick(15) #frame rate
timerthing()
Instead of a constant of 450, I'm after a constantly changing variable instead, so his movement is unpredictable. Any other suggestions would be appreciated - thanks.
import random
def timerthing():
pathenemy = random.randint(0,450)
time.sleep(1)
return pathenemy
But I suspect that's not really your problem. It sounds like you have a configuration problem rather than a source problem, and to solve that we'd need an entirely different kind of information from you.
If I had to do code this randomly moving enemy, I would implement two methods first:
One method that returns the possible directions my enemy can go through, given the state of the board. Yet I can't help you with that, since we don't have the "board" part of your code. Let's call this method getPossibleDirections()
One method move, with one argument direction, representing the direction taken by my enemy. Let's call this method move(direction)
Based on your code, I guess you can implement these two methods by yourself. And maybe you want your ennemy to move in the y direction too?
Like #Mark Storer said, you could use the random package, but I will use the random part of the numpy library (already included in python) for my answer:
# you will need to import numpy in the beginning of your file
import numpy as np
class enemy(...)
# your code goes there
def getPossibleDirections(self):
#your implementation
pass
def move(self, direction):
#your implementation
pass
def moveRandomly(self):
possibleDirections = self.getPossibleDirections()
chosenDirection = np.random.randint(0,len(possibleDirections))
# returns one number between 0 and len(possibleDirections) (not included) that can represent your direction
self.move(possibleDirection[chosenDirection])
To move your ennemy randomly, you will just have to call enemy.moveRandomly().

How to create free game collision with pyglet?

I have started working on a 2d game that is comparable to Terraria in terms of what I am dealing with. My problem is as follows The world is made of tiles/blocks then there is the player. I can't seem to find a way to make the player collide with the tiles, why not a set ground level? because if there isn't a tile under the player he should fall and what if the players on a hill? So long story short I need a way to make the player collide with the tile beneath them.
So here is what I have tried: making a simplified grid to verify if the is a tile below, a for loop where I compared all the tiles to the players feet but this was too much data to feed and I couldn't find a way to say check the tile under, above, left, and right of the player and only those positions. I am going to try to show useful code but it will be a bit jumbled seeing as I cant pas't all the files in.
class Physics:
#classmethod
def fall(cls, dt, o, world, mod):
if world.grid_fill[(o.grid_pos[0], o.grid_pos[1]-1)] == "Filled":
o.falling = False
else:
o.y -= o.height//8*dt
o.update_pos(o.x, o.y)
#classmethod
def run_physics(cls, dt, o, world):
for obj in o:
Physics.fall(dt, obj, world, None)
# player and Physics are in 2 different files as is world and the main loop
class Player:
def __init__(self, sprite, x, y, world):
self.img = sprite
self.img.set_position(x, y)
self.key_handler = key.KeyStateHandler()
self.type = "Player"
self.x = x
self.y = y
self.pos = (self.x, self.y)
self.grid_pos = world.pix_cords[self.pos]
self.width = self.img.width
self.height = self.img.height
self.speed = self.width//4
self.falling = False
def update_pos(self, x, y):
self.img.set_position(x, y)
def draw(self):
self.img.draw()
def check_fall(self, tile):
if self.y - self.height//2 == tile.y + tile.height:
return False
else:
return True
def update(self):
if self.key_handler[key.A]:
self.x -= self.speed
if self.key_handler[key.D]:
self.x += self.speed
if self.key_handler[key.W]:
self.y += self.speed
self.update_pos(self.x, self.y)
Before you say I didn't show how the ground is made that is because I can explain this by just saying it is made using a batch draw and all sprite cornets are centered to the sprite. Alright, when I run this code the player falls indefinitely but I want it to stop when there is a tile under it.
If you need to see the batch for the tile I will but I don't know if that is needed and if you see anything unprofessional about how this is written I am always willing to learn how to write more professionally.
This was a stupid question. Just need to make a simplified positioning system based on the blocks, not sprites.

How do I set a boundary in which my image can go through in Pygame?How do I keep an image from going behind another?

This is the code for my pygame
import pygame
import os
img_path=os.path.join('C:/Desktop/Python Stuff','Angry Birds.jpg')
class pic(object):
def __init__(self):
""" The constructor of the class """
self.image = pygame.image.load(img_path)
# the bird's position
self.x = 0
self.y = 0
def handle_keys(self):
""" Handles Keys """
key = pygame.key.get_pressed()
dist = 5
if key[pygame.K_DOWN]: # down key
self.y += dist # move down
elif key[pygame.K_UP]: # up key
self.y -= dist # move up
if key[pygame.K_RIGHT]: # right key
self.x += dist # move right
elif key[pygame.K_LEFT]: # left key
self.x -= dist # move left
def draw(self, surface):
""" Draw on surface """
# blit yourself at your current position
surface.blit(self.image, (self.x, self.y))
This is the screen size. Is this where the I should restrict the image's boundaries?
pygame.init()
screen=pygame.display.set_mode([1500,850])
Pic=pic()
pygame.display.set_caption('Angry Birds')
This is the image that I want to have a boundary for
pic=pygame.image.load('Angry Birds.jpg')
keep_going=True
while keep_going:
event=pygame.event.poll()
*emphasized text* if event.type == pygame.QUIT:
pygame.quit()
running = False
Pic.handle_keys()
screen.blit(pic, (-200, 0))
Pic.draw(screen)
This image is what the 'Angry Birds' image is going behind. How do I stop it from going behind this image?
tux=pygame.image.load('Rock Lee.gif')
screen.blit(tux,(500,600))
screen.blit(tux,(500,400))
screen.blit(tux,(500,0))
screen.blit(tux,(900,200))
screen.blit(tux,(900,400))
screen.blit(tux,(900,600))
screen.blit(tux,(1300,0))
screen.blit(tux,(1300,200))
screen.blit(tux,(1300,600))
pygame.display.get_surface([1500,850]).get_size([1500,850])
pygame.display.update()
A) Keep rect on screen
The simplest way would be using Rect.clamp_ip(rect) on a Sprite
screen_size = Rect(1500,850)
# right after when you move the bird
bird.rect.clamp_ip(screen_size)
B) rect on rect collision
# Where .xvel and .yvel are bird's movement per frame
new_rect = bird.rect.move(bird.vxel, bird.yvel)
if not new_rect.collide_rect(other_bird.rect)
bird.rect = new_rect
else
print("Ouch!")
Border collision
An easy way to implement border collision is to just check if the current position is outside the screen, and if it is you move it back. It's easiest done by creating a Rect object from the screen which you could pass in an update method of your class pic (classes should start with capital letter). So start with creating an update method were you pass the screen object.
Also, since the x and y position reference the top left of the image you need to take that in consideration when checking for border collision with the right side and the bottom. Best would be to create attributes width and height instead of what I'm doing below.
def update(self, screen):
"""Method that check border collision for object 'pic'."""
border = screen.get_rect()
width = self.image.get_width()
height = self.image.get_height()
if self.x < border.left:
# You collided with the left side of the border.
# Move your character back to the screen
self.x = border.left
elif self.x > border.right - width:
# You collided with the right side of the border.
# Move your character back to the screen
self.x = border.right - width
if self.y < border.top:
# You collided with the top of the border.
# Move your character back to the screen
self.y = border.top
elif self.y > border.bottom - height:
# You collided with the bottom of the border.
# Move your character back to the screen
self.y = border.bottom - height
All you have to do is call this method every time in your loop, like so:
Pic.handle_keys() # Variables should be all lowercase! Not like it's currently.
Pic.update(screen) # Variables should be all lowercase! Not like it's currently.
Pic.draw(screen) # Variables should be all lowercase! Not like it's currently.
Keep image in front
When blitting to the screen it draws each image on top of each other. In your case you're blitting your character and then the rocks, meaning the rocks always be on top of your character. Changing this is simple, blit the rocks first and the character last and your character will end up in front of your rocks.

Categories