pygame and tiled map collsions - python

I am trying to make the player sprite stop falling on the platform rect top. I have tried a lot of things over the past two days and I am more lost then I've ever been. Thank you so much. I am using tmx to load a tiled map. I add a Platform object every the name is detected and than I add that to a list. I enumerate through the list to check collision using sprite.collide(self.player, plat). This doesn't work.
def update(self):
for plat in self.platList:
self.hits = pg.sprite.spritecollide(self.player,self.platList,False)
if self.hits:
if self.player.pos.y > self.hits[0].y:
self.player.pos.y = self.hits[0].rect.top
for tile in self.map.tmxdata.objects:
if tile.name == "player":
self.player = Player(self,tile.x,tile.y)
if tile.name == "Platform":
self.platList.append(Platform(self, tile.x, tile.y,tile.width,tile.height))
class Platform(pg.sprite.Sprite):
def __init__(self,game,x,y,width,height):
self.game = game
pg.sprite.Sprite.__init__(self)
self.rect = pg.Rect(x,y,width,height)
self.y = y
self.rect.x = x
self.rect.y = y

I guess your problem is that you don't use the player's rect attribute to define the position of the player. You're using pygame's sprites, so use them the way it is intended.
All pygame functions that deal with sprites (e.g. spritecollide) will use the rect attribute, but in your code the player class has an additional pos attribute, and Platform also has a y attribute.
Remove them, and use rect exclusively to store the size and position of your sprites.
When you want to move a sprite, just change its rect (e.g. with move_ip etc.) or its attributes.

Related

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.

Pygame spritecollide hitboxes

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.

Does colliderect work with classes, if so then how do I use it?

I am having an issue adding "colliderect" to my game. I have this class for setting values to the objects, and then one for drawing the items to the screen.
**Edited **
Throughout edits I have updated what my code is having issues with. I have yet to make any of the bel
ow solutions work with my class. All answers that have been given are very helpful, but I am still having the same issues. Collierect just doesn't want to work, when I use pygame.sprite.Sprite for collisions I get a sprite begs flashes when it contacts another sprite.rect, but the rect's movement doesn't reverse.
Help on either of these issues would be greatly appreciated, but until I get things solved I will leave the question open.
Error I was getting
if paddle1.Render().colliderect(ball):
AttributeError: 'NoneType' object has no attribute 'colliderect'
Error I am getting now
if paddle1.Render().colliderect(ball):
AttributeError: 'Object' object has no attribute 'colliderect'
I don't know how to fix this issue.
Here's my code for the ball
#Create a class named sprite to use for the paddles and the ball.
class Object():
def __init__(self,x,y,width,height,color):
# Set X value
self.x = x
# Set Y value
self.y = y
# Set Width of object
self.width = width
# Set Height of object
self.height = height
# Set the (default) color of the object
self.color = (255,255,255)
#attirbute for drawing the sprite(s) to the screen
def Render(self):
pygame.draw.rect(screen,self.color,(self.x,self.y,self.width,self.height))
return self
#Create the sprites
paddle1 = Object(50,175,25,150,color[0])
paddle2 = Object(650,175,25,150,color[1])
ball = Object(300,250,25,25, color[2])
This is the code for colliderect
#Commands for ball collision
if paddle1.Render().colliderect(ball):
if True:
pass #do something
if paddle2.Render().colliderect(ball):
if True:
pass #do something
# --- Drawing code should go here
Aside from the if statement, what am I missing for colliderect to work?
Your method render() that your wrote does not return any value (Ok, it returns None.)
Were you intending to return the rect at the end of that method?
def render(self):
self.rect = pygame.draw.rect(screen,self.color,(self.x,self.y,self.width,self.height))
return self.rect
Then you could chain your method calls like you want:
paddle2.render().colliderect(ball):
...although you probably need to calculate and set the ball's rect and pass it:
paddle2.render().colliderect(ball.rect):
I haven't used pygame so I'm helping a little blindly, but I think you have some confusion over classes. Your sprite should be derived from pygame.sprites.Sprite. Then you specialize your sprite to be the kind of sprite you want it to be, but pygame's sprite implements most of the underlying logic.
Here's an example sprite implementation: http://www.101computing.net/creating-sprites-using-pygame/
Something like that might work. To use colliderect Pygame expects a Rect(x,y,width,height) or Rect((x,y),(width,height)).
So basically you want: myrectangle1.colliderect(myrectangle2)
Similarly to use collidepoint you need: myrectangle1.collidepoint(mypoint1)
#Create a class named sprite to use for the paddles and the ball.
class Object():
def __init__(self,x,y,width,height,color):
# Set X value
self.x = x
# Set Y value
self.y = y
# Set Width of object
self.width = width
# Set Height of object
self.height = height
# Create Rect of object=
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
# Set the (default) color of the object
self.color = (255,255,255)
#attirbute for drawing the sprite(s) to the screen
def Render(self):
pygame.draw.rect(screen,self.color, self.rect)
return self
#Create the sprites
paddle1 = Object(50,175,25,150,color[0])
paddle2 = Object(650,175,25,150,color[1])
ball = Object(300,250,25,25, color[2])
alternatively if you don't want to have a self.rect attribute then you need:
def Render(self):
pygame.draw.rect(screen,self.color,pygame.Rect(self.x,self.y,self.width,self.height))
return self
This Render method however will just draw your rectangle.
If you want it to return the rectangle attribute of your object to test for collision then you need:
def Render(self):
pygame.draw.rect(screen,self.color, self.rect) #draws the rect
return self.rect #returns the rect
To check for collision you would need:
if paddle1.rect.colliderect(ball.rect): #this is why I think having a rect attribute is cleaner than calling pygame.Rect(x,y,w,h) all the time.
or:
if paddle1.pygame.Rect(self.x, self.y, self.width, self.height).colliderect(ball.pygame.Rect(self.x, self.y, self.width, self.height)):

Get the position of the collided sprite

I need to get the position of the last sprite that collided between 2 groups. All the sprites in a group have the same name. here is the concentrate code:
rmog = pygame.sprite.Group()
rmg = pygame.sprite.Group()
class rmissileh(pygame.sprite.Sprite):
def __init__(self, image, speed):
pygame.sprite.Sprite.__init__(self)
self.speed = -10
self.speedx = 0
self.image = redm
self.rect = image.get_rect().move(s.rect.x+26,s.rect.y)
def move(self):
self.rect = self.rect.move(self.speedx, self.speed)redshot =
rmissileh(redm, -10 )
rmg.add(redshot)
class crmonstre(pygame.sprite.Sprite):
def __init__(self, image, speed):
pygame.sprite.Sprite.__init__(self)
self.speedy = 2
self.speedx = 1
self.image = rmonstre
self.step = 600
self.rect = rmonstre.get_rect().move(500,-100)
def move(self):
self.rect = self.rect.move(self.speedx,self.speedy)
redmonstre = crmonstre(rmonstre,1)
rmog.add(redmonstre)
redmonstre = crmonstre(rmonstre,1)
rmog.add(redmonstre)
redmonstre = crmonstre(rmonstre,1)# I added only three but there is many more overtime.
rmog.add(redmonstre)
while stop:
for event in pygame.event.get():
if event.type ==pygame.QUIT:
stop = False
for redmonstre in rmog:
fenetre.blit(fond, redmonstre.rect, redmonstre.rect)
redmonstre.move()
fenetre.blit(redmonstre.image,redmonstre.rect)
if pygame.sprite.groupcollide(rmg, rmog,True ,True):
fenetre.blit(fond, (0,0))
pygame.display.update()
pygame.quit()
quit
As you can see when a redmonster collide with a redshot i have to blit the whole background(named "fond") because i don't know his position, if I print(redmonstre.rect.x) that will print the x position of the last object that i created and not the position of the one that collided.
And also in the while loop to blit a moving object i do :
for redmonstre in rmog:
fenetre.blit(fond, redmonstre.rect, redmonstre.rect)
redmonstre.move()
fenetre.blit(redmonstre.image,redmonstre.rect)
i have many many for loop like that in my code and it doesn't feel right if you have an other way to do it please let me know.
I don't know how to find just the last collision. But here is how you could find all of the collisions, and hopefully that's a step in the right direction.
There are two ways you could do this. The first way would be to iterate through the list of sprites yourself. That would look kind of like this:
for sprite in rmg:
#Note that spritecollide will remove every sprite that collides from
#rmog, but it will not remove 'sprite' from rmg.
if (pygame.sprite.spritecollide(sprite, rmog, True)):
print (sprite.x)
rmg.remove(sprite)
spritecollide docs.
Here is the other way you could do it.
Look at the documentation for pygame.sprite.groupcollide. You'll see that it returns a dictionary of all the collisions. The dictionary will have all of the sprites from group 1 (which is rmg) as the keys, and the value for each key will be a list of every sprite from group 2 (which is rmog) that collided with it. So then to find every sprite that collided from group one, you could do:
dict = pygame.sprite.groupcollide(rmg, rmog,True ,True)
for sprite in dict:
if dict[sprite]:
print (sprite.x)
Also, a couple general tips that are unrelated to your problem,
Don't do this:
redmonstre = crmonstre(rmonstre,1)
rmog.add(redmonstre)
redmonstre = crmonstre(rmonstre,1)
rmog.add(redmonstre)
redmonstre = crmonstre(rmonstre,1)
rmog.add(redmonstre)
There is no need to have a name for each redmonstre, and it will only make it more confusing to have them all named the same. Instead, just add them directly to the list:
rmog.add(crmonstre(rmonstre, 1))
rmog.add(crmonstre(rmonstre, 1))
rmog.add(crmonstre(rmonstre, 1))
And don't do this:
for redmonstre in rmog:
fenetre.blit(fond, redmonstre.rect, redmonstre.rect)
redmonstre.move()
fenetre.blit(redmonstre.image,redmonstre.rect)
Blitting "fond" takes a (relatively) long time. If you blit it for every redmonstre, you will only see the last one. Just blit it once before the loop.
fenetre.blit(fond, redmonstre.rect, redmonstre.rect)
for redmonstre in rmog:
redmonstre.move()
fenetre.blit(redmonstre.image,redmonstre.rect)

Categories