I have two moving sprites, one is called wall and it just moves up and down; the other is called Player and it bounces around the screen and changes direction whenever it hit an obstacles.
I got this to work to a degree: The player ball does bounce around but sometimes there is a bug where the ball seems to be confused about the movement and just wobbles back and forth when colliding with the wall. The faster I set the speed of the player ball or the movement of the wall, the more frequent the bug becomes.
Here is the code for the project:
import pygame, sys
# The class that creates the player ball; it does not take user input but just bounces around
class Player(pygame.sprite.Sprite):
def __init__(self, color, width, height,speed_x,speed_y):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.speed_x = speed_x
self.speed_y = speed_y
def update(self,collision_group):
self.rect.x += self.speed_x
self.rect.y += self.speed_y
# code to get a bounce when the ball hits the edge of the screen
if self.rect.right >= screen_width or self.rect.left <= 0:
self.speed_x *= -1
if self.rect.bottom >= screen_height or self.rect.top <= 0:
self.speed_y *= -1
# Code to get a bounce when the player collides with the wall
collide_object = pygame.sprite.spritecollide(self,collision_group,False)
if collide_object:
if collide_object[0].rect.collidepoint(self.rect.midbottom) or collide_object[0].rect.collidepoint(self.rect.midtop):
self.speed_y *= -1
if collide_object[0].rect.collidepoint(self.rect.midright) or collide_object[0].rect.collidepoint(self.rect.midleft):
self.speed_x *= -1
# Code for the wall, it just moves up and down
class Wall(pygame.sprite.Sprite):
def __init__(self,width,height,pos_x,pos_y,color):
super().__init__()
self.image = pygame.Surface([width,height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = pos_x
self.rect.y = pos_y
self.speed = 5
def update(self):
self.rect.y += self.speed
if self.rect.bottom >= screen_height:
self.speed *= -1
if self.rect.top <= 0:
self.speed *= -1
# General setup
pygame.init()
clock = pygame.time.Clock()
# Game Screen
screen_width = 800
screen_height = 800
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption("Side by side collision")
# Creating the sprites and groups
moving_sprites = pygame.sprite.Group()
player = Player((255,0,0),20,20,8,8)
moving_sprites.add(player)
wall_sprites = pygame.sprite.Group()
center_wall = Wall(500,200,300,300,(255,255,0))
wall_sprites.add(center_wall)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Drawing
screen.fill((0,0,0))
wall_sprites.draw(screen)
wall_sprites.update()
moving_sprites.draw(screen)
moving_sprites.update(wall_sprites)
pygame.display.flip()
clock.tick(60)
many thanks in advance :)
You can realize that your code works perfectly if the the wall is moving away from the player when they collide. The problem occurs when they are moving towards each other. This is because your player is actually goes inside the wall, you detect a collision, reverse the speed of the player and at the next iteration your player is still inside the wall (because wall is moving towards your player, the player couldn't find enough time to escape it) and you detect a collision again, reverse the speed of the player...
You can modify your code like this to solve the problem:
if collide_object:
rect = collide_object[0].rect
if rect.collidepoint(self.rect.midbottom): # if we hit from bottom
if self.speed_y > 0: # and the player was moving down
self.speed_y *= -1 # go up
elif rect.collidepoint(self.rect.midtop): # if we hit from top
if self.speed_y < 0: # and the player was moving up
self.speed_y *= -1 # go down
Related
this is my first question in stackoverflow. I started learning python 3 months ago, and I'm building a platformer game using sprites. I had to isolate the problem, so I'm only posting the part where the issue is.
Whenever the square is landing on any platform, there is a detection of collision but the square actually goes through the platform approximately 10 pixels down (DURING the collision) and then it goes back up and it stabilizes on top of the platform.
You can SEE the effect better if you adjust the FPS to 2.
How can I fix this? I remember building a previous game BUT without using acceleration in "x" axis (friction) and "y" axis (gravity), just fast&stop movements of the square (left/right/up/down) detecting collision and the square never went through the platfom...
Here is the isolated problem:
import pygame as pg
import random
WIDTH = 900
HEIGHT = 600
NAME = "square"
FPS = 60
green = (0, 150, 0)
white = (255, 255, 255)
black = (0, 0, 0)
class Platform(pg.sprite.Sprite):
def __init__(self, x, y, width, height, color):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Player(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((40, 40))
self.image.fill(black)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.posx = WIDTH / 2
self.posy = HEIGHT * 0.10
self.velx = 0
self.vely = 0
self.accx = 0
self.accy = 0
self.jumping = False
self.jump_go = False
def jump(self):
if self.jumping == False:
self.jumping = True
self.vely = -25
def update(self):
self.accx = 0 #FRICTION
self.accy = 1 #GRAVITY
keys = pg.key.get_pressed()
if keys[pg.K_RIGHT]:
self.accx = 1
if keys[pg.K_LEFT]:
self.accx = -1
if self.vely != 0:
self.jump_go = False
#MOVEMENT IN X:
self.accx = self.accx + (self.velx * (-0.10))
self.velx = self.velx + self.accx
self.posx = self.posx + self.velx
self.rect.x = self.posx
if abs(self.velx) < 0.5:
self.velx = 0
#MOVEMENT IN Y:
self.accy = self.accy + (self.vely * (-0.05))
self.vely = self.vely + self.accy
self.posy = self.posy + self.vely
self.rect.y = self.posy
#IF PLAYER FALLS TO EDGE OF SCREEN:
if self.posy > HEIGHT:
self.posy = 0
if self.posx < 0:
self.posx = WIDTH
if self.posx > WIDTH:
self.posx = 0
class Game:
def __init__(self):
pg.init()
pg.mixer.init()
pg.font.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
self.clock = pg.time.Clock()
self.running = True
def new(self):
self.all_sprites = pg.sprite.Group()
self.all_platforms = pg.sprite.Group()
for i in range(3):
self.platform = Platform(random.randint(0, WIDTH), random.randint(HEIGHT * .15,
HEIGHT - 25), random.randint(100, 300), 50, green)
self.all_platforms.add(self.platform)
self.all_sprites.add(self.platform)
self.player = Player()
self.all_sprites.add(self.player)
self.run()
def run(self):
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.eventos()
self.update()
self.draw()
def eventos(self):
for event in pg.event.get():
if event.type == pg.QUIT:
if self.playing:
self.playing = False
self.running = False
if event.type == pg.KEYDOWN:
if self.player.jump_go:
if event.key == pg.K_a:
self.player.jump()
def update(self):
self.all_sprites.update()
# Check if player hit platfom:
if self.player.vely > 0:
hits = pg.sprite.spritecollide(self.player, self.all_platforms, False)
if hits:
for hit in hits:
if self.player.rect.top < hit.rect.top:
self.player.posy = hit.rect.top - self.player.rect.height
self.player.vely = 0
self.player.jumping = False
self.player.jump_go = True
def draw(self):
self.screen.fill(white)
self.all_sprites.draw(self.screen)
pg.display.flip()
g = Game()
while g.running:
g.new()
pg.quit()
In your collision check you get all hits, then you check for each hit
if self.player.rect.top < hit.rect.top:
this is too late.
If your player travels less then its height per frame its bottom will penetrate the hit.rect.top, then be checked, but its top is still above it so nothing happens.
Next frame it travels further, (still not quite fullfilling if self.player.rect.top < hit.rect.top:) and goes a bit further through your hit.
Some frames after it finally fullfills if self.player.rect.top < hit.rect.top: and its position is corrected by
if self.player.rect.top < hit.rect.top:
self.player.posy = hit.rect.top - self.player.rect.height
self.player.vely = 0
self.player.jumping = False
self.player.jump_go = True
leading to the described "pass through and jump up" effect you see.
You can mitigate the effect by changing
if self.player.rect.top < hit.rect.top:
to when the self.player.rect.bottom passes hit.rect.top - if bottom does not exist, use
if (self.player.rect.top - self.player.rect.height) < hit.rect.top:
to lessen the "pass through" effect.
I think you should move your collision detection to the Player.update() and Player.jump() functions.
The reason behind this is the complexity of collision detection. Each update() your player is moving some amount of pixels in two dimensions. But let's consider just a single dimension to make it simpler...
Say your player is moving 5 pixels "right" (+x) and an obstacle is 3 pixels away. Obviously it will collide with an overlap of 2 pixels. So what should happen in this case? That depends on your game logic, but in most cases the player should stop an only +3 pixels, resting next to the obstacle. This is a simple calculation knowing the direction of movement, and sizes of the sprites. The logic can determine: Want to move 5 right; Can only move 3 -> Move 3.
In your existing game code, the player is already moved before you check the collisions. You don't know where the player moved from, just that it now overlaps. This makes it more difficult to correct. You have to correct the collision after the fact, not before/during.
your input really helped me out in finding a soultion, this is what I did to fix my issue:
I created a new variable (instance attribute) in my "Player" class:
self.bottom_quart = self.rect.y + (self.rect.height/2)
Then, in the "Game" class, inside the "def update(self):" method, this is what it looks like now:
def update(self):
self.all_sprites.update()
#Check if player hit platfom:
if self.player.vely > 0:
hits = pg.sprite.spritecollide(self.player, self.all_platforms, False)
if hits:
for hit in hits:
if self.player.rect.bottom > hit.rect.top > self.player.rect.top
and self.player.bottom_quart < hit.rect.top:
self.player.rect.bottom = hit.rect.top
self.player.posy = hit.rect.top - self.player.rect.height
self.player.vely = 0
self.player.jumping = False
self.player.jump_go = True
So basically this fixed 2 things:
BEOFRE, when the player object was landing on a platform, it would "go thru" the platform and then placed itself on top of the platform. NOW, when the player lands on the platform, it DOESNT go thru, and sets itself smoothly on top of the platform. This was fixed by setting:
self.player.rect.bottom = hit.rect.top
self.player.posy = hit.rect.top - self.player.rect.height
The second issue that got fixed was that when the player hit a platform from the side, it would snap really fast back on top of the platform, which is not a normal behavior. That got fixed by creating the instance attribute:
self.bottom_quart = self.rect.y + (self.rect.height/2)
inside the "Player" class. That works as a new boundary that need to be True in the if statement deciding whether or not it sits on top of the platform or just passes thru when hit from the sides.
Thanks for your help!
I following this tutorial and I have added class Opponent() to class Platform() as shown here. Next I have been trying to add groupcollide_and_loop_for to the complete code so that the opponent is removed when hit by the bullet. I have been looking at this question about using groupcollide within class Opponent() itself. I tried several ways to call groupcollide inside def main in while not done but I didn't get any results or any errors, it just didn't work.
groupcollide_and_loop_for:
collided_opponents = pygame.sprite.groupcollide(opponents, bullet_list, False, True)
for opponent, bullets in collided_opponents.items():
for bullet in bullets:
score += 1 # Increment the score.
opponent.lives -= 1 # Decrement the lives.
pygame.display.set_caption(str(score))
Deploy the groupcollide and for loop above to the code below:
import pygame
# Global constants
bulletpicture = pygame.image.load("bullet.png")
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
class Player(pygame.sprite.Sprite):
"""
This class represents the bar at the bottom that the player controls.
"""
# -- Methods
def __init__(self):
""" Constructor function """
# Call the parent's constructor
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
width = 40
height = 60
self.image = pygame.Surface([width, height])
self.image.fill(RED)
# Set a referance to the image rect.
self.rect = self.image.get_rect()
# Set speed vector of player
self.change_x = 0
self.change_y = 0
# List of sprites we can bump against
self.level = None
def update(self):
""" Move the player. """
# Gravity
self.calc_grav()
# Move left/right
self.rect.x += self.change_x
# See if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# If we are moving right,
# set our right side to the left side of the item we hit
if self.change_x > 0:
self.rect.right = block.rect.left
elif self.change_x < 0:
# Otherwise if we are moving left, do the opposite.
self.rect.left = block.rect.right
# Move up/down
self.rect.y += self.change_y
# Check and see if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# Reset our position based on the top/bottom of the object.
if self.change_y > 0:
self.rect.bottom = block.rect.top
elif self.change_y < 0:
self.rect.top = block.rect.bottom
# Stop our vertical movement
self.change_y = 0
def calc_grav(self):
""" Calculate effect of gravity. """
if self.change_y == 0:
self.change_y = 1
else:
self.change_y += .35
# See if we are on the ground.
if self.rect.y >= SCREEN_HEIGHT - self.rect.height and self.change_y >= 0:
self.change_y = 0
self.rect.y = SCREEN_HEIGHT - self.rect.height
def jump(self):
""" Called when user hits 'jump' button. """
# move down a bit and see if there is a platform below us.
# Move down 2 pixels because it doesn't work well if we only move down 1
# when working with a platform moving down.
self.rect.y += 2
platform_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
self.rect.y -= 2
# If it is ok to jump, set our speed upwards
if len(platform_hit_list) > 0 or self.rect.bottom >= SCREEN_HEIGHT:
self.change_y = -10
# Player-controlled movement:
def go_left(self):
""" Called when the user hits the left arrow. """
self.change_x = -6
def go_right(self):
""" Called when the user hits the right arrow. """
self.change_x = 6
def stop(self):
""" Called when the user lets off the keyboard. """
self.change_x = 0
class Platform(pygame.sprite.Sprite):
""" Platform the user can jump on """
def __init__(self, width, height):
""" Platform constructor. Assumes constructed with user passing in
an array of 5 numbers like what's defined at the top of this code.
"""
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(GREEN)
self.rect = self.image.get_rect()
class Level():
""" This is a generic super-class used to define a level.
Create a child class for each level with level-specific
info. """
def __init__(self, player):
""" Constructor. Pass in a handle to player. Needed for when moving
platforms collide with the player. """
self.platform_list = pygame.sprite.Group()
self.enemy_list = pygame.sprite.Group()
self.player = player
# How far this world has been scrolled left/right
self.world_shift = 0
# Update everythign on this level
def update(self):
""" Update everything in this level."""
self.platform_list.update()
self.enemy_list.update()
def draw(self, screen):
""" Draw everything on this level. """
# Draw the background
screen.fill(BLUE)
# Draw all the sprite lists that we have
self.platform_list.draw(screen)
self.enemy_list.draw(screen)
def shift_world(self, shift_x):
""" When the user moves left/right and we need to scroll
everything: """
# Keep track of the shift amount
self.world_shift += shift_x
# Go through all the sprite lists and shift
for platform in self.platform_list:
platform.rect.x += shift_x
for enemy in self.enemy_list:
enemy.rect.x += shift_x
class Bullet(pygame.sprite.Sprite):
"""This class represents the bullet."""
def __init__(self,x,y):
super().__init__()
self.image = bulletpicture
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
def update(self):
"""Move the bullet."""
self.rect.y -= 5
# Remove the bullet if it flies up off the screen
if self.rect.y < -12:
self.kill() # Remove the sprite from all sprite groups.
class Opponent(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("cowboy.png") #.convert()
self.rect = self.image.get_rect()
self.lives = 1
self.rect.x = 50
self.rect.y = 280
self.change_x = 1
self.change_y = 0
# List of sprites we can bump against
self.level = None
pass
def update(self):
if self.lives <= 0:
self.kill() # Remove the sprite from all sprite groups.
self.rect.x += self.change_x
if self.rect.x > 280:
self.change_x *= -1
self.rect.x += self.change_x
if self.rect.x < 0:
self.change_x *= -1
self.rect.x += self.change_x
pass
# Create platforms for the level
class Level_01(Level):
""" Definition for level 1. """
def __init__(self, player):
""" Create level 1. """
# Call the parent constructor
Level.__init__(self, player)
self.level_limit = -1000
# Array with width, height, x, and y of platform
level = [[210, 70, 500, 500],
[210, 70, 800, 400],
[210, 70, 1000, 500],
[210, 70, 1120, 280],
]
# Go through the array above and add platforms
for platform in level:
block = Platform(platform[0], platform[1])
block.rect.x = platform[2]
block.rect.y = platform[3]
block.player = self.player
self.platform_list.add(block)
for enemy in level:
opponent = Opponent()
opponent.rect.x = 150
opponent.rect.y = 280
opponent.player = self.player
self.enemy_list.add(opponent)
# Create platforms for the level
class Level_02(Level):
""" Definition for level 2. """
def __init__(self, player):
""" Create level 1. """
# Call the parent constructor
Level.__init__(self, player)
self.level_limit = -1000
# Array with type of platform, and x, y location of the platform.
level = [[210, 30, 450, 570],
[210, 30, 850, 420],
[210, 30, 1000, 520],
[210, 30, 1120, 280],
]
# Go through the array above and add platforms
for platform in level:
block = Platform(platform[0], platform[1])
block.rect.x = platform[2]
block.rect.y = platform[3]
block.player = self.player
self.platform_list.add(block)
def main():
""" Main Program """
pygame.init()
# Set the height and width of the screen
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Side-scrolling Platformer")
# Create the player
player = Player()
# Create all the levels
level_list = []
level_list.append(Level_01(player))
level_list.append(Level_02(player))
# Set the current level
current_level_no = 0
current_level = level_list[current_level_no]
active_sprite_list = pygame.sprite.Group()
player.level = current_level
player.rect.x = 340
player.rect.y = SCREEN_HEIGHT - player.rect.height
active_sprite_list.add(player)
# --- NEW
bullet_list = pygame.sprite.Group()
player_list = pygame.sprite.Group()
player_list.add(player)
opponent = Opponent()
opponents = pygame.sprite.Group()
opponents.add(opponent)
score = 0
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
# Click a mouse button to instantiate a bullet.
bullet = Bullet(player.rect.x,player.rect.y)
bullet_list.add(bullet)
active_sprite_list.add(bullet)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.go_left()
if event.key == pygame.K_RIGHT:
player.go_right()
if event.key == pygame.K_UP:
player.jump()
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT and player.change_x < 0:
player.stop()
if event.key == pygame.K_RIGHT and player.change_x > 0:
player.stop()
# Update the player.
active_sprite_list.update()
# ----- The code commented below is what I tried in many ways
#collided_opponents = pygame.sprite.groupcollide(opponents, bullet_list, False, True)
#for opponent, bullets in collided_opponents.items():
# for bullet in bullets:
# score += 1 # Increment the score.
# opponent.lives -= 1 # Decrement the lives.
# pygame.display.set_caption(str(score))
# Update items in the level
current_level.update()
# If the player gets near the right side, shift the world left (-x)
if player.rect.right >= 500:
diff = player.rect.right - 500
player.rect.right = 500
current_level.shift_world(-diff)
# If the player gets near the left side, shift the world right (+x)
if player.rect.left <= 120:
diff = 120 - player.rect.left
player.rect.left = 120
current_level.shift_world(diff)
# If the player gets to the end of the level, go to the next level
current_position = player.rect.x + current_level.world_shift
if current_position < current_level.level_limit:
player.rect.x = 120
if current_level_no < len(level_list)-1:
current_level_no += 1
current_level = level_list[current_level_no]
player.level = current_level
# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT
current_level.draw(screen)
active_sprite_list.draw(screen)
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Limit to 60 frames per second
clock.tick(60)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()
if __name__ == "__main__":
main()
I used print() to see position for opponent and bullet. And I found that opponent never change position.
After digging in code I found you create two opponents.
in main() you create opponent which is not added to active_sprite_list so it doesn't move and it isn't displayed but you use it to check collision
in Level_01() you create opponent which is added to active_sprite_list so it moves and it is displayed on screen but you don't use it to check collision.
Because you check collision with opponent which never move so it never collide with bullet.
In main() you have to remove
opponent = Opponent()
opponents = pygame.sprite.Group()
opponents.add(opponent)
and use
#opponents = level_list[current_level_no].enemy_list
opponents = current_level.enemy_list
And use it also when you change level
current_level_no += 1
current_level = level_list[current_level_no]
#opponents = level_list[current_level_no].enemy_list
opponents = current_level.enemy_list
When i move right using the right key, i accelerate to a max speed. When i release it, i do decelerate to a stop so that is fine. However, when moving left using the left key, and after releasing it, i continue moving at a fixed speed and then come to an abrupt stop after a short while. Any idea what could be wrong with my code?
The original code is from http://programarcadegames.com/python_examples/show_file.php?file=platform_jumper.py
import pygame
# Global constants
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
class Player(pygame.sprite.Sprite):
""" This class represents the bar at the bottom that the player
controls. """
# -- Methods
def __init__(self):
""" Constructor function """
# Call the parent's constructor
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
width = 40
height = 60
self.image = pygame.Surface([width, height])
self.image.fill(RED)
# Set a referance to the image rect.
self.rect = self.image.get_rect()
# Set speed vector of player
self.xVel = 0
self.yVel = 0
# List of sprites we can bump against
self.level = None
def update(self):
""" Move the player. """
# Gravity
self.calc_grav()
# Move left/right
# See if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# If we are moving right,
# set our right side to the left side of the item we hit
if self.xVel > 0:
self.rect.right = block.rect.left
elif self.xVel < 0:
# Otherwise if we are moving left, do the opposite.
self.rect.left = block.rect.right
# Move up/down
self.rect.y += self.yVel
# Check and see if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# Reset our position based on the top/bottom of the object.
if self.yVel > 0:
self.rect.bottom = block.rect.top
elif self.yVel < 0:
self.rect.top = block.rect.bottom
# Stop our vertical movement
self.yVel = 0
def calc_grav(self):
""" Calculate effect of gravity. """
if self.yVel == 0:
self.yVel = 1
else:
self.yVel += .35
# See if we are on the ground.
if self.rect.y >= SCREEN_HEIGHT - self.rect.height and self.yVel >= 0:
self.yVel = 0
self.rect.y = SCREEN_HEIGHT - self.rect.height
def jump(self):
""" Called when user hits 'jump' button. """
# move down a bit and see if there is a platform below us.
# Move down 2 pixels because it doesn't work well if we only move down
# 1 when working with a platform moving down.
self.rect.y += 2
platform_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
self.rect.y -= 2
# If it is ok to jump, set our speed upwards
if len(platform_hit_list) > 0 or self.rect.bottom >= SCREEN_HEIGHT:
self.yVel = -10
class Platform(pygame.sprite.Sprite):
""" Platform the user can jump on """
def __init__(self, width, height):
""" Platform constructor. Assumes constructed with user passing in
an array of 5 numbers like what's defined at the top of this
code. """
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(GREEN)
self.rect = self.image.get_rect()
class Level(object):
""" This is a generic super-class used to define a level.
Create a child class for each level with level-specific
info. """
def __init__(self, player):
""" Constructor. Pass in a handle to player. Needed for when moving platforms
collide with the player. """
self.platform_list = pygame.sprite.Group()
self.enemy_list = pygame.sprite.Group()
self.player = player
# Background image
self.background = None
# Update everythign on this level
def update(self):
""" Update everything in this level."""
self.platform_list.update()
self.enemy_list.update()
def draw(self, screen):
""" Draw everything on this level. """
# Draw the background
screen.fill(BLUE)
# Draw all the sprite lists that we have
self.platform_list.draw(screen)
self.enemy_list.draw(screen)
# Create platforms for the level
class Level_01(Level):
""" Definition for level 1. """
def __init__(self, player):
""" Create level 1. """
# Call the parent constructor
Level.__init__(self, player)
# Array with width, height, x, and y of platform
level = [[210, 70, 500, 500],
[210, 70, 200, 400],
[210, 70, 600, 300],
]
# Go through the array above and add platforms
for platform in level:
block = Platform(platform[0], platform[1])
block.rect.x = platform[2]
block.rect.y = platform[3]
block.player = self.player
self.platform_list.add(block)
def main():
""" Main Program """
pygame.init()
# Set the height and width of the screen
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Platformer Jumper")
# Create the player
player = Player()
# Create all the levels
level_list = []
level_list.append(Level_01(player))
# Set the current level
current_level_no = 0
current_level = level_list[current_level_no]
active_sprite_list = pygame.sprite.Group()
player.level = current_level
player.rect.x = 340
player.rect.y = SCREEN_HEIGHT - player.rect.height
active_sprite_list.add(player)
accel_x = 0
max_speed = 6
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
player_running = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
accel_x = -0.5
if event.key == pygame.K_RIGHT:
accel_x = 0.5
if event.key == pygame.K_SPACE:
player.jump()
elif event.type == pygame.KEYUP:
if event.key in (pygame.K_LEFT, pygame.K_RIGHT):
accel_x = 0
player.xVel += accel_x # Accelerate.
if abs(player.xVel) >= max_speed: # If max_speed is exceeded.
# Normalize the x_change and multiply it with the max_speed.
player.xVel = player.xVel / abs(player.xVel) * max_speed
# Decelerate if no key is pressed.
if accel_x == 0:
player.xVel *= 0.5
player.rect.x += player.xVel
# Update the player.
active_sprite_list.update()
# Update items in the level
current_level.update()
# If the player gets near the right side, shift the world left (-x)
if player.rect.right > SCREEN_WIDTH:
player.rect.right = SCREEN_WIDTH
# If the player gets near the left side, shift the world right (+x)
if player.rect.left < 0:
player.rect.left = 0
# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT
current_level.draw(screen)
active_sprite_list.draw(screen)
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Limit to 60 frames per second
clock.tick(60)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()
if __name__ == "__main__":
main()
The isssue is caused, because the pygame.Rect operates with integral data:
The coordinates for Rect objects are all integers. [...]
When you do
player.rect.x += player.xVel
it is the same as you would do:
player.rect.x = int(player.rect.x + player.xVel)
The fraction part of player.xVel gets lost. The result of the addition operation is truncated and the player tends to the coordinate with the lower value (left).
Add a floating point x coordinate (self.px) to the class Player and use it to calculate the position of the player. Use round to set the integral rectangle position from self.px:
class Player(pygame.sprite.Sprite):
def __init__(self):
# [...]
# Set a referance to the image rect.
self.rect = self.image.get_rect()
self.px = self.rect.x
# [...]
def update(self):
# [...]
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# If we are moving right,
# set our right side to the left side of the item we hit
if self.xVel > 0:
self.rect.right = block.rect.left
elif self.xVel < 0:
# Otherwise if we are moving left, do the opposite.
self.rect.left = block.rect.right
self.px = self.rect.x
def main():
# [...]
player.rect.x = 340
player.px = player.rect.x
# [...]
while not done:
# [...]
player.px += player.xVel
player.rect.x = round(player.px)
# [...]
I'm trying to find a way to make perfect collision in my platformer game for school. Most of it works but there is a slight problem. When I stand on a platform above ground level and move left or right off of it, the player continues to float in midair at the same height of the platform that I got off of. This can be fixed by jumping and the collision reverts to normal. I am using a state system to track if the player is standing or not by having a folder of possible player states and switching between them. This is set to "Falling" by default because the player starts in midair when the game runs. The code for the game is divided into three separate files below (main.py, obj.py and settings.py). Please tell me how I can fix this glitch.
Main.py
import pygame
import random
from settings import *
from obj import *
pygame.init()
pygame.mixer.init()
pygame.font.init()
pygame.display.set_caption(TITLE)
screen = pygame.display.set_mode([WIDTH,HEIGHT])
clock = pygame.time.Clock()
me = Player()
all_sprites.add(me)
platforms = []
pf = Wall(20,40,500,480, 0)
pf2 = Wall(WIDTH,40, 400,500, 0)
platforms.append(pf)
platforms.append(pf2)
for i in platforms:
wall_sprites.add(i)
running = True
while running:
clock.tick(FPS)
all_sprites.update()
wall_sprites.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(GREY)
all_sprites.draw(screen)
wall_sprites.draw(screen)
pygame.display.update()
pygame.quit()
obj.py
import pygame
import math
from settings import *
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((40,40))
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = WIDTH / 2
self.rect.y = 70
self.vx = 0
self.vy = 0
self.SW = False # Can you screen wrap?
self.player_states = ["Standing","Falling"]
self.state = self.player_states[1]
def update(self):
self.vx = 0 # X speed set to 0 if no input is received
if self.state == self.player_states[1]:
self.vy += GRAVITY # Gravity only added while falling
else:
self.vy = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.vx = -SPEED
if keys[pygame.K_RIGHT]:
self.vx = SPEED
if keys[pygame.K_SPACE] and self.state == self.player_states[0]:
self.vy -= JUMP_SPEED
self.state = self.player_states[1]
self.rect.left += self.vx # X and Y positions are updated
self.collide(self.vx, 0, wall_sprites) # Collision is checked. Second param is 0 b/c we aren't checking for vertical collision here
self.rect.top += self.vy
self.collide(0, self.vy, wall_sprites)
if self.SW:
if self.rect.left > WIDTH:
self.rect.right = 0
if self.rect.right < 0:
self.rect.left = WIDTH
if self.rect.top > HEIGHT:
self.rect.bottom = 0
if self.rect.bottom < 0:
self.rect.top = HEIGHT
def collide(self, xDif, yDif, platform_list):
for i in platform_list: # Shuffle through list of platforms
if pygame.sprite.collide_rect(self, i): # If there is a collision between the player and a platform...
if xDif > 0: # And our x (horizontal) speed is greater than 0...
self.rect.right = i.rect.left # That means that we are moving right,
if xDif < 0: # So our right bounding box becomes equal to the left bounding box of all platforms and we don't collide
self.rect.left = i.rect.right
if yDif > 0:
self.rect.bottom = i.rect.top
self.state = self.player_states[0]
if yDif < 0:
self.rect.top = i.rect.bottom
class Wall(pygame.sprite.Sprite): # Collision is added for platforms just in case that they are moving. If they move to you, they push you
def __init__(self, width, height, xpos, ypos, speed):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((width,height))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.centerx = xpos
self.rect.centery = ypos
self.speed = speed
def update(self):
self.rect.left += self.speed
self.collide(self.speed, all_sprites) # Collision only for platforms moving left and right. Not up and down yet
def collide(self, xDif, player_list):
for i in player_list:
if pygame.sprite.collide_rect(self, i):
if xDif > 0: # If the platform is moving right... (has positive speed)
i.rect.left += self.speed # Platform pushes player
self.rect.right = i.rect.left # Player sticks to the wall and is pushed
if xDif < 0:
i.rect.right -= self.speed
self.rect.left = i.rect.right
settings.py
import pygame
FPS = 60
WIDTH = 800
HEIGHT = 600
TITLE = "Perfect collision"
GREY = (150,150,150)
BLACK = (0,0,0)
BLUE = (0,0,255)
SPEED = 5
JUMP_SPEED = 9
GRAVITY = 0.3
all_sprites = pygame.sprite.Group()
wall_sprites = pygame.sprite.Group()
Just add the GRAVITY to self.vy in every frame and set self.vy to 0 when the sprite touches the ground:
def update(self):
self.vx = 0
self.vy += GRAVITY
def collide(self, xDif, yDif, platform_list):
for i in platform_list:
if pygame.sprite.collide_rect(self, i):
# Code omitted.
if yDif > 0:
self.rect.bottom = i.rect.top
self.state = self.player_states[0]
self.vy = 0 # Set vy to 0 if the sprite touches the ground.
import pygame
#Colours used throughout the game
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BackGround = ('D:\Idea 2\sky_colour_image.jpg', [0,0])
# Screen dimensions
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
#Player class
class Player(pygame.sprite.Sprite):
#Player image
def __init__(self):
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
width = 40
height = 40
self.image = pygame.Surface([width, height])
self.image.fill(BLUE)
# Set a referance to the image rect.
self.rect = self.image.get_rect()
# Set speed vector of player
self.change_x = 0
self.change_y = 0
# List of sprites we can bump against
self.level = None
def update(self):
""" Move the player. """
# Gravity
self.calc_grav()
# Move left/right
self.rect.x += self.change_x
# See if we hit anything
block_hit_list = pygame.sprite.spritecollide(self,self.level.platform_list, False)
for block in block_hit_list:
# If we are moving right,
# set our right side to the left side of the item we hit
if self.change_x > 0:
self.rect.right = block.rect.left
elif self.change_x < 0:
# Otherwise if we are moving left, do the opposite.
self.rect.left = block.rect.right
# Move up/down
self.rect.y += self.change_y
# Check and see if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# Reset our position based on the top/bottom of the object.
if self.change_y > 0:
self.rect.bottom = block.rect.top
elif self.change_y < 0:
self.rect.top = block.rect.bottom
# Stop our vertical movement
self.change_y = 0
def calc_grav(self):
if self.change_y == 0:
self.change_y = 1
else:
self.change_y += .35
# See if we are on the ground.
if self.rect.y >= SCREEN_HEIGHT - self.rect.height and self.change_y >= 0:
self.change_y = 0
self.rect.y = SCREEN_HEIGHT - self.rect.height
def jump(self):
# move down a bit and see if there is a platform below us.
# Move down 2 pixels because it doesn't work well if we only move down
# 1 when working with a platform moving down.
self.rect.y += 2
platform_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
self.rect.y -= 2
# If it is ok to jump, set our speed upwards
if len(platform_hit_list) > 0 or self.rect.bottom >= SCREEN_HEIGHT:
self.change_y = -10
# Player-controlled movement:
def go_left(self):
self.change_x = -6
def go_right(self):
self.change_x = 6
def stop(self):
self.change_x = 0
class Platform(pygame.sprite.Sprite):
def __init__(self, width, height):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(GREEN)
self.rect = self.image.get_rect()
class Level(object):
def __init__(self, player):
self.platform_list = pygame.sprite.Group()
self.enemy_list = pygame.sprite.Group()
self.player = player
# Update everythign on this level
def update(self):
self.platform_list.update()
self.enemy_list.update()
def draw(self, screen):
# Draw the background
screen.fill(BLUE)
# Draw all the sprite lists that we have
self.platform_list.draw(screen)
self.enemy_list.draw(screen)
# Background class
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image = pygame.image.load("D:\Idea 2\sky_colour_image.jpg")
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
# Create platforms for the level
class Level_01(Level):
def __init__(self, player):
# Call the parent constructor
Level.__init__(self, player)
# Array with width, height, x, and y of platform
level = [[210, 70, 500, 500],
[210, 70, 200, 400],
[210, 70, 600, 300],
]
# Go through the array above and add platforms
for platform in level:
block = Platform(platform[0], platform[1])
block.rect.x = platform[2]
block.rect.y = platform[3]
block.player = self.player
self.platform_list.add(block)
def main():
pygame.init()
# Set the height and width of the screen
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Platformer Jumper")
# Create the player
player = Player()
# Create all the levels
level_list = []
level_list.append( Level_01(player) )
# Set the current level
current_level_no = 0
current_level = level_list[current_level_no]
active_sprite_list = pygame.sprite.Group()
player.level = current_level
player.rect.x = 340
player.rect.y = SCREEN_HEIGHT - player.rect.height
active_sprite_list.add(player)
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
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()
if event.key == pygame.K_UP:
player.jump()
screen.fill([255, 255, 255])
screen.blit(BackGround.image, BackGround.rect)
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT and player.change_x < 0:
player.stop()
if event.key == pygame.K_RIGHT and player.change_x > 0:
player.stop()
# Update the player.
active_sprite_list.update()
# Update items in the level
current_level.update()
# If the player gets near the right side, shift the world left (-x)
if player.rect.right > SCREEN_WIDTH:
player.rect.right = SCREEN_WIDTH
# If the player gets near the left side, shift the world right (+x)
if player.rect.left < 0:
player.rect.left = 0
current_level.draw(screen)
active_sprite_list.draw(screen)
# Limit to 60 frames per second
clock.tick(60)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
if __name__ == "__main__":
main()
So far this is supposed to be (nothing exciting yet) a simple platform jumper with sky backgrouns. Creating a game for a computing project, not that experienced in python. Aim to be adding in moving platforms etc soon, if anyone could help with that or the error I'd appreciate it.
Basically you are asking an object (looks like self here) and asking it for the image that is part of it. BUT the tuple doesn't have an attribute of image.
Not sure here, but I think you may have an indent error.
def __init__() should be indented since it is a method on your class. Python does not use curly braces, but space is significant.